ocuclaw 1.2.4 → 1.3.0
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 +18 -5
- package/dist/config/runtime-config.js +81 -3
- package/dist/domain/activity-status-adapter.js +138 -605
- package/dist/domain/activity-status-arbiter.js +109 -0
- package/dist/domain/activity-status-labels.js +906 -0
- package/dist/domain/code-span-regions.js +103 -0
- package/dist/domain/conversation-state.js +14 -1
- package/dist/domain/debug-store.js +38 -182
- package/dist/domain/glasses-ui-content-summary.js +62 -0
- package/dist/domain/glasses-ui-system-prompt.js +28 -0
- package/dist/domain/message-emoji-allowlist.js +16 -0
- package/dist/domain/message-emoji-filter.js +33 -55
- package/dist/domain/neural-emoji-reactor-system-prompt.js +43 -0
- package/dist/domain/neural-emoji-reactor-tag-config.js +56 -0
- package/dist/domain/neural-pace-modulator-system-prompt.js +32 -0
- package/dist/domain/neural-pace-modulator-tag-config.js +51 -0
- package/dist/domain/tagged-span-parser.js +121 -0
- package/dist/domain/tagged-span-strip.js +38 -0
- package/dist/even-ai/even-ai-endpoint.js +91 -0
- package/dist/even-ai/even-ai-run-waiter.js +14 -0
- package/dist/even-ai/even-ai-settings-store.js +14 -0
- package/dist/gateway/gateway-bridge.js +14 -2
- package/dist/gateway/gateway-timing-ledger.js +457 -0
- package/dist/gateway/openclaw-client.js +462 -38
- package/dist/index.js +28 -1
- package/dist/runtime/downstream-handler.js +754 -83
- package/dist/runtime/downstream-server.js +700 -534
- package/dist/runtime/ocuclaw-settings-store.js +74 -31
- package/dist/runtime/plugin-update-service.js +216 -0
- package/dist/runtime/protocol-adapter.js +9 -0
- package/dist/runtime/provider-usage-select.js +168 -0
- package/dist/runtime/relay-client-nudge-controller.js +553 -0
- package/dist/runtime/relay-core.js +1209 -204
- package/dist/runtime/relay-health-monitor.js +172 -0
- package/dist/runtime/relay-operation-registry.js +263 -0
- package/dist/runtime/relay-service.js +201 -1
- package/dist/runtime/relay-worker-approval-replay-cache.js +68 -0
- package/dist/runtime/relay-worker-entry.js +32 -0
- package/dist/runtime/relay-worker-health.js +272 -0
- package/dist/runtime/relay-worker-protocol.js +285 -0
- package/dist/runtime/relay-worker-queue.js +202 -0
- package/dist/runtime/relay-worker-supervisor.js +1081 -0
- package/dist/runtime/relay-worker-transport.js +1051 -0
- package/dist/runtime/session-context-service.js +189 -0
- package/dist/runtime/session-service.js +615 -24
- package/dist/runtime/upstream-runtime.js +1167 -60
- package/dist/tools/device-info-tool.js +242 -0
- package/dist/tools/glasses-ui-cron.js +427 -0
- package/dist/tools/glasses-ui-descriptors.js +261 -0
- package/dist/tools/glasses-ui-limits.js +21 -0
- package/dist/tools/glasses-ui-paint-floor.js +99 -0
- package/dist/tools/glasses-ui-recipes.js +746 -0
- package/dist/tools/glasses-ui-surfaces.js +278 -0
- package/dist/tools/glasses-ui-template.js +182 -0
- package/dist/tools/glasses-ui-tool.js +1147 -0
- package/dist/tools/session-title-tool.js +209 -0
- package/dist/version.js +2 -0
- package/openclaw.plugin.json +163 -15
- package/package.json +12 -4
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { classifyRank } from "./activity-status-arbiter.js";
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_MAX_LABEL_CHARS,
|
|
4
|
+
SHORT_LABEL_MAX_CHARS,
|
|
5
|
+
isObject,
|
|
6
|
+
asString,
|
|
7
|
+
normalizeLowerToken,
|
|
8
|
+
pickString,
|
|
9
|
+
pickStringEntry,
|
|
10
|
+
collapseWhitespace,
|
|
11
|
+
sanitizeText,
|
|
12
|
+
intentFromToolName,
|
|
13
|
+
mapToolLabel,
|
|
14
|
+
} from "./activity-status-labels.js";
|
|
2
15
|
|
|
3
16
|
const GLOBAL_RUN_KEY = "__global__";
|
|
4
|
-
const DEFAULT_MAX_LABEL_CHARS = 120;
|
|
5
|
-
const TOOL_PREVIEW_CHARS = 30;
|
|
6
|
-
|
|
7
|
-
const REDACT_QUERY_KEYS = "(token|access_token|api_key|key|password|secret)";
|
|
8
17
|
const THINKING_SUMMARY_KEYS = ["summary", "thinkingSummary", "reasoningSummary", "intentLabel"];
|
|
9
|
-
const THINKING_DETAIL_KEYS = ["thinking", "reasoning", "thinkingText", "
|
|
18
|
+
const THINKING_DETAIL_KEYS = ["thinking", "reasoning", "thinkingText", "analysis"];
|
|
10
19
|
const GENERIC_THINKING_LABEL = "Thinking...";
|
|
11
20
|
const ACTIVITY_INTENTS = new Set([
|
|
12
21
|
"thinking",
|
|
@@ -28,48 +37,11 @@ const ACTIVITY_INTENTS = new Set([
|
|
|
28
37
|
"message.send",
|
|
29
38
|
"session.manage",
|
|
30
39
|
"canvas.edit",
|
|
40
|
+
"session.title.update",
|
|
41
|
+
"device.check",
|
|
31
42
|
"generic",
|
|
32
43
|
]);
|
|
33
44
|
|
|
34
|
-
function isObject(value) {
|
|
35
|
-
return value && typeof value === "object" && !Array.isArray(value);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function asString(value) {
|
|
39
|
-
return typeof value === "string" ? value : null;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function isNullishToken(value) {
|
|
43
|
-
if (typeof value !== "string") return false;
|
|
44
|
-
const normalized = value.trim().toLowerCase();
|
|
45
|
-
return (
|
|
46
|
-
normalized === "null" ||
|
|
47
|
-
normalized === "undefined" ||
|
|
48
|
-
normalized === "(null)" ||
|
|
49
|
-
normalized === "(undefined)" ||
|
|
50
|
-
normalized === "none"
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function normalizeLowerToken(value) {
|
|
55
|
-
const text = asString(value);
|
|
56
|
-
return text ? text.trim().toLowerCase() : "";
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function pickString(obj, keys) {
|
|
60
|
-
const entry = pickStringEntry(obj, keys);
|
|
61
|
-
return entry ? entry.value : null;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function pickStringEntry(obj, keys) {
|
|
65
|
-
if (!isObject(obj)) return null;
|
|
66
|
-
for (const key of keys) {
|
|
67
|
-
const value = asString(obj[key]);
|
|
68
|
-
if (value && value.trim()) return { key, value };
|
|
69
|
-
}
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
45
|
function normalizeThinkingText(raw) {
|
|
74
46
|
const text = asString(raw);
|
|
75
47
|
if (!text) return null;
|
|
@@ -206,13 +178,6 @@ function isThinkingActivity(activity, category) {
|
|
|
206
178
|
return !(activity && activity.tool);
|
|
207
179
|
}
|
|
208
180
|
|
|
209
|
-
function shortText(text, maxChars) {
|
|
210
|
-
if (!text) return "";
|
|
211
|
-
if (text.length <= maxChars) return text;
|
|
212
|
-
if (maxChars <= 3) return ".".repeat(Math.max(maxChars, 0));
|
|
213
|
-
return `${text.slice(0, maxChars - 3)}...`;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
181
|
function lowercaseLeadingWord(rawText) {
|
|
217
182
|
if (typeof rawText !== "string" || rawText.length === 0) return rawText;
|
|
218
183
|
const match = rawText.match(/[A-Za-z][A-Za-z0-9_-]*/);
|
|
@@ -223,218 +188,6 @@ function lowercaseLeadingWord(rawText) {
|
|
|
223
188
|
return `${rawText.slice(0, start)}${token.toLowerCase()}${rawText.slice(end)}`;
|
|
224
189
|
}
|
|
225
190
|
|
|
226
|
-
function collapseWhitespace(text) {
|
|
227
|
-
return text.replace(/\s+/g, " ").trim();
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function redactSecrets(rawText) {
|
|
231
|
-
if (!rawText) return "";
|
|
232
|
-
let text = String(rawText);
|
|
233
|
-
|
|
234
|
-
text = text.replace(
|
|
235
|
-
new RegExp(`([?&]${REDACT_QUERY_KEYS}=)[^&#\\s]+`, "gi"),
|
|
236
|
-
"$1[redacted]",
|
|
237
|
-
);
|
|
238
|
-
text = text.replace(
|
|
239
|
-
/((?:api[_-]?key|token|password|secret)\s*[=:]\s*)([^,\s"'`]+)/gi,
|
|
240
|
-
"$1[redacted]",
|
|
241
|
-
);
|
|
242
|
-
text = text.replace(/(authorization\s*:\s*bearer\s+)[^\s"'`]+/gi, "$1[redacted]");
|
|
243
|
-
text = text.replace(/\bBearer\s+[A-Za-z0-9._-]{8,}\b/g, "Bearer [redacted]");
|
|
244
|
-
text = text.replace(/\b(sk-[A-Za-z0-9]{16,}|ghp_[A-Za-z0-9]{20,}|xox[baprs]-[A-Za-z0-9-]{10,})\b/g, "[redacted]");
|
|
245
|
-
|
|
246
|
-
return text;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function sanitizeText(rawText, maxChars) {
|
|
250
|
-
const redacted = redactSecrets(rawText);
|
|
251
|
-
const collapsed = collapseWhitespace(redacted);
|
|
252
|
-
return shortText(collapsed, maxChars);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function hostFromUrl(urlString) {
|
|
256
|
-
if (!urlString) return null;
|
|
257
|
-
try {
|
|
258
|
-
const parsed = new URL(urlString);
|
|
259
|
-
return parsed.host || null;
|
|
260
|
-
} catch {
|
|
261
|
-
return null;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function extractFirstUrl(text) {
|
|
266
|
-
if (!text) return null;
|
|
267
|
-
const match = text.match(/https?:\/\/[^\s"'`]+/i);
|
|
268
|
-
return match ? match[0] : null;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function extractBrowserQueryFromCommand(command) {
|
|
272
|
-
const match = command.match(/[?&]q=([^&"'`\s]+)/i);
|
|
273
|
-
if (!match) return null;
|
|
274
|
-
try {
|
|
275
|
-
return decodeURIComponent(match[1].replace(/\+/g, " "));
|
|
276
|
-
} catch {
|
|
277
|
-
return match[1].replace(/\+/g, " ");
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function stripQuotes(value) {
|
|
282
|
-
if (!value) return value;
|
|
283
|
-
return String(value).replace(/^['"]+|['"]+$/g, "");
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function escapeRegex(value) {
|
|
287
|
-
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function filenameFromPath(pathValue) {
|
|
291
|
-
if (!pathValue || typeof pathValue !== "string") return null;
|
|
292
|
-
const cleaned = stripQuotes(pathValue.trim());
|
|
293
|
-
if (!cleaned) return null;
|
|
294
|
-
const normalized = cleaned.replace(/[;,)]+$/g, "");
|
|
295
|
-
if (!normalized) return null;
|
|
296
|
-
if (isNullishToken(normalized)) return null;
|
|
297
|
-
// Ignore shell variable/subshell paths like "$f", "${file}", or "$(mktemp)".
|
|
298
|
-
if (/\$[({]?[A-Za-z_][A-Za-z0-9_]*[)}]?/.test(normalized) || /\$\(.+\)/.test(normalized)) {
|
|
299
|
-
return null;
|
|
300
|
-
}
|
|
301
|
-
if (/^(?:\/dev\/(?:null|stdout|stderr)|nul)$/i.test(normalized)) return null;
|
|
302
|
-
return path.basename(normalized);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
function pickMktempTemplatePath(rawArgs) {
|
|
306
|
-
if (!rawArgs || typeof rawArgs !== "string") return null;
|
|
307
|
-
const tokens = rawArgs
|
|
308
|
-
.match(/"[^"]*"|'[^']*'|[^\s]+/g)
|
|
309
|
-
?.map((token) => stripQuotes(token).trim())
|
|
310
|
-
?.filter(Boolean);
|
|
311
|
-
if (!tokens || tokens.length === 0) return null;
|
|
312
|
-
|
|
313
|
-
for (let index = tokens.length - 1; index >= 0; index -= 1) {
|
|
314
|
-
const token = tokens[index];
|
|
315
|
-
if (!token || token === "mktemp" || token.startsWith("-")) continue;
|
|
316
|
-
if (isNullishToken(token)) continue;
|
|
317
|
-
return token;
|
|
318
|
-
}
|
|
319
|
-
return null;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
function extractMktempBindings(command) {
|
|
323
|
-
if (!command || typeof command !== "string") return [];
|
|
324
|
-
const out = [];
|
|
325
|
-
const regex = /([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\$\(\s*mktemp\b([^)]*)\)/g;
|
|
326
|
-
let match;
|
|
327
|
-
while ((match = regex.exec(command)) !== null) {
|
|
328
|
-
const varName = match[1];
|
|
329
|
-
const templatePath = pickMktempTemplatePath(match[2]);
|
|
330
|
-
const fileName = filenameFromPath(templatePath);
|
|
331
|
-
if (!fileName) continue;
|
|
332
|
-
out.push({ varName, fileName });
|
|
333
|
-
}
|
|
334
|
-
return out;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
function commandRefsVarWithRedirect(command, varName, operator) {
|
|
338
|
-
if (!command || !varName) return false;
|
|
339
|
-
const varRef = `\\$\\{?${escapeRegex(varName)}\\}?`;
|
|
340
|
-
const op = escapeRegex(operator);
|
|
341
|
-
const regex = new RegExp(`(?:^|\\s)${op}\\s*(?:["']?${varRef}["']?)`);
|
|
342
|
-
return regex.test(command);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
function commandReadsVarWithCat(command, varName) {
|
|
346
|
-
if (!command || !varName) return false;
|
|
347
|
-
const varRef = `\\$\\{?${escapeRegex(varName)}\\}?`;
|
|
348
|
-
const regex = new RegExp(`(?:^|\\s)cat\\s+(?:["']?${varRef}["']?)`);
|
|
349
|
-
return regex.test(command);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
function categoryFromToolName(lowName) {
|
|
353
|
-
if (lowName.startsWith("browser") || lowName === "web" || lowName === "web.search") return "browser";
|
|
354
|
-
if (lowName === "read" || lowName === "write" || lowName === "edit" || lowName.startsWith("fs.")) return "filesystem";
|
|
355
|
-
if (lowName === "search" || lowName.startsWith("vector") || lowName === "grep" || lowName === "find") return "search";
|
|
356
|
-
if (lowName === "exec" || lowName === "bash" || lowName.startsWith("shell")) return "terminal";
|
|
357
|
-
return "generic";
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
function intentFromToolName(lowName, args) {
|
|
361
|
-
const query = pickString(args, ["query", "q", "term", "search"]);
|
|
362
|
-
|
|
363
|
-
switch (lowName) {
|
|
364
|
-
case "read":
|
|
365
|
-
case "fs.read":
|
|
366
|
-
return "fs.read";
|
|
367
|
-
case "write":
|
|
368
|
-
case "apply_patch":
|
|
369
|
-
case "fs.write":
|
|
370
|
-
return "fs.write";
|
|
371
|
-
case "edit":
|
|
372
|
-
case "fs.edit":
|
|
373
|
-
return "fs.edit";
|
|
374
|
-
case "search":
|
|
375
|
-
case "grep":
|
|
376
|
-
case "find":
|
|
377
|
-
return "search.files";
|
|
378
|
-
case "browser.search":
|
|
379
|
-
case "web.search":
|
|
380
|
-
return "search.web";
|
|
381
|
-
case "browser.click":
|
|
382
|
-
case "browser.navigate":
|
|
383
|
-
return "browser.navigate";
|
|
384
|
-
case "browser.fill":
|
|
385
|
-
return "browser.fill";
|
|
386
|
-
case "browser":
|
|
387
|
-
case "web":
|
|
388
|
-
return query ? "search.web" : "browser.browse";
|
|
389
|
-
case "exec":
|
|
390
|
-
case "bash":
|
|
391
|
-
return "terminal.exec";
|
|
392
|
-
case "git":
|
|
393
|
-
return "terminal.git";
|
|
394
|
-
case "llm_task":
|
|
395
|
-
return "agent.subtask";
|
|
396
|
-
case "agent_send":
|
|
397
|
-
return "agent.coordinate";
|
|
398
|
-
case "message":
|
|
399
|
-
return "message.send";
|
|
400
|
-
case "sessions_list":
|
|
401
|
-
case "sessions_read":
|
|
402
|
-
case "session_status":
|
|
403
|
-
return "session.manage";
|
|
404
|
-
case "canvas":
|
|
405
|
-
return "canvas.edit";
|
|
406
|
-
case "fetch":
|
|
407
|
-
return "network.fetch";
|
|
408
|
-
default:
|
|
409
|
-
break;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
if (lowName.startsWith("browser")) {
|
|
413
|
-
if (lowName.includes("fill")) return "browser.fill";
|
|
414
|
-
if (lowName.includes("click") || lowName.includes("navigate")) return "browser.navigate";
|
|
415
|
-
if (lowName.includes("search")) return "search.web";
|
|
416
|
-
return "browser.browse";
|
|
417
|
-
}
|
|
418
|
-
if (lowName.startsWith("web")) {
|
|
419
|
-
return lowName.includes("search") ? "search.web" : "browser.browse";
|
|
420
|
-
}
|
|
421
|
-
if (lowName.includes("search")) {
|
|
422
|
-
return lowName.includes("web") || lowName.includes("browser")
|
|
423
|
-
? "search.web"
|
|
424
|
-
: "search.files";
|
|
425
|
-
}
|
|
426
|
-
if (lowName.startsWith("fs.")) {
|
|
427
|
-
if (lowName.includes("read")) return "fs.read";
|
|
428
|
-
if (lowName.includes("edit")) return "fs.edit";
|
|
429
|
-
return "fs.write";
|
|
430
|
-
}
|
|
431
|
-
if (lowName.startsWith("session")) return "session.manage";
|
|
432
|
-
if (lowName.startsWith("http")) return "network.fetch";
|
|
433
|
-
if (lowName.startsWith("git")) return "terminal.git";
|
|
434
|
-
if (lowName.startsWith("shell")) return "terminal.exec";
|
|
435
|
-
return "generic";
|
|
436
|
-
}
|
|
437
|
-
|
|
438
191
|
function isExplanatoryThinkingLabel(label) {
|
|
439
192
|
const normalizedLabel = normalizeThinkingText(label);
|
|
440
193
|
if (!normalizedLabel) return false;
|
|
@@ -444,345 +197,6 @@ function isExplanatoryThinkingLabel(label) {
|
|
|
444
197
|
return normalizedToken !== "thinking" && normalizedToken !== "thinking...";
|
|
445
198
|
}
|
|
446
199
|
|
|
447
|
-
function labelFromExecCommand(command) {
|
|
448
|
-
const raw = command ? String(command).trim() : "";
|
|
449
|
-
if (!raw) {
|
|
450
|
-
return {
|
|
451
|
-
label: "Running a command...",
|
|
452
|
-
category: "terminal",
|
|
453
|
-
intent: "terminal.exec",
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
if (raw.includes("agent-browser")) {
|
|
458
|
-
const query = extractBrowserQueryFromCommand(raw);
|
|
459
|
-
if (query) {
|
|
460
|
-
return {
|
|
461
|
-
label: `Searching "${shortText(sanitizeText(query, TOOL_PREVIEW_CHARS), TOOL_PREVIEW_CHARS)}"...`,
|
|
462
|
-
category: "browser",
|
|
463
|
-
intent: "search.web",
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
const browserUrl = extractFirstUrl(raw);
|
|
467
|
-
if (browserUrl) {
|
|
468
|
-
const host = hostFromUrl(browserUrl);
|
|
469
|
-
return {
|
|
470
|
-
label: host ? `Browsing ${host}...` : "Using browser...",
|
|
471
|
-
category: "browser",
|
|
472
|
-
intent: "browser.browse",
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
|
-
return {
|
|
476
|
-
label: "Using browser...",
|
|
477
|
-
category: "browser",
|
|
478
|
-
intent: "browser.browse",
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
if (/(^|\s)(curl|wget)\b/i.test(raw) || /https?:\/\//i.test(raw)) {
|
|
483
|
-
const url = extractFirstUrl(raw);
|
|
484
|
-
const host = hostFromUrl(url);
|
|
485
|
-
if (host) {
|
|
486
|
-
return {
|
|
487
|
-
label: `Fetching from ${host}...`,
|
|
488
|
-
category: "network",
|
|
489
|
-
intent: "network.fetch",
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
return {
|
|
493
|
-
label: "Fetching data...",
|
|
494
|
-
category: "network",
|
|
495
|
-
intent: "network.fetch",
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
const appendMatch = raw.match(/(?:^|\s)>>\s*([^\s]+)/);
|
|
500
|
-
if (appendMatch) {
|
|
501
|
-
const fileName = filenameFromPath(appendMatch[1]);
|
|
502
|
-
if (fileName) {
|
|
503
|
-
return {
|
|
504
|
-
label: `Appending to ${fileName}...`,
|
|
505
|
-
category: "filesystem",
|
|
506
|
-
intent: "fs.write",
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
const writeMatch = raw.match(/(?:^|\s)>\s*([^\s]+)/);
|
|
512
|
-
if (writeMatch) {
|
|
513
|
-
const fileName = filenameFromPath(writeMatch[1]);
|
|
514
|
-
if (fileName) {
|
|
515
|
-
return {
|
|
516
|
-
label: `Writing ${fileName}...`,
|
|
517
|
-
category: "filesystem",
|
|
518
|
-
intent: "fs.write",
|
|
519
|
-
};
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
const mktempBindings = extractMktempBindings(raw);
|
|
524
|
-
for (const binding of mktempBindings) {
|
|
525
|
-
if (commandRefsVarWithRedirect(raw, binding.varName, ">>")) {
|
|
526
|
-
return {
|
|
527
|
-
label: `Appending to ${binding.fileName}...`,
|
|
528
|
-
category: "filesystem",
|
|
529
|
-
intent: "fs.write",
|
|
530
|
-
};
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
for (const binding of mktempBindings) {
|
|
534
|
-
if (commandRefsVarWithRedirect(raw, binding.varName, ">")) {
|
|
535
|
-
return {
|
|
536
|
-
label: `Writing ${binding.fileName}...`,
|
|
537
|
-
category: "filesystem",
|
|
538
|
-
intent: "fs.write",
|
|
539
|
-
};
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
for (const binding of mktempBindings) {
|
|
543
|
-
if (commandReadsVarWithCat(raw, binding.varName)) {
|
|
544
|
-
return {
|
|
545
|
-
label: `Reading ${binding.fileName}...`,
|
|
546
|
-
category: "filesystem",
|
|
547
|
-
intent: "fs.read",
|
|
548
|
-
};
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
const catMatch = raw.match(/^cat\s+([^\s>]+)/);
|
|
553
|
-
if (catMatch) {
|
|
554
|
-
const fileName = filenameFromPath(catMatch[1]);
|
|
555
|
-
if (fileName) {
|
|
556
|
-
return {
|
|
557
|
-
label: `Reading ${fileName}...`,
|
|
558
|
-
category: "filesystem",
|
|
559
|
-
intent: "fs.read",
|
|
560
|
-
};
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
if (/^(grep|rg|find)\b/.test(raw)) {
|
|
565
|
-
return {
|
|
566
|
-
label: "Searching files...",
|
|
567
|
-
category: "search",
|
|
568
|
-
intent: "search.files",
|
|
569
|
-
};
|
|
570
|
-
}
|
|
571
|
-
if (/\bgit\s+/.test(raw)) {
|
|
572
|
-
return {
|
|
573
|
-
label: "Running git...",
|
|
574
|
-
category: "terminal",
|
|
575
|
-
intent: "terminal.git",
|
|
576
|
-
};
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
return {
|
|
580
|
-
label: `Running: ${shortText(sanitizeText(raw, TOOL_PREVIEW_CHARS), TOOL_PREVIEW_CHARS)}`,
|
|
581
|
-
category: "terminal",
|
|
582
|
-
intent: "terminal.exec",
|
|
583
|
-
};
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
function mapToolLabel(toolName, activityPath, args, options) {
|
|
587
|
-
const maxLabelChars = options.maxLabelChars;
|
|
588
|
-
const lowName = String(toolName || "").toLowerCase();
|
|
589
|
-
const rawPathValue = asString(activityPath) || pickString(args, [
|
|
590
|
-
"path",
|
|
591
|
-
"filePath",
|
|
592
|
-
"file_path",
|
|
593
|
-
"filepath",
|
|
594
|
-
"file",
|
|
595
|
-
"target",
|
|
596
|
-
"outputPath",
|
|
597
|
-
"output_path",
|
|
598
|
-
"output",
|
|
599
|
-
"destination",
|
|
600
|
-
"dest",
|
|
601
|
-
]);
|
|
602
|
-
const pathValue = rawPathValue && !isNullishToken(rawPathValue) ? rawPathValue : null;
|
|
603
|
-
const fileName = filenameFromPath(pathValue);
|
|
604
|
-
const query = pickString(args, ["query", "q", "term", "search"]);
|
|
605
|
-
const url = pickString(args, ["url", "href", "uri"]);
|
|
606
|
-
const command = pickString(args, ["command", "cmd", "shell"]);
|
|
607
|
-
|
|
608
|
-
switch (lowName) {
|
|
609
|
-
case "write":
|
|
610
|
-
case "apply_patch":
|
|
611
|
-
case "fs.write":
|
|
612
|
-
return {
|
|
613
|
-
label: `Writing ${fileName || "file"}...`,
|
|
614
|
-
detail: pathValue || command || null,
|
|
615
|
-
category: "filesystem",
|
|
616
|
-
intent: "fs.write",
|
|
617
|
-
};
|
|
618
|
-
case "read":
|
|
619
|
-
case "fs.read":
|
|
620
|
-
return {
|
|
621
|
-
label: `Reading ${fileName || "file"}...`,
|
|
622
|
-
detail: pathValue || command || null,
|
|
623
|
-
category: "filesystem",
|
|
624
|
-
intent: "fs.read",
|
|
625
|
-
};
|
|
626
|
-
case "edit":
|
|
627
|
-
case "fs.edit":
|
|
628
|
-
return {
|
|
629
|
-
label: `Editing ${fileName || "file"}...`,
|
|
630
|
-
detail: pathValue || command || null,
|
|
631
|
-
category: "filesystem",
|
|
632
|
-
intent: "fs.edit",
|
|
633
|
-
};
|
|
634
|
-
case "search":
|
|
635
|
-
if (query) {
|
|
636
|
-
const shortQuery = shortText(sanitizeText(query, TOOL_PREVIEW_CHARS), TOOL_PREVIEW_CHARS);
|
|
637
|
-
return {
|
|
638
|
-
label: `Searching for "${shortQuery}"...`,
|
|
639
|
-
detail: query,
|
|
640
|
-
category: "search",
|
|
641
|
-
intent: "search.files",
|
|
642
|
-
};
|
|
643
|
-
}
|
|
644
|
-
return {
|
|
645
|
-
label: "Searching files...",
|
|
646
|
-
detail: pathValue || null,
|
|
647
|
-
category: "search",
|
|
648
|
-
intent: "search.files",
|
|
649
|
-
};
|
|
650
|
-
case "bash":
|
|
651
|
-
case "exec": {
|
|
652
|
-
const fromCommand = labelFromExecCommand(command);
|
|
653
|
-
return {
|
|
654
|
-
label: fromCommand.label,
|
|
655
|
-
detail: command || pathValue || null,
|
|
656
|
-
category: fromCommand.category,
|
|
657
|
-
intent: fromCommand.intent,
|
|
658
|
-
};
|
|
659
|
-
}
|
|
660
|
-
case "browser.search":
|
|
661
|
-
case "web.search":
|
|
662
|
-
if (query) {
|
|
663
|
-
const shortQuery = shortText(sanitizeText(query, TOOL_PREVIEW_CHARS), TOOL_PREVIEW_CHARS);
|
|
664
|
-
return {
|
|
665
|
-
label: `Searching the web for "${shortQuery}"...`,
|
|
666
|
-
detail: query,
|
|
667
|
-
category: "browser",
|
|
668
|
-
intent: "search.web",
|
|
669
|
-
};
|
|
670
|
-
}
|
|
671
|
-
if (url) {
|
|
672
|
-
return {
|
|
673
|
-
label: "Searching the web...",
|
|
674
|
-
detail: url,
|
|
675
|
-
category: "browser",
|
|
676
|
-
intent: "search.web",
|
|
677
|
-
};
|
|
678
|
-
}
|
|
679
|
-
return {
|
|
680
|
-
label: "Searching the web...",
|
|
681
|
-
detail: null,
|
|
682
|
-
category: "browser",
|
|
683
|
-
intent: "search.web",
|
|
684
|
-
};
|
|
685
|
-
case "browser":
|
|
686
|
-
case "web":
|
|
687
|
-
if (query) {
|
|
688
|
-
const shortQuery = shortText(sanitizeText(query, TOOL_PREVIEW_CHARS), TOOL_PREVIEW_CHARS);
|
|
689
|
-
return {
|
|
690
|
-
label: `Searching the web for "${shortQuery}"...`,
|
|
691
|
-
detail: query,
|
|
692
|
-
category: "browser",
|
|
693
|
-
intent: "search.web",
|
|
694
|
-
};
|
|
695
|
-
}
|
|
696
|
-
if (url) {
|
|
697
|
-
return {
|
|
698
|
-
label: "Browsing the web...",
|
|
699
|
-
detail: url,
|
|
700
|
-
category: "browser",
|
|
701
|
-
intent: "browser.browse",
|
|
702
|
-
};
|
|
703
|
-
}
|
|
704
|
-
return {
|
|
705
|
-
label: "Browsing the web...",
|
|
706
|
-
detail: null,
|
|
707
|
-
category: "browser",
|
|
708
|
-
intent: "browser.browse",
|
|
709
|
-
};
|
|
710
|
-
case "browser.click":
|
|
711
|
-
return {
|
|
712
|
-
label: "Navigating a webpage...",
|
|
713
|
-
detail: url || null,
|
|
714
|
-
category: "browser",
|
|
715
|
-
intent: "browser.navigate",
|
|
716
|
-
};
|
|
717
|
-
case "browser.fill":
|
|
718
|
-
return {
|
|
719
|
-
label: "Filling out a form...",
|
|
720
|
-
detail: url || null,
|
|
721
|
-
category: "browser",
|
|
722
|
-
intent: "browser.fill",
|
|
723
|
-
};
|
|
724
|
-
case "browser.navigate":
|
|
725
|
-
return {
|
|
726
|
-
label: "Opening a webpage...",
|
|
727
|
-
detail: url || null,
|
|
728
|
-
category: "browser",
|
|
729
|
-
intent: "browser.navigate",
|
|
730
|
-
};
|
|
731
|
-
case "llm_task":
|
|
732
|
-
return {
|
|
733
|
-
label: "Running a sub-task...",
|
|
734
|
-
detail: null,
|
|
735
|
-
category: "generic",
|
|
736
|
-
intent: "agent.subtask",
|
|
737
|
-
};
|
|
738
|
-
case "agent_send":
|
|
739
|
-
return {
|
|
740
|
-
label: "Coordinating with another agent...",
|
|
741
|
-
detail: null,
|
|
742
|
-
category: "generic",
|
|
743
|
-
intent: "agent.coordinate",
|
|
744
|
-
};
|
|
745
|
-
case "message":
|
|
746
|
-
return {
|
|
747
|
-
label: "Sending a message...",
|
|
748
|
-
detail: null,
|
|
749
|
-
category: "generic",
|
|
750
|
-
intent: "message.send",
|
|
751
|
-
};
|
|
752
|
-
case "sessions_list":
|
|
753
|
-
case "sessions_read":
|
|
754
|
-
case "session_status":
|
|
755
|
-
return {
|
|
756
|
-
label: "Checking sessions...",
|
|
757
|
-
detail: null,
|
|
758
|
-
category: "generic",
|
|
759
|
-
intent: "session.manage",
|
|
760
|
-
};
|
|
761
|
-
case "canvas":
|
|
762
|
-
return {
|
|
763
|
-
label: "Working on canvas...",
|
|
764
|
-
detail: null,
|
|
765
|
-
category: "generic",
|
|
766
|
-
intent: "canvas.edit",
|
|
767
|
-
};
|
|
768
|
-
default:
|
|
769
|
-
if (fileName) {
|
|
770
|
-
return {
|
|
771
|
-
label: `${toolName} ${fileName}...`,
|
|
772
|
-
detail: pathValue || null,
|
|
773
|
-
category: categoryFromToolName(lowName),
|
|
774
|
-
intent: intentFromToolName(lowName, args),
|
|
775
|
-
};
|
|
776
|
-
}
|
|
777
|
-
return {
|
|
778
|
-
label: `Using ${toolName}...`,
|
|
779
|
-
detail: query || url || command || null,
|
|
780
|
-
category: categoryFromToolName(lowName),
|
|
781
|
-
intent: intentFromToolName(lowName, args),
|
|
782
|
-
};
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
|
|
786
200
|
function normalizePhase(phase, state) {
|
|
787
201
|
const p = typeof phase === "string" ? phase.toLowerCase() : "";
|
|
788
202
|
if (p === "start" || p === "update" || p === "end") return p;
|
|
@@ -818,6 +232,13 @@ function sanitizeIdPart(value) {
|
|
|
818
232
|
.replace(/^-|-$/g, "");
|
|
819
233
|
}
|
|
820
234
|
|
|
235
|
+
function clampFreshnessWindow(value) {
|
|
236
|
+
const n = Number.isFinite(value) ? Math.floor(value) : 5000;
|
|
237
|
+
if (n < 3000) return 3000;
|
|
238
|
+
if (n > 8000) return 8000;
|
|
239
|
+
return n;
|
|
240
|
+
}
|
|
241
|
+
|
|
821
242
|
function createActivityStatusAdapter(opts) {
|
|
822
243
|
const options = opts || {};
|
|
823
244
|
const enabled = options.enabled !== false;
|
|
@@ -826,6 +247,9 @@ function createActivityStatusAdapter(opts) {
|
|
|
826
247
|
Number.isFinite(options.maxLabelChars) && options.maxLabelChars > 0
|
|
827
248
|
? Math.floor(options.maxLabelChars)
|
|
828
249
|
: DEFAULT_MAX_LABEL_CHARS;
|
|
250
|
+
const freshnessWindowMs = clampFreshnessWindow(options.freshnessWindowMs);
|
|
251
|
+
const now =
|
|
252
|
+
typeof options.now === "function" ? options.now : () => Date.now();
|
|
829
253
|
|
|
830
254
|
/** @type {Map<string, {seq: number, toolStartCount: number, currentActivityId: string|null, toolContextByActivityId: Map<string, {label: string, detail: string|null, category: string|null, intent: string|null}>}>} */
|
|
831
255
|
const runStates = new Map();
|
|
@@ -838,6 +262,14 @@ function createActivityStatusAdapter(opts) {
|
|
|
838
262
|
toolStartCount: 0,
|
|
839
263
|
currentActivityId: null,
|
|
840
264
|
toolContextByActivityId: new Map(),
|
|
265
|
+
capabilities: {
|
|
266
|
+
sawSummary: false,
|
|
267
|
+
sawToolCall: false,
|
|
268
|
+
sawThinkingBlock: false,
|
|
269
|
+
hasToolCallId: false,
|
|
270
|
+
hasSignature: false,
|
|
271
|
+
hasTurnId: false,
|
|
272
|
+
},
|
|
841
273
|
};
|
|
842
274
|
runStates.set(runKey, state);
|
|
843
275
|
}
|
|
@@ -855,6 +287,10 @@ function createActivityStatusAdapter(opts) {
|
|
|
855
287
|
const runKey = normalizeRunKey(activity.runId);
|
|
856
288
|
const runState = getRunState(runKey);
|
|
857
289
|
const phase = normalizePhase(activity.phase, activity.state);
|
|
290
|
+
const rawPhase = asString(activity.phase) && activity.phase.trim()
|
|
291
|
+
? activity.phase.trim()
|
|
292
|
+
: null;
|
|
293
|
+
const preserveErrorPhase = rawPhase && rawPhase.toLowerCase() === "error";
|
|
858
294
|
|
|
859
295
|
runState.seq += 1;
|
|
860
296
|
const seq = Number.isFinite(activity.seq) ? Math.floor(activity.seq) : runState.seq;
|
|
@@ -887,6 +323,7 @@ function createActivityStatusAdapter(opts) {
|
|
|
887
323
|
}
|
|
888
324
|
|
|
889
325
|
let label = asString(activity.label);
|
|
326
|
+
let shortLabel = null;
|
|
890
327
|
let detail = asString(activity.detail);
|
|
891
328
|
let category = asString(activity.category);
|
|
892
329
|
let thinkingSummarySource = null;
|
|
@@ -923,6 +360,7 @@ function createActivityStatusAdapter(opts) {
|
|
|
923
360
|
const mappedTool = activity.tool
|
|
924
361
|
? mapToolLabel(activity.tool, activity.path, args, {
|
|
925
362
|
maxLabelChars,
|
|
363
|
+
stabilityKey: activityId,
|
|
926
364
|
})
|
|
927
365
|
: null;
|
|
928
366
|
const isThinking = isThinkingActivity(activity, category);
|
|
@@ -943,21 +381,30 @@ function createActivityStatusAdapter(opts) {
|
|
|
943
381
|
detail = resolvedThinking.detail;
|
|
944
382
|
}
|
|
945
383
|
|
|
384
|
+
// Agent-authored summaries get a clamp-only shortLabel when the
|
|
385
|
+
// 64-char header budget needs it; generic "Thinking..." never does.
|
|
386
|
+
if (label && isExplanatoryThinkingLabel(label) && label.length > SHORT_LABEL_MAX_CHARS) {
|
|
387
|
+
shortLabel = label;
|
|
388
|
+
}
|
|
389
|
+
|
|
946
390
|
if (!includeThinking) {
|
|
947
391
|
suppressThinkingContent = true;
|
|
948
392
|
label = null;
|
|
949
393
|
detail = null;
|
|
394
|
+
shortLabel = null;
|
|
950
395
|
thinkingSummarySource = null;
|
|
951
396
|
}
|
|
952
397
|
} else if (!label && activity.tool) {
|
|
953
398
|
if (previousToolContext && !hasCurrentToolContext) {
|
|
954
399
|
label = previousToolContext.label;
|
|
400
|
+
shortLabel = previousToolContext.shortLabel || null;
|
|
955
401
|
if (!detail) detail = previousToolContext.detail;
|
|
956
402
|
if (!category) category = previousToolContext.category;
|
|
957
403
|
}
|
|
958
404
|
if (!label) {
|
|
959
405
|
const mapped = mappedTool;
|
|
960
406
|
label = mapped.label;
|
|
407
|
+
shortLabel = mapped.shortLabel || null;
|
|
961
408
|
if (!detail) detail = mapped.detail;
|
|
962
409
|
if (!category) category = mapped.category;
|
|
963
410
|
}
|
|
@@ -979,6 +426,7 @@ function createActivityStatusAdapter(opts) {
|
|
|
979
426
|
if (activity.tool && activityId && label) {
|
|
980
427
|
runState.toolContextByActivityId.set(activityId, {
|
|
981
428
|
label,
|
|
429
|
+
shortLabel: shortLabel || null,
|
|
982
430
|
detail: detail || null,
|
|
983
431
|
category: category || null,
|
|
984
432
|
intent: intent || null,
|
|
@@ -989,12 +437,13 @@ function createActivityStatusAdapter(opts) {
|
|
|
989
437
|
...activity,
|
|
990
438
|
activityId,
|
|
991
439
|
seq,
|
|
992
|
-
phase,
|
|
440
|
+
phase: preserveErrorPhase ? "error" : phase,
|
|
993
441
|
};
|
|
994
442
|
|
|
995
443
|
if (suppressThinkingContent) {
|
|
996
444
|
delete result.label;
|
|
997
445
|
delete result.detail;
|
|
446
|
+
delete result.shortLabel;
|
|
998
447
|
}
|
|
999
448
|
|
|
1000
449
|
if (runKey !== GLOBAL_RUN_KEY && !result.runId) {
|
|
@@ -1004,6 +453,9 @@ function createActivityStatusAdapter(opts) {
|
|
|
1004
453
|
if (typeof activity.isError === "boolean") {
|
|
1005
454
|
result.isError = activity.isError;
|
|
1006
455
|
}
|
|
456
|
+
if (typeof activity.code === "string" && activity.code.trim()) {
|
|
457
|
+
result.code = activity.code.trim();
|
|
458
|
+
}
|
|
1007
459
|
if (Number.isFinite(activity.exitCode)) {
|
|
1008
460
|
result.exitCode = Math.trunc(activity.exitCode);
|
|
1009
461
|
}
|
|
@@ -1011,8 +463,89 @@ function createActivityStatusAdapter(opts) {
|
|
|
1011
463
|
result.durationMs = Math.trunc(activity.durationMs);
|
|
1012
464
|
}
|
|
1013
465
|
|
|
466
|
+
if (isObject(activity.rateLimitInfo)) {
|
|
467
|
+
result.rateLimitInfo = activity.rateLimitInfo;
|
|
468
|
+
}
|
|
469
|
+
if (activity.failoverPending === true) {
|
|
470
|
+
result.failoverPending = true;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const candidateRank = classifyRank({
|
|
474
|
+
isError: activity.isError === true,
|
|
475
|
+
phaseIsError: preserveErrorPhase === true,
|
|
476
|
+
hasRateLimitInfo: isObject(activity.rateLimitInfo),
|
|
477
|
+
failoverPending: activity.failoverPending === true,
|
|
478
|
+
hasTool: !!activity.tool,
|
|
479
|
+
isThinking,
|
|
480
|
+
includeThinking,
|
|
481
|
+
thinkingSummarySource,
|
|
482
|
+
label,
|
|
483
|
+
});
|
|
484
|
+
result.candidateRank = candidateRank;
|
|
485
|
+
result.sourceType = candidateRank;
|
|
486
|
+
|
|
487
|
+
const caps = runState.capabilities;
|
|
488
|
+
if (candidateRank === "generated_summary") caps.sawSummary = true;
|
|
489
|
+
if (activity.tool) caps.sawToolCall = true;
|
|
490
|
+
if (isThinking) caps.sawThinkingBlock = true;
|
|
491
|
+
if (typeof activity.toolCallId === "string" && activity.toolCallId.trim()) {
|
|
492
|
+
caps.hasToolCallId = true;
|
|
493
|
+
}
|
|
494
|
+
if (
|
|
495
|
+
typeof activity.thinkingSignatureId === "string" &&
|
|
496
|
+
activity.thinkingSignatureId.trim()
|
|
497
|
+
) {
|
|
498
|
+
caps.hasSignature = true;
|
|
499
|
+
}
|
|
500
|
+
if (typeof activity.turnId === "string" && activity.turnId.trim()) {
|
|
501
|
+
caps.hasTurnId = true;
|
|
502
|
+
}
|
|
503
|
+
result.capabilityFlags = { ...caps };
|
|
504
|
+
|
|
505
|
+
result.candidateAtMs = now();
|
|
506
|
+
result.freshnessWindowMs = freshnessWindowMs;
|
|
507
|
+
|
|
508
|
+
// Association ids (optional, normalized): trim, or drop when empty. The
|
|
509
|
+
// {...activity} spread already copies them, but normalize at the contract
|
|
510
|
+
// boundary so untrusted callers can't leak whitespace/empty ids. See
|
|
511
|
+
// transport spec docs/superpowers/specs/2026-06-01-...-redesign-design.md §4/§11.
|
|
512
|
+
if (typeof activity.toolCallId === "string" && activity.toolCallId.trim()) {
|
|
513
|
+
result.toolCallId = activity.toolCallId.trim();
|
|
514
|
+
} else {
|
|
515
|
+
delete result.toolCallId;
|
|
516
|
+
}
|
|
517
|
+
if (typeof activity.turnId === "string" && activity.turnId.trim()) {
|
|
518
|
+
result.turnId = activity.turnId.trim();
|
|
519
|
+
} else {
|
|
520
|
+
delete result.turnId;
|
|
521
|
+
}
|
|
522
|
+
if (typeof activity.thinkingSignatureId === "string" && activity.thinkingSignatureId.trim()) {
|
|
523
|
+
result.thinkingSignatureId = activity.thinkingSignatureId.trim();
|
|
524
|
+
} else {
|
|
525
|
+
delete result.thinkingSignatureId;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
delete result.shortLabel;
|
|
1014
529
|
if (enabled) {
|
|
1015
|
-
if (label)
|
|
530
|
+
if (label) {
|
|
531
|
+
const preserveErrorLabelCase =
|
|
532
|
+
activity.isError === true || preserveErrorPhase;
|
|
533
|
+
result.label = sanitizeText(
|
|
534
|
+
preserveErrorLabelCase ? label : lowercaseLeadingWord(label),
|
|
535
|
+
maxLabelChars,
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
if (label && shortLabel) {
|
|
539
|
+
const preserveShortLabelCase =
|
|
540
|
+
activity.isError === true || preserveErrorPhase;
|
|
541
|
+
const sanitizedShort = sanitizeText(
|
|
542
|
+
preserveShortLabelCase ? shortLabel : lowercaseLeadingWord(shortLabel),
|
|
543
|
+
SHORT_LABEL_MAX_CHARS,
|
|
544
|
+
);
|
|
545
|
+
if (sanitizedShort && sanitizedShort !== result.label) {
|
|
546
|
+
result.shortLabel = sanitizedShort;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
1016
549
|
if (detail) result.detail = sanitizeText(detail, Math.max(maxLabelChars, 200));
|
|
1017
550
|
if (category) result.category = sanitizeIdPart(category.toLowerCase()) || "generic";
|
|
1018
551
|
if (thinkingSummarySource) result.thinkingSummarySource = thinkingSummarySource;
|