@shawnowen/comet-mcp 2.3.1 → 2.4.2
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 +97 -19
- package/dist/alert-dispatcher.d.ts +23 -0
- package/dist/alert-dispatcher.js +101 -0
- package/dist/binding-reaper.d.ts +46 -0
- package/dist/binding-reaper.js +73 -0
- package/dist/bound-session.d.ts +23 -0
- package/dist/bound-session.js +119 -0
- package/dist/bridge-config.d.ts +6 -0
- package/dist/bridge-config.js +78 -0
- package/dist/cdp-client.d.ts +40 -4
- package/dist/cdp-client.js +502 -155
- package/dist/comet-ai.d.ts +15 -0
- package/dist/comet-ai.js +114 -38
- package/dist/delegate-binding.d.ts +19 -0
- package/dist/delegate-binding.js +73 -0
- package/dist/http-server.js +2188 -47
- package/dist/index.js +3545 -788
- package/dist/observer.d.ts +47 -0
- package/dist/observer.js +516 -0
- package/dist/project-config.d.ts +46 -0
- package/dist/project-config.js +166 -0
- package/dist/session-registry.d.ts +57 -0
- package/dist/session-registry.js +500 -0
- package/dist/sidecar-artifacts.d.ts +49 -0
- package/dist/sidecar-artifacts.js +146 -0
- package/dist/snapshot-capture.d.ts +3 -0
- package/dist/snapshot-capture.js +91 -0
- package/dist/tab-group-archive.js +3 -1
- package/dist/tab-groups.d.ts +28 -1
- package/dist/tab-groups.js +205 -3
- package/dist/types.d.ts +237 -0
- package/dist/window-bindings.d.ts +160 -0
- package/dist/window-bindings.js +561 -0
- package/extension/background.js +1577 -300
- package/extension/icons/icon.svg +9 -0
- package/extension/icons/icon128.png +0 -0
- package/extension/icons/icon16.png +0 -0
- package/extension/icons/icon48.png +0 -0
- package/extension/manifest.json +34 -4
- package/extension/perplexity-capability-manifest.json +1181 -0
- package/extension/perplexity-capability-manifest.schema.json +142 -0
- package/extension/session-logic.js +3054 -0
- package/extension/session-manager.html +311 -0
- package/extension/sidepanel.css +5338 -528
- package/extension/sidepanel.html +282 -2
- package/extension/sidepanel.js +10604 -950
- package/extension/window-policy.js +162 -0
- package/package.json +10 -7
- package/vendor/lifecycle-mcp-adapter.mjs +103 -0
- package/vendor/lifecycle-metadata.mjs +252 -0
- package/vendor/readiness-report.mjs +742 -0
- package/dist/cdp-client.d.ts.map +0 -1
- package/dist/cdp-client.js.map +0 -1
- package/dist/comet-ai.d.ts.map +0 -1
- package/dist/comet-ai.js.map +0 -1
- package/dist/http-server.d.ts.map +0 -1
- package/dist/http-server.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/tab-group-archive.d.ts.map +0 -1
- package/dist/tab-group-archive.js.map +0 -1
- package/dist/tab-groups.d.ts.map +0 -1
- package/dist/tab-groups.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
|
@@ -0,0 +1,3054 @@
|
|
|
1
|
+
// session-logic.js — Extracted pure logic functions (Spec 036)
|
|
2
|
+
// Dual-format: ESM exports for Node.js tests, globalThis for Chrome extension
|
|
3
|
+
// Wrapped in IIFE to avoid polluting global scope (individual function names
|
|
4
|
+
// must not leak, or background.js/sidepanel.js delegate consts will collide).
|
|
5
|
+
|
|
6
|
+
(function () {
|
|
7
|
+
const DEFAULT_DOMAIN_DISPLAY_MAP = {
|
|
8
|
+
"github.com": "GitHub",
|
|
9
|
+
"docs.google.com": "Google Docs",
|
|
10
|
+
"sheets.google.com": "Google Sheets",
|
|
11
|
+
"drive.google.com": "Google Drive",
|
|
12
|
+
"mail.google.com": "Gmail",
|
|
13
|
+
"app.asana.com": "Asana",
|
|
14
|
+
"linear.app": "Linear",
|
|
15
|
+
"slack.com": "Slack",
|
|
16
|
+
"notion.so": "Notion",
|
|
17
|
+
"figma.com": "Figma",
|
|
18
|
+
"app.shortwave.com": "Shortwave",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const EQUANAUT_PROVIDER_LABELS = {
|
|
22
|
+
gateway: "Equabot Gateway",
|
|
23
|
+
anthropic: "Anthropic",
|
|
24
|
+
openai: "OpenAI",
|
|
25
|
+
"openai-codex": "OpenAI Codex",
|
|
26
|
+
google: "Google Gemini",
|
|
27
|
+
xai: "xAI",
|
|
28
|
+
deepseek: "DeepSeek",
|
|
29
|
+
perplexity: "Perplexity",
|
|
30
|
+
ollama: "Local Ollama",
|
|
31
|
+
"ollama-cloud": "Ollama Cloud",
|
|
32
|
+
local: "Local Ollama",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const EQUANAUT_PROVIDER_ORDER = [
|
|
36
|
+
"gateway",
|
|
37
|
+
"anthropic",
|
|
38
|
+
"openai",
|
|
39
|
+
"openai-codex",
|
|
40
|
+
"google",
|
|
41
|
+
"xai",
|
|
42
|
+
"deepseek",
|
|
43
|
+
"perplexity",
|
|
44
|
+
"ollama-cloud",
|
|
45
|
+
"ollama",
|
|
46
|
+
"local",
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
const EQUANAUT_PROVIDER_GLYPHS = {
|
|
50
|
+
gateway: "EQ",
|
|
51
|
+
anthropic: "AI",
|
|
52
|
+
openai: "OA",
|
|
53
|
+
"openai-codex": "CX",
|
|
54
|
+
google: "G",
|
|
55
|
+
xai: "xAI",
|
|
56
|
+
deepseek: "DS",
|
|
57
|
+
perplexity: "PX",
|
|
58
|
+
ollama: "OL",
|
|
59
|
+
"ollama-cloud": "OC",
|
|
60
|
+
local: "OL",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const EQUANAUT_MODEL_CAPABILITIES = {
|
|
64
|
+
fast: "Fast",
|
|
65
|
+
vision: "Vision",
|
|
66
|
+
reasoning: "Reasoning",
|
|
67
|
+
tools: "Tools",
|
|
68
|
+
image: "Image",
|
|
69
|
+
pdf: "PDF",
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const EQUANAUT_MODEL_CAPABILITY_ORDER = ["fast", "vision", "reasoning", "tools", "image", "pdf"];
|
|
73
|
+
|
|
74
|
+
const EQUANAUT_COMMAND_CENTER_FALLBACK_MODELS = [
|
|
75
|
+
{
|
|
76
|
+
id: "gateway:default",
|
|
77
|
+
name: "Equabot Gateway (Default)",
|
|
78
|
+
provider: "gateway",
|
|
79
|
+
type: "cloud",
|
|
80
|
+
status: "unknown",
|
|
81
|
+
source: "command-center-fallback",
|
|
82
|
+
details: "Uses the Equa account gateway primary model",
|
|
83
|
+
isDefault: true,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: "gateway:anthropic/claude-opus-4-5",
|
|
87
|
+
name: "Claude Opus 4.5",
|
|
88
|
+
provider: "anthropic",
|
|
89
|
+
type: "cloud",
|
|
90
|
+
status: "unknown",
|
|
91
|
+
source: "command-center-fallback",
|
|
92
|
+
contextLength: 200000,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "gateway:anthropic/claude-sonnet-4-20250514",
|
|
96
|
+
name: "Claude Sonnet 4",
|
|
97
|
+
provider: "anthropic",
|
|
98
|
+
type: "cloud",
|
|
99
|
+
status: "unknown",
|
|
100
|
+
source: "command-center-fallback",
|
|
101
|
+
contextLength: 200000,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: "gateway:openai/gpt-5.2",
|
|
105
|
+
name: "GPT-5.2 (OpenAI)",
|
|
106
|
+
provider: "openai",
|
|
107
|
+
type: "cloud",
|
|
108
|
+
status: "unknown",
|
|
109
|
+
source: "command-center-fallback",
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: "gateway:google/gemini-3-pro-preview",
|
|
113
|
+
name: "Gemini 3.1 Pro (Google)",
|
|
114
|
+
provider: "google",
|
|
115
|
+
type: "cloud",
|
|
116
|
+
status: "unknown",
|
|
117
|
+
source: "command-center-fallback",
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: "gateway:google/gemini-2.5-pro",
|
|
121
|
+
name: "Gemini 2.5 Pro",
|
|
122
|
+
provider: "google",
|
|
123
|
+
type: "cloud",
|
|
124
|
+
status: "unknown",
|
|
125
|
+
source: "command-center-fallback",
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: "gateway:xai/grok-3",
|
|
129
|
+
name: "Grok 3",
|
|
130
|
+
provider: "xai",
|
|
131
|
+
type: "cloud",
|
|
132
|
+
status: "unknown",
|
|
133
|
+
source: "command-center-fallback",
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: "gateway:deepseek/deepseek-chat",
|
|
137
|
+
name: "DeepSeek Chat",
|
|
138
|
+
provider: "deepseek",
|
|
139
|
+
type: "cloud",
|
|
140
|
+
status: "unknown",
|
|
141
|
+
source: "command-center-fallback",
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
id: "deepseek-coder-v2:latest",
|
|
145
|
+
name: "deepseek-coder-v2:latest",
|
|
146
|
+
provider: "ollama",
|
|
147
|
+
type: "local",
|
|
148
|
+
status: "unknown",
|
|
149
|
+
source: "command-center-fallback",
|
|
150
|
+
details: "Command Center standalone fallback",
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: "qwen2.5:72b",
|
|
154
|
+
name: "qwen2.5:72b",
|
|
155
|
+
provider: "ollama",
|
|
156
|
+
type: "local",
|
|
157
|
+
status: "unknown",
|
|
158
|
+
source: "command-center-fallback",
|
|
159
|
+
details: "Command Center standalone fallback",
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
id: "llama3.3:70b",
|
|
163
|
+
name: "llama3.3:70b",
|
|
164
|
+
provider: "ollama",
|
|
165
|
+
type: "local",
|
|
166
|
+
status: "unknown",
|
|
167
|
+
source: "command-center-fallback",
|
|
168
|
+
details: "Command Center standalone fallback",
|
|
169
|
+
},
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
function normalizeProviderKey(provider) {
|
|
173
|
+
return String(provider || "")
|
|
174
|
+
.trim()
|
|
175
|
+
.toLowerCase();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function providerLabel(provider) {
|
|
179
|
+
const key = normalizeProviderKey(provider);
|
|
180
|
+
return EQUANAUT_PROVIDER_LABELS[key] || (provider ? String(provider) : "Other");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function getEquanautProviderGlyph(provider) {
|
|
184
|
+
const key = normalizeProviderKey(provider);
|
|
185
|
+
if (EQUANAUT_PROVIDER_GLYPHS[key]) return EQUANAUT_PROVIDER_GLYPHS[key];
|
|
186
|
+
const label = providerLabel(key);
|
|
187
|
+
const letters = label.match(/[A-Za-z0-9]/g) || [];
|
|
188
|
+
return (letters[0] || "?").toUpperCase() + (letters[1] || "").toUpperCase();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function getEquanautProviderFilterKey(model) {
|
|
192
|
+
if (!model) return "gateway";
|
|
193
|
+
if (model.location === "local") return "ollama";
|
|
194
|
+
if (model.location === "cloud" && model.provider === "ollama") return "ollama-cloud";
|
|
195
|
+
return (
|
|
196
|
+
normalizeProviderKey(model.provider || inferEquanautProviderFromId(model.id)) || "gateway"
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function getEquanautModelSearchText(model) {
|
|
201
|
+
const capabilities = Array.isArray(model?.capabilities)
|
|
202
|
+
? model.capabilities
|
|
203
|
+
: getEquanautModelCapabilities(model);
|
|
204
|
+
return [
|
|
205
|
+
model?.id,
|
|
206
|
+
model?.sourceId,
|
|
207
|
+
model?.name,
|
|
208
|
+
model?.provider,
|
|
209
|
+
model?.providerLabel,
|
|
210
|
+
model?.location,
|
|
211
|
+
model?.route,
|
|
212
|
+
model?.meta,
|
|
213
|
+
model?.details,
|
|
214
|
+
model?.source,
|
|
215
|
+
model?.costTier || getEquanautModelCostTier(model),
|
|
216
|
+
...capabilities,
|
|
217
|
+
...capabilities.map((capability) => EQUANAUT_MODEL_CAPABILITIES[capability]),
|
|
218
|
+
]
|
|
219
|
+
.filter(Boolean)
|
|
220
|
+
.join(" ")
|
|
221
|
+
.toLowerCase();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function getEquanautModelCostTier(model) {
|
|
225
|
+
const text = [
|
|
226
|
+
model?.id,
|
|
227
|
+
model?.name,
|
|
228
|
+
model?.provider,
|
|
229
|
+
model?.providerLabel,
|
|
230
|
+
model?.location,
|
|
231
|
+
model?.meta,
|
|
232
|
+
model?.details,
|
|
233
|
+
]
|
|
234
|
+
.filter(Boolean)
|
|
235
|
+
.join(" ")
|
|
236
|
+
.toLowerCase();
|
|
237
|
+
|
|
238
|
+
if (model?.location === "local") return "$";
|
|
239
|
+
if (/(flash|fast|mini|nano|haiku|instant|cheap|low cost|gpt-oss)/i.test(text)) return "$";
|
|
240
|
+
if (/(opus|gpt-5|gpt5|grok|frontier|pro preview|3\.1 pro|4\.5)/i.test(text)) return "$$$";
|
|
241
|
+
if (/(sonnet|pro|default|gateway|claude|gemini|openai|anthropic)/i.test(text)) return "$$";
|
|
242
|
+
return "$$";
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function getEquanautModelCapabilities(model) {
|
|
246
|
+
const text = [
|
|
247
|
+
model?.id,
|
|
248
|
+
model?.name,
|
|
249
|
+
model?.provider,
|
|
250
|
+
model?.providerLabel,
|
|
251
|
+
model?.location,
|
|
252
|
+
model?.route,
|
|
253
|
+
model?.meta,
|
|
254
|
+
model?.details,
|
|
255
|
+
model?.source,
|
|
256
|
+
]
|
|
257
|
+
.filter(Boolean)
|
|
258
|
+
.join(" ")
|
|
259
|
+
.toLowerCase();
|
|
260
|
+
const caps = new Set();
|
|
261
|
+
const contextLength = Number(model?.contextLength) || 0;
|
|
262
|
+
|
|
263
|
+
if (/(flash|fast|mini|nano|haiku|instant|gpt-oss|qwen|llama|local|ollama)/.test(text)) {
|
|
264
|
+
caps.add("fast");
|
|
265
|
+
}
|
|
266
|
+
if (/(vision|visual|image|multimodal|gemini|gpt-4o|gpt-image|claude-3)/.test(text)) {
|
|
267
|
+
caps.add("vision");
|
|
268
|
+
}
|
|
269
|
+
if (
|
|
270
|
+
/(reason|thinking|opus|sonnet|pro|gpt-5|gpt5|gpt-oss|grok|deepseek|qwen|glm|claude)/.test(
|
|
271
|
+
text
|
|
272
|
+
) ||
|
|
273
|
+
contextLength >= 64000
|
|
274
|
+
) {
|
|
275
|
+
caps.add("reasoning");
|
|
276
|
+
}
|
|
277
|
+
if (/(tool|function|gateway|command center|equabot|comet|mcp)/.test(text)) {
|
|
278
|
+
caps.add("tools");
|
|
279
|
+
}
|
|
280
|
+
if (/(image|imagen|dall|nano banana|gemini)/.test(text)) {
|
|
281
|
+
caps.add("image");
|
|
282
|
+
}
|
|
283
|
+
if (
|
|
284
|
+
/(pdf|document|long context|claude|gemini|gpt-5|gpt5|gpt-4|opus|sonnet)/.test(text) ||
|
|
285
|
+
contextLength >= 128000
|
|
286
|
+
) {
|
|
287
|
+
caps.add("pdf");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (model?.route === "command-center") caps.add("tools");
|
|
291
|
+
|
|
292
|
+
return EQUANAUT_MODEL_CAPABILITY_ORDER.filter((capability) => caps.has(capability));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function getEquanautCapabilityLabel(capability) {
|
|
296
|
+
return EQUANAUT_MODEL_CAPABILITIES[capability] || String(capability || "");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function filterEquanautModels(models, opts) {
|
|
300
|
+
const options = opts || {};
|
|
301
|
+
const routingMode = options.routingMode || "auto";
|
|
302
|
+
const provider = normalizeProviderKey(options.provider || "all") || "all";
|
|
303
|
+
const capability = String(options.capability || "all")
|
|
304
|
+
.trim()
|
|
305
|
+
.toLowerCase();
|
|
306
|
+
const query = String(options.query || "")
|
|
307
|
+
.trim()
|
|
308
|
+
.toLowerCase();
|
|
309
|
+
|
|
310
|
+
return (models || []).filter((model) => {
|
|
311
|
+
if (!isEquanautModelAllowedForRouting(model, routingMode)) return false;
|
|
312
|
+
if (provider !== "all" && getEquanautProviderFilterKey(model) !== provider) return false;
|
|
313
|
+
if (
|
|
314
|
+
capability &&
|
|
315
|
+
capability !== "all" &&
|
|
316
|
+
!getEquanautModelCapabilities(model).includes(capability)
|
|
317
|
+
) {
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
if (query && !getEquanautModelSearchText(model).includes(query)) return false;
|
|
321
|
+
return true;
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function normalizeGatewayModelPath(provider, modelId) {
|
|
326
|
+
const id = String(modelId || "").trim();
|
|
327
|
+
if (!id) return "";
|
|
328
|
+
if (id === "default" || id.includes("/")) return id;
|
|
329
|
+
const key = normalizeProviderKey(provider);
|
|
330
|
+
return key ? `${key}/${id}` : id;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function formatEquanautContextLength(value) {
|
|
334
|
+
const n = Number(value);
|
|
335
|
+
if (!Number.isFinite(n) || n <= 0) return "";
|
|
336
|
+
if (n >= 1000000) return `${(n / 1000000).toFixed(n % 1000000 === 0 ? 0 : 1)}M ctx`;
|
|
337
|
+
if (n >= 1000) return `${Math.round(n / 1000)}k ctx`;
|
|
338
|
+
return `${n} ctx`;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function inferEquanautProviderFromId(id) {
|
|
342
|
+
const raw = String(id || "").replace(/^gateway:/, "");
|
|
343
|
+
if (raw.includes("/")) return raw.split("/")[0];
|
|
344
|
+
if (/^claude-|^sonnet-|^opus-/i.test(raw)) return "anthropic";
|
|
345
|
+
if (/^gpt-|^o[0-9]/i.test(raw)) return "openai";
|
|
346
|
+
if (/^gemini-/i.test(raw)) return "google";
|
|
347
|
+
if (/^grok-/i.test(raw)) return "xai";
|
|
348
|
+
if (/^deepseek/i.test(raw)) return raw.includes(":") ? "ollama" : "deepseek";
|
|
349
|
+
return raw.includes(":") ? "ollama" : "gateway";
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function normalizeEquanautModelEntry(entry, opts) {
|
|
353
|
+
if (!entry || typeof entry !== "object") return null;
|
|
354
|
+
const options = opts || {};
|
|
355
|
+
const source = String(options.source || entry.source || "").trim() || "unknown";
|
|
356
|
+
const rawId = String(entry.id || entry.model || entry.name || "").trim();
|
|
357
|
+
if (!rawId || rawId === "ollama:offline") return null;
|
|
358
|
+
|
|
359
|
+
const rawProvider = normalizeProviderKey(entry.provider || entry.providerKey);
|
|
360
|
+
const rawType = String(entry.type || "")
|
|
361
|
+
.trim()
|
|
362
|
+
.toLowerCase();
|
|
363
|
+
const status =
|
|
364
|
+
String(entry.status || "")
|
|
365
|
+
.trim()
|
|
366
|
+
.toLowerCase() || "unknown";
|
|
367
|
+
|
|
368
|
+
let id = rawId;
|
|
369
|
+
let provider = rawProvider || inferEquanautProviderFromId(rawId);
|
|
370
|
+
let location = rawType === "local" ? "local" : "";
|
|
371
|
+
let route = "standalone";
|
|
372
|
+
|
|
373
|
+
if (id.startsWith("ollama:")) {
|
|
374
|
+
id = id.slice("ollama:".length);
|
|
375
|
+
provider = "ollama";
|
|
376
|
+
location = "local";
|
|
377
|
+
} else if (id.startsWith("gateway:")) {
|
|
378
|
+
provider = inferEquanautProviderFromId(id) || provider;
|
|
379
|
+
location = "gateway";
|
|
380
|
+
route = "command-center";
|
|
381
|
+
} else if (source === "ollama" && rawType === "cloud") {
|
|
382
|
+
provider = "ollama";
|
|
383
|
+
location = "cloud";
|
|
384
|
+
} else if (
|
|
385
|
+
source === "gateway-rpc" &&
|
|
386
|
+
provider &&
|
|
387
|
+
provider !== "ollama" &&
|
|
388
|
+
provider !== "local"
|
|
389
|
+
) {
|
|
390
|
+
id = `gateway:${normalizeGatewayModelPath(provider, id)}`;
|
|
391
|
+
location = "gateway";
|
|
392
|
+
route = "command-center";
|
|
393
|
+
} else if (rawType === "cloud") {
|
|
394
|
+
location = "gateway";
|
|
395
|
+
route = "command-center";
|
|
396
|
+
} else if (!location) {
|
|
397
|
+
location = provider === "ollama" || id.includes(":") ? "local" : "gateway";
|
|
398
|
+
if (location === "gateway") route = "command-center";
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (location === "gateway") route = "command-center";
|
|
402
|
+
if (provider === "local") provider = "ollama";
|
|
403
|
+
|
|
404
|
+
const contextLength =
|
|
405
|
+
Number(entry.contextLength || entry.contextWindow || entry.contextTokens) || undefined;
|
|
406
|
+
const contextMeta = formatEquanautContextLength(contextLength);
|
|
407
|
+
const statusMeta = status && status !== "available" && status !== "unknown" ? status : "";
|
|
408
|
+
const detailMeta =
|
|
409
|
+
typeof entry.details === "string" && entry.details.trim() ? entry.details.trim() : "";
|
|
410
|
+
const sizeMeta =
|
|
411
|
+
typeof entry.sizeGb === "number" && entry.sizeGb > 0 ? `${entry.sizeGb}GB` : "";
|
|
412
|
+
const meta = [contextMeta, sizeMeta, detailMeta, statusMeta].filter(Boolean).join(" - ");
|
|
413
|
+
|
|
414
|
+
const normalized = {
|
|
415
|
+
id,
|
|
416
|
+
name: String(entry.name || id).trim() || id,
|
|
417
|
+
provider,
|
|
418
|
+
providerLabel: providerLabel(provider),
|
|
419
|
+
type: location === "local" ? "local" : "chat",
|
|
420
|
+
location,
|
|
421
|
+
route,
|
|
422
|
+
contextLength,
|
|
423
|
+
status,
|
|
424
|
+
enabled: entry.enabled !== false && status !== "offline",
|
|
425
|
+
isDefault: Boolean(entry.isDefault),
|
|
426
|
+
source,
|
|
427
|
+
sourceId: rawId,
|
|
428
|
+
meta,
|
|
429
|
+
};
|
|
430
|
+
normalized.providerKey = getEquanautProviderFilterKey(normalized);
|
|
431
|
+
normalized.providerGlyph = getEquanautProviderGlyph(normalized.providerKey);
|
|
432
|
+
normalized.costTier = getEquanautModelCostTier(normalized);
|
|
433
|
+
normalized.capabilities = getEquanautModelCapabilities(normalized);
|
|
434
|
+
return normalized;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function normalizeEquanautOllamaModel(entry) {
|
|
438
|
+
const id = String(entry?.name || entry?.model || "").trim();
|
|
439
|
+
if (!id) return null;
|
|
440
|
+
const isCloud = /:cloud$/.test(id) || (entry.size === 0 && !entry?.details?.family);
|
|
441
|
+
const family = entry?.details?.family || id.split(":")[0] || id;
|
|
442
|
+
const params = entry?.details?.parameter_size || "";
|
|
443
|
+
const sizeGb = entry.size ? (entry.size / 1024 ** 3).toFixed(1) + "GB" : "";
|
|
444
|
+
return normalizeEquanautModelEntry(
|
|
445
|
+
{
|
|
446
|
+
id,
|
|
447
|
+
name: id,
|
|
448
|
+
provider: isCloud ? "ollama" : family,
|
|
449
|
+
type: isCloud ? "cloud" : "local",
|
|
450
|
+
status: "available",
|
|
451
|
+
details: [params, sizeGb].filter(Boolean).join(" - ") || (isCloud ? "Ollama Cloud" : ""),
|
|
452
|
+
source: "ollama",
|
|
453
|
+
},
|
|
454
|
+
{ source: "ollama" }
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function modelsFromEquanautGatewayConfig(raw, opts) {
|
|
459
|
+
const config = raw && typeof raw === "object" ? raw : {};
|
|
460
|
+
const source = opts?.source || "gateway-config";
|
|
461
|
+
const models = [];
|
|
462
|
+
const primaryModel =
|
|
463
|
+
typeof config.primaryModel === "string" && config.primaryModel.trim()
|
|
464
|
+
? config.primaryModel.trim()
|
|
465
|
+
: typeof config.primary === "string" && config.primary.trim()
|
|
466
|
+
? config.primary.trim()
|
|
467
|
+
: "default";
|
|
468
|
+
models.push(
|
|
469
|
+
normalizeEquanautModelEntry(
|
|
470
|
+
{
|
|
471
|
+
id: primaryModel === "default" ? "gateway:default" : `gateway:${primaryModel}`,
|
|
472
|
+
name: primaryModel === "default" ? "Equabot Gateway (Default)" : primaryModel,
|
|
473
|
+
provider: inferEquanautProviderFromId(primaryModel),
|
|
474
|
+
type: "cloud",
|
|
475
|
+
status: "available",
|
|
476
|
+
source,
|
|
477
|
+
isDefault: true,
|
|
478
|
+
details: "Gateway primary model",
|
|
479
|
+
},
|
|
480
|
+
{ source }
|
|
481
|
+
)
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
const providers = Array.isArray(config.providers)
|
|
485
|
+
? config.providers
|
|
486
|
+
: config.providers && typeof config.providers === "object"
|
|
487
|
+
? Object.entries(config.providers).map(([key, value]) =>
|
|
488
|
+
typeof value === "object" && value
|
|
489
|
+
? { key, ...value }
|
|
490
|
+
: { key, models: Array.isArray(value) ? value : [] }
|
|
491
|
+
)
|
|
492
|
+
: [];
|
|
493
|
+
for (const provider of providers) {
|
|
494
|
+
const key = normalizeProviderKey(provider?.key || provider?.provider || provider?.id);
|
|
495
|
+
if (!key) continue;
|
|
496
|
+
const providerModels = Array.isArray(provider.models)
|
|
497
|
+
? provider.models
|
|
498
|
+
: provider.models && typeof provider.models === "object"
|
|
499
|
+
? Object.values(provider.models)
|
|
500
|
+
: [];
|
|
501
|
+
for (const model of providerModels) {
|
|
502
|
+
const modelId =
|
|
503
|
+
typeof model === "string"
|
|
504
|
+
? model.trim()
|
|
505
|
+
: typeof model?.id === "string"
|
|
506
|
+
? model.id.trim()
|
|
507
|
+
: "";
|
|
508
|
+
if (!modelId) continue;
|
|
509
|
+
const modelName =
|
|
510
|
+
typeof model === "object" && typeof model.name === "string" && model.name.trim()
|
|
511
|
+
? model.name.trim()
|
|
512
|
+
: modelId;
|
|
513
|
+
models.push(
|
|
514
|
+
normalizeEquanautModelEntry(
|
|
515
|
+
{
|
|
516
|
+
id: `gateway:${normalizeGatewayModelPath(key, modelId)}`,
|
|
517
|
+
name: modelName,
|
|
518
|
+
provider: key,
|
|
519
|
+
type: "cloud",
|
|
520
|
+
status: "available",
|
|
521
|
+
source,
|
|
522
|
+
details: `Routes through Equabot Gateway via ${key}`,
|
|
523
|
+
},
|
|
524
|
+
{ source }
|
|
525
|
+
)
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return models.filter(Boolean);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function normalizeEquanautModelCatalog(raw, opts) {
|
|
533
|
+
if (!raw) return [];
|
|
534
|
+
const options = opts || {};
|
|
535
|
+
if (Array.isArray(raw)) {
|
|
536
|
+
return raw.map((entry) => normalizeEquanautModelEntry(entry, options)).filter(Boolean);
|
|
537
|
+
}
|
|
538
|
+
if (typeof raw !== "object") return [];
|
|
539
|
+
if (Array.isArray(raw.models) || Array.isArray(raw.items)) {
|
|
540
|
+
return (raw.models || raw.items)
|
|
541
|
+
.map((entry) => normalizeEquanautModelEntry(entry, options))
|
|
542
|
+
.filter(Boolean);
|
|
543
|
+
}
|
|
544
|
+
if (Array.isArray(raw.providers) || (raw.providers && typeof raw.providers === "object")) {
|
|
545
|
+
return modelsFromEquanautGatewayConfig(raw, options);
|
|
546
|
+
}
|
|
547
|
+
return [];
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function sortEquanautModelCatalog(models) {
|
|
551
|
+
return [...(models || [])].sort((a, b) => {
|
|
552
|
+
const ai = EQUANAUT_PROVIDER_ORDER.indexOf(a.provider);
|
|
553
|
+
const bi = EQUANAUT_PROVIDER_ORDER.indexOf(b.provider);
|
|
554
|
+
const ar = ai === -1 ? 999 : ai;
|
|
555
|
+
const br = bi === -1 ? 999 : bi;
|
|
556
|
+
if (ar !== br) return ar - br;
|
|
557
|
+
const al = a.location === "local" ? 1 : 0;
|
|
558
|
+
const bl = b.location === "local" ? 1 : 0;
|
|
559
|
+
if (al !== bl) return al - bl;
|
|
560
|
+
return String(a.name || a.id).localeCompare(String(b.name || b.id));
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function mergeEquanautModelCatalogs() {
|
|
565
|
+
const merged = new Map();
|
|
566
|
+
const lists = Array.from(arguments).flat().filter(Boolean);
|
|
567
|
+
for (const entry of lists) {
|
|
568
|
+
const normalized =
|
|
569
|
+
entry && entry.id && entry.location
|
|
570
|
+
? entry
|
|
571
|
+
: normalizeEquanautModelEntry(entry, { source: "merge" });
|
|
572
|
+
if (!normalized) continue;
|
|
573
|
+
if (!merged.has(normalized.id)) {
|
|
574
|
+
merged.set(normalized.id, normalized);
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
const prior = merged.get(normalized.id);
|
|
578
|
+
merged.set(normalized.id, {
|
|
579
|
+
...prior,
|
|
580
|
+
...normalized,
|
|
581
|
+
meta: normalized.meta || prior.meta,
|
|
582
|
+
enabled: prior.enabled || normalized.enabled,
|
|
583
|
+
isDefault: prior.isDefault || normalized.isDefault,
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
return sortEquanautModelCatalog(Array.from(merged.values()));
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function groupEquanautModelCatalog(models) {
|
|
590
|
+
const groups = [];
|
|
591
|
+
const byKey = new Map();
|
|
592
|
+
for (const model of sortEquanautModelCatalog(models || [])) {
|
|
593
|
+
const key =
|
|
594
|
+
model.location === "local"
|
|
595
|
+
? "ollama"
|
|
596
|
+
: model.location === "cloud"
|
|
597
|
+
? "ollama-cloud"
|
|
598
|
+
: model.provider || "gateway";
|
|
599
|
+
if (!byKey.has(key)) {
|
|
600
|
+
const group = { key, label: providerLabel(key), entries: [] };
|
|
601
|
+
byKey.set(key, group);
|
|
602
|
+
groups.push(group);
|
|
603
|
+
}
|
|
604
|
+
byKey.get(key).entries.push(model);
|
|
605
|
+
}
|
|
606
|
+
return groups;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function getEquanautCommandCenterFallbackModels() {
|
|
610
|
+
return EQUANAUT_COMMAND_CENTER_FALLBACK_MODELS.map((entry) =>
|
|
611
|
+
normalizeEquanautModelEntry(entry, { source: "command-center-fallback" })
|
|
612
|
+
).filter(Boolean);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function getEquanautModelBadge(model) {
|
|
616
|
+
if (model?.location === "local") return { label: "Local", className: "local" };
|
|
617
|
+
if (model?.route === "command-center") return { label: "Gateway", className: "gateway" };
|
|
618
|
+
return { label: "Cloud", className: "cloud" };
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function getEquanautModelDisplayName(modelOrId) {
|
|
622
|
+
const raw =
|
|
623
|
+
typeof modelOrId === "object" && modelOrId
|
|
624
|
+
? modelOrId.name || modelOrId.id || modelOrId.sourceId
|
|
625
|
+
: modelOrId;
|
|
626
|
+
const value = String(raw || "").trim();
|
|
627
|
+
if (!value) return "Choose model";
|
|
628
|
+
if (value === "gateway:default" || /^equabot gateway \(default\)$/i.test(value)) {
|
|
629
|
+
return "Gateway Default";
|
|
630
|
+
}
|
|
631
|
+
const withoutGateway = value.replace(/^gateway:/, "");
|
|
632
|
+
const withoutProvider = withoutGateway.includes("/")
|
|
633
|
+
? withoutGateway.split("/").slice(1).join("/")
|
|
634
|
+
: withoutGateway;
|
|
635
|
+
const compact = withoutProvider
|
|
636
|
+
.replace(/\s+\((openai|google|anthropic|xai|ollama|deepseek|perplexity)\)$/i, "")
|
|
637
|
+
.trim();
|
|
638
|
+
return compact || value;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function isEquanautModelConfigured(model) {
|
|
642
|
+
if (!model || typeof model !== "object") return false;
|
|
643
|
+
const status = String(model.status || "")
|
|
644
|
+
.trim()
|
|
645
|
+
.toLowerCase();
|
|
646
|
+
return model.enabled !== false && status === "available";
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function getEquanautModelSettingsUrl(modelOrId, reason) {
|
|
650
|
+
const id =
|
|
651
|
+
typeof modelOrId === "object" && modelOrId
|
|
652
|
+
? modelOrId.id || modelOrId.sourceId || modelOrId.name
|
|
653
|
+
: modelOrId;
|
|
654
|
+
const url = new URL("https://app.equa.cc/equabotz/settings");
|
|
655
|
+
url.searchParams.set("section", "models");
|
|
656
|
+
if (id) url.searchParams.set("model", String(id));
|
|
657
|
+
if (reason) url.searchParams.set("reason", String(reason));
|
|
658
|
+
url.searchParams.set("source", "browser-extension");
|
|
659
|
+
return url.toString();
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function isEquanautCommandCenterModel(modelId) {
|
|
663
|
+
return String(modelId || "").startsWith("gateway:");
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function resolveEquanautOllamaModelId(modelId) {
|
|
667
|
+
const id = String(modelId || "").trim();
|
|
668
|
+
if (!id || isEquanautCommandCenterModel(id)) return null;
|
|
669
|
+
return id.startsWith("ollama:") ? id.slice("ollama:".length) : id;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function normalizeEquanautRoutingMode(mode) {
|
|
673
|
+
const value = String(mode || "")
|
|
674
|
+
.trim()
|
|
675
|
+
.toLowerCase();
|
|
676
|
+
return value === "local" || value === "cloud-only" ? value : "auto";
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function isEquanautModelAllowedForRouting(model, routingMode) {
|
|
680
|
+
const mode = normalizeEquanautRoutingMode(routingMode);
|
|
681
|
+
if (!model || model.enabled === false) return false;
|
|
682
|
+
if (mode === "local") return model.location === "local";
|
|
683
|
+
if (mode === "cloud-only") return model.route === "command-center";
|
|
684
|
+
return true;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function pickEquanautModelForRoutingMode(models, currentId, routingMode, fallbackId) {
|
|
688
|
+
const mode = normalizeEquanautRoutingMode(routingMode);
|
|
689
|
+
const list = Array.isArray(models) ? models : [];
|
|
690
|
+
const current = list.find((model) => model.id === currentId);
|
|
691
|
+
if (isEquanautModelAllowedForRouting(current, mode)) return current.id;
|
|
692
|
+
|
|
693
|
+
const preferredDefault = list.find(
|
|
694
|
+
(model) => model.isDefault && isEquanautModelAllowedForRouting(model, mode)
|
|
695
|
+
);
|
|
696
|
+
if (preferredDefault) return preferredDefault.id;
|
|
697
|
+
|
|
698
|
+
const available = list.find(
|
|
699
|
+
(model) => model.status === "available" && isEquanautModelAllowedForRouting(model, mode)
|
|
700
|
+
);
|
|
701
|
+
if (available) return available.id;
|
|
702
|
+
|
|
703
|
+
const anyAllowed = list.find((model) => isEquanautModelAllowedForRouting(model, mode));
|
|
704
|
+
if (anyAllowed) return anyAllowed.id;
|
|
705
|
+
|
|
706
|
+
const fallback = String(fallbackId || "").trim();
|
|
707
|
+
if (mode === "cloud-only")
|
|
708
|
+
return fallback.startsWith("gateway:") ? fallback : "gateway:default";
|
|
709
|
+
if (mode === "local") return fallback && !fallback.startsWith("gateway:") ? fallback : "";
|
|
710
|
+
return fallback || "gateway:default";
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function timeAgo(ts, now) {
|
|
714
|
+
if (now === undefined) now = Date.now();
|
|
715
|
+
const seconds = Math.floor((now - ts) / 1000);
|
|
716
|
+
if (seconds < 60) return "just now";
|
|
717
|
+
const minutes = Math.floor(seconds / 60);
|
|
718
|
+
if (minutes < 60) return `${minutes} min ago`;
|
|
719
|
+
const hours = Math.floor(minutes / 60);
|
|
720
|
+
if (hours < 24) return `${hours}h ago`;
|
|
721
|
+
const days = Math.floor(hours / 24);
|
|
722
|
+
return `${days}d ago`;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function fsFuzzyMatch(query, text) {
|
|
726
|
+
const lower = text.toLowerCase();
|
|
727
|
+
const q = query.toLowerCase();
|
|
728
|
+
if (lower.includes(q)) return true;
|
|
729
|
+
let qi = 0;
|
|
730
|
+
for (let i = 0; i < lower.length && qi < q.length; i++) {
|
|
731
|
+
if (lower[i] === q[qi]) qi++;
|
|
732
|
+
}
|
|
733
|
+
return qi === q.length;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function fsFormatEventDescription(event) {
|
|
737
|
+
const title = event.title || event.groupTitle || "";
|
|
738
|
+
switch (event.type) {
|
|
739
|
+
case "group_created":
|
|
740
|
+
return "Created group" + (title ? ': "' + title + '"' : "");
|
|
741
|
+
case "group_archived":
|
|
742
|
+
return "Archived" + (title ? ': "' + title + '"' : "");
|
|
743
|
+
case "group_restored":
|
|
744
|
+
return "Restored" + (title ? ': "' + title + '"' : "");
|
|
745
|
+
case "group_deleted":
|
|
746
|
+
return "Deleted" + (title ? ': "' + title + '"' : "");
|
|
747
|
+
case "save_and_close":
|
|
748
|
+
return (
|
|
749
|
+
"Saved & closed " +
|
|
750
|
+
(event.tabCount || "?") +
|
|
751
|
+
" tabs in " +
|
|
752
|
+
(event.groupCount || "?") +
|
|
753
|
+
" groups"
|
|
754
|
+
);
|
|
755
|
+
case "lifecycle_start":
|
|
756
|
+
return "Agent started: " + (event.agentId || "unknown");
|
|
757
|
+
case "lifecycle_complete":
|
|
758
|
+
return "Agent completed: " + (event.agentId || "unknown");
|
|
759
|
+
case "lifecycle_abort":
|
|
760
|
+
return "Agent aborted: " + (event.agentId || "unknown");
|
|
761
|
+
case "tab_deleted":
|
|
762
|
+
return "Removed tab from " + (title || "group");
|
|
763
|
+
case "bulk_action":
|
|
764
|
+
return (event.action || "Bulk action") + " on " + (event.count || "?") + " items";
|
|
765
|
+
default:
|
|
766
|
+
return event.type || "Unknown action";
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
function fsApplyFilters(groups, query, filterMode, hideArchived) {
|
|
771
|
+
let filtered = groups;
|
|
772
|
+
|
|
773
|
+
if (filterMode === "named") {
|
|
774
|
+
filtered = filtered.filter(
|
|
775
|
+
(g) => g.title && g.title !== "Untitled" && g.title !== "Ungrouped Tabs"
|
|
776
|
+
);
|
|
777
|
+
} else if (filterMode === "folders") {
|
|
778
|
+
filtered = filtered.filter((g) => g.folderId);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (hideArchived) {
|
|
782
|
+
filtered = filtered.filter((g) => g.status !== "archived");
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
if (query) {
|
|
786
|
+
filtered = filtered.filter((g) => {
|
|
787
|
+
const tabs = g.urls || g.tabs || [];
|
|
788
|
+
return (
|
|
789
|
+
(g.title || "").toLowerCase().includes(query) ||
|
|
790
|
+
tabs.some(
|
|
791
|
+
(t) =>
|
|
792
|
+
(t.title || "").toLowerCase().includes(query) ||
|
|
793
|
+
(t.url || "").toLowerCase().includes(query)
|
|
794
|
+
)
|
|
795
|
+
);
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
return filtered;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function normalizeArchiveWorkflowStatus(status) {
|
|
803
|
+
const raw = String(status || "")
|
|
804
|
+
.trim()
|
|
805
|
+
.toLowerCase();
|
|
806
|
+
if (raw === "done") return "done";
|
|
807
|
+
if (raw === "trashed") return "trashed";
|
|
808
|
+
return "pending";
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
function fsPartitionTaskthreadGroups(groups) {
|
|
812
|
+
const pending = [];
|
|
813
|
+
const done = [];
|
|
814
|
+
|
|
815
|
+
for (const group of Array.isArray(groups) ? groups : []) {
|
|
816
|
+
const status = normalizeArchiveWorkflowStatus(group?.status);
|
|
817
|
+
if (status === "pending") pending.push(group);
|
|
818
|
+
if (status === "done") done.push(group);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
return { pending, done };
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Extended filter engine for the full-screen filter bar
|
|
825
|
+
function fsApplyAdvancedFilters(groups, opts) {
|
|
826
|
+
let filtered = groups;
|
|
827
|
+
|
|
828
|
+
// Sidebar filter mode (legacy compat)
|
|
829
|
+
if (opts.filterMode === "named") {
|
|
830
|
+
filtered = filtered.filter(
|
|
831
|
+
(g) => g.title && g.title !== "Untitled" && g.title !== "Ungrouped Tabs"
|
|
832
|
+
);
|
|
833
|
+
} else if (opts.filterMode === "folders") {
|
|
834
|
+
filtered = filtered.filter((g) => g.folderId);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// Status filter
|
|
838
|
+
if (opts.status && opts.status !== "all") {
|
|
839
|
+
const expectedStatus = normalizeArchiveWorkflowStatus(opts.status);
|
|
840
|
+
filtered = filtered.filter(
|
|
841
|
+
(g) => normalizeArchiveWorkflowStatus(g.status) === expectedStatus
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Color filter (array of active colors)
|
|
846
|
+
if (opts.colors && opts.colors.length > 0) {
|
|
847
|
+
filtered = filtered.filter((g) => opts.colors.includes(g.color || "grey"));
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Starred only
|
|
851
|
+
if (opts.starred) {
|
|
852
|
+
filtered = filtered.filter((g) => g.starred);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// Locked only
|
|
856
|
+
if (opts.locked) {
|
|
857
|
+
filtered = filtered.filter((g) =>
|
|
858
|
+
opts.lockedSet ? opts.lockedSet.has(g.taskThreadId || g.id) : g.locked
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// Stale only (>7 days old)
|
|
863
|
+
if (opts.stale) {
|
|
864
|
+
const staleThreshold = 7 * 24 * 60 * 60 * 1000;
|
|
865
|
+
const now = Date.now();
|
|
866
|
+
filtered = filtered.filter((g) => {
|
|
867
|
+
const ts = g.archivedAt || g.closedAt;
|
|
868
|
+
return ts && now - new Date(ts).getTime() > staleThreshold;
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// Tab count range
|
|
873
|
+
if (opts.minTabs > 0) {
|
|
874
|
+
filtered = filtered.filter((g) => (g.urls || g.tabs || []).length >= opts.minTabs);
|
|
875
|
+
}
|
|
876
|
+
if (opts.maxTabs > 0 && opts.maxTabs < Infinity) {
|
|
877
|
+
filtered = filtered.filter((g) => (g.urls || g.tabs || []).length <= opts.maxTabs);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// Text search (title + description + tab titles + tab URLs + tab descriptions)
|
|
881
|
+
if (opts.query) {
|
|
882
|
+
const q = opts.query.toLowerCase();
|
|
883
|
+
filtered = filtered.filter((g) => {
|
|
884
|
+
const tabs = g.urls || g.tabs || [];
|
|
885
|
+
return (
|
|
886
|
+
(g.title || "").toLowerCase().includes(q) ||
|
|
887
|
+
normalizeEntityDescription(g).toLowerCase().includes(q) ||
|
|
888
|
+
tabs.some(
|
|
889
|
+
(t) =>
|
|
890
|
+
(t.title || "").toLowerCase().includes(q) ||
|
|
891
|
+
(t.url || "").toLowerCase().includes(q) ||
|
|
892
|
+
normalizeEntityDescription(t).toLowerCase().includes(q)
|
|
893
|
+
)
|
|
894
|
+
);
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
return filtered;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Sort groups by various criteria
|
|
902
|
+
function fsSortGroups(groups, sortBy) {
|
|
903
|
+
const sorted = [...groups];
|
|
904
|
+
switch (sortBy) {
|
|
905
|
+
case "newest":
|
|
906
|
+
sorted.sort((a, b) => {
|
|
907
|
+
const ta = new Date(a.archivedAt || a.closedAt || 0).getTime();
|
|
908
|
+
const tb = new Date(b.archivedAt || b.closedAt || 0).getTime();
|
|
909
|
+
return tb - ta;
|
|
910
|
+
});
|
|
911
|
+
break;
|
|
912
|
+
case "oldest":
|
|
913
|
+
sorted.sort((a, b) => {
|
|
914
|
+
const ta = new Date(a.archivedAt || a.closedAt || 0).getTime();
|
|
915
|
+
const tb = new Date(b.archivedAt || b.closedAt || 0).getTime();
|
|
916
|
+
return ta - tb;
|
|
917
|
+
});
|
|
918
|
+
break;
|
|
919
|
+
case "az":
|
|
920
|
+
sorted.sort((a, b) => (a.title || "Untitled").localeCompare(b.title || "Untitled"));
|
|
921
|
+
break;
|
|
922
|
+
case "za":
|
|
923
|
+
sorted.sort((a, b) => (b.title || "Untitled").localeCompare(a.title || "Untitled"));
|
|
924
|
+
break;
|
|
925
|
+
case "most-tabs":
|
|
926
|
+
sorted.sort((a, b) => (b.urls || b.tabs || []).length - (a.urls || a.tabs || []).length);
|
|
927
|
+
break;
|
|
928
|
+
case "fewest-tabs":
|
|
929
|
+
sorted.sort((a, b) => (a.urls || a.tabs || []).length - (b.urls || b.tabs || []).length);
|
|
930
|
+
break;
|
|
931
|
+
default:
|
|
932
|
+
break;
|
|
933
|
+
}
|
|
934
|
+
return sorted;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// Group groups into sections by a property
|
|
938
|
+
function fsGroupBy(groups, groupByKey) {
|
|
939
|
+
if (!groupByKey || groupByKey === "none") {
|
|
940
|
+
return [{ key: null, label: null, groups: groups }];
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
const sections = new Map();
|
|
944
|
+
|
|
945
|
+
for (const group of groups) {
|
|
946
|
+
let sectionKey, sectionLabel;
|
|
947
|
+
|
|
948
|
+
switch (groupByKey) {
|
|
949
|
+
case "color": {
|
|
950
|
+
sectionKey = group.color || "grey";
|
|
951
|
+
sectionLabel = sectionKey.charAt(0).toUpperCase() + sectionKey.slice(1);
|
|
952
|
+
break;
|
|
953
|
+
}
|
|
954
|
+
case "status": {
|
|
955
|
+
sectionKey = normalizeArchiveWorkflowStatus(group.status);
|
|
956
|
+
const statusLabels = { pending: "Pending", done: "Done", trashed: "Trashed" };
|
|
957
|
+
sectionLabel = statusLabels[sectionKey] || sectionKey;
|
|
958
|
+
break;
|
|
959
|
+
}
|
|
960
|
+
case "date": {
|
|
961
|
+
const ts = new Date(group.archivedAt || group.closedAt || 0).getTime();
|
|
962
|
+
const now = Date.now();
|
|
963
|
+
const dayMs = 24 * 60 * 60 * 1000;
|
|
964
|
+
const age = now - ts;
|
|
965
|
+
if (age < dayMs) {
|
|
966
|
+
sectionKey = "today";
|
|
967
|
+
sectionLabel = "Today";
|
|
968
|
+
} else if (age < 7 * dayMs) {
|
|
969
|
+
sectionKey = "this-week";
|
|
970
|
+
sectionLabel = "This Week";
|
|
971
|
+
} else if (age < 30 * dayMs) {
|
|
972
|
+
sectionKey = "this-month";
|
|
973
|
+
sectionLabel = "This Month";
|
|
974
|
+
} else {
|
|
975
|
+
sectionKey = "older";
|
|
976
|
+
sectionLabel = "Older";
|
|
977
|
+
}
|
|
978
|
+
break;
|
|
979
|
+
}
|
|
980
|
+
case "size": {
|
|
981
|
+
const tabCount = (group.urls || group.tabs || []).length;
|
|
982
|
+
if (tabCount === 0) {
|
|
983
|
+
sectionKey = "empty";
|
|
984
|
+
sectionLabel = "Empty";
|
|
985
|
+
} else if (tabCount <= 3) {
|
|
986
|
+
sectionKey = "small";
|
|
987
|
+
sectionLabel = "Small (1-3 tabs)";
|
|
988
|
+
} else if (tabCount <= 10) {
|
|
989
|
+
sectionKey = "medium";
|
|
990
|
+
sectionLabel = "Medium (4-10 tabs)";
|
|
991
|
+
} else {
|
|
992
|
+
sectionKey = "large";
|
|
993
|
+
sectionLabel = "Large (10+ tabs)";
|
|
994
|
+
}
|
|
995
|
+
break;
|
|
996
|
+
}
|
|
997
|
+
default:
|
|
998
|
+
sectionKey = "other";
|
|
999
|
+
sectionLabel = "Other";
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
if (!sections.has(sectionKey)) {
|
|
1003
|
+
sections.set(sectionKey, { key: sectionKey, label: sectionLabel, groups: [] });
|
|
1004
|
+
}
|
|
1005
|
+
sections.get(sectionKey).groups.push(group);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
return Array.from(sections.values());
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
function fsResolveSelectedTabs(selectedSet, cachedGroups) {
|
|
1012
|
+
const result = [];
|
|
1013
|
+
for (const key of selectedSet) {
|
|
1014
|
+
const lastDash = key.lastIndexOf("-");
|
|
1015
|
+
if (lastDash === -1) continue;
|
|
1016
|
+
const groupId = key.slice(0, lastDash);
|
|
1017
|
+
const idx = parseInt(key.slice(lastDash + 1));
|
|
1018
|
+
const group = cachedGroups.find((g) => (g.taskThreadId || g.id) === groupId);
|
|
1019
|
+
if (!group) continue;
|
|
1020
|
+
const tabs = group.urls || group.tabs || [];
|
|
1021
|
+
if (idx >= 0 && idx < tabs.length) {
|
|
1022
|
+
result.push({ groupId, tabIdx: idx, tab: tabs[idx] });
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
return result;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
function getDomainDisplayName(url, domainMap) {
|
|
1029
|
+
if (domainMap === undefined) domainMap = DEFAULT_DOMAIN_DISPLAY_MAP;
|
|
1030
|
+
try {
|
|
1031
|
+
const hostname = new URL(url).hostname.replace(/^www\./, "");
|
|
1032
|
+
return domainMap[hostname] || hostname;
|
|
1033
|
+
} catch {
|
|
1034
|
+
return "Other";
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
const DEFAULT_SAFETY_REMINDER_HOST_ALLOWLIST = Object.freeze([
|
|
1039
|
+
"localhost",
|
|
1040
|
+
"127.0.0.1",
|
|
1041
|
+
"*.equa.test",
|
|
1042
|
+
"*.up.railway.app",
|
|
1043
|
+
"staging.equa.cc",
|
|
1044
|
+
"*.staging.equa.cc",
|
|
1045
|
+
"*.preview.equa.cc",
|
|
1046
|
+
"*.dev.equa.cc",
|
|
1047
|
+
]);
|
|
1048
|
+
|
|
1049
|
+
const EQUANAUT_COMMAND_LIBRARY_GROUPS = Object.freeze([
|
|
1050
|
+
{
|
|
1051
|
+
plugin: "Comet",
|
|
1052
|
+
pluginId: "comet",
|
|
1053
|
+
source: "skill",
|
|
1054
|
+
category: "browser_control",
|
|
1055
|
+
rootPath: "/Users/shawnowen/.agents/skills",
|
|
1056
|
+
order: 10,
|
|
1057
|
+
requiredTools: ["comet_connect"],
|
|
1058
|
+
keywords: ["browser", "comet", "agent", "sidecar"],
|
|
1059
|
+
template:
|
|
1060
|
+
"Use this Comet browser skill only after comet_connect establishes the owned tab group and top-display window proof.",
|
|
1061
|
+
commands: [
|
|
1062
|
+
{
|
|
1063
|
+
id: "comet-agent",
|
|
1064
|
+
title: "Comet agent",
|
|
1065
|
+
description:
|
|
1066
|
+
"Load the comet-agent browser orchestration skill and route browser work through Comet-Bridge.",
|
|
1067
|
+
requiredTools: ["comet_connect", "comet_ask", "comet_screenshot", "comet_read_page"],
|
|
1068
|
+
aliases: ["browser-agent", "comet-browser-agent"],
|
|
1069
|
+
keywords: ["browser", "orchestrator", "agent", "sidecar", "skills"],
|
|
1070
|
+
template:
|
|
1071
|
+
"Use the comet-agent skill. Start with comet_connect, verify top-display windowed full-display placement, then use the owned tab group only.",
|
|
1072
|
+
},
|
|
1073
|
+
"comet-assistant",
|
|
1074
|
+
"comet-auto",
|
|
1075
|
+
"comet-browse",
|
|
1076
|
+
{ id: "comet-connect", title: "comet_connect", aliases: ["connect", "browser-connect"] },
|
|
1077
|
+
"comet-delegate",
|
|
1078
|
+
{
|
|
1079
|
+
id: "comet-ask",
|
|
1080
|
+
skillName: "comet-agent",
|
|
1081
|
+
title: "comet_ask",
|
|
1082
|
+
requiredTools: ["comet_connect", "comet_ask"],
|
|
1083
|
+
aliases: ["ask", "browser-ask"],
|
|
1084
|
+
},
|
|
1085
|
+
"comet-interact",
|
|
1086
|
+
"comet-keepalive",
|
|
1087
|
+
"comet-labs",
|
|
1088
|
+
"comet-monitor",
|
|
1089
|
+
"comet-network",
|
|
1090
|
+
"comet-pdf",
|
|
1091
|
+
"comet-read",
|
|
1092
|
+
{
|
|
1093
|
+
id: "comet-read-page",
|
|
1094
|
+
skillName: "comet-read",
|
|
1095
|
+
title: "comet_read_page",
|
|
1096
|
+
requiredTools: ["comet_connect", "comet_read_page"],
|
|
1097
|
+
aliases: ["read-page", "browser-read"],
|
|
1098
|
+
},
|
|
1099
|
+
"comet-research",
|
|
1100
|
+
"comet-scrape",
|
|
1101
|
+
{
|
|
1102
|
+
id: "comet-screenshot",
|
|
1103
|
+
title: "comet_screenshot",
|
|
1104
|
+
requiredTools: ["comet_connect", "comet_screenshot"],
|
|
1105
|
+
aliases: ["screenshot", "browser-screenshot"],
|
|
1106
|
+
},
|
|
1107
|
+
"comet-search",
|
|
1108
|
+
"comet-session",
|
|
1109
|
+
"comet-shortcuts",
|
|
1110
|
+
"comet-skill-inventory",
|
|
1111
|
+
"comet-workspace",
|
|
1112
|
+
],
|
|
1113
|
+
},
|
|
1114
|
+
{
|
|
1115
|
+
plugin: "Spequa",
|
|
1116
|
+
pluginId: "spequa",
|
|
1117
|
+
source: "plugin",
|
|
1118
|
+
category: "spequa",
|
|
1119
|
+
rootPath: "/Users/shawnowen/.codex/plugins/cache/equa-local/spequa/0.9.2/skills",
|
|
1120
|
+
prefix: "spequa:",
|
|
1121
|
+
order: 200,
|
|
1122
|
+
keywords: ["spequa", "sdd", "spec", "pipeline"],
|
|
1123
|
+
template:
|
|
1124
|
+
"Route to the Spequa command skill and follow its source prompt under /Users/shawnowen/dev/equa/tools/spequa/.codex/prompts/.",
|
|
1125
|
+
commands: [
|
|
1126
|
+
{ id: "spequa", fullId: "spequa", aliases: ["spequa:spequa"] },
|
|
1127
|
+
"0-pipeline",
|
|
1128
|
+
"1-constitution",
|
|
1129
|
+
"2-problems",
|
|
1130
|
+
"3-spequafy",
|
|
1131
|
+
"3.0-diagnose",
|
|
1132
|
+
"3.1-analyze-domain",
|
|
1133
|
+
"4-clarify",
|
|
1134
|
+
"4.1-expert-decision",
|
|
1135
|
+
"5-plan",
|
|
1136
|
+
"5.1-research",
|
|
1137
|
+
"6-tasks",
|
|
1138
|
+
"6.1-taskstoissues",
|
|
1139
|
+
"7-checklist",
|
|
1140
|
+
"8-analyze",
|
|
1141
|
+
"9-test",
|
|
1142
|
+
"10-implement",
|
|
1143
|
+
"11-docs",
|
|
1144
|
+
"12-score-docs",
|
|
1145
|
+
"13-code-review",
|
|
1146
|
+
"14-create-pull-request",
|
|
1147
|
+
"14.1-pr-checks",
|
|
1148
|
+
"15-comet-review",
|
|
1149
|
+
"15.1-comet-pr-review",
|
|
1150
|
+
"16-close",
|
|
1151
|
+
],
|
|
1152
|
+
},
|
|
1153
|
+
{
|
|
1154
|
+
plugin: "Equa Web Test Skills",
|
|
1155
|
+
pluginId: "equa-web-test-skills",
|
|
1156
|
+
source: "skill",
|
|
1157
|
+
category: "qa",
|
|
1158
|
+
rootPath: "/Users/shawnowen/.agents/skills",
|
|
1159
|
+
order: 500,
|
|
1160
|
+
keywords: ["equa", "qa", "test", "smoke", "browser"],
|
|
1161
|
+
template:
|
|
1162
|
+
"Route to the requested Equa QA skill and preserve the repo-specific browser validation contract.",
|
|
1163
|
+
commands: [
|
|
1164
|
+
"auth-flow-qa",
|
|
1165
|
+
"dataroom-document-checklist",
|
|
1166
|
+
"e2e-auth",
|
|
1167
|
+
"e2e-captable",
|
|
1168
|
+
"e2e-full",
|
|
1169
|
+
"e2e-org",
|
|
1170
|
+
"e2e-smoke",
|
|
1171
|
+
"equa-orchestrator-qa",
|
|
1172
|
+
"equa-start-dev",
|
|
1173
|
+
"equa-web-test-skills",
|
|
1174
|
+
"mattermost-messaging-qa",
|
|
1175
|
+
"pr-review-browser-qa",
|
|
1176
|
+
],
|
|
1177
|
+
},
|
|
1178
|
+
{
|
|
1179
|
+
plugin: "Knowledge Base",
|
|
1180
|
+
pluginId: "knowledge-base",
|
|
1181
|
+
source: "skill",
|
|
1182
|
+
category: "knowledge",
|
|
1183
|
+
rootPath: "/Users/shawnowen/.agents/skills",
|
|
1184
|
+
order: 650,
|
|
1185
|
+
keywords: ["kb", "knowledge", "entity", "search"],
|
|
1186
|
+
template:
|
|
1187
|
+
"Route to the Knowledge Base skill and use the user's arguments as the lookup query.",
|
|
1188
|
+
commands: [
|
|
1189
|
+
"kb-ask",
|
|
1190
|
+
"kb-entity",
|
|
1191
|
+
"kb-index",
|
|
1192
|
+
"kb-search",
|
|
1193
|
+
"task-threads",
|
|
1194
|
+
"task-blocker-analysis",
|
|
1195
|
+
],
|
|
1196
|
+
},
|
|
1197
|
+
{
|
|
1198
|
+
plugin: "Local Agents",
|
|
1199
|
+
pluginId: "local-agents",
|
|
1200
|
+
source: "skill",
|
|
1201
|
+
category: "local_agent",
|
|
1202
|
+
rootPath: "/Users/shawnowen/.agents/skills",
|
|
1203
|
+
order: 760,
|
|
1204
|
+
keywords: ["local", "agent", "handoff", "llm"],
|
|
1205
|
+
template:
|
|
1206
|
+
"Route to the requested local agent skill and keep the invocation as structured sidecar context.",
|
|
1207
|
+
commands: [
|
|
1208
|
+
"cursor-agent-handoff",
|
|
1209
|
+
"email-thread-research",
|
|
1210
|
+
"gitguardian-remediate",
|
|
1211
|
+
"linear-tasks",
|
|
1212
|
+
"local-analysis",
|
|
1213
|
+
"local-code",
|
|
1214
|
+
"local-creative",
|
|
1215
|
+
"local-llm",
|
|
1216
|
+
"mattermost-infra-deploy",
|
|
1217
|
+
"source-command-handoff-cursor-agent",
|
|
1218
|
+
"switch-model",
|
|
1219
|
+
],
|
|
1220
|
+
},
|
|
1221
|
+
{
|
|
1222
|
+
plugin: "QuickBooks Ops",
|
|
1223
|
+
pluginId: "qbo",
|
|
1224
|
+
source: "skill",
|
|
1225
|
+
category: "finance",
|
|
1226
|
+
rootPath: "/Users/shawnowen/.agents/skills",
|
|
1227
|
+
order: 850,
|
|
1228
|
+
keywords: ["qbo", "quickbooks", "accounting", "finance"],
|
|
1229
|
+
template: "Route to the requested QBO skill and preserve accounting workflow context.",
|
|
1230
|
+
commands: [
|
|
1231
|
+
"qbo-accounting",
|
|
1232
|
+
"qbo-ar-review",
|
|
1233
|
+
"qbo-bank-fetch",
|
|
1234
|
+
"qbo-bill-email",
|
|
1235
|
+
"qbo-bill-open",
|
|
1236
|
+
"qbo-journal-entry",
|
|
1237
|
+
"qbo-month-close",
|
|
1238
|
+
"qbo-pay-bill",
|
|
1239
|
+
"qbo-reconcile",
|
|
1240
|
+
"qbo-report",
|
|
1241
|
+
"qbo-tax-prep",
|
|
1242
|
+
"qbo-vendor-reconcile",
|
|
1243
|
+
],
|
|
1244
|
+
},
|
|
1245
|
+
{
|
|
1246
|
+
plugin: "Prescription Refill SOP",
|
|
1247
|
+
pluginId: "prescription-refill",
|
|
1248
|
+
source: "skill",
|
|
1249
|
+
category: "personal_ops",
|
|
1250
|
+
rootPath: "/Users/shawnowen/.agents/skills/prescription-refill-skills",
|
|
1251
|
+
order: 970,
|
|
1252
|
+
keywords: ["prescription", "refill", "mychart", "costco"],
|
|
1253
|
+
template:
|
|
1254
|
+
"Route to the requested prescription refill SOP skill and preserve health workflow context.",
|
|
1255
|
+
commands: [
|
|
1256
|
+
"costco-pharmacy-phone-call",
|
|
1257
|
+
"log-refill-event-to-workbook",
|
|
1258
|
+
"monitor-shortwave-refill-emails",
|
|
1259
|
+
"prescription-refill-sop-orchestrator",
|
|
1260
|
+
"submit-mychart-refill-request",
|
|
1261
|
+
"update-refill-tracker-doc",
|
|
1262
|
+
"verify-mychart-refill-approval",
|
|
1263
|
+
],
|
|
1264
|
+
},
|
|
1265
|
+
{
|
|
1266
|
+
plugin: "SVF Operations",
|
|
1267
|
+
pluginId: "svf",
|
|
1268
|
+
source: "skill",
|
|
1269
|
+
category: "property_ops",
|
|
1270
|
+
rootPath: "/Users/shawnowen/.codex/skills",
|
|
1271
|
+
order: 1080,
|
|
1272
|
+
keywords: ["svf", "property", "documents", "sales"],
|
|
1273
|
+
template: "Route to the requested Sugarloaf Valley Farms skill.",
|
|
1274
|
+
commands: [
|
|
1275
|
+
"svf-chain-of-title",
|
|
1276
|
+
"svf-document-intake",
|
|
1277
|
+
"svf-north-lots-sales-ops",
|
|
1278
|
+
"svf-property-manager",
|
|
1279
|
+
"svf-task-thread-orchestrator",
|
|
1280
|
+
],
|
|
1281
|
+
},
|
|
1282
|
+
{
|
|
1283
|
+
plugin: "Codex Local Skills",
|
|
1284
|
+
pluginId: "codex-local",
|
|
1285
|
+
source: "skill",
|
|
1286
|
+
category: "codex",
|
|
1287
|
+
rootPath: "/Users/shawnowen/.codex/skills",
|
|
1288
|
+
order: 1160,
|
|
1289
|
+
keywords: ["codex", "local", "screen", "migration"],
|
|
1290
|
+
template: "Route to the requested local Codex skill.",
|
|
1291
|
+
commands: ["chronicle", "migrate-to-codex"],
|
|
1292
|
+
},
|
|
1293
|
+
{
|
|
1294
|
+
plugin: "Browser Use",
|
|
1295
|
+
pluginId: "browser-use",
|
|
1296
|
+
source: "plugin",
|
|
1297
|
+
category: "browser",
|
|
1298
|
+
rootPath:
|
|
1299
|
+
"/Users/shawnowen/.codex/plugins/cache/openai-bundled/browser-use/0.1.0-alpha2/skills",
|
|
1300
|
+
prefix: "browser-use:",
|
|
1301
|
+
order: 1240,
|
|
1302
|
+
keywords: ["browser", "in-app", "openai"],
|
|
1303
|
+
template:
|
|
1304
|
+
"Route to the Browser Use plugin skill when the in-app browser is explicitly requested.",
|
|
1305
|
+
commands: ["browser"],
|
|
1306
|
+
},
|
|
1307
|
+
{
|
|
1308
|
+
plugin: "Computer Use",
|
|
1309
|
+
pluginId: "computer-use",
|
|
1310
|
+
source: "plugin",
|
|
1311
|
+
category: "desktop",
|
|
1312
|
+
rootPath: "/Users/shawnowen/.codex/plugins/cache/openai-bundled/computer-use/1.0.780/skills",
|
|
1313
|
+
prefix: "computer-use:",
|
|
1314
|
+
order: 1260,
|
|
1315
|
+
keywords: ["computer", "desktop", "macos"],
|
|
1316
|
+
template: "Route to the Computer Use plugin skill for desktop-level evidence or UI control.",
|
|
1317
|
+
commands: ["computer-use"],
|
|
1318
|
+
},
|
|
1319
|
+
{
|
|
1320
|
+
plugin: "Figma",
|
|
1321
|
+
pluginId: "figma",
|
|
1322
|
+
source: "plugin",
|
|
1323
|
+
category: "design",
|
|
1324
|
+
rootPath: "/Users/shawnowen/.codex/plugins/cache/openai-curated/figma/63976030/skills",
|
|
1325
|
+
prefix: "figma:",
|
|
1326
|
+
order: 1300,
|
|
1327
|
+
keywords: ["figma", "design", "figjam"],
|
|
1328
|
+
template: "Route to the requested Figma skill and follow its prerequisite instructions.",
|
|
1329
|
+
commands: [
|
|
1330
|
+
{ id: "figma-code-connect", skillName: "figma-code-connect-components" },
|
|
1331
|
+
"figma-create-design-system-rules",
|
|
1332
|
+
"figma-create-new-file",
|
|
1333
|
+
"figma-generate-design",
|
|
1334
|
+
"figma-generate-library",
|
|
1335
|
+
"figma-implement-design",
|
|
1336
|
+
"figma-use",
|
|
1337
|
+
],
|
|
1338
|
+
},
|
|
1339
|
+
{
|
|
1340
|
+
plugin: "GitHub",
|
|
1341
|
+
pluginId: "github",
|
|
1342
|
+
source: "plugin",
|
|
1343
|
+
category: "devops",
|
|
1344
|
+
rootPath: "/Users/shawnowen/.codex/plugins/cache/openai-curated/github/63976030/skills",
|
|
1345
|
+
prefix: "github:",
|
|
1346
|
+
order: 1410,
|
|
1347
|
+
keywords: ["github", "pr", "issue", "ci"],
|
|
1348
|
+
template: "Route to the requested GitHub skill and preserve repository context.",
|
|
1349
|
+
commands: ["github", "gh-address-comments", "gh-fix-ci", "yeet"],
|
|
1350
|
+
},
|
|
1351
|
+
{
|
|
1352
|
+
plugin: "Gmail",
|
|
1353
|
+
pluginId: "gmail",
|
|
1354
|
+
source: "plugin",
|
|
1355
|
+
category: "workspace",
|
|
1356
|
+
rootPath: "/Users/shawnowen/.codex/plugins/cache/openai-curated/gmail/63976030/skills",
|
|
1357
|
+
prefix: "gmail:",
|
|
1358
|
+
order: 1480,
|
|
1359
|
+
keywords: ["gmail", "email", "inbox"],
|
|
1360
|
+
template: "Route to the requested Gmail skill with the user's mailbox context.",
|
|
1361
|
+
commands: ["gmail", "gmail-inbox-triage"],
|
|
1362
|
+
},
|
|
1363
|
+
{
|
|
1364
|
+
plugin: "Google Calendar",
|
|
1365
|
+
pluginId: "google-calendar",
|
|
1366
|
+
source: "plugin",
|
|
1367
|
+
category: "workspace",
|
|
1368
|
+
rootPath:
|
|
1369
|
+
"/Users/shawnowen/.codex/plugins/cache/openai-curated/google-calendar/63976030/skills",
|
|
1370
|
+
prefix: "google-calendar:",
|
|
1371
|
+
order: 1520,
|
|
1372
|
+
keywords: ["google", "calendar", "schedule"],
|
|
1373
|
+
template: "Route to the requested Google Calendar skill.",
|
|
1374
|
+
commands: [
|
|
1375
|
+
"google-calendar",
|
|
1376
|
+
"google-calendar-daily-brief",
|
|
1377
|
+
"google-calendar-free-up-time",
|
|
1378
|
+
"google-calendar-group-scheduler",
|
|
1379
|
+
"google-calendar-meeting-prep",
|
|
1380
|
+
],
|
|
1381
|
+
},
|
|
1382
|
+
{
|
|
1383
|
+
plugin: "Google Drive",
|
|
1384
|
+
pluginId: "google-drive",
|
|
1385
|
+
source: "plugin",
|
|
1386
|
+
category: "workspace",
|
|
1387
|
+
rootPath: "/Users/shawnowen/.codex/plugins/cache/openai-curated/google-drive/63976030/skills",
|
|
1388
|
+
prefix: "google-drive:",
|
|
1389
|
+
order: 1580,
|
|
1390
|
+
keywords: ["google", "drive", "docs", "sheets", "slides"],
|
|
1391
|
+
template: "Route to the requested Google Drive skill.",
|
|
1392
|
+
commands: [
|
|
1393
|
+
"google-drive",
|
|
1394
|
+
"google-docs",
|
|
1395
|
+
"google-drive-comments",
|
|
1396
|
+
"google-sheets",
|
|
1397
|
+
"google-slides",
|
|
1398
|
+
],
|
|
1399
|
+
},
|
|
1400
|
+
{
|
|
1401
|
+
plugin: "Linear",
|
|
1402
|
+
pluginId: "linear",
|
|
1403
|
+
source: "plugin",
|
|
1404
|
+
category: "planning",
|
|
1405
|
+
rootPath: "/Users/shawnowen/.codex/plugins/cache/openai-curated/linear/63976030/skills",
|
|
1406
|
+
prefix: "linear:",
|
|
1407
|
+
order: 1640,
|
|
1408
|
+
keywords: ["linear", "issues", "projects"],
|
|
1409
|
+
template: "Route to the Linear skill with issue or project context.",
|
|
1410
|
+
commands: ["linear"],
|
|
1411
|
+
},
|
|
1412
|
+
{
|
|
1413
|
+
plugin: "OpenAI Developers",
|
|
1414
|
+
pluginId: "openai-developers",
|
|
1415
|
+
source: "plugin",
|
|
1416
|
+
category: "developer",
|
|
1417
|
+
rootPath:
|
|
1418
|
+
"/Users/shawnowen/.codex/plugins/cache/openai-curated/openai-developers/63976030/skills",
|
|
1419
|
+
prefix: "openai-developers:",
|
|
1420
|
+
order: 1690,
|
|
1421
|
+
keywords: ["openai", "api", "agents", "chatgpt"],
|
|
1422
|
+
template: "Route to the requested OpenAI Developers skill.",
|
|
1423
|
+
commands: [
|
|
1424
|
+
"agents-sdk",
|
|
1425
|
+
"build-chatgpt-app",
|
|
1426
|
+
"chatgpt-app-submission",
|
|
1427
|
+
"openai-api-troubleshooting",
|
|
1428
|
+
"openai-platform-api-key",
|
|
1429
|
+
],
|
|
1430
|
+
},
|
|
1431
|
+
{
|
|
1432
|
+
plugin: "Documents",
|
|
1433
|
+
pluginId: "documents",
|
|
1434
|
+
source: "plugin",
|
|
1435
|
+
category: "document",
|
|
1436
|
+
rootPath:
|
|
1437
|
+
"/Users/shawnowen/.codex/plugins/cache/openai-primary-runtime/documents/26.506.11943/skills",
|
|
1438
|
+
prefix: "documents:",
|
|
1439
|
+
order: 1760,
|
|
1440
|
+
keywords: ["documents", "docx"],
|
|
1441
|
+
template: "Route to the Documents runtime skill.",
|
|
1442
|
+
commands: ["documents"],
|
|
1443
|
+
},
|
|
1444
|
+
{
|
|
1445
|
+
plugin: "Presentations",
|
|
1446
|
+
pluginId: "presentations",
|
|
1447
|
+
source: "plugin",
|
|
1448
|
+
category: "document",
|
|
1449
|
+
rootPath:
|
|
1450
|
+
"/Users/shawnowen/.codex/plugins/cache/openai-primary-runtime/presentations/26.506.11943/skills",
|
|
1451
|
+
prefix: "presentations:",
|
|
1452
|
+
order: 1780,
|
|
1453
|
+
keywords: ["presentations", "slides", "pptx"],
|
|
1454
|
+
template: "Route to the Presentations runtime skill.",
|
|
1455
|
+
commands: ["presentations"],
|
|
1456
|
+
},
|
|
1457
|
+
{
|
|
1458
|
+
plugin: "Spreadsheets",
|
|
1459
|
+
pluginId: "spreadsheets",
|
|
1460
|
+
source: "plugin",
|
|
1461
|
+
category: "document",
|
|
1462
|
+
rootPath:
|
|
1463
|
+
"/Users/shawnowen/.codex/plugins/cache/openai-primary-runtime/spreadsheets/26.506.11943/skills",
|
|
1464
|
+
prefix: "spreadsheets:",
|
|
1465
|
+
order: 1800,
|
|
1466
|
+
keywords: ["spreadsheets", "sheets", "xlsx"],
|
|
1467
|
+
template: "Route to the Spreadsheets runtime skill.",
|
|
1468
|
+
commands: ["spreadsheets"],
|
|
1469
|
+
},
|
|
1470
|
+
{
|
|
1471
|
+
plugin: "Superpowers",
|
|
1472
|
+
pluginId: "superpowers",
|
|
1473
|
+
source: "plugin",
|
|
1474
|
+
category: "workflow",
|
|
1475
|
+
rootPath:
|
|
1476
|
+
"/Users/shawnowen/.codex/plugins/cache/claude-plugins-official/superpowers/5.1.0/skills",
|
|
1477
|
+
prefix: "superpowers:",
|
|
1478
|
+
order: 1850,
|
|
1479
|
+
keywords: ["superpowers", "workflow", "planning", "tdd"],
|
|
1480
|
+
template: "Route to the requested Superpowers workflow skill.",
|
|
1481
|
+
commands: [
|
|
1482
|
+
"brainstorming",
|
|
1483
|
+
"dispatching-parallel-agents",
|
|
1484
|
+
"executing-plans",
|
|
1485
|
+
"finishing-a-development-branch",
|
|
1486
|
+
"receiving-code-review",
|
|
1487
|
+
"requesting-code-review",
|
|
1488
|
+
"subagent-driven-development",
|
|
1489
|
+
"systematic-debugging",
|
|
1490
|
+
"test-driven-development",
|
|
1491
|
+
"using-git-worktrees",
|
|
1492
|
+
"using-superpowers",
|
|
1493
|
+
"verification-before-completion",
|
|
1494
|
+
"writing-plans",
|
|
1495
|
+
"writing-skills",
|
|
1496
|
+
],
|
|
1497
|
+
},
|
|
1498
|
+
{
|
|
1499
|
+
plugin: "Security",
|
|
1500
|
+
pluginId: "codex-security",
|
|
1501
|
+
source: "plugin",
|
|
1502
|
+
category: "security",
|
|
1503
|
+
rootPath:
|
|
1504
|
+
"/Users/shawnowen/.codex/plugins/cache/openai-curated/codex-security/63976030/skills",
|
|
1505
|
+
prefix: "codex-security:",
|
|
1506
|
+
order: 2020,
|
|
1507
|
+
keywords: ["security", "threat", "scan"],
|
|
1508
|
+
template: "Route to the requested Codex Security skill.",
|
|
1509
|
+
commands: [
|
|
1510
|
+
"attack-path-analysis",
|
|
1511
|
+
"finding-discovery",
|
|
1512
|
+
"fix-finding",
|
|
1513
|
+
"security-scan",
|
|
1514
|
+
"threat-model",
|
|
1515
|
+
"validation",
|
|
1516
|
+
],
|
|
1517
|
+
},
|
|
1518
|
+
{
|
|
1519
|
+
plugin: "CodeRabbit",
|
|
1520
|
+
pluginId: "coderabbit",
|
|
1521
|
+
source: "plugin",
|
|
1522
|
+
category: "code_review",
|
|
1523
|
+
rootPath: "/Users/shawnowen/.codex/plugins/cache/openai-curated/coderabbit/63976030/skills",
|
|
1524
|
+
prefix: "coderabbit:",
|
|
1525
|
+
order: 2120,
|
|
1526
|
+
keywords: ["coderabbit", "review"],
|
|
1527
|
+
template: "Route to the CodeRabbit review skill.",
|
|
1528
|
+
commands: ["coderabbit-review"],
|
|
1529
|
+
},
|
|
1530
|
+
{
|
|
1531
|
+
plugin: "Frontend Design",
|
|
1532
|
+
pluginId: "frontend-design",
|
|
1533
|
+
source: "plugin",
|
|
1534
|
+
category: "design",
|
|
1535
|
+
rootPath:
|
|
1536
|
+
"/Users/shawnowen/.codex/plugins/cache/claude-plugins-official/frontend-design/local/skills",
|
|
1537
|
+
prefix: "frontend-design:",
|
|
1538
|
+
order: 2180,
|
|
1539
|
+
keywords: ["frontend", "design", "ui"],
|
|
1540
|
+
template: "Route to the Frontend Design skill.",
|
|
1541
|
+
commands: ["frontend-design"],
|
|
1542
|
+
},
|
|
1543
|
+
{
|
|
1544
|
+
plugin: "Hookify",
|
|
1545
|
+
pluginId: "hookify",
|
|
1546
|
+
source: "plugin",
|
|
1547
|
+
category: "writing",
|
|
1548
|
+
rootPath:
|
|
1549
|
+
"/Users/shawnowen/.codex/plugins/cache/claude-plugins-official/hookify/local/skills",
|
|
1550
|
+
prefix: "hookify:",
|
|
1551
|
+
order: 2220,
|
|
1552
|
+
keywords: ["hookify", "writing", "rules"],
|
|
1553
|
+
template: "Route to the Hookify writing-rules skill.",
|
|
1554
|
+
commands: [{ id: "writing-hookify-rules", skillName: "writing-rules" }],
|
|
1555
|
+
},
|
|
1556
|
+
{
|
|
1557
|
+
plugin: "Skill Creator",
|
|
1558
|
+
pluginId: "skill-creator",
|
|
1559
|
+
source: "plugin",
|
|
1560
|
+
category: "developer",
|
|
1561
|
+
rootPath:
|
|
1562
|
+
"/Users/shawnowen/.codex/plugins/cache/claude-plugins-official/skill-creator/local/skills",
|
|
1563
|
+
prefix: "skill-creator:",
|
|
1564
|
+
order: 2260,
|
|
1565
|
+
keywords: ["skill", "creator"],
|
|
1566
|
+
template: "Route to the Skill Creator skill.",
|
|
1567
|
+
commands: ["skill-creator"],
|
|
1568
|
+
},
|
|
1569
|
+
]);
|
|
1570
|
+
|
|
1571
|
+
const EQUANAUT_PLUGIN_LIBRARY_GROUPS = Object.freeze([
|
|
1572
|
+
["comet", "Comet Browser Skills", "local", "browser_control"],
|
|
1573
|
+
["spequa", "Spequa", "plugin", "spequa"],
|
|
1574
|
+
["equa-web-test-skills", "Equa Web Test Skills", "local", "qa"],
|
|
1575
|
+
["knowledge-base", "Knowledge Base", "local", "knowledge"],
|
|
1576
|
+
["local-agents", "Local Agents", "local", "local_agent"],
|
|
1577
|
+
["qbo", "QuickBooks Ops", "local", "finance"],
|
|
1578
|
+
["prescription-refill", "Prescription Refill SOP", "local", "personal_ops"],
|
|
1579
|
+
["svf", "SVF Operations", "local", "property_ops"],
|
|
1580
|
+
["codex-local", "Codex Local Skills", "local", "codex"],
|
|
1581
|
+
["browser-use", "Browser Use", "plugin", "browser"],
|
|
1582
|
+
["computer-use", "Computer Use", "plugin", "desktop"],
|
|
1583
|
+
["figma", "Figma", "plugin", "design"],
|
|
1584
|
+
["github", "GitHub", "plugin", "devops"],
|
|
1585
|
+
["gmail", "Gmail", "plugin", "workspace"],
|
|
1586
|
+
["google-calendar", "Google Calendar", "plugin", "workspace"],
|
|
1587
|
+
["google-drive", "Google Drive", "plugin", "workspace"],
|
|
1588
|
+
["linear", "Linear", "plugin", "planning"],
|
|
1589
|
+
["openai-developers", "OpenAI Developers", "plugin", "developer"],
|
|
1590
|
+
["documents", "Documents", "plugin", "document"],
|
|
1591
|
+
["presentations", "Presentations", "plugin", "document"],
|
|
1592
|
+
["spreadsheets", "Spreadsheets", "plugin", "document"],
|
|
1593
|
+
["superpowers", "Superpowers", "plugin", "workflow"],
|
|
1594
|
+
["codex-security", "Codex Security", "plugin", "security"],
|
|
1595
|
+
["coderabbit", "CodeRabbit", "plugin", "code_review"],
|
|
1596
|
+
["frontend-design", "Frontend Design", "plugin", "design"],
|
|
1597
|
+
["hookify", "Hookify", "plugin", "writing"],
|
|
1598
|
+
["skill-creator", "Skill Creator", "plugin", "developer"],
|
|
1599
|
+
]);
|
|
1600
|
+
|
|
1601
|
+
function humanizeSlashCommandTitle(value) {
|
|
1602
|
+
const tail = String(value || "")
|
|
1603
|
+
.split(":")
|
|
1604
|
+
.pop()
|
|
1605
|
+
.replace(/^spequa$/, "spequa")
|
|
1606
|
+
.replace(/[._-]+/g, " ")
|
|
1607
|
+
.trim();
|
|
1608
|
+
return tail ? tail.replace(/\b\w/g, (letter) => letter.toUpperCase()) : "Command";
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
function slashSkillPath(rootPath, skillName) {
|
|
1612
|
+
if (!rootPath || !skillName) return null;
|
|
1613
|
+
return `${rootPath}/${skillName}/SKILL.md`;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
function expandCommandLibraryGroup(group, groupIndex) {
|
|
1617
|
+
const commands = Array.isArray(group.commands) ? group.commands : [];
|
|
1618
|
+
const prefix = group.prefix || "";
|
|
1619
|
+
return commands.map((entry, index) => {
|
|
1620
|
+
const item = typeof entry === "string" ? { id: entry } : entry || {};
|
|
1621
|
+
const skillName = item.skillName || item.skill || item.id;
|
|
1622
|
+
const id = item.fullId || `${prefix}${item.id}`;
|
|
1623
|
+
const requiredTools = normalizeSlashCommandArray(item.requiredTools || group.requiredTools);
|
|
1624
|
+
const keywords = [
|
|
1625
|
+
...normalizeSlashCommandArray(group.keywords),
|
|
1626
|
+
...normalizeSlashCommandArray(item.keywords),
|
|
1627
|
+
];
|
|
1628
|
+
return {
|
|
1629
|
+
id,
|
|
1630
|
+
order: Number(group.order || groupIndex * 1000) + index,
|
|
1631
|
+
title: item.title || humanizeSlashCommandTitle(id),
|
|
1632
|
+
description:
|
|
1633
|
+
item.description ||
|
|
1634
|
+
`Load ${id} from the ${group.plugin} ${group.source === "plugin" ? "plugin" : "skill library"}.`,
|
|
1635
|
+
source: item.source || group.source || "skill",
|
|
1636
|
+
plugin: group.plugin,
|
|
1637
|
+
pluginId: group.pluginId,
|
|
1638
|
+
category: item.category || group.category || "general",
|
|
1639
|
+
skillName,
|
|
1640
|
+
skillPath:
|
|
1641
|
+
item.skillPath || slashSkillPath(group.rootPath, item.skillPathSegment || skillName),
|
|
1642
|
+
requiredTools,
|
|
1643
|
+
aliases: normalizeSlashCommandArray(item.aliases),
|
|
1644
|
+
keywords,
|
|
1645
|
+
template:
|
|
1646
|
+
item.template ||
|
|
1647
|
+
group.template ||
|
|
1648
|
+
`Route to ${id} and include the user's arguments as structured command context.`,
|
|
1649
|
+
};
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
function buildDefaultEquanautSlashCommands() {
|
|
1654
|
+
return EQUANAUT_COMMAND_LIBRARY_GROUPS.flatMap(expandCommandLibraryGroup);
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
function pluginCommandCount(pluginId) {
|
|
1658
|
+
return buildDefaultEquanautSlashCommands().filter((command) => command.pluginId === pluginId)
|
|
1659
|
+
.length;
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
const DEFAULT_EQUANAUT_SLASH_COMMANDS = Object.freeze(buildDefaultEquanautSlashCommands());
|
|
1663
|
+
const DEFAULT_EQUANAUT_PLUGIN_LIBRARY = Object.freeze(
|
|
1664
|
+
EQUANAUT_PLUGIN_LIBRARY_GROUPS.map(([id, name, source, category], index) => ({
|
|
1665
|
+
id,
|
|
1666
|
+
name,
|
|
1667
|
+
title: name,
|
|
1668
|
+
source,
|
|
1669
|
+
sourceFamily: "plugin",
|
|
1670
|
+
category,
|
|
1671
|
+
status: "installed",
|
|
1672
|
+
installed: true,
|
|
1673
|
+
commandCount: pluginCommandCount(id),
|
|
1674
|
+
readOnly: true,
|
|
1675
|
+
allowedOperations: ["inspect", "search", "invoke_slash_command"],
|
|
1676
|
+
order: (index + 1) * 10,
|
|
1677
|
+
}))
|
|
1678
|
+
);
|
|
1679
|
+
|
|
1680
|
+
function normalizeHostname(url) {
|
|
1681
|
+
try {
|
|
1682
|
+
return new URL(url).hostname.replace(/^www\./, "").toLowerCase();
|
|
1683
|
+
} catch {
|
|
1684
|
+
return "";
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
function hostMatchesPattern(hostname, pattern) {
|
|
1689
|
+
const normalizedPattern = String(pattern || "")
|
|
1690
|
+
.trim()
|
|
1691
|
+
.replace(/^www\./, "")
|
|
1692
|
+
.toLowerCase();
|
|
1693
|
+
if (!hostname || !normalizedPattern) return false;
|
|
1694
|
+
if (normalizedPattern.startsWith("*.")) {
|
|
1695
|
+
const suffix = normalizedPattern.slice(2);
|
|
1696
|
+
return hostname === suffix || hostname.endsWith(`.${suffix}`);
|
|
1697
|
+
}
|
|
1698
|
+
return hostname === normalizedPattern;
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
function isSafetyReminderAllowlisted(url, allowlist) {
|
|
1702
|
+
const hostname = normalizeHostname(url);
|
|
1703
|
+
const patterns = Array.isArray(allowlist) ? allowlist : DEFAULT_SAFETY_REMINDER_HOST_ALLOWLIST;
|
|
1704
|
+
return patterns.some((pattern) => hostMatchesPattern(hostname, pattern));
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
function shouldInjectSafetyReminder(url, allowlist) {
|
|
1708
|
+
return !isSafetyReminderAllowlisted(url, allowlist);
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
function normalizeSlashCommandId(value) {
|
|
1712
|
+
return String(value || "")
|
|
1713
|
+
.trim()
|
|
1714
|
+
.replace(/^\/+/, "")
|
|
1715
|
+
.toLowerCase();
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
function normalizeSlashCommandArray(value) {
|
|
1719
|
+
return Array.isArray(value)
|
|
1720
|
+
? value.map((item) => String(item || "").trim()).filter(Boolean)
|
|
1721
|
+
: [];
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
function normalizeEquanautSlashCommand(record, index) {
|
|
1725
|
+
const input = record || {};
|
|
1726
|
+
const id = normalizeSlashCommandId(
|
|
1727
|
+
input.id || input.name || input.title || `command-${index + 1}`
|
|
1728
|
+
);
|
|
1729
|
+
const title = String(input.title || input.name || `/${id}`).replace(/^\/+/, "");
|
|
1730
|
+
const requiredTools = normalizeSlashCommandArray(input.requiredTools || input.tools);
|
|
1731
|
+
const aliases = normalizeSlashCommandArray(input.aliases);
|
|
1732
|
+
const keywords = normalizeSlashCommandArray(input.keywords);
|
|
1733
|
+
return {
|
|
1734
|
+
id,
|
|
1735
|
+
name: `/${id}`,
|
|
1736
|
+
order: Number.isFinite(Number(input.order)) ? Number(input.order) : index + 1,
|
|
1737
|
+
title,
|
|
1738
|
+
description: String(input.description || "").trim(),
|
|
1739
|
+
sourceFamily: "slash_command",
|
|
1740
|
+
source: input.source || "skill",
|
|
1741
|
+
plugin: input.plugin || "Comet",
|
|
1742
|
+
category: input.category || "general",
|
|
1743
|
+
skillName: input.skillName || input.skill || null,
|
|
1744
|
+
skillPath: input.skillPath || null,
|
|
1745
|
+
requiredTools,
|
|
1746
|
+
aliases,
|
|
1747
|
+
keywords,
|
|
1748
|
+
template: String(input.template || "").trim(),
|
|
1749
|
+
status: input.installed === false ? "unavailable" : "installed",
|
|
1750
|
+
installed: input.installed !== false,
|
|
1751
|
+
readOnly: true,
|
|
1752
|
+
allowedOperations: ["inspect", "search", "invoke_slash_command"],
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
function normalizeEquanautPlugin(record, index) {
|
|
1757
|
+
const input = record || {};
|
|
1758
|
+
const id = normalizeSlashCommandId(
|
|
1759
|
+
input.id || input.name || input.title || `plugin-${index + 1}`
|
|
1760
|
+
);
|
|
1761
|
+
return {
|
|
1762
|
+
id,
|
|
1763
|
+
name: String(input.name || input.title || id).trim() || id,
|
|
1764
|
+
title: String(input.title || input.name || id).trim() || id,
|
|
1765
|
+
sourceFamily: "plugin",
|
|
1766
|
+
source: input.source || "plugin",
|
|
1767
|
+
category: input.category || "general",
|
|
1768
|
+
status: input.installed === false ? "unavailable" : input.status || "installed",
|
|
1769
|
+
installed: input.installed !== false,
|
|
1770
|
+
commandCount: Number.isFinite(Number(input.commandCount)) ? Number(input.commandCount) : 0,
|
|
1771
|
+
version: input.version || null,
|
|
1772
|
+
rootPath: input.rootPath || null,
|
|
1773
|
+
readOnly: true,
|
|
1774
|
+
allowedOperations: ["inspect", "search", "invoke_slash_command"],
|
|
1775
|
+
order: Number.isFinite(Number(input.order)) ? Number(input.order) : index + 1,
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
function buildEquanautSlashCommandCatalog(input) {
|
|
1780
|
+
const records = Array.isArray(input)
|
|
1781
|
+
? input
|
|
1782
|
+
: Array.isArray(input?.commands)
|
|
1783
|
+
? input.commands
|
|
1784
|
+
: DEFAULT_EQUANAUT_SLASH_COMMANDS;
|
|
1785
|
+
return records
|
|
1786
|
+
.map((record, index) => normalizeEquanautSlashCommand(record, index))
|
|
1787
|
+
.filter((record) => record.id)
|
|
1788
|
+
.sort((a, b) => a.order - b.order || a.id.localeCompare(b.id));
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
function buildEquanautPluginCatalog(input) {
|
|
1792
|
+
const records = Array.isArray(input)
|
|
1793
|
+
? input
|
|
1794
|
+
: Array.isArray(input?.plugins)
|
|
1795
|
+
? input.plugins
|
|
1796
|
+
: DEFAULT_EQUANAUT_PLUGIN_LIBRARY;
|
|
1797
|
+
return records
|
|
1798
|
+
.map((record, index) => normalizeEquanautPlugin(record, index))
|
|
1799
|
+
.filter((record) => record.id)
|
|
1800
|
+
.sort((a, b) => a.order - b.order || a.id.localeCompare(b.id));
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
function filterEquanautPlugins(catalog, query) {
|
|
1804
|
+
const plugins = buildEquanautPluginCatalog(catalog);
|
|
1805
|
+
const normalizedQuery = normalizeSlashCommandId(query);
|
|
1806
|
+
if (!normalizedQuery) return plugins;
|
|
1807
|
+
return plugins.filter((plugin) => {
|
|
1808
|
+
const haystack = [
|
|
1809
|
+
plugin.id,
|
|
1810
|
+
plugin.name,
|
|
1811
|
+
plugin.title,
|
|
1812
|
+
plugin.source,
|
|
1813
|
+
plugin.category,
|
|
1814
|
+
plugin.status,
|
|
1815
|
+
plugin.rootPath,
|
|
1816
|
+
]
|
|
1817
|
+
.filter(Boolean)
|
|
1818
|
+
.join(" ")
|
|
1819
|
+
.toLowerCase();
|
|
1820
|
+
return haystack.includes(normalizedQuery);
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
function filterEquanautSlashCommands(catalog, query) {
|
|
1825
|
+
const commands = buildEquanautSlashCommandCatalog(catalog);
|
|
1826
|
+
const normalizedQuery = normalizeSlashCommandId(query);
|
|
1827
|
+
if (!normalizedQuery) return commands;
|
|
1828
|
+
return commands.filter((command) => {
|
|
1829
|
+
const haystack = [
|
|
1830
|
+
command.id,
|
|
1831
|
+
command.name,
|
|
1832
|
+
command.title,
|
|
1833
|
+
command.description,
|
|
1834
|
+
command.source,
|
|
1835
|
+
command.plugin,
|
|
1836
|
+
command.category,
|
|
1837
|
+
command.skillName,
|
|
1838
|
+
command.skillPath,
|
|
1839
|
+
...command.requiredTools,
|
|
1840
|
+
...command.aliases,
|
|
1841
|
+
...command.keywords,
|
|
1842
|
+
]
|
|
1843
|
+
.filter(Boolean)
|
|
1844
|
+
.join(" ")
|
|
1845
|
+
.toLowerCase();
|
|
1846
|
+
return haystack.includes(normalizedQuery);
|
|
1847
|
+
});
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
function parseEquanautSlashCommandInvocation(text) {
|
|
1851
|
+
const raw = String(text || "").trim();
|
|
1852
|
+
const match = raw.match(/^\/([A-Za-z0-9][A-Za-z0-9:_-]*)(?:\s+([\s\S]*))?$/);
|
|
1853
|
+
if (!match) return null;
|
|
1854
|
+
const commandId = normalizeSlashCommandId(match[1]);
|
|
1855
|
+
return {
|
|
1856
|
+
raw,
|
|
1857
|
+
commandId,
|
|
1858
|
+
commandText: `/${commandId}`,
|
|
1859
|
+
args: String(match[2] || "").trim(),
|
|
1860
|
+
hasArgs: Boolean(match[2] && String(match[2]).trim()),
|
|
1861
|
+
};
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
function resolveEquanautSlashCommand(invocation, catalog) {
|
|
1865
|
+
const commandId = normalizeSlashCommandId(
|
|
1866
|
+
typeof invocation === "string" ? invocation : invocation?.commandId
|
|
1867
|
+
);
|
|
1868
|
+
if (!commandId) return null;
|
|
1869
|
+
const commands = buildEquanautSlashCommandCatalog(catalog);
|
|
1870
|
+
return (
|
|
1871
|
+
commands.find((command) => {
|
|
1872
|
+
if (command.id === commandId) return true;
|
|
1873
|
+
if (normalizeSlashCommandId(command.title) === commandId) return true;
|
|
1874
|
+
return command.aliases.some((alias) => normalizeSlashCommandId(alias) === commandId);
|
|
1875
|
+
}) || null
|
|
1876
|
+
);
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
function normalizeBindingWindowSnapshot(snapshot) {
|
|
1880
|
+
const windows = Array.isArray(snapshot?.windows) ? snapshot.windows : [];
|
|
1881
|
+
const map = new Map();
|
|
1882
|
+
for (const windowInfo of windows) {
|
|
1883
|
+
if (windowInfo?.windowId === undefined || windowInfo?.windowId === null) continue;
|
|
1884
|
+
map.set(String(windowInfo.windowId), {
|
|
1885
|
+
windowId: Number(windowInfo.windowId),
|
|
1886
|
+
binding: windowInfo.binding || null,
|
|
1887
|
+
bindingStatus: windowInfo.bindingStatus || windowInfo.binding?.status || "unbound",
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1890
|
+
return map;
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
function formatBindingStatusLabel(windowInfo) {
|
|
1894
|
+
const status = String(windowInfo?.bindingStatus || windowInfo?.binding?.status || "unbound");
|
|
1895
|
+
const labels = {
|
|
1896
|
+
active: "Bound",
|
|
1897
|
+
completed: "Complete",
|
|
1898
|
+
stale: "Stale",
|
|
1899
|
+
reaped: "Reaped",
|
|
1900
|
+
conflict: "Conflict",
|
|
1901
|
+
unbound: "Unbound",
|
|
1902
|
+
};
|
|
1903
|
+
return labels[status] || status;
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
function getBindingStatusClass(windowInfo) {
|
|
1907
|
+
const status = String(windowInfo?.bindingStatus || windowInfo?.binding?.status || "unbound")
|
|
1908
|
+
.toLowerCase()
|
|
1909
|
+
.replace(/[^a-z0-9_-]/g, "");
|
|
1910
|
+
return `binding-status-${status || "unbound"}`;
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
function escapeHtml(str) {
|
|
1914
|
+
return String(str)
|
|
1915
|
+
.replace(/&/g, "&")
|
|
1916
|
+
.replace(/</g, "<")
|
|
1917
|
+
.replace(/>/g, ">")
|
|
1918
|
+
.replace(/"/g, """)
|
|
1919
|
+
.replace(/'/g, "'");
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
function groupAndSortForDeletion(selectedTabs) {
|
|
1923
|
+
const byGroup = new Map();
|
|
1924
|
+
for (const s of selectedTabs) {
|
|
1925
|
+
if (!byGroup.has(s.groupId)) byGroup.set(s.groupId, []);
|
|
1926
|
+
byGroup.get(s.groupId).push(s.tabIdx);
|
|
1927
|
+
}
|
|
1928
|
+
for (const [, indices] of byGroup) {
|
|
1929
|
+
indices.sort((a, b) => b - a);
|
|
1930
|
+
}
|
|
1931
|
+
return byGroup;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
const SUPPORTED_ROUTER_INTENTS = Object.freeze([
|
|
1935
|
+
"inspect",
|
|
1936
|
+
"search",
|
|
1937
|
+
"open",
|
|
1938
|
+
"restore",
|
|
1939
|
+
"archive",
|
|
1940
|
+
"rename",
|
|
1941
|
+
"update_description",
|
|
1942
|
+
"bulk_update_descriptions",
|
|
1943
|
+
"rename_window",
|
|
1944
|
+
"rename_group",
|
|
1945
|
+
"rename_tab",
|
|
1946
|
+
"rename_archive",
|
|
1947
|
+
"update_task_status",
|
|
1948
|
+
"save_snapshot",
|
|
1949
|
+
"manage_selected_item",
|
|
1950
|
+
"mark_pending",
|
|
1951
|
+
"mark_done",
|
|
1952
|
+
"dispatch_to_fleet",
|
|
1953
|
+
"get_fleet_status",
|
|
1954
|
+
"open_orchestrator_url",
|
|
1955
|
+
"invoke_slash_command",
|
|
1956
|
+
"bind_bwoa_session",
|
|
1957
|
+
"snapshot_bwoa_window",
|
|
1958
|
+
"delegate_bwoa_spoke",
|
|
1959
|
+
"complete_bwoa_spoke",
|
|
1960
|
+
]);
|
|
1961
|
+
|
|
1962
|
+
const READ_ONLY_ROUTER_INTENTS = new Set(["inspect", "search", "get_fleet_status"]);
|
|
1963
|
+
const BWOA_DEFAULT_SPACE_URL =
|
|
1964
|
+
"https://www.perplexity.ai/spaces/hub-browser-agent-window-all-t-dcF8yEn2RFejcQJxYBf3Zg";
|
|
1965
|
+
|
|
1966
|
+
const BWOA_MUTATING_INTENTS = new Set([
|
|
1967
|
+
"bind_bwoa_session",
|
|
1968
|
+
"snapshot_bwoa_window",
|
|
1969
|
+
"delegate_bwoa_spoke",
|
|
1970
|
+
"complete_bwoa_spoke",
|
|
1971
|
+
]);
|
|
1972
|
+
|
|
1973
|
+
function normalizeRole(value) {
|
|
1974
|
+
return String(value || "")
|
|
1975
|
+
.trim()
|
|
1976
|
+
.toLowerCase()
|
|
1977
|
+
.replace(/[\s-]+/g, "_");
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
function isPrimeRouterActor(data) {
|
|
1981
|
+
const principal = data?.principal || {};
|
|
1982
|
+
const activeWindowScope = data?.activeWindowScope || {};
|
|
1983
|
+
const roles = [
|
|
1984
|
+
data?.actorRole,
|
|
1985
|
+
data?.role,
|
|
1986
|
+
data?.actor,
|
|
1987
|
+
principal.role,
|
|
1988
|
+
principal.actorRole,
|
|
1989
|
+
activeWindowScope.actorRole,
|
|
1990
|
+
].map(normalizeRole);
|
|
1991
|
+
return (
|
|
1992
|
+
data?.prime === true ||
|
|
1993
|
+
principal.prime === true ||
|
|
1994
|
+
roles.includes("prime") ||
|
|
1995
|
+
roles.includes("equanaut_prime") ||
|
|
1996
|
+
roles.includes("equanautprime")
|
|
1997
|
+
);
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
function isExtensionOwnedMetadataTarget(target) {
|
|
2001
|
+
const source = String(target?.sourceFamily || target?.source || "").toLowerCase();
|
|
2002
|
+
const entityType = String(target?.entityType || target?.type || "").toLowerCase();
|
|
2003
|
+
return !!(
|
|
2004
|
+
target?.extensionOwned === true ||
|
|
2005
|
+
target?.extensionManaged === true ||
|
|
2006
|
+
target?.managedByExtension === true ||
|
|
2007
|
+
[
|
|
2008
|
+
"archived",
|
|
2009
|
+
"archive",
|
|
2010
|
+
"recent",
|
|
2011
|
+
"saved",
|
|
2012
|
+
"unified",
|
|
2013
|
+
"selected",
|
|
2014
|
+
"selected_item",
|
|
2015
|
+
"task-thread",
|
|
2016
|
+
"task_thread",
|
|
2017
|
+
"gateway",
|
|
2018
|
+
].includes(source) ||
|
|
2019
|
+
[
|
|
2020
|
+
"archive",
|
|
2021
|
+
"archive-tab",
|
|
2022
|
+
"recent-window",
|
|
2023
|
+
"recent-tab",
|
|
2024
|
+
"selected-item",
|
|
2025
|
+
"task-thread",
|
|
2026
|
+
"window-label",
|
|
2027
|
+
].includes(entityType)
|
|
2028
|
+
);
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
function isPrimeLiveTargetVerified(target) {
|
|
2032
|
+
const owner = normalizeRole(target?.profileOwner || target?.owner);
|
|
2033
|
+
const displayRole = normalizeRole(target?.displayRole);
|
|
2034
|
+
if (owner !== "agent") return false;
|
|
2035
|
+
return !!(
|
|
2036
|
+
displayRole === "top_agents" ||
|
|
2037
|
+
target?.extensionOwned === true ||
|
|
2038
|
+
target?.extensionManaged === true ||
|
|
2039
|
+
target?.managedByExtension === true
|
|
2040
|
+
);
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
function isCapabilityUsable(capability) {
|
|
2044
|
+
return !!(
|
|
2045
|
+
capability &&
|
|
2046
|
+
capability.reachable === true &&
|
|
2047
|
+
capability.fresh !== false &&
|
|
2048
|
+
capability.connected !== false &&
|
|
2049
|
+
capability.usable !== false
|
|
2050
|
+
);
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
function selectEquanautRouterRoute(capabilities) {
|
|
2054
|
+
const caps = capabilities || {};
|
|
2055
|
+
if (isCapabilityUsable(caps.equaGateway)) {
|
|
2056
|
+
return { route: "gateway", degraded: false };
|
|
2057
|
+
}
|
|
2058
|
+
if (isCapabilityUsable(caps.equaApi)) {
|
|
2059
|
+
return {
|
|
2060
|
+
route: "equa_api_enriched",
|
|
2061
|
+
degraded: true,
|
|
2062
|
+
degradedReason: "gateway unavailable; using Equa API context enrichment",
|
|
2063
|
+
};
|
|
2064
|
+
}
|
|
2065
|
+
if (isCapabilityUsable(caps.cometBridgeAsk)) {
|
|
2066
|
+
return {
|
|
2067
|
+
route: "comet_bridge",
|
|
2068
|
+
degraded: true,
|
|
2069
|
+
degradedReason: "gateway and Equa API unavailable; using Comet bridge",
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
if (isCapabilityUsable(caps.localModel)) {
|
|
2073
|
+
return {
|
|
2074
|
+
route: "local_model",
|
|
2075
|
+
degraded: true,
|
|
2076
|
+
degradedReason: "gateway, Equa API, and Comet bridge unavailable; using local model",
|
|
2077
|
+
};
|
|
2078
|
+
}
|
|
2079
|
+
return {
|
|
2080
|
+
route: "unavailable",
|
|
2081
|
+
degraded: true,
|
|
2082
|
+
degradedReason: "no router backend is reachable",
|
|
2083
|
+
};
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
function stringOrNull(value) {
|
|
2087
|
+
if (value === undefined || value === null || value === "") return null;
|
|
2088
|
+
return String(value);
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
function hasOwn(record, key) {
|
|
2092
|
+
return !!record && Object.prototype.hasOwnProperty.call(record, key);
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
function normalizeEntityDescription(record) {
|
|
2096
|
+
if (!record || typeof record !== "object") return "";
|
|
2097
|
+
if (hasOwn(record, "description")) return String(record.description ?? "");
|
|
2098
|
+
if (hasOwn(record, "note")) return String(record.note ?? "");
|
|
2099
|
+
if (hasOwn(record, "notes")) {
|
|
2100
|
+
const notes = record.notes;
|
|
2101
|
+
if (Array.isArray(notes)) return notes.map((note) => String(note ?? "")).join("\n");
|
|
2102
|
+
return String(notes ?? "");
|
|
2103
|
+
}
|
|
2104
|
+
return "";
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
function normalizeCatalogTabs(record) {
|
|
2108
|
+
const tabs = Array.isArray(record?.tabs)
|
|
2109
|
+
? record.tabs
|
|
2110
|
+
: Array.isArray(record?.urls)
|
|
2111
|
+
? record.urls
|
|
2112
|
+
: [];
|
|
2113
|
+
return tabs.map((tab, index) => {
|
|
2114
|
+
const description = normalizeEntityDescription(tab);
|
|
2115
|
+
return {
|
|
2116
|
+
id: stringOrNull(
|
|
2117
|
+
tab.id || tab.tabId || `${record.taskThreadId || record.id || "tab"}-${index + 1}`
|
|
2118
|
+
),
|
|
2119
|
+
title: String(tab.title || tab.url || "Untitled"),
|
|
2120
|
+
url: stringOrNull(tab.url),
|
|
2121
|
+
active: !!tab.active,
|
|
2122
|
+
description,
|
|
2123
|
+
inheritedDescription: description
|
|
2124
|
+
? ""
|
|
2125
|
+
: String(tab.inheritedDescription || record?.description || ""),
|
|
2126
|
+
};
|
|
2127
|
+
});
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
function catalogTitle(record) {
|
|
2131
|
+
return (
|
|
2132
|
+
record.title ||
|
|
2133
|
+
record.sessionName ||
|
|
2134
|
+
record.taskGoal ||
|
|
2135
|
+
record.displayName ||
|
|
2136
|
+
record.name ||
|
|
2137
|
+
record.label ||
|
|
2138
|
+
record.url ||
|
|
2139
|
+
"Untitled"
|
|
2140
|
+
);
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
function catalogStatus(record, fallback) {
|
|
2144
|
+
return record.status || record.taskStatus || record.state || fallback || "unknown";
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
function catalogPrimeOperationsForSource(sourceFamily) {
|
|
2148
|
+
const source = String(sourceFamily || "").toLowerCase();
|
|
2149
|
+
if (source === "live") {
|
|
2150
|
+
return [
|
|
2151
|
+
"inspect",
|
|
2152
|
+
"search",
|
|
2153
|
+
"update_description",
|
|
2154
|
+
"bulk_update_descriptions",
|
|
2155
|
+
"rename_window",
|
|
2156
|
+
"rename_group",
|
|
2157
|
+
"rename_tab",
|
|
2158
|
+
"save_snapshot",
|
|
2159
|
+
];
|
|
2160
|
+
}
|
|
2161
|
+
if (["archived", "archive", "recent", "saved", "unified", "selected"].includes(source)) {
|
|
2162
|
+
return [
|
|
2163
|
+
"inspect",
|
|
2164
|
+
"search",
|
|
2165
|
+
"update_description",
|
|
2166
|
+
"bulk_update_descriptions",
|
|
2167
|
+
"rename_archive",
|
|
2168
|
+
"rename_tab",
|
|
2169
|
+
"update_task_status",
|
|
2170
|
+
"manage_selected_item",
|
|
2171
|
+
];
|
|
2172
|
+
}
|
|
2173
|
+
if (source === "gateway") {
|
|
2174
|
+
return ["inspect", "search", "update_description"];
|
|
2175
|
+
}
|
|
2176
|
+
return ["inspect", "search"];
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
function normalizeCatalogRecord(record, sourceFamily, index) {
|
|
2180
|
+
const source = sourceFamily || "unknown";
|
|
2181
|
+
const nativeId =
|
|
2182
|
+
record.taskThreadId ||
|
|
2183
|
+
record.sessionKey ||
|
|
2184
|
+
record.id ||
|
|
2185
|
+
record.sessionId ||
|
|
2186
|
+
record.tabGroupId ||
|
|
2187
|
+
record.groupId ||
|
|
2188
|
+
record.windowId ||
|
|
2189
|
+
`${source}-${index + 1}`;
|
|
2190
|
+
return {
|
|
2191
|
+
id: String(nativeId),
|
|
2192
|
+
sourceFamily: source,
|
|
2193
|
+
title: String(catalogTitle(record)).trim() || "Untitled",
|
|
2194
|
+
description: normalizeEntityDescription(record),
|
|
2195
|
+
agentInstructions: String(
|
|
2196
|
+
record.agentInstructions || record.equanautInstructions || ""
|
|
2197
|
+
).trim(),
|
|
2198
|
+
status: catalogStatus(record, source === "archived" ? "archived" : null),
|
|
2199
|
+
windowId: stringOrNull(record.windowId),
|
|
2200
|
+
taskThreadId: stringOrNull(record.taskThreadId || record.threadId),
|
|
2201
|
+
agentId: stringOrNull(record.agentId),
|
|
2202
|
+
orchestratorUrl: stringOrNull(record.orchestratorUrl || record.perplexityUrl),
|
|
2203
|
+
tabs: normalizeCatalogTabs(record),
|
|
2204
|
+
lastActivityAt:
|
|
2205
|
+
record.lastActivityAt ||
|
|
2206
|
+
record.lastActivity ||
|
|
2207
|
+
record.updatedAt ||
|
|
2208
|
+
record.archivedAt ||
|
|
2209
|
+
record.closedAt ||
|
|
2210
|
+
null,
|
|
2211
|
+
allowedOperations: ["inspect", "search"],
|
|
2212
|
+
primeAllowedOperations: catalogPrimeOperationsForSource(source),
|
|
2213
|
+
extensionOwned: record.extensionOwned !== false,
|
|
2214
|
+
readOnly: true,
|
|
2215
|
+
};
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
function normalizeBwoaStatus(value) {
|
|
2219
|
+
const status = normalizeRole(value || "active");
|
|
2220
|
+
if (
|
|
2221
|
+
["active", "pending", "running", "paused", "completed", "stale", "failed"].includes(status)
|
|
2222
|
+
) {
|
|
2223
|
+
return status;
|
|
2224
|
+
}
|
|
2225
|
+
return "active";
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
function normalizeTrackedUrl(record, index) {
|
|
2229
|
+
const url = String(record?.url || record?.href || "").trim();
|
|
2230
|
+
return {
|
|
2231
|
+
id: String(record?.id || record?.bookmarkId || record?.tabId || `url-${index + 1}`),
|
|
2232
|
+
title: String(record?.title || record?.name || url || "Untitled"),
|
|
2233
|
+
url,
|
|
2234
|
+
source: String(record?.source || record?.sourceFamily || "tracked"),
|
|
2235
|
+
taskThreadId: stringOrNull(record?.taskThreadId || record?.threadId),
|
|
2236
|
+
description: normalizeEntityDescription(record),
|
|
2237
|
+
};
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
function normalizeBwoaSpoke(record, index) {
|
|
2241
|
+
return {
|
|
2242
|
+
id: String(record?.id || record?.agentId || `spoke-${index + 1}`),
|
|
2243
|
+
agentId: stringOrNull(record?.agentId || record?.id),
|
|
2244
|
+
taskThreadId: stringOrNull(record?.taskThreadId || record?.threadId),
|
|
2245
|
+
threadUrl: stringOrNull(record?.threadUrl || record?.url || record?.orchestratorUrl),
|
|
2246
|
+
goal: String(record?.goal || record?.taskGoal || record?.title || ""),
|
|
2247
|
+
successBenchmark: String(record?.successBenchmark || record?.benchmark || ""),
|
|
2248
|
+
status: normalizeBwoaStatus(record?.status || "pending"),
|
|
2249
|
+
successScore:
|
|
2250
|
+
record?.successScore === undefined || record?.successScore === null
|
|
2251
|
+
? null
|
|
2252
|
+
: Number(record.successScore),
|
|
2253
|
+
finalSummary: String(record?.finalSummary || record?.summary || ""),
|
|
2254
|
+
logsReference: stringOrNull(record?.logsReference || record?.logRef || record?.threadUrl),
|
|
2255
|
+
};
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
function normalizeBwoaTab(record, index) {
|
|
2259
|
+
return {
|
|
2260
|
+
id: String(record?.id || record?.tabId || `tab-${index + 1}`),
|
|
2261
|
+
tabId: record?.tabId === undefined ? null : Number(record.tabId),
|
|
2262
|
+
groupId: record?.groupId === undefined ? null : Number(record.groupId),
|
|
2263
|
+
windowId: record?.windowId === undefined ? null : Number(record.windowId),
|
|
2264
|
+
title: String(record?.title || record?.url || "Untitled"),
|
|
2265
|
+
url: stringOrNull(record?.url),
|
|
2266
|
+
active: !!record?.active,
|
|
2267
|
+
pinned: !!record?.pinned,
|
|
2268
|
+
description: normalizeEntityDescription(record),
|
|
2269
|
+
taskThreadId: stringOrNull(record?.taskThreadId || record?.threadId),
|
|
2270
|
+
};
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
function normalizeBwoaGroup(record, index) {
|
|
2274
|
+
const tabs = Array.isArray(record?.tabs) ? record.tabs : [];
|
|
2275
|
+
return {
|
|
2276
|
+
id: String(record?.id || record?.groupId || `group-${index + 1}`),
|
|
2277
|
+
groupId: record?.groupId === undefined ? record?.id || null : Number(record.groupId),
|
|
2278
|
+
windowId: record?.windowId === undefined ? null : Number(record.windowId),
|
|
2279
|
+
title: String(record?.title || record?.name || "Untitled Group"),
|
|
2280
|
+
color: String(record?.color || "grey"),
|
|
2281
|
+
status: normalizeBwoaStatus(record?.status || "active"),
|
|
2282
|
+
description: normalizeEntityDescription(record),
|
|
2283
|
+
taskThreadId: stringOrNull(record?.taskThreadId || record?.threadId),
|
|
2284
|
+
tabs: tabs.map((tab, tabIndex) => normalizeBwoaTab(tab, tabIndex)),
|
|
2285
|
+
};
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
function buildBwoaWindowHubSession(input) {
|
|
2289
|
+
const data = input || {};
|
|
2290
|
+
const now = data.updatedAt || data.lastSnapshotAt || data.createdAt || new Date().toISOString();
|
|
2291
|
+
const epoaSessionId = String(data.epoaSessionId || data.primeSessionId || "").trim();
|
|
2292
|
+
const windowId =
|
|
2293
|
+
data.windowId === undefined || data.windowId === null || data.windowId === ""
|
|
2294
|
+
? null
|
|
2295
|
+
: Number(data.windowId);
|
|
2296
|
+
const taskThreadId = String(data.taskThreadId || data.threadId || "").trim();
|
|
2297
|
+
const groups = (Array.isArray(data.tabGroups) ? data.tabGroups : data.groups || []).map(
|
|
2298
|
+
(group, index) => normalizeBwoaGroup(group, index)
|
|
2299
|
+
);
|
|
2300
|
+
const openTabs = (Array.isArray(data.openTabs) ? data.openTabs : data.tabs || []).map(
|
|
2301
|
+
(tab, index) => normalizeBwoaTab(tab, index)
|
|
2302
|
+
);
|
|
2303
|
+
|
|
2304
|
+
return {
|
|
2305
|
+
schemaVersion: "bwoa.window-hub-session.v1",
|
|
2306
|
+
id: String(
|
|
2307
|
+
data.id ||
|
|
2308
|
+
data.sessionKey ||
|
|
2309
|
+
[
|
|
2310
|
+
"bwoa",
|
|
2311
|
+
epoaSessionId || "unbound-prime",
|
|
2312
|
+
windowId === null ? "window-unknown" : `window-${windowId}`,
|
|
2313
|
+
].join(":")
|
|
2314
|
+
),
|
|
2315
|
+
epoaSessionId,
|
|
2316
|
+
bwoaSpaceUrl: String(data.bwoaSpaceUrl || data.spaceUrl || BWOA_DEFAULT_SPACE_URL),
|
|
2317
|
+
windowId,
|
|
2318
|
+
windowOrdinal: Number(data.windowOrdinal || data.windowNumber || 1),
|
|
2319
|
+
tabGroupId:
|
|
2320
|
+
data.tabGroupId === undefined || data.tabGroupId === null || data.tabGroupId === ""
|
|
2321
|
+
? null
|
|
2322
|
+
: Number(data.tabGroupId),
|
|
2323
|
+
taskThreadId,
|
|
2324
|
+
agentId: String(data.agentId || "bwoa-window-hub"),
|
|
2325
|
+
status: normalizeBwoaStatus(data.status || "active"),
|
|
2326
|
+
name: String(data.name || data.title || "Window Hub Orchestrator"),
|
|
2327
|
+
description: normalizeEntityDescription(data),
|
|
2328
|
+
bookmarkedUrls: (data.bookmarkedUrls || data.bookmarks || []).map(normalizeTrackedUrl),
|
|
2329
|
+
trackedUrls: (data.trackedUrls || []).map(normalizeTrackedUrl),
|
|
2330
|
+
tabGroups: groups,
|
|
2331
|
+
openTabs,
|
|
2332
|
+
spawnedSpokes: (data.spawnedSpokes || data.spokes || []).map(normalizeBwoaSpoke),
|
|
2333
|
+
lastSnapshotAt: now,
|
|
2334
|
+
createdAt: data.createdAt || now,
|
|
2335
|
+
updatedAt: now,
|
|
2336
|
+
auditTrail: Array.isArray(data.auditTrail) ? data.auditTrail : [],
|
|
2337
|
+
};
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
function countBwoaWindowHubSession(session) {
|
|
2341
|
+
const data = session || {};
|
|
2342
|
+
const groups = Array.isArray(data.tabGroups) ? data.tabGroups : [];
|
|
2343
|
+
const openTabs = Array.isArray(data.openTabs) ? data.openTabs : [];
|
|
2344
|
+
const groupTabs = groups.reduce(
|
|
2345
|
+
(sum, group) => sum + (Array.isArray(group.tabs) ? group.tabs.length : 0),
|
|
2346
|
+
0
|
|
2347
|
+
);
|
|
2348
|
+
return {
|
|
2349
|
+
tabGroups: groups.length,
|
|
2350
|
+
openTabs: openTabs.length || groupTabs,
|
|
2351
|
+
bookmarkedUrls: (data.bookmarkedUrls || []).length,
|
|
2352
|
+
trackedUrls: (data.trackedUrls || []).length,
|
|
2353
|
+
spawnedSpokes: (data.spawnedSpokes || []).length,
|
|
2354
|
+
activeSpokes: (data.spawnedSpokes || []).filter((spoke) =>
|
|
2355
|
+
["active", "running", "pending"].includes(normalizeBwoaStatus(spoke.status))
|
|
2356
|
+
).length,
|
|
2357
|
+
};
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
function buildBwoaDelegationEnvelope(input) {
|
|
2361
|
+
const data = input || {};
|
|
2362
|
+
const session = buildBwoaWindowHubSession(data.session || data);
|
|
2363
|
+
const goal = String(data.goal || data.taskGoal || "").trim();
|
|
2364
|
+
const taskThreadId = String(data.taskThreadId || session.taskThreadId || "").trim();
|
|
2365
|
+
return {
|
|
2366
|
+
schemaVersion: "bwoa.delegation-envelope.v1",
|
|
2367
|
+
goal,
|
|
2368
|
+
successBenchmark: String(data.successBenchmark || data.benchmark || ""),
|
|
2369
|
+
windowId: session.windowId,
|
|
2370
|
+
windowOrdinal: session.windowOrdinal,
|
|
2371
|
+
taskThreadId,
|
|
2372
|
+
allowedTargets: Array.isArray(data.allowedTargets)
|
|
2373
|
+
? data.allowedTargets
|
|
2374
|
+
: [
|
|
2375
|
+
{
|
|
2376
|
+
entityType: "window",
|
|
2377
|
+
windowId: session.windowId,
|
|
2378
|
+
scope: "bwoa_window",
|
|
2379
|
+
},
|
|
2380
|
+
],
|
|
2381
|
+
requiredReportFormat:
|
|
2382
|
+
data.requiredReportFormat ||
|
|
2383
|
+
"Session Report with deployed spokes, benchmark, final output summary, success score, pass/fail status, and logs reference.",
|
|
2384
|
+
bwoaSpaceUrl: session.bwoaSpaceUrl,
|
|
2385
|
+
createdAt: data.createdAt || new Date().toISOString(),
|
|
2386
|
+
};
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
function normalizeBwoaConfigItem(record, index, fallbackKind) {
|
|
2390
|
+
const value = record || {};
|
|
2391
|
+
const id = value.id || value.name || value.path || value.url || `${fallbackKind}-${index + 1}`;
|
|
2392
|
+
return {
|
|
2393
|
+
id: String(id),
|
|
2394
|
+
kind: String(value.kind || fallbackKind),
|
|
2395
|
+
name: String(value.name || value.title || id),
|
|
2396
|
+
purpose: String(value.purpose || value.description || ""),
|
|
2397
|
+
required: value.required !== false,
|
|
2398
|
+
};
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
function buildBwoaWindowHubConfiguration(input) {
|
|
2402
|
+
const data = input || {};
|
|
2403
|
+
const session = buildBwoaWindowHubSession(data.session || data);
|
|
2404
|
+
const windowOrdinal = Number(data.windowOrdinal || session.windowOrdinal || 1);
|
|
2405
|
+
const spaceUrl = String(data.bwoaSpaceUrl || data.spaceUrl || session.bwoaSpaceUrl);
|
|
2406
|
+
const defaultConnections = [
|
|
2407
|
+
{
|
|
2408
|
+
id: "epoa-control-channel",
|
|
2409
|
+
kind: "conversation",
|
|
2410
|
+
name: "EPOA to BWOA direct channel",
|
|
2411
|
+
purpose:
|
|
2412
|
+
"Codex Prime sends only compact session envelopes, delegation envelopes, and report requests.",
|
|
2413
|
+
},
|
|
2414
|
+
{
|
|
2415
|
+
id: "bwoa-perplexity-space",
|
|
2416
|
+
kind: "space",
|
|
2417
|
+
name: "Hub Browser Agent Window All Tabs",
|
|
2418
|
+
purpose: spaceUrl,
|
|
2419
|
+
},
|
|
2420
|
+
{
|
|
2421
|
+
id: "comet-bridge",
|
|
2422
|
+
kind: "mcp",
|
|
2423
|
+
name: "Comet-Bridge",
|
|
2424
|
+
purpose:
|
|
2425
|
+
"Authoritative browser control, tab group inventory, lifecycle metadata, and top-display window binding.",
|
|
2426
|
+
},
|
|
2427
|
+
{
|
|
2428
|
+
id: "task-thread-registry",
|
|
2429
|
+
kind: "metadata",
|
|
2430
|
+
name: "Task Thread Registry",
|
|
2431
|
+
purpose: "Joins BWOA hub state to spawned spoke work and completion reports.",
|
|
2432
|
+
},
|
|
2433
|
+
];
|
|
2434
|
+
const defaultSkills = [
|
|
2435
|
+
"bwoa-window-hub",
|
|
2436
|
+
"comet-browser-control-toolkit",
|
|
2437
|
+
"comet-connect",
|
|
2438
|
+
"comet-workspace",
|
|
2439
|
+
"comet-task-threads",
|
|
2440
|
+
"comet-delegate",
|
|
2441
|
+
"comet-session",
|
|
2442
|
+
"comet-skill-inventory",
|
|
2443
|
+
"15-comet-review",
|
|
2444
|
+
].map((name) => ({
|
|
2445
|
+
id: name,
|
|
2446
|
+
kind: "skill",
|
|
2447
|
+
name,
|
|
2448
|
+
purpose: "BWOA browser orchestration surface",
|
|
2449
|
+
}));
|
|
2450
|
+
const defaultFiles = [
|
|
2451
|
+
"codex-comet-browser-control-toolkit/skills/bwoa-window-hub/SKILL.md",
|
|
2452
|
+
"codex-comet-browser-control-toolkit/commands/bwoa-window-hub.md",
|
|
2453
|
+
"docs/BWOA-WINDOW-HUB-ORCHESTRATOR.md",
|
|
2454
|
+
"comet-mcp/extension/session-logic.js",
|
|
2455
|
+
"test/bwoa-window-hub-session.test.mjs",
|
|
2456
|
+
"specs/086-branch-window-hub/contracts/bwoa-window-hub-session.md",
|
|
2457
|
+
"specs/086-branch-window-hub/quickstart.md",
|
|
2458
|
+
].map((path) => ({
|
|
2459
|
+
id: path,
|
|
2460
|
+
kind: "file",
|
|
2461
|
+
name: path,
|
|
2462
|
+
purpose: "Source-level BWOA contract evidence",
|
|
2463
|
+
}));
|
|
2464
|
+
|
|
2465
|
+
return {
|
|
2466
|
+
schemaVersion: "bwoa.window-hub-configuration.v1",
|
|
2467
|
+
id: String(data.id || `bwoa-window-${windowOrdinal}-configuration`),
|
|
2468
|
+
role: "browser_window_orchestrator_agent",
|
|
2469
|
+
windowOrdinal,
|
|
2470
|
+
bwoaSpaceUrl: spaceUrl,
|
|
2471
|
+
profileOwner: "agent",
|
|
2472
|
+
displayRole: "top_agents",
|
|
2473
|
+
primaryMode: String(data.primaryMode || "Computer"),
|
|
2474
|
+
planningMode: String(data.planningMode || "Orchestrator"),
|
|
2475
|
+
settings: {
|
|
2476
|
+
oneWindowPerHub: true,
|
|
2477
|
+
oneManagedTabGroupPerWindow: true,
|
|
2478
|
+
preserveOpenTabs: true,
|
|
2479
|
+
preserveTrackedUrls: true,
|
|
2480
|
+
snapshotCadence: ["bind", "pre_delegate", "post_delegate", "complete", "handoff"],
|
|
2481
|
+
authoritativeState: ["extension_storage", "mcp_metadata", "snapshots", "task_threads"],
|
|
2482
|
+
prohibitedAutomation: ["chrome", "puppeteer", "playwright_keyboard", "codex_browser"],
|
|
2483
|
+
...(data.settings || {}),
|
|
2484
|
+
},
|
|
2485
|
+
connections: (data.connections || defaultConnections).map((item, index) =>
|
|
2486
|
+
normalizeBwoaConfigItem(item, index, "connection")
|
|
2487
|
+
),
|
|
2488
|
+
requiredSkills: (data.requiredSkills || defaultSkills).map((item, index) =>
|
|
2489
|
+
normalizeBwoaConfigItem(item, index, "skill")
|
|
2490
|
+
),
|
|
2491
|
+
requiredFiles: (data.requiredFiles || defaultFiles).map((item, index) =>
|
|
2492
|
+
normalizeBwoaConfigItem(item, index, "file")
|
|
2493
|
+
),
|
|
2494
|
+
instructionBlocks: data.instructionBlocks || [
|
|
2495
|
+
"Bind one Codex/EPOA session to one BWOA Space and one top-display Comet window.",
|
|
2496
|
+
"Snapshot tab groups, open tabs, bookmarked URLs, and tracked URLs before delegating spoke work.",
|
|
2497
|
+
"Delegate only bounded spoke goals with success benchmarks and allowed targets.",
|
|
2498
|
+
"Return the required Session Report upward to EPOA after every spoke completes or blocks.",
|
|
2499
|
+
],
|
|
2500
|
+
threadProtocol: {
|
|
2501
|
+
epoaToBwoa: "bwoa.delegation-envelope.v1",
|
|
2502
|
+
bwoaState: "bwoa.window-hub-session.v1",
|
|
2503
|
+
bwoaToEpoa: "Session Report",
|
|
2504
|
+
bwoaToSpoke: "bounded task-thread with benchmark, allowed targets, and logs reference",
|
|
2505
|
+
...(data.threadProtocol || {}),
|
|
2506
|
+
},
|
|
2507
|
+
session,
|
|
2508
|
+
};
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
function safeBwoaJson(value) {
|
|
2512
|
+
try {
|
|
2513
|
+
return JSON.stringify(value, null, 2);
|
|
2514
|
+
} catch (_err) {
|
|
2515
|
+
return "{}";
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
function buildBwoaEpoaHandoffPrompt(input) {
|
|
2520
|
+
const data = input || {};
|
|
2521
|
+
const session = buildBwoaWindowHubSession(data.session || data);
|
|
2522
|
+
const configuration = buildBwoaWindowHubConfiguration({
|
|
2523
|
+
...data,
|
|
2524
|
+
session,
|
|
2525
|
+
});
|
|
2526
|
+
const goal = String(
|
|
2527
|
+
data.goal ||
|
|
2528
|
+
"Bind this BWOA Space to the current EPOA session and maintain Window #1 browser task context."
|
|
2529
|
+
).trim();
|
|
2530
|
+
return [
|
|
2531
|
+
"# EPOA to BWOA Handoff",
|
|
2532
|
+
"",
|
|
2533
|
+
`Goal: ${goal}`,
|
|
2534
|
+
"",
|
|
2535
|
+
"You are BWOA, the Browser Window Orchestrator Agent for Window #1. Treat Codex/EPOA as the only upstream orchestrator for this browser-window hub.",
|
|
2536
|
+
"",
|
|
2537
|
+
"Required operating rules:",
|
|
2538
|
+
"- Keep this Perplexity Space as the conversational surface only.",
|
|
2539
|
+
"- Keep durable state in Comet-Bridge extension storage, MCP metadata, snapshots, and task-thread records.",
|
|
2540
|
+
"- Preserve tab groups, open tabs, bookmarked URLs, and tracked URLs for the full task lifecycle.",
|
|
2541
|
+
"- Delegate bounded browser work to spoke task threads only through the delegation envelope.",
|
|
2542
|
+
"- Return the required Session Report to EPOA after every spoke completes or blocks.",
|
|
2543
|
+
"- Do not mutate human-owned or bottom-display browser targets.",
|
|
2544
|
+
"- Do not replace Comet-Bridge with Chrome, Puppeteer, Playwright-as-keyboard, or Codex Browser.",
|
|
2545
|
+
"",
|
|
2546
|
+
"Configuration:",
|
|
2547
|
+
"```json",
|
|
2548
|
+
safeBwoaJson(configuration),
|
|
2549
|
+
"```",
|
|
2550
|
+
"",
|
|
2551
|
+
"Current BWOA session:",
|
|
2552
|
+
"```json",
|
|
2553
|
+
safeBwoaJson(session),
|
|
2554
|
+
"```",
|
|
2555
|
+
].join("\n");
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2558
|
+
function buildBwoaSpokePrompt(input) {
|
|
2559
|
+
const data = input || {};
|
|
2560
|
+
const envelope = buildBwoaDelegationEnvelope(data.envelope || data);
|
|
2561
|
+
return [
|
|
2562
|
+
"# BWOA to Spoke Delegation",
|
|
2563
|
+
"",
|
|
2564
|
+
`Goal: ${envelope.goal || "Complete the delegated browser task."}`,
|
|
2565
|
+
`Success Benchmark: ${envelope.successBenchmark || "Meet the delegated success criteria."}`,
|
|
2566
|
+
`Window Scope: Window #${envelope.windowOrdinal} (${envelope.windowId || "unbound"})`,
|
|
2567
|
+
`Task Thread: ${envelope.taskThreadId || "unbound"}`,
|
|
2568
|
+
"",
|
|
2569
|
+
"Allowed targets:",
|
|
2570
|
+
"```json",
|
|
2571
|
+
safeBwoaJson(envelope.allowedTargets),
|
|
2572
|
+
"```",
|
|
2573
|
+
"",
|
|
2574
|
+
"Delegation envelope:",
|
|
2575
|
+
"```json",
|
|
2576
|
+
safeBwoaJson(envelope),
|
|
2577
|
+
"```",
|
|
2578
|
+
"",
|
|
2579
|
+
"Return exactly this report shape to BWOA/EPOA:",
|
|
2580
|
+
"",
|
|
2581
|
+
"### Session Report: <task or goal>",
|
|
2582
|
+
"- **Spoke Agent(s) Deployed:** <ids or none>",
|
|
2583
|
+
"- **Success Benchmark:** <1-2 sentences>",
|
|
2584
|
+
"- **Final Output Summary:** <compressed result>",
|
|
2585
|
+
"- **Success Score:** <0-100>%",
|
|
2586
|
+
"- **Status:** <PASS|FAIL>",
|
|
2587
|
+
"- **Logs Reference:** <thread URL or record id>",
|
|
2588
|
+
].join("\n");
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
function validateBwoaRouterIntent(input) {
|
|
2592
|
+
const data = input || {};
|
|
2593
|
+
const intent = String(data.intent || "");
|
|
2594
|
+
if (!BWOA_MUTATING_INTENTS.has(intent)) return null;
|
|
2595
|
+
|
|
2596
|
+
const activeWindowScope = data.activeWindowScope || {};
|
|
2597
|
+
const target = data.target || {};
|
|
2598
|
+
const policyTier = isPrimeRouterActor(data) ? "prime" : "normal";
|
|
2599
|
+
|
|
2600
|
+
if (policyTier !== "prime") {
|
|
2601
|
+
return denyRouterIntent(
|
|
2602
|
+
intent,
|
|
2603
|
+
"BWOA_PRIME_REQUIRED",
|
|
2604
|
+
"BWOA session mutation requires Equanaut Prime or EPOA authority.",
|
|
2605
|
+
activeWindowScope,
|
|
2606
|
+
policyTier
|
|
2607
|
+
);
|
|
2608
|
+
}
|
|
2609
|
+
|
|
2610
|
+
if (normalizeRole(target.profileOwner) === "human") {
|
|
2611
|
+
return denyRouterIntent(
|
|
2612
|
+
intent,
|
|
2613
|
+
"HUMAN_PROFILE_DENIED",
|
|
2614
|
+
"BWOA cannot mutate human-owned browser state.",
|
|
2615
|
+
activeWindowScope,
|
|
2616
|
+
policyTier
|
|
2617
|
+
);
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
const targetDisplayRole = normalizeRole(target.displayRole);
|
|
2621
|
+
if (targetDisplayRole === "primary_user" || targetDisplayRole === "bottom_user") {
|
|
2622
|
+
return denyRouterIntent(
|
|
2623
|
+
intent,
|
|
2624
|
+
"BOTTOM_DISPLAY_MUTATION_DENIED",
|
|
2625
|
+
"BWOA targets must remain in the top-display agent workspace.",
|
|
2626
|
+
activeWindowScope,
|
|
2627
|
+
policyTier
|
|
2628
|
+
);
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
const session = buildBwoaWindowHubSession(data.session || target);
|
|
2632
|
+
const activeWindowId = activeWindowScope.windowId ? String(activeWindowScope.windowId) : "";
|
|
2633
|
+
const targetWindowId =
|
|
2634
|
+
session.windowId === null ? String(target.windowId || "") : String(session.windowId);
|
|
2635
|
+
if (targetWindowId && activeWindowId && targetWindowId !== activeWindowId) {
|
|
2636
|
+
if (!isPrimeLiveTargetVerified({ ...target, windowId: targetWindowId })) {
|
|
2637
|
+
return denyRouterIntent(
|
|
2638
|
+
intent,
|
|
2639
|
+
"BWOA_TARGET_UNVERIFIED",
|
|
2640
|
+
"BWOA cross-window mutation requires verified top-display agent ownership.",
|
|
2641
|
+
activeWindowScope,
|
|
2642
|
+
policyTier
|
|
2643
|
+
);
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
return {
|
|
2648
|
+
intent,
|
|
2649
|
+
target,
|
|
2650
|
+
session,
|
|
2651
|
+
scope: "bwoa_window_hub",
|
|
2652
|
+
mutation: true,
|
|
2653
|
+
requiresConfirmation: false,
|
|
2654
|
+
preview: data.preview || `${intent} for Window #${session.windowOrdinal}`,
|
|
2655
|
+
audit: {
|
|
2656
|
+
requestedBy: "equanaut_prime",
|
|
2657
|
+
activeWindowId: activeWindowId || "unknown",
|
|
2658
|
+
targetWindowId: targetWindowId || "unknown",
|
|
2659
|
+
policyTier,
|
|
2660
|
+
reason: data.reason || "bwoa_orchestration",
|
|
2661
|
+
createdAt: new Date().toISOString(),
|
|
2662
|
+
},
|
|
2663
|
+
allowed: true,
|
|
2664
|
+
};
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
function normalizeFleetRecord(record, index) {
|
|
2668
|
+
const base = normalizeCatalogRecord(record, "fleet", index);
|
|
2669
|
+
return {
|
|
2670
|
+
...base,
|
|
2671
|
+
status: catalogStatus(record, "unknown"),
|
|
2672
|
+
agentType: record.agentType || record.type || null,
|
|
2673
|
+
allowedOperations: ["inspect", "search", "get_fleet_status", "dispatch_to_fleet"],
|
|
2674
|
+
readOnly: true,
|
|
2675
|
+
};
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
function buildGlobalContextCatalog(input) {
|
|
2679
|
+
const data = input || {};
|
|
2680
|
+
const now = data.now || new Date().toISOString();
|
|
2681
|
+
const liveSessions = (data.liveSessions || []).map((r, i) =>
|
|
2682
|
+
normalizeCatalogRecord(r, "live", i)
|
|
2683
|
+
);
|
|
2684
|
+
const archivedSessions = (data.archivedSessions || []).map((r, i) =>
|
|
2685
|
+
normalizeCatalogRecord(r, "archived", i)
|
|
2686
|
+
);
|
|
2687
|
+
const recentlyClosed = (data.recentlyClosed || []).map((r, i) =>
|
|
2688
|
+
normalizeCatalogRecord(r, "recent", i)
|
|
2689
|
+
);
|
|
2690
|
+
const unifiedThreads = (data.unifiedThreads || []).map((r, i) =>
|
|
2691
|
+
normalizeCatalogRecord(r, "unified", i)
|
|
2692
|
+
);
|
|
2693
|
+
const fleetMembers = (data.fleetMembers || []).map((r, i) => normalizeFleetRecord(r, i));
|
|
2694
|
+
const gatewayTools = (data.gatewayTools || []).map((r, i) =>
|
|
2695
|
+
normalizeCatalogRecord(r, "gateway", i)
|
|
2696
|
+
);
|
|
2697
|
+
const selectedItems = (data.selectedItems || []).map((r, i) =>
|
|
2698
|
+
normalizeCatalogRecord(r, "selected", i)
|
|
2699
|
+
);
|
|
2700
|
+
const lifecycleBindings = (data.lifecycleBindings || []).map((r, i) =>
|
|
2701
|
+
normalizeCatalogRecord(r, "lifecycle", i)
|
|
2702
|
+
);
|
|
2703
|
+
const windowLabels = (data.windowLabels || []).map((r, i) =>
|
|
2704
|
+
normalizeCatalogRecord(r, "window-label", i)
|
|
2705
|
+
);
|
|
2706
|
+
const descriptions = (data.descriptions || []).map((r, i) =>
|
|
2707
|
+
normalizeCatalogRecord(r, "description", i)
|
|
2708
|
+
);
|
|
2709
|
+
const slashCommands = Array.isArray(data.slashCommands)
|
|
2710
|
+
? buildEquanautSlashCommandCatalog(data.slashCommands)
|
|
2711
|
+
: buildEquanautSlashCommandCatalog();
|
|
2712
|
+
const plugins = Array.isArray(data.plugins)
|
|
2713
|
+
? buildEquanautPluginCatalog(data.plugins)
|
|
2714
|
+
: buildEquanautPluginCatalog();
|
|
2715
|
+
|
|
2716
|
+
return {
|
|
2717
|
+
liveSessions,
|
|
2718
|
+
archivedSessions,
|
|
2719
|
+
recentlyClosed,
|
|
2720
|
+
unifiedThreads,
|
|
2721
|
+
fleetMembers,
|
|
2722
|
+
gatewayTools,
|
|
2723
|
+
selectedItems,
|
|
2724
|
+
lifecycleBindings,
|
|
2725
|
+
windowLabels,
|
|
2726
|
+
descriptions,
|
|
2727
|
+
slashCommands,
|
|
2728
|
+
plugins,
|
|
2729
|
+
freshness: {
|
|
2730
|
+
liveSessionsAt: data.liveSessionsAt || now,
|
|
2731
|
+
catalogAt: data.catalogAt || now,
|
|
2732
|
+
staleSources: Array.isArray(data.staleSources) ? data.staleSources : [],
|
|
2733
|
+
},
|
|
2734
|
+
};
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
function countGlobalContextCatalog(catalog) {
|
|
2738
|
+
const c = catalog || {};
|
|
2739
|
+
return {
|
|
2740
|
+
live: (c.liveSessions || []).length,
|
|
2741
|
+
archived: (c.archivedSessions || []).length,
|
|
2742
|
+
recent: (c.recentlyClosed || []).length,
|
|
2743
|
+
unified: (c.unifiedThreads || []).length,
|
|
2744
|
+
fleet: (c.fleetMembers || []).length,
|
|
2745
|
+
gatewayTools: (c.gatewayTools || []).length,
|
|
2746
|
+
slashCommands: (c.slashCommands || []).length,
|
|
2747
|
+
plugins: (c.plugins || []).length,
|
|
2748
|
+
selectedItems: (c.selectedItems || []).length,
|
|
2749
|
+
lifecycleBindings: (c.lifecycleBindings || []).length,
|
|
2750
|
+
windowLabels: (c.windowLabels || []).length,
|
|
2751
|
+
descriptions: (c.descriptions || []).length,
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
function buildAgentContextEnvelope(input) {
|
|
2756
|
+
const data = input || {};
|
|
2757
|
+
const submittedAt = data.submittedAt || new Date().toISOString();
|
|
2758
|
+
const globalCatalog = data.globalCatalog || buildGlobalContextCatalog({ now: submittedAt });
|
|
2759
|
+
const capabilities = data.capabilities || {};
|
|
2760
|
+
const activeWindowScope = data.activeWindowScope || {
|
|
2761
|
+
windowId: "unknown",
|
|
2762
|
+
groups: [],
|
|
2763
|
+
ungroupedTabs: [],
|
|
2764
|
+
mutationPolicy: {},
|
|
2765
|
+
};
|
|
2766
|
+
const catalogCounts = countGlobalContextCatalog(globalCatalog);
|
|
2767
|
+
const scopedGroupCount = Array.isArray(activeWindowScope.groups)
|
|
2768
|
+
? activeWindowScope.groups.length
|
|
2769
|
+
: 0;
|
|
2770
|
+
const scopedTabCount =
|
|
2771
|
+
(activeWindowScope.groups || []).reduce((sum, group) => {
|
|
2772
|
+
return sum + (Array.isArray(group.tabs) ? group.tabs.length : group.tabCount || 0);
|
|
2773
|
+
}, 0) +
|
|
2774
|
+
(Array.isArray(activeWindowScope.ungroupedTabs) ? activeWindowScope.ungroupedTabs.length : 0);
|
|
2775
|
+
|
|
2776
|
+
return {
|
|
2777
|
+
schemaVersion: data.schemaVersion || "1.0",
|
|
2778
|
+
submittedAt,
|
|
2779
|
+
userText: String(data.userText || ""),
|
|
2780
|
+
activeWindowScope,
|
|
2781
|
+
globalCatalog,
|
|
2782
|
+
capabilities,
|
|
2783
|
+
allowedIntents: data.allowedIntents || Array.from(SUPPORTED_ROUTER_INTENTS),
|
|
2784
|
+
slashCommand: data.slashCommand || null,
|
|
2785
|
+
attachments: data.attachments || [],
|
|
2786
|
+
telemetry: {
|
|
2787
|
+
...(data.telemetry || {}),
|
|
2788
|
+
activeWindowId: activeWindowScope.windowId || "unknown",
|
|
2789
|
+
scopedGroupCount,
|
|
2790
|
+
scopedTabCount,
|
|
2791
|
+
catalogCounts,
|
|
2792
|
+
slashCommandId: data.slashCommand?.commandId || data.slashCommand?.command?.id || null,
|
|
2793
|
+
},
|
|
2794
|
+
};
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2797
|
+
function denyRouterIntent(intent, code, message, activeWindowScope, policyTier = "normal") {
|
|
2798
|
+
return {
|
|
2799
|
+
intent,
|
|
2800
|
+
scope: "denied",
|
|
2801
|
+
mutation: !READ_ONLY_ROUTER_INTENTS.has(intent),
|
|
2802
|
+
requiresConfirmation: false,
|
|
2803
|
+
preview: message,
|
|
2804
|
+
denial: { code, message },
|
|
2805
|
+
audit: {
|
|
2806
|
+
requestedBy: "sidepanel",
|
|
2807
|
+
activeWindowId: activeWindowScope?.windowId || "unknown",
|
|
2808
|
+
policyTier,
|
|
2809
|
+
createdAt: new Date().toISOString(),
|
|
2810
|
+
},
|
|
2811
|
+
allowed: false,
|
|
2812
|
+
};
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2815
|
+
function validateRouterIntent(input) {
|
|
2816
|
+
const data = input || {};
|
|
2817
|
+
const intent = String(data.intent || "");
|
|
2818
|
+
const target = data.target || {};
|
|
2819
|
+
const activeWindowScope = data.activeWindowScope || {};
|
|
2820
|
+
const activeWindowId = activeWindowScope.windowId ? String(activeWindowScope.windowId) : "";
|
|
2821
|
+
const isPrime = isPrimeRouterActor(data);
|
|
2822
|
+
const policyTier = isPrime ? "prime" : "normal";
|
|
2823
|
+
|
|
2824
|
+
if (!SUPPORTED_ROUTER_INTENTS.includes(intent)) {
|
|
2825
|
+
return denyRouterIntent(
|
|
2826
|
+
intent || "unknown",
|
|
2827
|
+
"UNSUPPORTED_INTENT",
|
|
2828
|
+
`Unsupported router intent: ${intent || "unknown"}`,
|
|
2829
|
+
activeWindowScope,
|
|
2830
|
+
policyTier
|
|
2831
|
+
);
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
const bwoaDecision = validateBwoaRouterIntent(data);
|
|
2835
|
+
if (bwoaDecision) return bwoaDecision;
|
|
2836
|
+
|
|
2837
|
+
const mutation = !READ_ONLY_ROUTER_INTENTS.has(intent);
|
|
2838
|
+
if (!mutation) {
|
|
2839
|
+
return {
|
|
2840
|
+
intent,
|
|
2841
|
+
target,
|
|
2842
|
+
scope: "global_read_only",
|
|
2843
|
+
mutation: false,
|
|
2844
|
+
requiresConfirmation: false,
|
|
2845
|
+
preview: `Read-only ${intent} for ${target.title || target.id || "router context"}`,
|
|
2846
|
+
audit: {
|
|
2847
|
+
requestedBy: "sidepanel",
|
|
2848
|
+
activeWindowId: activeWindowId || "unknown",
|
|
2849
|
+
policyTier,
|
|
2850
|
+
createdAt: new Date().toISOString(),
|
|
2851
|
+
},
|
|
2852
|
+
allowed: true,
|
|
2853
|
+
};
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2856
|
+
if (
|
|
2857
|
+
normalizeRole(activeWindowScope.profileOwner) === "human" ||
|
|
2858
|
+
normalizeRole(target.profileOwner) === "human"
|
|
2859
|
+
) {
|
|
2860
|
+
return denyRouterIntent(
|
|
2861
|
+
intent,
|
|
2862
|
+
"HUMAN_PROFILE_DENIED",
|
|
2863
|
+
"Agents cannot mutate human-owned browser profile or window state.",
|
|
2864
|
+
activeWindowScope,
|
|
2865
|
+
policyTier
|
|
2866
|
+
);
|
|
2867
|
+
}
|
|
2868
|
+
|
|
2869
|
+
const targetDisplayRole = normalizeRole(target.displayRole);
|
|
2870
|
+
if (targetDisplayRole === "primary_user" || targetDisplayRole === "bottom_user") {
|
|
2871
|
+
return denyRouterIntent(
|
|
2872
|
+
intent,
|
|
2873
|
+
"BOTTOM_DISPLAY_MUTATION_DENIED",
|
|
2874
|
+
"Agents cannot mutate primary or bottom-display user browser windows.",
|
|
2875
|
+
activeWindowScope,
|
|
2876
|
+
policyTier
|
|
2877
|
+
);
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2880
|
+
const targetWindowId = target.windowId ? String(target.windowId) : "";
|
|
2881
|
+
const allowOtherWindow =
|
|
2882
|
+
activeWindowScope.mutationPolicy && activeWindowScope.mutationPolicy.allowOtherWindowMutation;
|
|
2883
|
+
const crossWindowMutation = !!(
|
|
2884
|
+
targetWindowId &&
|
|
2885
|
+
activeWindowId &&
|
|
2886
|
+
targetWindowId !== activeWindowId
|
|
2887
|
+
);
|
|
2888
|
+
if (crossWindowMutation && !allowOtherWindow) {
|
|
2889
|
+
if (isPrime && isPrimeLiveTargetVerified(target)) {
|
|
2890
|
+
// Prime may continue after verified top-display or extension-managed ownership.
|
|
2891
|
+
} else if (isPrime) {
|
|
2892
|
+
return denyRouterIntent(
|
|
2893
|
+
intent,
|
|
2894
|
+
"PRIME_TARGET_UNVERIFIED",
|
|
2895
|
+
"Equanaut Prime cross-window live mutation requires top-display agent ownership or extension-managed target proof.",
|
|
2896
|
+
activeWindowScope,
|
|
2897
|
+
policyTier
|
|
2898
|
+
);
|
|
2899
|
+
} else {
|
|
2900
|
+
return denyRouterIntent(
|
|
2901
|
+
intent,
|
|
2902
|
+
"OTHER_WINDOW_MUTATION_DENIED",
|
|
2903
|
+
"Normal sidepanel requests may mutate only the active sidepanel window. Open or rebind the target first.",
|
|
2904
|
+
activeWindowScope,
|
|
2905
|
+
policyTier
|
|
2906
|
+
);
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2910
|
+
if (
|
|
2911
|
+
["open", "restore", "open_orchestrator_url"].includes(intent) &&
|
|
2912
|
+
activeWindowScope.displayRole &&
|
|
2913
|
+
normalizeRole(activeWindowScope.displayRole) !== "top_agents"
|
|
2914
|
+
) {
|
|
2915
|
+
return denyRouterIntent(
|
|
2916
|
+
intent,
|
|
2917
|
+
"TOP_DISPLAY_REQUIRED",
|
|
2918
|
+
"Open and restore actions must use the top-display agent workspace.",
|
|
2919
|
+
activeWindowScope,
|
|
2920
|
+
policyTier
|
|
2921
|
+
);
|
|
2922
|
+
}
|
|
2923
|
+
|
|
2924
|
+
if (intent === "dispatch_to_fleet") {
|
|
2925
|
+
const status = String(target.status || "").toLowerCase();
|
|
2926
|
+
if (!["active", "running", "idle", "online"].includes(status)) {
|
|
2927
|
+
return denyRouterIntent(
|
|
2928
|
+
intent,
|
|
2929
|
+
"FLEET_TARGET_UNAVAILABLE",
|
|
2930
|
+
"Dispatch requires a live or reachable fleet target.",
|
|
2931
|
+
activeWindowScope,
|
|
2932
|
+
policyTier
|
|
2933
|
+
);
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
|
|
2937
|
+
const extensionOwnedMetadata = isExtensionOwnedMetadataTarget(target);
|
|
2938
|
+
const scope =
|
|
2939
|
+
intent === "dispatch_to_fleet"
|
|
2940
|
+
? "fleet"
|
|
2941
|
+
: isPrime && (extensionOwnedMetadata || crossWindowMutation)
|
|
2942
|
+
? "prime_global"
|
|
2943
|
+
: "active_window";
|
|
2944
|
+
|
|
2945
|
+
return {
|
|
2946
|
+
intent,
|
|
2947
|
+
target,
|
|
2948
|
+
scope,
|
|
2949
|
+
mutation: true,
|
|
2950
|
+
requiresConfirmation: !!data.requiresConfirmation,
|
|
2951
|
+
preview: data.preview || `${intent} ${target.title || target.id || "target"}`,
|
|
2952
|
+
audit: {
|
|
2953
|
+
requestedBy: "sidepanel",
|
|
2954
|
+
activeWindowId: activeWindowId || "unknown",
|
|
2955
|
+
policyTier,
|
|
2956
|
+
reason: data.reason || "user_request",
|
|
2957
|
+
createdAt: new Date().toISOString(),
|
|
2958
|
+
},
|
|
2959
|
+
allowed: true,
|
|
2960
|
+
};
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2963
|
+
const SessionLogic = {
|
|
2964
|
+
timeAgo,
|
|
2965
|
+
fsFuzzyMatch,
|
|
2966
|
+
fsFormatEventDescription,
|
|
2967
|
+
fsApplyFilters,
|
|
2968
|
+
normalizeArchiveWorkflowStatus,
|
|
2969
|
+
fsPartitionTaskthreadGroups,
|
|
2970
|
+
fsApplyAdvancedFilters,
|
|
2971
|
+
fsSortGroups,
|
|
2972
|
+
fsGroupBy,
|
|
2973
|
+
fsResolveSelectedTabs,
|
|
2974
|
+
getDomainDisplayName,
|
|
2975
|
+
DEFAULT_SAFETY_REMINDER_HOST_ALLOWLIST,
|
|
2976
|
+
DEFAULT_EQUANAUT_SLASH_COMMANDS,
|
|
2977
|
+
DEFAULT_EQUANAUT_PLUGIN_LIBRARY,
|
|
2978
|
+
isSafetyReminderAllowlisted,
|
|
2979
|
+
shouldInjectSafetyReminder,
|
|
2980
|
+
normalizeEquanautModelEntry,
|
|
2981
|
+
normalizeEquanautModelCatalog,
|
|
2982
|
+
normalizeEquanautOllamaModel,
|
|
2983
|
+
modelsFromEquanautGatewayConfig,
|
|
2984
|
+
mergeEquanautModelCatalogs,
|
|
2985
|
+
groupEquanautModelCatalog,
|
|
2986
|
+
getEquanautCommandCenterFallbackModels,
|
|
2987
|
+
getEquanautModelBadge,
|
|
2988
|
+
getEquanautProviderGlyph,
|
|
2989
|
+
getEquanautProviderFilterKey,
|
|
2990
|
+
getEquanautModelCostTier,
|
|
2991
|
+
getEquanautModelCapabilities,
|
|
2992
|
+
getEquanautCapabilityLabel,
|
|
2993
|
+
filterEquanautModels,
|
|
2994
|
+
getEquanautModelDisplayName,
|
|
2995
|
+
isEquanautModelConfigured,
|
|
2996
|
+
getEquanautModelSettingsUrl,
|
|
2997
|
+
EQUANAUT_MODEL_CAPABILITIES,
|
|
2998
|
+
EQUANAUT_MODEL_CAPABILITY_ORDER,
|
|
2999
|
+
isEquanautCommandCenterModel,
|
|
3000
|
+
resolveEquanautOllamaModelId,
|
|
3001
|
+
normalizeEquanautRoutingMode,
|
|
3002
|
+
isEquanautModelAllowedForRouting,
|
|
3003
|
+
pickEquanautModelForRoutingMode,
|
|
3004
|
+
normalizeSlashCommandId,
|
|
3005
|
+
normalizeEquanautSlashCommand,
|
|
3006
|
+
normalizeEquanautPlugin,
|
|
3007
|
+
buildEquanautSlashCommandCatalog,
|
|
3008
|
+
buildEquanautPluginCatalog,
|
|
3009
|
+
filterEquanautSlashCommands,
|
|
3010
|
+
filterEquanautPlugins,
|
|
3011
|
+
parseEquanautSlashCommandInvocation,
|
|
3012
|
+
resolveEquanautSlashCommand,
|
|
3013
|
+
normalizeBindingWindowSnapshot,
|
|
3014
|
+
formatBindingStatusLabel,
|
|
3015
|
+
getBindingStatusClass,
|
|
3016
|
+
escapeHtml,
|
|
3017
|
+
groupAndSortForDeletion,
|
|
3018
|
+
SUPPORTED_ROUTER_INTENTS,
|
|
3019
|
+
selectEquanautRouterRoute,
|
|
3020
|
+
isPrimeRouterActor,
|
|
3021
|
+
isExtensionOwnedMetadataTarget,
|
|
3022
|
+
isPrimeLiveTargetVerified,
|
|
3023
|
+
normalizeEntityDescription,
|
|
3024
|
+
normalizeCatalogRecord,
|
|
3025
|
+
buildGlobalContextCatalog,
|
|
3026
|
+
countGlobalContextCatalog,
|
|
3027
|
+
buildAgentContextEnvelope,
|
|
3028
|
+
buildBwoaWindowHubSession,
|
|
3029
|
+
countBwoaWindowHubSession,
|
|
3030
|
+
buildBwoaDelegationEnvelope,
|
|
3031
|
+
buildBwoaWindowHubConfiguration,
|
|
3032
|
+
buildBwoaEpoaHandoffPrompt,
|
|
3033
|
+
buildBwoaSpokePrompt,
|
|
3034
|
+
validateBwoaRouterIntent,
|
|
3035
|
+
validateRouterIntent,
|
|
3036
|
+
DEFAULT_DOMAIN_DISPLAY_MAP,
|
|
3037
|
+
BWOA_DEFAULT_SPACE_URL,
|
|
3038
|
+
};
|
|
3039
|
+
|
|
3040
|
+
// Browser: register on globalThis
|
|
3041
|
+
if (typeof globalThis !== "undefined") {
|
|
3042
|
+
globalThis.SessionLogic = SessionLogic;
|
|
3043
|
+
// sidepanel.js calls timeAgo() unqualified at 9 sites — keep that contract
|
|
3044
|
+
// intact even though the IIFE otherwise hides individual names. Without
|
|
3045
|
+
// this, fsRenderGroups throws "timeAgo is not defined" on the first card
|
|
3046
|
+
// and the middle column of session-manager.html renders nothing.
|
|
3047
|
+
globalThis.timeAgo = timeAgo;
|
|
3048
|
+
}
|
|
3049
|
+
|
|
3050
|
+
// Node.js CJS: export for test require()
|
|
3051
|
+
if (typeof module !== "undefined" && module.exports) {
|
|
3052
|
+
module.exports = SessionLogic;
|
|
3053
|
+
}
|
|
3054
|
+
})();
|