crewswarm 0.9.2 → 0.9.3
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 +22 -9
- package/apps/dashboard/dist/assets/{chat-core-Cx4sTxDd.js → chat-core-3KirthZA.js} +1 -1
- package/apps/dashboard/dist/assets/index-GSWxxEPO.js +2 -0
- package/apps/dashboard/dist/assets/{tab-pm-loop-tab-Bfd449B4.js → tab-pm-loop-tab-DiAPTJXu.js} +1 -1
- package/apps/dashboard/dist/assets/{tab-projects-tab-DhNWnlzt.js → tab-projects-tab-SFH4E--a.js} +1 -1
- package/apps/dashboard/dist/assets/tab-settings-tab-BselH1c0.js +1 -0
- package/apps/dashboard/dist/index.html +82 -11
- package/apps/vibe/README.md +2 -2
- package/apps/vibe/package.json +1 -1
- package/apps/vibe/server.mjs +3 -3
- package/crew-lead.mjs +34 -4
- package/lib/bridges/gateway-ws.mjs +4 -0
- package/lib/crew-lead/chat-handler.mjs +34 -0
- package/lib/crew-lead/http-server.mjs +55 -14
- package/lib/crew-lead/llm-caller.mjs +24 -8
- package/lib/crew-lead/prompts.mjs +7 -0
- package/lib/crew-lead/wave-dispatcher.mjs +15 -3
- package/lib/crew-lead/ws-router.mjs +219 -27
- package/lib/engines/engine-registry.mjs +9 -0
- package/lib/engines/rt-envelope.mjs +1 -0
- package/lib/engines/runners.mjs +5 -2
- package/lib/runtime/paths.mjs +12 -8
- package/package.json +35 -15
- package/scripts/capture-build-flow.mjs +118 -0
- package/scripts/coverage-report.mjs +209 -0
- package/scripts/coverage-summary.mjs +47 -0
- package/scripts/dashboard-validation.mjs +74 -0
- package/scripts/dashboard.mjs +560 -70
- package/scripts/live-bridge-matrix.mjs +79 -0
- package/scripts/live-cli-matrix.mjs +166 -0
- package/scripts/live-crewchat-check.mjs +42 -0
- package/scripts/live-engine-matrix.mjs +50 -0
- package/scripts/live-provider-failover-matrix.mjs +107 -0
- package/scripts/live-provider-matrix.mjs +228 -0
- package/scripts/restart-all-from-repo.sh +4 -4
- package/scripts/smoke-dispatch.mjs +4 -1
- package/scripts/test-blast-radius.mjs +204 -0
- package/scripts/test-report-summary.mjs +88 -0
- package/scripts/test-reporter.mjs +651 -0
- package/scripts/test-rerun.mjs +136 -0
- package/scripts/tmux-bridge +130 -0
- package/apps/dashboard/dist/assets/chat-core-Cx4sTxDd.js.br +0 -0
- package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js.br +0 -0
- package/apps/dashboard/dist/assets/components-BS9fQjE_.js.br +0 -0
- package/apps/dashboard/dist/assets/core-utils-CmOkXgzi.js.br +0 -0
- package/apps/dashboard/dist/assets/index-CF0aJRtC.css.br +0 -0
- package/apps/dashboard/dist/assets/index-DnClJ1ee.js +0 -2
- package/apps/dashboard/dist/assets/index-DnClJ1ee.js.br +0 -0
- package/apps/dashboard/dist/assets/orchestration-Ca2DLWN-.js.br +0 -0
- package/apps/dashboard/dist/assets/setup-wizard-CA0Or47w.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-agents-tab-BgpIsjkw.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-comms-tab-kguqTIzD.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-contacts-tab-DiOyMYth.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-engines-tab-BsdZVvU0.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-memory-tab-Cu6u13EQ.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-pm-loop-tab-Bfd449B4.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-projects-tab-DhNWnlzt.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-prompts-tab-DVkUNaJd.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-services-tab-DU_LH3uG.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js +0 -1
- package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-spending-tab-DEccQHnt.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-swarm-chat-tab-BNrd88-r.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-swarm-tab-B1AcjL1W.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-usage-tab-BIOOnB-Y.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-waves-tab-SaJDkb4x.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-workflows-tab-B-soSy1k.js.br +0 -0
- package/apps/dashboard/dist/index.html.br +0 -0
- package/apps/dashboard/dist/index.html.gz +0 -0
- package/apps/dashboard/index.html +0 -6529
- package/apps/dashboard/package.json +0 -15
- package/apps/dashboard/src/app.js +0 -2828
- package/apps/dashboard/src/app.js.br +0 -0
- package/apps/dashboard/src/app.js.gz +0 -0
- package/apps/dashboard/src/chat/chat-actions.js +0 -1847
- package/apps/dashboard/src/chat/chat-actions.js.br +0 -0
- package/apps/dashboard/src/chat/unified-messages.js +0 -327
- package/apps/dashboard/src/chat/unified-messages.js.br +0 -0
- package/apps/dashboard/src/cli-process.js +0 -208
- package/apps/dashboard/src/cli-process.js.br +0 -0
- package/apps/dashboard/src/cli-process.js.gz +0 -0
- package/apps/dashboard/src/components/active-tasks-panel.js +0 -175
- package/apps/dashboard/src/components/active-tasks-panel.js.br +0 -0
- package/apps/dashboard/src/core/api.js +0 -18
- package/apps/dashboard/src/core/api.js.br +0 -0
- package/apps/dashboard/src/core/dom.js +0 -228
- package/apps/dashboard/src/core/dom.js.br +0 -0
- package/apps/dashboard/src/core/state.js +0 -91
- package/apps/dashboard/src/core/state.js.br +0 -0
- package/apps/dashboard/src/core/task-manager.js +0 -134
- package/apps/dashboard/src/core/task-manager.js.br +0 -0
- package/apps/dashboard/src/orchestration-status.js +0 -127
- package/apps/dashboard/src/orchestration-status.js.br +0 -0
- package/apps/dashboard/src/setup-wizard.js +0 -562
- package/apps/dashboard/src/setup-wizard.js.br +0 -0
- package/apps/dashboard/src/styles.css +0 -2085
- package/apps/dashboard/src/styles.css.br +0 -0
- package/apps/dashboard/src/styles.css.gz +0 -0
- package/apps/dashboard/src/tabs/agents-tab.js +0 -2237
- package/apps/dashboard/src/tabs/agents-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/benchmarks-tab.js +0 -229
- package/apps/dashboard/src/tabs/benchmarks-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/comms-tab.js +0 -955
- package/apps/dashboard/src/tabs/comms-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/contacts-tab.js +0 -654
- package/apps/dashboard/src/tabs/contacts-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/engines-tab.js +0 -175
- package/apps/dashboard/src/tabs/engines-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/memory-tab.js +0 -182
- package/apps/dashboard/src/tabs/memory-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/models-tab.js +0 -450
- package/apps/dashboard/src/tabs/models-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/pm-loop-tab.js +0 -185
- package/apps/dashboard/src/tabs/pm-loop-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/projects-tab.js +0 -663
- package/apps/dashboard/src/tabs/projects-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/projects-tab.js.gz +0 -0
- package/apps/dashboard/src/tabs/prompts-tab.js +0 -160
- package/apps/dashboard/src/tabs/prompts-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/services-tab.js +0 -202
- package/apps/dashboard/src/tabs/services-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/settings-tab.js +0 -861
- package/apps/dashboard/src/tabs/settings-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/skills-tab.js +0 -284
- package/apps/dashboard/src/tabs/skills-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/spending-tab.js +0 -173
- package/apps/dashboard/src/tabs/spending-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/swarm-chat-tab.js +0 -660
- package/apps/dashboard/src/tabs/swarm-chat-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/swarm-tab.js +0 -538
- package/apps/dashboard/src/tabs/swarm-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/usage-tab.js +0 -390
- package/apps/dashboard/src/tabs/usage-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/waves-tab.js +0 -238
- package/apps/dashboard/src/tabs/waves-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/workflows-tab.js +0 -747
- package/apps/dashboard/src/tabs/workflows-tab.js.br +0 -0
- package/apps/vibe/.crew/agent-memory/pipeline.json +0 -304
- package/apps/vibe/.crew/cost.json +0 -17
- package/apps/vibe/.crew/json-parse-metrics.jsonl +0 -27
- package/apps/vibe/.crew/pipeline-metrics.jsonl +0 -27
- package/apps/vibe/.crew/pipeline-runs/pipeline-0f90c392-2425-4ae5-850c-bd9d17b1d690.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-1c269dd9-a63f-4fba-af81-5cf08048ef06.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-288a7765-da24-4a22-89bc-1f3cc9b0562c.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-2c78fd22-a657-4bd1-bc49-0679fb384409.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-3da23550-22ed-4904-9a0a-8e79c1f3024c.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-3e6fe08d-3264-404a-8df3-aab7efef10e7.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-42eec610-57fe-4e09-9e7e-b315038495c2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4438eb4c-ae13-42b1-90e2-b043d8983be8.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4740a9f5-86e7-44b6-a394-de433e291727.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-49e1da6a-957e-48fd-9220-415019e4f8e2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4c9251db-be68-427b-a3fc-a264f2b5778d.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6413fa33-a802-4b57-a8c0-a9056ad67842.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-65e29a57-664d-4196-8109-017e364f182e.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6aa04bc5-9593-4b1f-b58d-3bf2978cb602.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6e1cba53-9b70-457e-99e0-59199149dd21.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-749f41cc-4dac-4204-be64-873a6080a0d2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-74d68121-e181-4864-bd9a-c3211341dfaf.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-8509bc24-142d-4e07-b44a-a50bf99d1103.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-960339c6-07ca-43ce-9900-f6e1702b39b9.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9bef2dd2-6122-42e5-b3d9-19f4d80f9e40.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9c6480a9-7031-4146-b241-825b9a2d1de1.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9fd42426-8492-4157-9d5f-e1537c060489.jsonl +0 -2
- package/apps/vibe/.crew/pipeline-runs/pipeline-ad6d40a3-2f5e-46a9-a345-47caaccc51aa.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-bc606133-8d5b-4535-8d85-f1a29cdaa981.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c1418f4e-b773-4ca1-84a3-216acf36e2f2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c1a13ccd-634a-4d01-a4a7-1177b8a752ff.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c7d27b42-249e-4bd4-8f26-6aa998110b8a.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-cca2e9b9-4a34-4d25-a311-5c793fa7e91e.jsonl +0 -5
- package/apps/vibe/.crew/sandbox.json +0 -7
- package/apps/vibe/.crew/session.json +0 -330
- package/apps/vibe/.crew/training-data.jsonl +0 -0
- package/apps/vibe/.github/workflows/studio-quality.yml +0 -37
- package/apps/vibe/.studio-data/project-messages/chuck-norris.jsonl +0 -18
- package/apps/vibe/.studio-data/project-messages/general.jsonl +0 -81
- package/apps/vibe/.studio-data/project-messages/studio-local.jsonl +0 -18
- package/apps/vibe/ARCHITECTURE.md +0 -3393
- package/apps/vibe/QUICK-REFERENCE.md +0 -211
- package/apps/vibe/ROADMAP.md +0 -41
- package/apps/vibe/STUDIO-SETUP-COMPLETE.md +0 -35
- package/apps/vibe/VISUAL-GUIDE.md +0 -378
- package/apps/vibe/capture-demo.mjs +0 -160
- package/apps/vibe/capture-full-demo.mjs +0 -255
- package/apps/vibe/capture-quickstart.mjs +0 -256
- package/apps/vibe/capture-vibe-assets.mjs +0 -71
- package/apps/vibe/capture-vibe-video.mjs +0 -260
- package/apps/vibe/check-buttons.js +0 -41
- package/apps/vibe/diagnose.html +0 -106
- package/apps/vibe/fix-buttons.js +0 -103
- package/apps/vibe/index.html +0 -3404
- package/apps/vibe/package-lock.json +0 -920
- package/apps/vibe/scripts/studio-pty-host.py +0 -117
- package/apps/vibe/src/main.js +0 -2940
- package/apps/vibe/src/register-all-languages.js +0 -98
- package/apps/vibe/start-studio.sh +0 -11
- package/apps/vibe/test/accessibility-tests.js +0 -77
- package/apps/vibe/test/browser-performance-audit.mjs +0 -205
- package/apps/vibe/test/performance-tests.js +0 -120
- package/apps/vibe/test/security-tests.js +0 -213
- package/apps/vibe/tests/e2e.local.mjs +0 -54
- package/apps/vibe/tests/server.smoke.mjs +0 -106
- package/apps/vibe/update_website.mjs +0 -74
- package/apps/vibe/vite.config.js +0 -19
- package/lib/crew-lead/chat-handler.mjs.bak +0 -1274
- package/lib/engines/rt-envelope.mjs.backup-current +0 -870
|
@@ -1,2828 +0,0 @@
|
|
|
1
|
-
import { getJSON, postJSON } from "./core/api.js";
|
|
2
|
-
import { checkFirstRun } from "./setup-wizard.js";
|
|
3
|
-
import {
|
|
4
|
-
escHtml,
|
|
5
|
-
showNotification,
|
|
6
|
-
fmt,
|
|
7
|
-
createdAt,
|
|
8
|
-
appendChatBubble,
|
|
9
|
-
showLoading,
|
|
10
|
-
showEmpty,
|
|
11
|
-
showError,
|
|
12
|
-
renderStatusBadge,
|
|
13
|
-
} from "./core/dom.js";
|
|
14
|
-
import {
|
|
15
|
-
state,
|
|
16
|
-
persistState,
|
|
17
|
-
saveScrollPosition,
|
|
18
|
-
restoreScrollPosition,
|
|
19
|
-
} from "./core/state.js";
|
|
20
|
-
import { initActiveTasksPanel } from "./components/active-tasks-panel.js";
|
|
21
|
-
import { startOrchestrationStatusUpdates } from "./orchestration-status.js";
|
|
22
|
-
import "./cli-process.js";
|
|
23
|
-
import "./chat/unified-messages.js";
|
|
24
|
-
import {
|
|
25
|
-
initSwarmChatTab,
|
|
26
|
-
showSwarmChat,
|
|
27
|
-
handleSwarmSSEEvent,
|
|
28
|
-
} from "./tabs/swarm-chat-tab.js";
|
|
29
|
-
// Lazy-loaded benchmarks tab for code splitting
|
|
30
|
-
let benchmarksTabModule = null;
|
|
31
|
-
async function loadBenchmarksTabModule() {
|
|
32
|
-
if (!benchmarksTabModule) {
|
|
33
|
-
benchmarksTabModule = await import("./tabs/benchmarks-tab.js");
|
|
34
|
-
}
|
|
35
|
-
return benchmarksTabModule;
|
|
36
|
-
}
|
|
37
|
-
import { initWavesTab } from "./tabs/waves-tab.js";
|
|
38
|
-
import { initWorkflowsTab, showWorkflows } from "./tabs/workflows-tab.js";
|
|
39
|
-
import {
|
|
40
|
-
initMemoryTab,
|
|
41
|
-
showMemory,
|
|
42
|
-
loadMemoryStats,
|
|
43
|
-
searchMemory,
|
|
44
|
-
migrateMemory,
|
|
45
|
-
compactMemory,
|
|
46
|
-
} from "./tabs/memory-tab.js";
|
|
47
|
-
import {
|
|
48
|
-
initServicesTab,
|
|
49
|
-
showServices,
|
|
50
|
-
loadServices,
|
|
51
|
-
restartService,
|
|
52
|
-
stopService,
|
|
53
|
-
} from "./tabs/services-tab.js";
|
|
54
|
-
import {
|
|
55
|
-
initAgentsTab,
|
|
56
|
-
showAgents,
|
|
57
|
-
loadAgents_cfg,
|
|
58
|
-
applyToolPreset,
|
|
59
|
-
toggleAgentBody,
|
|
60
|
-
deleteAgent,
|
|
61
|
-
saveAgentModel,
|
|
62
|
-
saveAgentFallback,
|
|
63
|
-
saveAgentVoice,
|
|
64
|
-
toggleEmojiPicker,
|
|
65
|
-
saveAgentIdentity,
|
|
66
|
-
saveAgentPrompt,
|
|
67
|
-
resetAgentSession,
|
|
68
|
-
saveAgentTools,
|
|
69
|
-
setRoute,
|
|
70
|
-
saveOpenCodeConfig,
|
|
71
|
-
saveOpenCodeFallback,
|
|
72
|
-
saveCursorCliConfig,
|
|
73
|
-
saveClaudeCodeConfig,
|
|
74
|
-
saveCodexConfig,
|
|
75
|
-
saveGeminiCliConfig,
|
|
76
|
-
saveCrewCLIConfig,
|
|
77
|
-
bulkSetRoute,
|
|
78
|
-
startCrew,
|
|
79
|
-
populateModelDropdown,
|
|
80
|
-
applyNewAgentToolPreset,
|
|
81
|
-
applyPromptPreset,
|
|
82
|
-
} from "./tabs/agents-tab.js";
|
|
83
|
-
import { initPromptsTab, initPromptsTabDeps } from "./tabs/prompts-tab.js";
|
|
84
|
-
import {
|
|
85
|
-
showSkills,
|
|
86
|
-
showRunSkills,
|
|
87
|
-
loadRunSkills,
|
|
88
|
-
runSkillFromUI,
|
|
89
|
-
loadSkills,
|
|
90
|
-
renderSkillsList,
|
|
91
|
-
filterSkills,
|
|
92
|
-
editSkill,
|
|
93
|
-
toggleAddSkill,
|
|
94
|
-
toggleImportSkill,
|
|
95
|
-
importSkillFromUrl,
|
|
96
|
-
cancelSkillForm,
|
|
97
|
-
updateSkillAuthFields,
|
|
98
|
-
saveSkill,
|
|
99
|
-
deleteSkill,
|
|
100
|
-
} from "./tabs/skills-tab.js";
|
|
101
|
-
import {
|
|
102
|
-
showContacts,
|
|
103
|
-
loadContacts,
|
|
104
|
-
applyContactFilters,
|
|
105
|
-
initContactsList,
|
|
106
|
-
} from "./tabs/contacts-tab.js";
|
|
107
|
-
import {
|
|
108
|
-
loadEngines,
|
|
109
|
-
deleteEngine,
|
|
110
|
-
toggleImportEngine,
|
|
111
|
-
importEngineFromUrl,
|
|
112
|
-
} from "./tabs/engines-tab.js";
|
|
113
|
-
import { initChatActions } from "./chat/chat-actions.js";
|
|
114
|
-
import {
|
|
115
|
-
initSwarmTab,
|
|
116
|
-
showSwarm,
|
|
117
|
-
showRT,
|
|
118
|
-
showDLQ,
|
|
119
|
-
loadSessions,
|
|
120
|
-
loadMessages,
|
|
121
|
-
loadRTMessages,
|
|
122
|
-
toggleRTPause,
|
|
123
|
-
clearRTMessages,
|
|
124
|
-
loadDLQ,
|
|
125
|
-
replayDLQ,
|
|
126
|
-
deleteDLQ,
|
|
127
|
-
} from "./tabs/swarm-tab.js";
|
|
128
|
-
import {
|
|
129
|
-
initModelsTab,
|
|
130
|
-
initAddProviderForm,
|
|
131
|
-
showModels,
|
|
132
|
-
showProviders,
|
|
133
|
-
loadSearchTools,
|
|
134
|
-
saveSearchTool,
|
|
135
|
-
testSearchTool,
|
|
136
|
-
loadBuiltinProviders,
|
|
137
|
-
saveBuiltinKey,
|
|
138
|
-
testBuiltinProvider,
|
|
139
|
-
fetchBuiltinModels,
|
|
140
|
-
loadProviders,
|
|
141
|
-
toggleKeyVis,
|
|
142
|
-
saveKey,
|
|
143
|
-
testKey,
|
|
144
|
-
fetchModels,
|
|
145
|
-
} from "./tabs/models-tab.js";
|
|
146
|
-
import {
|
|
147
|
-
initSettingsTab,
|
|
148
|
-
loadOpenClawStatus,
|
|
149
|
-
loadRTToken,
|
|
150
|
-
saveRTToken,
|
|
151
|
-
loadConfigLockStatus,
|
|
152
|
-
lockConfig,
|
|
153
|
-
unlockConfig,
|
|
154
|
-
loadOpencodeProject,
|
|
155
|
-
saveOpencodeSettings,
|
|
156
|
-
saveOpencodeModel,
|
|
157
|
-
loadBgConsciousness,
|
|
158
|
-
toggleBgConsciousness,
|
|
159
|
-
saveBgConsciousnessModel,
|
|
160
|
-
loadCursorWaves,
|
|
161
|
-
toggleCursorWaves,
|
|
162
|
-
loadAutonomousMentions,
|
|
163
|
-
toggleAutonomousMentions,
|
|
164
|
-
loadClaudeCode,
|
|
165
|
-
toggleClaudeCode,
|
|
166
|
-
loadCodexExecutor,
|
|
167
|
-
toggleCodexExecutor,
|
|
168
|
-
loadGeminiCliExecutor,
|
|
169
|
-
toggleGeminiCliExecutor,
|
|
170
|
-
loadCrewCliExecutor,
|
|
171
|
-
toggleCrewCliExecutor,
|
|
172
|
-
loadOpencodeExecutor,
|
|
173
|
-
toggleOpencodeExecutor,
|
|
174
|
-
loadGlobalFallback,
|
|
175
|
-
saveGlobalFallback,
|
|
176
|
-
loadGlobalOcLoop,
|
|
177
|
-
saveGlobalOcLoop,
|
|
178
|
-
saveGlobalOcLoopRounds,
|
|
179
|
-
loadPassthroughNotify,
|
|
180
|
-
savePassthroughNotify,
|
|
181
|
-
loadLoopBrain,
|
|
182
|
-
saveLoopBrain,
|
|
183
|
-
loadEnvAdvanced,
|
|
184
|
-
loadTmuxBridge,
|
|
185
|
-
toggleTmuxBridge,
|
|
186
|
-
} from "./tabs/settings-tab.js";
|
|
187
|
-
import {
|
|
188
|
-
initCommsTab,
|
|
189
|
-
showMessaging,
|
|
190
|
-
loadCommsTabData,
|
|
191
|
-
loadTgStatus,
|
|
192
|
-
loadTgConfig,
|
|
193
|
-
saveTgConfig,
|
|
194
|
-
startTgBridge,
|
|
195
|
-
stopTgBridge,
|
|
196
|
-
loadWaStatus,
|
|
197
|
-
renderWaContactRows,
|
|
198
|
-
loadWaConfig,
|
|
199
|
-
saveWaConfig,
|
|
200
|
-
startWaBridge,
|
|
201
|
-
stopWaBridge,
|
|
202
|
-
loadWaMessages,
|
|
203
|
-
loadTgMessages,
|
|
204
|
-
loadTelegramSessions,
|
|
205
|
-
} from "./tabs/comms-tab.js";
|
|
206
|
-
import {
|
|
207
|
-
showBuild as _showBuild,
|
|
208
|
-
showProjects as _showProjects,
|
|
209
|
-
loadProjects,
|
|
210
|
-
toggleProjectEdit,
|
|
211
|
-
saveProjectEdit,
|
|
212
|
-
initProjectsList,
|
|
213
|
-
populateChatProjectDropdown,
|
|
214
|
-
onChatProjectChange,
|
|
215
|
-
updateChatProjectHint,
|
|
216
|
-
autoSelectChatProject,
|
|
217
|
-
resumeProject,
|
|
218
|
-
stopProjectPMLoop,
|
|
219
|
-
startProjectPMLoop,
|
|
220
|
-
deleteProject,
|
|
221
|
-
openProjectInBuild as _openProjectInBuild,
|
|
222
|
-
loadBuildProjectPicker,
|
|
223
|
-
onBuildProjectChange,
|
|
224
|
-
stopBuild,
|
|
225
|
-
stopContinuousBuild,
|
|
226
|
-
retryFailed,
|
|
227
|
-
openRoadmapEditor,
|
|
228
|
-
closeRoadmapEditor,
|
|
229
|
-
saveRoadmap,
|
|
230
|
-
addRoadmapItem,
|
|
231
|
-
skipNextItem,
|
|
232
|
-
resetAllFailed,
|
|
233
|
-
loadPhasedProgress,
|
|
234
|
-
runBuild,
|
|
235
|
-
enhancePrompt,
|
|
236
|
-
continuousBuildRun,
|
|
237
|
-
} from "./tabs/projects-tab.js";
|
|
238
|
-
import {
|
|
239
|
-
loadTokenUsage,
|
|
240
|
-
loadOcStats as loadOcStatsFromUsage,
|
|
241
|
-
loadToolMatrix,
|
|
242
|
-
restartAgentFromUI,
|
|
243
|
-
checkCrewLeadStatus,
|
|
244
|
-
renderTaskLifecycle,
|
|
245
|
-
} from "./tabs/usage-tab.js";
|
|
246
|
-
import {
|
|
247
|
-
loadAllUsage,
|
|
248
|
-
loadSpending,
|
|
249
|
-
resetSpending,
|
|
250
|
-
saveGlobalCaps,
|
|
251
|
-
reportOcCost,
|
|
252
|
-
} from "./tabs/spending-tab.js";
|
|
253
|
-
import {
|
|
254
|
-
checkPmStatus,
|
|
255
|
-
startPmLoop,
|
|
256
|
-
stopPmLoop,
|
|
257
|
-
toggleRoadmap,
|
|
258
|
-
initPmLoopTab,
|
|
259
|
-
} from "./tabs/pm-loop-tab.js";
|
|
260
|
-
|
|
261
|
-
let selected = null;
|
|
262
|
-
// Lightweight status updater (just status dot + DLQ badge, no content loading)
|
|
263
|
-
async function updateStatusBadges() {
|
|
264
|
-
try {
|
|
265
|
-
const dot = document.getElementById("statusDot");
|
|
266
|
-
document.getElementById("status").textContent = "online";
|
|
267
|
-
dot.className = "status-dot online";
|
|
268
|
-
await checkCrewLeadStatus();
|
|
269
|
-
const dlqData = await getJSON("/api/dlq");
|
|
270
|
-
const badge = document.getElementById("dlqBadge");
|
|
271
|
-
if (dlqData.length) {
|
|
272
|
-
badge.textContent = dlqData.length;
|
|
273
|
-
badge.classList.remove("hidden");
|
|
274
|
-
} else {
|
|
275
|
-
badge.classList.add("hidden");
|
|
276
|
-
}
|
|
277
|
-
} catch (e) {
|
|
278
|
-
document.getElementById("status").textContent = "error";
|
|
279
|
-
document.getElementById("statusDot").className = "status-dot error";
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Legacy alias for backwards compatibility
|
|
284
|
-
async function refreshAll() {
|
|
285
|
-
await updateStatusBadges();
|
|
286
|
-
}
|
|
287
|
-
function setNavActive(navId) {
|
|
288
|
-
document
|
|
289
|
-
.querySelectorAll(".nav-item")
|
|
290
|
-
.forEach((b) => b.classList.remove("active"));
|
|
291
|
-
const el = document.getElementById(navId);
|
|
292
|
-
if (el) el.classList.add("active");
|
|
293
|
-
}
|
|
294
|
-
function hideAllViews() {
|
|
295
|
-
// Save scroll position of the current active view before hiding
|
|
296
|
-
saveScrollPosition(state.activeTab);
|
|
297
|
-
document.querySelectorAll(".view, .view-sessions").forEach((el) => {
|
|
298
|
-
el.classList.remove("active");
|
|
299
|
-
// Also clear any inline display styles that might interfere
|
|
300
|
-
if (el.style.display) el.style.display = "";
|
|
301
|
-
});
|
|
302
|
-
const mb = document.querySelector(".msg-bar");
|
|
303
|
-
if (mb) mb.style.display = "";
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
initServicesTab({ hideAllViews, setNavActive });
|
|
307
|
-
initAgentsTab({ hideAllViews, setNavActive, refreshAgents: loadAgents_cfg });
|
|
308
|
-
initPromptsTabDeps({ hideAllViews, setNavActive });
|
|
309
|
-
initSwarmTab({ hideAllViews, setNavActive });
|
|
310
|
-
initMemoryTab(state);
|
|
311
|
-
initWavesTab();
|
|
312
|
-
initWorkflowsTab({ hideAllViews, setNavActive });
|
|
313
|
-
|
|
314
|
-
async function pickFolder(inputId) {
|
|
315
|
-
const input = document.getElementById(inputId);
|
|
316
|
-
const def = encodeURIComponent(input?.value || window._crewHome || "");
|
|
317
|
-
const d = await getJSON("/api/pick-folder?default=" + def).catch(() => null);
|
|
318
|
-
if (d?.path) {
|
|
319
|
-
if (input) input.value = d.path;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
async function loadCrewLeadInfo() {
|
|
323
|
-
try {
|
|
324
|
-
const d = await getJSON("/api/agents-config");
|
|
325
|
-
const cl = (d.agents || []).find((a) => a.id === "crew-lead");
|
|
326
|
-
if (!cl) return;
|
|
327
|
-
window._crewLeadInfo = {
|
|
328
|
-
emoji: cl.emoji || "🧠",
|
|
329
|
-
name: cl.name || "crew-lead",
|
|
330
|
-
theme: cl.theme || "",
|
|
331
|
-
};
|
|
332
|
-
const titleEl = document.getElementById("chatAgentTitle");
|
|
333
|
-
const subEl = document.getElementById("chatAgentSub");
|
|
334
|
-
if (titleEl)
|
|
335
|
-
titleEl.textContent = (cl.emoji || "🧠") + " " + (cl.name || "Crew Lead");
|
|
336
|
-
if (subEl && cl.theme)
|
|
337
|
-
subEl.textContent =
|
|
338
|
-
cl.theme + " — chat naturally, dispatch tasks to the crew";
|
|
339
|
-
} catch (e) {
|
|
340
|
-
/* keep defaults */
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Hash routing: view id is only the segment before ? and before /.
|
|
346
|
-
* e.g. #chat?project=general → view "chat" (NOT "chat?project=general").
|
|
347
|
-
*/
|
|
348
|
-
function parseRouteFromHash() {
|
|
349
|
-
const raw = (location.hash || "#chat").slice(1);
|
|
350
|
-
const noQuery = raw.split("?")[0] || "chat";
|
|
351
|
-
const parts = noQuery.split("/");
|
|
352
|
-
const view = parts[0] || "chat";
|
|
353
|
-
const subtab = parts[1];
|
|
354
|
-
return { view, subtab, raw };
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function currentHashBaseView() {
|
|
358
|
-
return parseRouteFromHash().view;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* Set chat project in the URL without firing hashchange — prevents a second
|
|
363
|
-
* showChat() + loadChatHistory() that clears the thread while a reply streams.
|
|
364
|
-
*/
|
|
365
|
-
function setChatHashProject(projectId) {
|
|
366
|
-
const id =
|
|
367
|
-
projectId && String(projectId).trim() && projectId !== "undefined"
|
|
368
|
-
? projectId
|
|
369
|
-
: "general";
|
|
370
|
-
const next = `#chat?project=${encodeURIComponent(id)}`;
|
|
371
|
-
if (location.hash !== next) {
|
|
372
|
-
history.replaceState(null, "", next);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
async function loadSharedActiveProjectId() {
|
|
377
|
-
try {
|
|
378
|
-
const data = await getJSON("/api/ui/active-project");
|
|
379
|
-
const projectId = String(data?.projectId || "").trim();
|
|
380
|
-
return projectId || "general";
|
|
381
|
-
} catch {
|
|
382
|
-
return "general";
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
async function persistSharedActiveProjectId(projectId) {
|
|
387
|
-
const normalizedId =
|
|
388
|
-
projectId && String(projectId).trim() && projectId !== "undefined"
|
|
389
|
-
? String(projectId).trim()
|
|
390
|
-
: "general";
|
|
391
|
-
try {
|
|
392
|
-
await postJSON("/api/ui/active-project", { projectId: normalizedId });
|
|
393
|
-
} catch {
|
|
394
|
-
/* best effort */
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
async function syncChatProjectFromSharedState() {
|
|
399
|
-
if (parseRouteFromHash().view !== "chat") return;
|
|
400
|
-
const sharedProjectId = await loadSharedActiveProjectId();
|
|
401
|
-
const normalizedSharedId =
|
|
402
|
-
sharedProjectId && sharedProjectId !== "undefined"
|
|
403
|
-
? sharedProjectId
|
|
404
|
-
: "general";
|
|
405
|
-
const currentProjectId =
|
|
406
|
-
state.chatActiveProjectId && state.chatActiveProjectId !== "undefined"
|
|
407
|
-
? state.chatActiveProjectId
|
|
408
|
-
: "general";
|
|
409
|
-
if (normalizedSharedId !== currentProjectId && window.selectProjectTab) {
|
|
410
|
-
window.selectProjectTab(normalizedSharedId);
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
async function showChat() {
|
|
415
|
-
hideAllViews();
|
|
416
|
-
document.getElementById("chatView").classList.add("active");
|
|
417
|
-
setNavActive("navChat");
|
|
418
|
-
state.activeTab = "chat";
|
|
419
|
-
persistState();
|
|
420
|
-
const mb = document.querySelector(".msg-bar");
|
|
421
|
-
if (mb) mb.style.display = "none";
|
|
422
|
-
|
|
423
|
-
// Start orchestration status updates
|
|
424
|
-
startOrchestrationStatusUpdates();
|
|
425
|
-
|
|
426
|
-
// Check if chat content is already loaded (DOM preserved from last visit)
|
|
427
|
-
const chatBox = document.getElementById("chatMessages");
|
|
428
|
-
if (chatBox?.dataset.historyLoading === "true") {
|
|
429
|
-
await waitForChatHistoryIdle();
|
|
430
|
-
}
|
|
431
|
-
const alreadyLoaded =
|
|
432
|
-
chatBox &&
|
|
433
|
-
chatBox.dataset.historyLoaded === "true" &&
|
|
434
|
-
chatBox.children.length > 0;
|
|
435
|
-
|
|
436
|
-
// Refresh project dropdown (uses TTL cache so fast on re-visits)
|
|
437
|
-
try {
|
|
438
|
-
const data = await getJSON("/api/projects");
|
|
439
|
-
const projects = data.projects || [];
|
|
440
|
-
state.projectsData = {};
|
|
441
|
-
projects.forEach((p) => {
|
|
442
|
-
state.projectsData[p.id] = p;
|
|
443
|
-
});
|
|
444
|
-
persistState();
|
|
445
|
-
populateChatProjectDropdown(projects);
|
|
446
|
-
} catch (e) {
|
|
447
|
-
console.warn("Failed to refresh projects dropdown:", e);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// Read project from URL first (most explicit), then shared UI state, then localStorage.
|
|
451
|
-
const urlParams = new URLSearchParams(
|
|
452
|
-
window.location.hash.replace(/^#chat\?/, ""),
|
|
453
|
-
);
|
|
454
|
-
const urlProject = urlParams.get("project");
|
|
455
|
-
if (urlProject) {
|
|
456
|
-
state.chatActiveProjectId = urlProject;
|
|
457
|
-
} else {
|
|
458
|
-
const sharedProjectId = await loadSharedActiveProjectId();
|
|
459
|
-
try {
|
|
460
|
-
state.chatActiveProjectId =
|
|
461
|
-
sharedProjectId ||
|
|
462
|
-
localStorage.getItem("crewswarm_chat_active_project_id") ||
|
|
463
|
-
"general";
|
|
464
|
-
} catch {
|
|
465
|
-
state.chatActiveProjectId = sharedProjectId || "general";
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
// Set initial URL if not set (replaceState — no hashchange storm)
|
|
470
|
-
if (!window.location.hash.includes("?project=")) {
|
|
471
|
-
setChatHashProject(state.chatActiveProjectId);
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
console.log("🔵 [INIT] Active project from URL:", state.chatActiveProjectId);
|
|
475
|
-
|
|
476
|
-
// Highlight the correct tab based on URL
|
|
477
|
-
const tabsContainer = document.getElementById("chatProjectTabs");
|
|
478
|
-
if (tabsContainer) {
|
|
479
|
-
Array.from(tabsContainer.children).forEach((tab) => {
|
|
480
|
-
if (tab.dataset.projectId === state.chatActiveProjectId) {
|
|
481
|
-
tab.classList.add("active");
|
|
482
|
-
} else {
|
|
483
|
-
tab.classList.remove("active");
|
|
484
|
-
}
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
const sel = document.getElementById("chatProjectSelect");
|
|
489
|
-
if (
|
|
490
|
-
sel &&
|
|
491
|
-
state.chatActiveProjectId &&
|
|
492
|
-
sel.querySelector('option[value="' + state.chatActiveProjectId + '"]')
|
|
493
|
-
)
|
|
494
|
-
sel.value = state.chatActiveProjectId;
|
|
495
|
-
persistSharedActiveProjectId(state.chatActiveProjectId);
|
|
496
|
-
checkCrewLeadStatus();
|
|
497
|
-
startAgentReplyListener();
|
|
498
|
-
loadCrewLeadInfo();
|
|
499
|
-
|
|
500
|
-
// Load agents into chat agent selector
|
|
501
|
-
if (window.loadChatAgentSelector) {
|
|
502
|
-
window.loadChatAgentSelector();
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
// Only reload history if the chat box is empty (first visit or after clear)
|
|
506
|
-
if (!alreadyLoaded) {
|
|
507
|
-
await loadChatHistory();
|
|
508
|
-
} else {
|
|
509
|
-
// Restore scroll position from previous visit
|
|
510
|
-
restoreScrollPosition("chat");
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
function showFiles() {
|
|
514
|
-
hideAllViews();
|
|
515
|
-
document.getElementById("filesView").classList.add("active");
|
|
516
|
-
setNavActive("navFiles");
|
|
517
|
-
state.activeTab = "files";
|
|
518
|
-
persistState();
|
|
519
|
-
loadFiles();
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// ── Chat / crew-lead ──────────────────────────────────────────────────────────
|
|
523
|
-
// Session ID: Always "owner" for dashboard, projectId handles isolation
|
|
524
|
-
function getChatSessionId() {
|
|
525
|
-
return "owner";
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
let chatPollInterval = null;
|
|
529
|
-
let agentReplySSE = null;
|
|
530
|
-
|
|
531
|
-
/** Assistant rows from appendChatBubble use align-items:flex-start on the wrapper. */
|
|
532
|
-
function chatThreadHasAssistantText(box, text) {
|
|
533
|
-
if (!box || text == null) return false;
|
|
534
|
-
const want = String(text).trim();
|
|
535
|
-
if (!want) return false;
|
|
536
|
-
for (let i = box.children.length - 1; i >= 0; i--) {
|
|
537
|
-
const row = box.children[i];
|
|
538
|
-
if (row.id === "streaming-wrapper") continue;
|
|
539
|
-
if (row.children.length < 2) continue;
|
|
540
|
-
if (!String(row.style.alignItems || "").includes("flex-start")) continue;
|
|
541
|
-
const bubble = row.children[1];
|
|
542
|
-
if ((bubble.textContent || "").trim() === want) return true;
|
|
543
|
-
}
|
|
544
|
-
return false;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
/** Most recent assistant bubble text (for consecutive duplicate SSE guard). */
|
|
548
|
-
function lastAssistantBubbleText(box) {
|
|
549
|
-
if (!box) return "";
|
|
550
|
-
for (let i = box.children.length - 1; i >= 0; i--) {
|
|
551
|
-
const row = box.children[i];
|
|
552
|
-
if (row.id === "streaming-wrapper") continue;
|
|
553
|
-
if (row.children.length < 2) continue;
|
|
554
|
-
if (!String(row.style.alignItems || "").includes("flex-start")) continue;
|
|
555
|
-
return (row.children[1].textContent || "").trim();
|
|
556
|
-
}
|
|
557
|
-
return "";
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
function startAgentReplyListener() {
|
|
561
|
-
if (agentReplySSE) return; // already listening
|
|
562
|
-
|
|
563
|
-
// Connect DIRECTLY to crew-lead's SSE endpoint (not via dashboard proxy)
|
|
564
|
-
// This prevents SSE breakage when dashboard restarts
|
|
565
|
-
const crewLeadPort = 5010;
|
|
566
|
-
// Use same hostname as dashboard to avoid CORS issues (localhost vs 127.0.0.1)
|
|
567
|
-
const hostname = window.location.hostname || '127.0.0.1';
|
|
568
|
-
const sseUrl = `http://${hostname}:${crewLeadPort}/events`;
|
|
569
|
-
|
|
570
|
-
console.log("[crewswarm] Starting EventSource listener for", sseUrl);
|
|
571
|
-
agentReplySSE = new EventSource(sseUrl);
|
|
572
|
-
const sseLog =
|
|
573
|
-
typeof localStorage !== "undefined" &&
|
|
574
|
-
localStorage.getItem("crewswarm_debug_sse") === "1";
|
|
575
|
-
agentReplySSE.onmessage = (e) => {
|
|
576
|
-
if (!e.data) {
|
|
577
|
-
console.warn("[crewswarm] SSE message with null/empty data");
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
try {
|
|
581
|
-
const d = JSON.parse(e.data);
|
|
582
|
-
const normalizeProjectId = (value) =>
|
|
583
|
-
!value || value === "general" ? "general" : value;
|
|
584
|
-
const currentSessionId = getChatSessionId();
|
|
585
|
-
|
|
586
|
-
if (sseLog) {
|
|
587
|
-
console.log("[crewswarm] SSE:", d.type, e.data.slice(0, 120));
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
const box = document.getElementById("chatMessages");
|
|
591
|
-
|
|
592
|
-
if (handleSwarmSSEEvent(d)) {
|
|
593
|
-
return;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
// Handle streaming tokens
|
|
597
|
-
if (d.type === "chat_stream" && d.sessionId === currentSessionId) {
|
|
598
|
-
const messageProjectId = normalizeProjectId(d.projectId);
|
|
599
|
-
const currentProjId = normalizeProjectId(state.chatActiveProjectId);
|
|
600
|
-
|
|
601
|
-
if (currentProjId !== messageProjectId) {
|
|
602
|
-
return; // Wrong project, skip
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
let streamBubble = document.getElementById("streaming-bubble");
|
|
606
|
-
if (!streamBubble) {
|
|
607
|
-
// Create wrapper + label + bubble (matching appendChatBubble structure)
|
|
608
|
-
const wrapper = document.createElement("div");
|
|
609
|
-
wrapper.id = "streaming-wrapper";
|
|
610
|
-
wrapper.style.cssText =
|
|
611
|
-
"display:flex;flex-direction:column;align-items:flex-start;gap:4px;";
|
|
612
|
-
|
|
613
|
-
const label = document.createElement("div");
|
|
614
|
-
label.style.cssText =
|
|
615
|
-
"font-size:11px;color:var(--text-3);padding:0 6px;";
|
|
616
|
-
const cl = window._crewLeadInfo || { emoji: "🧠", name: "crew-lead" };
|
|
617
|
-
label.textContent = cl.emoji + " " + cl.name + " (streaming...)";
|
|
618
|
-
|
|
619
|
-
streamBubble = document.createElement("div");
|
|
620
|
-
streamBubble.id = "streaming-bubble";
|
|
621
|
-
streamBubble.className = "chat-bubble assistant";
|
|
622
|
-
streamBubble.style.cssText =
|
|
623
|
-
"max-width:80%;padding:10px 14px;border-radius:14px 14px 14px 4px;background:var(--surface-2);color:var(--text-2);font-size:14px;line-height:1.5;white-space:pre-wrap;word-break:break-word;border:1px solid var(--border);";
|
|
624
|
-
// Keep a persistent text node so we can append chunks incrementally
|
|
625
|
-
// instead of rewriting the whole bubble content on every frame.
|
|
626
|
-
streamBubble._textNode = document.createTextNode("");
|
|
627
|
-
streamBubble.appendChild(streamBubble._textNode);
|
|
628
|
-
|
|
629
|
-
wrapper.appendChild(label);
|
|
630
|
-
wrapper.appendChild(streamBubble);
|
|
631
|
-
if (box) box.appendChild(wrapper);
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
// Batch token paint to animation frames for smoother streaming.
|
|
635
|
-
const nextChunk = (streamBubble.dataset.streamChunk || "") + d.token;
|
|
636
|
-
streamBubble.dataset.streamChunk = nextChunk;
|
|
637
|
-
if (!streamBubble._rafId) {
|
|
638
|
-
streamBubble._rafId = requestAnimationFrame(() => {
|
|
639
|
-
const chunk = streamBubble.dataset.streamChunk || "";
|
|
640
|
-
if (chunk) {
|
|
641
|
-
if (!streamBubble._textNode) {
|
|
642
|
-
streamBubble._textNode = document.createTextNode("");
|
|
643
|
-
streamBubble.appendChild(streamBubble._textNode);
|
|
644
|
-
}
|
|
645
|
-
streamBubble._textNode.textContent += chunk;
|
|
646
|
-
streamBubble.dataset.streamChunk = "";
|
|
647
|
-
}
|
|
648
|
-
if (box) box.scrollTop = box.scrollHeight;
|
|
649
|
-
streamBubble._rafId = null;
|
|
650
|
-
});
|
|
651
|
-
}
|
|
652
|
-
return;
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
if (d.type === "draft_discarded" && d.draftId) {
|
|
656
|
-
const el = document.querySelector(
|
|
657
|
-
'[data-draft-id="' + d.draftId + '"]',
|
|
658
|
-
);
|
|
659
|
-
if (el) el.remove();
|
|
660
|
-
return;
|
|
661
|
-
}
|
|
662
|
-
if (d.type === "context_warning" && d.sessionId === getChatSessionId()) {
|
|
663
|
-
const existing = document.getElementById("contextWarningBanner");
|
|
664
|
-
if (existing) existing.remove();
|
|
665
|
-
const banner = document.createElement("div");
|
|
666
|
-
banner.id = "contextWarningBanner";
|
|
667
|
-
const isCritical = d.level === "critical";
|
|
668
|
-
banner.style.cssText = `display:flex;align-items:center;gap:10px;padding:8px 14px;border-radius:8px;margin:6px 0;font-size:12px;background:${isCritical ? "rgba(239,68,68,0.1)" : "rgba(245,158,11,0.1)"};border:1px solid ${isCritical ? "rgba(239,68,68,0.3)" : "rgba(245,158,11,0.3)"};color:${isCritical ? "#f87171" : "#f59e0b"};`;
|
|
669
|
-
banner.innerHTML = `<span style="flex:1;">${d.message}</span><button onclick="clearChatHistory()" style="padding:2px 8px;font-size:11px;border-radius:4px;border:1px solid currentColor;background:transparent;color:inherit;cursor:pointer;">Clear now</button><button onclick="this.parentElement.remove()" style="background:none;border:none;cursor:pointer;color:inherit;font-size:14px;padding:0 2px;">✕</button>`;
|
|
670
|
-
const box = document.getElementById("chatMessages");
|
|
671
|
-
if (box) {
|
|
672
|
-
box.appendChild(banner);
|
|
673
|
-
box.scrollTop = box.scrollHeight;
|
|
674
|
-
}
|
|
675
|
-
return;
|
|
676
|
-
}
|
|
677
|
-
if (d.type === "chat_message" && d.sessionId === getChatSessionId()) {
|
|
678
|
-
// Additional check: if message has projectId, only show if it matches current project
|
|
679
|
-
const currentProjectId = normalizeProjectId(state.chatActiveProjectId);
|
|
680
|
-
const messageProjectId = normalizeProjectId(d.projectId);
|
|
681
|
-
|
|
682
|
-
if (currentProjectId !== messageProjectId) {
|
|
683
|
-
console.log("[crewswarm] ❌ SKIP - projectId mismatch:", {
|
|
684
|
-
current: currentProjectId || "(General)",
|
|
685
|
-
message: messageProjectId || "(General)",
|
|
686
|
-
});
|
|
687
|
-
return;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
console.log("[crewswarm] ✅ Displaying message for current session");
|
|
691
|
-
|
|
692
|
-
if (d.role === "user") {
|
|
693
|
-
// Skip SSE echo of messages we already appended locally via sendChat()
|
|
694
|
-
if (d.content === lastSentContent) {
|
|
695
|
-
console.log("[crewswarm] Skipping SSE echo of locally-sent message");
|
|
696
|
-
lastSentContent = null;
|
|
697
|
-
return;
|
|
698
|
-
}
|
|
699
|
-
if (d.content !== lastAppendedUserContent) {
|
|
700
|
-
console.log(
|
|
701
|
-
"[crewswarm] Appending user bubble:",
|
|
702
|
-
d.content.slice(0, 50),
|
|
703
|
-
);
|
|
704
|
-
appendChatBubble("user", d.content);
|
|
705
|
-
lastAppendedUserContent = d.content;
|
|
706
|
-
} else {
|
|
707
|
-
console.log("[crewswarm] Skipping duplicate user message");
|
|
708
|
-
}
|
|
709
|
-
} else if (d.role === "assistant") {
|
|
710
|
-
document
|
|
711
|
-
.querySelectorAll('[id^="typing-"]')
|
|
712
|
-
.forEach((el) => el.remove());
|
|
713
|
-
|
|
714
|
-
const assistantTrimmed = String(d.content || "").trim();
|
|
715
|
-
if (
|
|
716
|
-
assistantTrimmed &&
|
|
717
|
-
lastAssistantBubbleText(box) === assistantTrimmed
|
|
718
|
-
) {
|
|
719
|
-
lastAppendedAssistantContent = d.content;
|
|
720
|
-
if (box) box.scrollTop = box.scrollHeight;
|
|
721
|
-
return;
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
const streamWrapper = document.getElementById("streaming-wrapper");
|
|
725
|
-
const streamBubble = document.getElementById("streaming-bubble");
|
|
726
|
-
|
|
727
|
-
if (streamBubble) {
|
|
728
|
-
if (streamBubble._rafId) cancelAnimationFrame(streamBubble._rafId);
|
|
729
|
-
streamBubble._rafId = null;
|
|
730
|
-
const pending = streamBubble.dataset.streamChunk || "";
|
|
731
|
-
if (pending) {
|
|
732
|
-
if (!streamBubble._textNode) {
|
|
733
|
-
streamBubble._textNode = document.createTextNode("");
|
|
734
|
-
streamBubble.appendChild(streamBubble._textNode);
|
|
735
|
-
}
|
|
736
|
-
streamBubble._textNode.textContent += pending;
|
|
737
|
-
streamBubble.dataset.streamChunk = "";
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
// Promote streaming row → final bubble in place (do NOT remove then re-append).
|
|
742
|
-
// Removing the stream loses the only visible copy if append/skip heuristics race.
|
|
743
|
-
if (streamWrapper && streamBubble) {
|
|
744
|
-
const cl = window._crewLeadInfo || { emoji: "🧠", name: "crew-lead" };
|
|
745
|
-
const labelEl = streamWrapper.firstElementChild;
|
|
746
|
-
if (labelEl && labelEl !== streamBubble) {
|
|
747
|
-
labelEl.textContent = cl.emoji + " " + cl.name;
|
|
748
|
-
}
|
|
749
|
-
const finalText = d.content ?? "";
|
|
750
|
-
if (streamBubble._textNode) {
|
|
751
|
-
streamBubble._textNode.textContent = finalText;
|
|
752
|
-
} else {
|
|
753
|
-
streamBubble.textContent = finalText;
|
|
754
|
-
}
|
|
755
|
-
streamWrapper.removeAttribute("id");
|
|
756
|
-
streamBubble.removeAttribute("id");
|
|
757
|
-
delete streamBubble.dataset.streamChunk;
|
|
758
|
-
lastAppendedAssistantContent = d.content;
|
|
759
|
-
} else {
|
|
760
|
-
if (streamWrapper) streamWrapper.remove();
|
|
761
|
-
const skipDuplicate =
|
|
762
|
-
d.content === lastAppendedAssistantContent &&
|
|
763
|
-
chatThreadHasAssistantText(box, d.content);
|
|
764
|
-
if (!skipDuplicate) {
|
|
765
|
-
console.log("[crewswarm] Appending assistant bubble (final)");
|
|
766
|
-
appendChatBubble(
|
|
767
|
-
"assistant",
|
|
768
|
-
d.content,
|
|
769
|
-
d.fallbackModel,
|
|
770
|
-
d.fallbackReason,
|
|
771
|
-
d.model,
|
|
772
|
-
d.engineUsed,
|
|
773
|
-
);
|
|
774
|
-
lastAppendedAssistantContent = d.content;
|
|
775
|
-
} else {
|
|
776
|
-
console.log("[crewswarm] Skipping duplicate assistant message");
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
if (box) box.scrollTop = box.scrollHeight;
|
|
781
|
-
return;
|
|
782
|
-
}
|
|
783
|
-
if (
|
|
784
|
-
d.type === "pending_project" &&
|
|
785
|
-
d.sessionId === getChatSessionId() &&
|
|
786
|
-
d.pendingProject &&
|
|
787
|
-
box
|
|
788
|
-
) {
|
|
789
|
-
appendRoadmapCard(box, d.pendingProject);
|
|
790
|
-
box.scrollTop = box.scrollHeight;
|
|
791
|
-
return;
|
|
792
|
-
}
|
|
793
|
-
// agent_working from OpenCode bridge — show pulsing coding dot on agent card
|
|
794
|
-
if (d.type === "agent_working" && d.agent) {
|
|
795
|
-
const dot = document.getElementById("coding-dot-" + d.agent);
|
|
796
|
-
if (dot) dot.style.display = "inline-flex";
|
|
797
|
-
}
|
|
798
|
-
// agent_idle from OpenCode bridge — hide coding dot
|
|
799
|
-
if (d.type === "agent_idle" && d.agent) {
|
|
800
|
-
const dot = document.getElementById("coding-dot-" + d.agent);
|
|
801
|
-
if (dot) dot.style.display = "none";
|
|
802
|
-
}
|
|
803
|
-
// OpenCode serve live events — tool calls, file edits, session boundaries
|
|
804
|
-
if (d.type === "opencode_event") {
|
|
805
|
-
const feed = document.getElementById("ocFeed");
|
|
806
|
-
const liveDot = document.getElementById("ocFeedDot");
|
|
807
|
-
if (!feed) return;
|
|
808
|
-
if (liveDot) liveDot.style.display = "inline-block";
|
|
809
|
-
const row = document.createElement("div");
|
|
810
|
-
row.style.cssText =
|
|
811
|
-
"display:flex;align-items:center;gap:8px;padding:5px 10px;border-radius:8px;background:var(--bg-2);font-size:12px;font-family:var(--font-mono,monospace);animation:fadeIn .25s ease;";
|
|
812
|
-
const time = new Date(d.ts || Date.now()).toLocaleTimeString([], {
|
|
813
|
-
hour: "2-digit",
|
|
814
|
-
minute: "2-digit",
|
|
815
|
-
second: "2-digit",
|
|
816
|
-
});
|
|
817
|
-
let icon = "⚙️",
|
|
818
|
-
label = "";
|
|
819
|
-
if (d.kind === "session_start") {
|
|
820
|
-
icon = "▶";
|
|
821
|
-
row.style.borderLeft = "3px solid var(--green-hi)";
|
|
822
|
-
var _sd = d.dir || "";
|
|
823
|
-
label = "session started" + (_sd ? " — " + _sd.split("/").pop() : "");
|
|
824
|
-
} else if (d.kind === "session_end") {
|
|
825
|
-
icon = "■";
|
|
826
|
-
row.style.borderLeft = "3px solid var(--text-3)";
|
|
827
|
-
label = "session ended";
|
|
828
|
-
if (liveDot) liveDot.style.display = "none";
|
|
829
|
-
} else if (d.kind === "file_edit") {
|
|
830
|
-
icon = "✏️";
|
|
831
|
-
row.style.borderLeft = "3px solid var(--amber)";
|
|
832
|
-
label =
|
|
833
|
-
(d.file || d.path || "") +
|
|
834
|
-
(d.extra
|
|
835
|
-
? ' <span style="opacity:.5;">' + d.extra + "</span>"
|
|
836
|
-
: "");
|
|
837
|
-
} else if (d.kind === "error") {
|
|
838
|
-
icon = "✗";
|
|
839
|
-
row.style.borderLeft = "3px solid var(--red-hi)";
|
|
840
|
-
row.style.color = "var(--red-hi)";
|
|
841
|
-
label = d.message || "error";
|
|
842
|
-
} else if (d.kind === "tool") {
|
|
843
|
-
const toolColors = {
|
|
844
|
-
read_file: "var(--accent)",
|
|
845
|
-
write_file: "var(--amber)",
|
|
846
|
-
bash: "var(--purple)",
|
|
847
|
-
list_directory: "var(--green)",
|
|
848
|
-
grep: "var(--green)",
|
|
849
|
-
};
|
|
850
|
-
const tc = toolColors[d.tool] || "var(--text-2)";
|
|
851
|
-
icon = d.phase === "done" ? "✓" : "→";
|
|
852
|
-
row.style.borderLeft = "3px solid " + tc;
|
|
853
|
-
row.style.color =
|
|
854
|
-
d.phase === "done" ? "var(--text-2)" : "var(--text-1)";
|
|
855
|
-
label =
|
|
856
|
-
'<span style="color:' +
|
|
857
|
-
tc +
|
|
858
|
-
';font-weight:600;">' +
|
|
859
|
-
(d.tool || "") +
|
|
860
|
-
"</span>" +
|
|
861
|
-
(d.label
|
|
862
|
-
? ' <span style="opacity:.6;">' + d.label + "</span>"
|
|
863
|
-
: "");
|
|
864
|
-
}
|
|
865
|
-
row.innerHTML =
|
|
866
|
-
'<span style="opacity:.4;flex-shrink:0;">' +
|
|
867
|
-
time +
|
|
868
|
-
"</span>" +
|
|
869
|
-
'<span style="flex-shrink:0;">' +
|
|
870
|
-
icon +
|
|
871
|
-
"</span>" +
|
|
872
|
-
'<span style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">' +
|
|
873
|
-
label +
|
|
874
|
-
"</span>";
|
|
875
|
-
feed.appendChild(row);
|
|
876
|
-
// Cap at 80 rows
|
|
877
|
-
while (feed.children.length > 80) feed.removeChild(feed.firstChild);
|
|
878
|
-
feed.scrollTop = feed.scrollHeight;
|
|
879
|
-
return;
|
|
880
|
-
}
|
|
881
|
-
// agent_working: crew-lead dispatched a task — show a "waiting" indicator
|
|
882
|
-
if (d.type === "agent_working" && d.agent) {
|
|
883
|
-
const spinnerId = "agent-spinner-" + (d.taskId || d.agent);
|
|
884
|
-
if (box && !document.getElementById(spinnerId)) {
|
|
885
|
-
const el = document.createElement("div");
|
|
886
|
-
el.id = spinnerId;
|
|
887
|
-
el.className = "msg a";
|
|
888
|
-
el.style.cssText = "opacity:.7; font-style:italic;";
|
|
889
|
-
el.innerHTML =
|
|
890
|
-
'<div class="meta"><strong>' +
|
|
891
|
-
d.agent +
|
|
892
|
-
"</strong> · working…</div>" +
|
|
893
|
-
'<div class="t" style="display:flex;align-items:center;gap:8px;">' +
|
|
894
|
-
'<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--accent);animation:pulse 1s ease-in-out infinite;"></span>' +
|
|
895
|
-
"Processing task…</div>";
|
|
896
|
-
box.appendChild(el);
|
|
897
|
-
box.scrollTop = box.scrollHeight;
|
|
898
|
-
}
|
|
899
|
-
return;
|
|
900
|
-
}
|
|
901
|
-
// agent_reply: task completion from any crew member — replace spinner, show reply, notify
|
|
902
|
-
if (d.type === "agent_reply" || (d.from && d.content)) {
|
|
903
|
-
if (!d.from || !d.content) return;
|
|
904
|
-
// Skip passthrough summaries — the dashboard already rendered the live stream
|
|
905
|
-
if (d._passthroughSummary) return;
|
|
906
|
-
const spinnerId = "agent-spinner-" + (d.taskId || d.from);
|
|
907
|
-
const spinnerEl = document.getElementById(spinnerId);
|
|
908
|
-
if (spinnerEl) spinnerEl.remove();
|
|
909
|
-
const agentSpinner = document.getElementById("agent-spinner-" + d.from);
|
|
910
|
-
if (agentSpinner) agentSpinner.remove();
|
|
911
|
-
appendChatBubble(
|
|
912
|
-
"🤖 " + d.from,
|
|
913
|
-
d.content,
|
|
914
|
-
false,
|
|
915
|
-
null,
|
|
916
|
-
null,
|
|
917
|
-
d.engineUsed,
|
|
918
|
-
);
|
|
919
|
-
if (box) box.scrollTop = box.scrollHeight;
|
|
920
|
-
showNotification(d.from + " finished a task");
|
|
921
|
-
return;
|
|
922
|
-
}
|
|
923
|
-
// task.timeout: dispatch never claimed or timed out — replace spinner with "No reply" message
|
|
924
|
-
if (d.type === "task.timeout" && d.agent) {
|
|
925
|
-
const spinnerId = "agent-spinner-" + (d.taskId || d.agent);
|
|
926
|
-
const spinnerEl = document.getElementById(spinnerId);
|
|
927
|
-
if (spinnerEl) spinnerEl.remove();
|
|
928
|
-
const agentSpinner = document.getElementById(
|
|
929
|
-
"agent-spinner-" + d.agent,
|
|
930
|
-
);
|
|
931
|
-
if (agentSpinner) agentSpinner.remove();
|
|
932
|
-
const msg =
|
|
933
|
-
"[crew-lead] Task to " +
|
|
934
|
-
d.agent +
|
|
935
|
-
" timed out (no reply in 90s). Consider @@SERVICE restart " +
|
|
936
|
-
d.agent +
|
|
937
|
-
" or re-dispatch to another agent.";
|
|
938
|
-
if (box) {
|
|
939
|
-
const el = document.createElement("div");
|
|
940
|
-
el.className = "msg a";
|
|
941
|
-
el.style.cssText =
|
|
942
|
-
"opacity:.85; font-style:italic; color:var(--text-3);";
|
|
943
|
-
el.innerHTML =
|
|
944
|
-
'<div class="meta"><strong>' +
|
|
945
|
-
d.agent +
|
|
946
|
-
'</strong> · no reply</div><div class="t">' +
|
|
947
|
-
escHtml(msg) +
|
|
948
|
-
"</div>";
|
|
949
|
-
box.appendChild(el);
|
|
950
|
-
box.scrollTop = box.scrollHeight;
|
|
951
|
-
}
|
|
952
|
-
showNotification("Task to " + d.agent + " timed out");
|
|
953
|
-
return;
|
|
954
|
-
}
|
|
955
|
-
// pipeline_progress: a wave or step dispatched
|
|
956
|
-
if (d.type === "pipeline_progress") {
|
|
957
|
-
let label;
|
|
958
|
-
if (d.agents) {
|
|
959
|
-
label =
|
|
960
|
-
"Wave " +
|
|
961
|
-
(d.waveIndex + 1) +
|
|
962
|
-
"/" +
|
|
963
|
-
d.totalWaves +
|
|
964
|
-
" → " +
|
|
965
|
-
d.agents.join(" + ");
|
|
966
|
-
} else {
|
|
967
|
-
label = "Step " + (d.stepIndex + 1) + "/" + d.total + " → " + d.agent;
|
|
968
|
-
}
|
|
969
|
-
const el = document.createElement("div");
|
|
970
|
-
el.style.cssText =
|
|
971
|
-
"font-size:11px;color:var(--text-3);padding:2px 8px;margin:2px 0;";
|
|
972
|
-
el.textContent = "↳ " + label;
|
|
973
|
-
if (box) {
|
|
974
|
-
box.appendChild(el);
|
|
975
|
-
box.scrollTop = box.scrollHeight;
|
|
976
|
-
}
|
|
977
|
-
return;
|
|
978
|
-
}
|
|
979
|
-
// pipeline_quality_gate: wave had issues
|
|
980
|
-
if (d.type === "pipeline_quality_gate") {
|
|
981
|
-
const el = document.createElement("div");
|
|
982
|
-
const retryNote = d.willRetry
|
|
983
|
-
? " — retrying wave"
|
|
984
|
-
: " — advancing anyway";
|
|
985
|
-
el.style.cssText =
|
|
986
|
-
"font-size:11px;color:var(--warning, #e8a030);padding:2px 8px;margin:2px 0;";
|
|
987
|
-
el.textContent =
|
|
988
|
-
"⚠️ Wave " +
|
|
989
|
-
(d.waveIndex + 1) +
|
|
990
|
-
" quality gate: " +
|
|
991
|
-
(d.issues || []).join("; ") +
|
|
992
|
-
retryNote;
|
|
993
|
-
if (box) {
|
|
994
|
-
box.appendChild(el);
|
|
995
|
-
box.scrollTop = box.scrollHeight;
|
|
996
|
-
}
|
|
997
|
-
return;
|
|
998
|
-
}
|
|
999
|
-
// project_launched: new project registered — reload dropdown and auto-select
|
|
1000
|
-
if (d.type === "project_launched" && d.project) {
|
|
1001
|
-
const newId = d.project.projectId || d.project.id;
|
|
1002
|
-
setTimeout(async () => {
|
|
1003
|
-
await loadProjects();
|
|
1004
|
-
if (newId) autoSelectChatProject(newId);
|
|
1005
|
-
const box = document.getElementById("chatMessages");
|
|
1006
|
-
if (box) {
|
|
1007
|
-
const el = document.createElement("div");
|
|
1008
|
-
el.style.cssText =
|
|
1009
|
-
"font-size:11px;color:var(--green);padding:2px 8px;margin:2px 0;";
|
|
1010
|
-
el.textContent =
|
|
1011
|
-
'📁 Project "' +
|
|
1012
|
-
(d.project.name || newId) +
|
|
1013
|
-
'" registered — selected in chat';
|
|
1014
|
-
box.appendChild(el);
|
|
1015
|
-
box.scrollTop = box.scrollHeight;
|
|
1016
|
-
}
|
|
1017
|
-
}, 800);
|
|
1018
|
-
return;
|
|
1019
|
-
}
|
|
1020
|
-
// pipeline_done: all steps complete
|
|
1021
|
-
if (d.type === "pipeline_done") {
|
|
1022
|
-
const el = document.createElement("div");
|
|
1023
|
-
el.style.cssText =
|
|
1024
|
-
"font-size:11px;color:var(--green);padding:2px 8px;margin:2px 0;";
|
|
1025
|
-
el.textContent = "✅ Pipeline complete";
|
|
1026
|
-
if (box) {
|
|
1027
|
-
box.appendChild(el);
|
|
1028
|
-
box.scrollTop = box.scrollHeight;
|
|
1029
|
-
}
|
|
1030
|
-
return;
|
|
1031
|
-
}
|
|
1032
|
-
// confirm_run_cmd: an agent wants to run a shell command — show approval toast
|
|
1033
|
-
if (d.type === "confirm_run_cmd" && d.approvalId) {
|
|
1034
|
-
showCmdApprovalToast(d.approvalId, d.agent, d.cmd);
|
|
1035
|
-
return;
|
|
1036
|
-
}
|
|
1037
|
-
// telemetry: task.lifecycle (schema 1.1) — keep list and refresh Task lifecycle panel if visible
|
|
1038
|
-
if (d.type === "telemetry" && d.payload) {
|
|
1039
|
-
window._telemetryEvents = window._telemetryEvents || [];
|
|
1040
|
-
window._telemetryEvents.push(d.payload);
|
|
1041
|
-
if (window._telemetryEvents.length > 100)
|
|
1042
|
-
window._telemetryEvents.shift();
|
|
1043
|
-
const tlView = document.getElementById("toolMatrixView");
|
|
1044
|
-
if (tlView && tlView.classList.contains("active"))
|
|
1045
|
-
renderTaskLifecycle(window._telemetryEvents);
|
|
1046
|
-
}
|
|
1047
|
-
} catch {}
|
|
1048
|
-
};
|
|
1049
|
-
agentReplySSE.onopen = () => {
|
|
1050
|
-
console.log("[crewswarm] SSE connection opened");
|
|
1051
|
-
window._sseReconnectDelay = 2000;
|
|
1052
|
-
};
|
|
1053
|
-
agentReplySSE.onerror = (err) => {
|
|
1054
|
-
console.error("[crewswarm] SSE error:", err);
|
|
1055
|
-
agentReplySSE.close();
|
|
1056
|
-
agentReplySSE = null;
|
|
1057
|
-
// Reconnect with exponential backoff (2s → 4s → 8s → 30s max)
|
|
1058
|
-
if (window._sseReconnectTimer) clearTimeout(window._sseReconnectTimer);
|
|
1059
|
-
window._sseReconnectTimer = setTimeout(() => {
|
|
1060
|
-
window._sseReconnectTimer = null;
|
|
1061
|
-
window._sseReconnectDelay = Math.min(
|
|
1062
|
-
(window._sseReconnectDelay || 2000) * 2,
|
|
1063
|
-
30000,
|
|
1064
|
-
);
|
|
1065
|
-
startAgentReplyListener();
|
|
1066
|
-
}, window._sseReconnectDelay || 2000);
|
|
1067
|
-
};
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
// ── Command approval toast ────────────────────────────────────────────────────
|
|
1071
|
-
|
|
1072
|
-
function showCmdApprovalToast(approvalId, agent, cmd) {
|
|
1073
|
-
const existing = document.getElementById("cmd-approval-" + approvalId);
|
|
1074
|
-
if (existing) return;
|
|
1075
|
-
|
|
1076
|
-
const toast = document.createElement("div");
|
|
1077
|
-
toast.id = "cmd-approval-" + approvalId;
|
|
1078
|
-
toast.style.cssText = [
|
|
1079
|
-
"position:fixed;bottom:80px;right:24px;z-index:9999;",
|
|
1080
|
-
"background:var(--bg-card);border:1px solid var(--border);border-radius:12px;",
|
|
1081
|
-
"padding:16px 20px;max-width:440px;box-shadow:0 8px 32px rgba(0,0,0,.4);",
|
|
1082
|
-
"display:flex;flex-direction:column;gap:10px;",
|
|
1083
|
-
].join("");
|
|
1084
|
-
|
|
1085
|
-
const header = document.createElement("div");
|
|
1086
|
-
header.style.cssText = "font-size:13px;font-weight:600;color:var(--text-1);";
|
|
1087
|
-
header.textContent = "🔐 " + agent + " wants to run a command";
|
|
1088
|
-
|
|
1089
|
-
const cmdEl = document.createElement("code");
|
|
1090
|
-
cmdEl.style.cssText =
|
|
1091
|
-
"display:block;font-size:12px;color:var(--accent);background:var(--bg-1);padding:6px 10px;border-radius:6px;word-break:break-all;";
|
|
1092
|
-
cmdEl.textContent = cmd;
|
|
1093
|
-
|
|
1094
|
-
// "Always allow" toggle — infers pattern from first word of command
|
|
1095
|
-
const alwaysRow = document.createElement("label");
|
|
1096
|
-
alwaysRow.style.cssText =
|
|
1097
|
-
"display:flex;align-items:center;gap:8px;font-size:12px;color:var(--text-2);cursor:pointer;";
|
|
1098
|
-
const alwaysChk = document.createElement("input");
|
|
1099
|
-
alwaysChk.type = "checkbox";
|
|
1100
|
-
alwaysChk.style.cssText =
|
|
1101
|
-
"width:14px;height:14px;cursor:pointer;accent-color:var(--green);";
|
|
1102
|
-
const cmdBase = cmd.trim().split(/\s+/)[0];
|
|
1103
|
-
const suggestedPattern = cmdBase + " *";
|
|
1104
|
-
alwaysRow.appendChild(alwaysChk);
|
|
1105
|
-
alwaysRow.appendChild(document.createTextNode("Always allow "));
|
|
1106
|
-
const patternSpan = document.createElement("code");
|
|
1107
|
-
patternSpan.style.cssText =
|
|
1108
|
-
"font-size:11px;background:var(--bg-1);padding:2px 6px;border-radius:4px;color:var(--accent);";
|
|
1109
|
-
patternSpan.textContent = suggestedPattern;
|
|
1110
|
-
alwaysRow.appendChild(patternSpan);
|
|
1111
|
-
|
|
1112
|
-
const timer = document.createElement("div");
|
|
1113
|
-
timer.style.cssText = "font-size:11px;color:var(--text-3);";
|
|
1114
|
-
let secs = 60;
|
|
1115
|
-
timer.textContent = "Auto-reject in " + secs + "s";
|
|
1116
|
-
const countdown = setInterval(() => {
|
|
1117
|
-
secs--;
|
|
1118
|
-
timer.textContent = "Auto-reject in " + secs + "s";
|
|
1119
|
-
if (secs <= 0) {
|
|
1120
|
-
clearInterval(countdown);
|
|
1121
|
-
toast.remove();
|
|
1122
|
-
}
|
|
1123
|
-
}, 1000);
|
|
1124
|
-
|
|
1125
|
-
const btns = document.createElement("div");
|
|
1126
|
-
btns.style.cssText = "display:flex;gap:8px;";
|
|
1127
|
-
|
|
1128
|
-
const approve = document.createElement("button");
|
|
1129
|
-
approve.textContent = "✅ Allow";
|
|
1130
|
-
approve.style.cssText =
|
|
1131
|
-
"flex:1;padding:8px;border-radius:8px;border:none;background:var(--green);color:#fff;cursor:pointer;font-weight:600;font-size:13px;";
|
|
1132
|
-
approve.onclick = async () => {
|
|
1133
|
-
clearInterval(countdown);
|
|
1134
|
-
toast.remove();
|
|
1135
|
-
if (alwaysChk.checked) {
|
|
1136
|
-
await fetch("/api/cmd-allowlist", {
|
|
1137
|
-
method: "POST",
|
|
1138
|
-
headers: { "content-type": "application/json" },
|
|
1139
|
-
body: JSON.stringify({ pattern: suggestedPattern }),
|
|
1140
|
-
});
|
|
1141
|
-
showNotification("Allowlisted: " + suggestedPattern);
|
|
1142
|
-
}
|
|
1143
|
-
await fetch("/api/cmd-approve", {
|
|
1144
|
-
method: "POST",
|
|
1145
|
-
headers: { "content-type": "application/json" },
|
|
1146
|
-
body: JSON.stringify({ approvalId }),
|
|
1147
|
-
}).catch((e) => showNotification("Approve failed: " + e.message, true));
|
|
1148
|
-
if (!alwaysChk.checked) showNotification(agent + ": command approved");
|
|
1149
|
-
};
|
|
1150
|
-
|
|
1151
|
-
const reject = document.createElement("button");
|
|
1152
|
-
reject.textContent = "⛔ Deny";
|
|
1153
|
-
reject.style.cssText =
|
|
1154
|
-
"flex:1;padding:8px;border-radius:8px;border:none;background:var(--red-hi);color:#fff;cursor:pointer;font-weight:600;font-size:13px;";
|
|
1155
|
-
reject.onclick = async () => {
|
|
1156
|
-
clearInterval(countdown);
|
|
1157
|
-
toast.remove();
|
|
1158
|
-
await fetch("/api/cmd-reject", {
|
|
1159
|
-
method: "POST",
|
|
1160
|
-
headers: { "content-type": "application/json" },
|
|
1161
|
-
body: JSON.stringify({ approvalId }),
|
|
1162
|
-
}).catch((e) => showNotification("Reject failed: " + e.message, true));
|
|
1163
|
-
showNotification(agent + ": command denied");
|
|
1164
|
-
};
|
|
1165
|
-
|
|
1166
|
-
btns.appendChild(approve);
|
|
1167
|
-
btns.appendChild(reject);
|
|
1168
|
-
toast.appendChild(header);
|
|
1169
|
-
toast.appendChild(cmdEl);
|
|
1170
|
-
toast.appendChild(alwaysRow);
|
|
1171
|
-
toast.appendChild(timer);
|
|
1172
|
-
toast.appendChild(btns);
|
|
1173
|
-
document.body.appendChild(toast);
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
// ── Cmd allowlist manager ──────────────────────────────────────────────────────
|
|
1177
|
-
|
|
1178
|
-
const CMD_PRESETS = [
|
|
1179
|
-
{ label: "npm", pattern: "npm *", desc: "install, run, build, test…" },
|
|
1180
|
-
{ label: "node", pattern: "node *", desc: "run any node script" },
|
|
1181
|
-
{ label: "python", pattern: "python *", desc: "python / python3 scripts" },
|
|
1182
|
-
{ label: "pip", pattern: "pip *", desc: "pip install packages" },
|
|
1183
|
-
{ label: "git", pattern: "git *", desc: "all git operations" },
|
|
1184
|
-
{ label: "cursor", pattern: "cursor *", desc: "open files in Cursor" },
|
|
1185
|
-
{ label: "make", pattern: "make *", desc: "Makefile targets" },
|
|
1186
|
-
{ label: "yarn", pattern: "yarn *", desc: "yarn install / build / run" },
|
|
1187
|
-
{ label: "pnpm", pattern: "pnpm *", desc: "pnpm package manager" },
|
|
1188
|
-
{
|
|
1189
|
-
label: "ls / cat / echo",
|
|
1190
|
-
pattern: "ls *",
|
|
1191
|
-
desc: "read-only shell utilities",
|
|
1192
|
-
},
|
|
1193
|
-
];
|
|
1194
|
-
|
|
1195
|
-
async function loadCmdAllowlist() {
|
|
1196
|
-
const box = document.getElementById("cmdAllowlistItems");
|
|
1197
|
-
const presetsBox = document.getElementById("cmdPresets");
|
|
1198
|
-
if (!box) return;
|
|
1199
|
-
|
|
1200
|
-
const d = await getJSON("/api/cmd-allowlist").catch(() => ({ list: [] }));
|
|
1201
|
-
const list = d.list || [];
|
|
1202
|
-
|
|
1203
|
-
// Render presets checklist (only when the presets container exists — Settings view)
|
|
1204
|
-
if (presetsBox) {
|
|
1205
|
-
presetsBox.innerHTML = "";
|
|
1206
|
-
CMD_PRESETS.forEach(function (preset) {
|
|
1207
|
-
const checked = list.includes(preset.pattern);
|
|
1208
|
-
const row = document.createElement("label");
|
|
1209
|
-
row.style.cssText =
|
|
1210
|
-
"display:flex;align-items:center;gap:8px;cursor:pointer;padding:4px 6px;border-radius:6px;transition:background 0.1s;";
|
|
1211
|
-
row.onmouseover = function () {
|
|
1212
|
-
row.style.background = "var(--bg-hover)";
|
|
1213
|
-
};
|
|
1214
|
-
row.onmouseout = function () {
|
|
1215
|
-
row.style.background = "";
|
|
1216
|
-
};
|
|
1217
|
-
|
|
1218
|
-
const chk = document.createElement("input");
|
|
1219
|
-
chk.type = "checkbox";
|
|
1220
|
-
chk.checked = checked;
|
|
1221
|
-
chk.style.cssText =
|
|
1222
|
-
"width:14px;height:14px;cursor:pointer;accent-color:var(--green);flex-shrink:0;";
|
|
1223
|
-
chk.onchange = async function () {
|
|
1224
|
-
if (chk.checked) {
|
|
1225
|
-
await fetch("/api/cmd-allowlist", {
|
|
1226
|
-
method: "POST",
|
|
1227
|
-
headers: { "content-type": "application/json" },
|
|
1228
|
-
body: JSON.stringify({ pattern: preset.pattern }),
|
|
1229
|
-
}).catch((e) =>
|
|
1230
|
-
showNotification("Failed to add pattern: " + e.message, true),
|
|
1231
|
-
);
|
|
1232
|
-
} else {
|
|
1233
|
-
await fetch("/api/cmd-allowlist", {
|
|
1234
|
-
method: "DELETE",
|
|
1235
|
-
headers: { "content-type": "application/json" },
|
|
1236
|
-
body: JSON.stringify({ pattern: preset.pattern }),
|
|
1237
|
-
}).catch((e) =>
|
|
1238
|
-
showNotification("Failed to remove pattern: " + e.message, true),
|
|
1239
|
-
);
|
|
1240
|
-
}
|
|
1241
|
-
loadCmdAllowlist();
|
|
1242
|
-
};
|
|
1243
|
-
|
|
1244
|
-
const nameEl = document.createElement("code");
|
|
1245
|
-
nameEl.style.cssText =
|
|
1246
|
-
"font-size:12px;color:var(--accent);min-width:90px;";
|
|
1247
|
-
nameEl.textContent = preset.pattern;
|
|
1248
|
-
|
|
1249
|
-
const descEl = document.createElement("span");
|
|
1250
|
-
descEl.style.cssText = "font-size:11px;color:var(--text-3);";
|
|
1251
|
-
descEl.textContent = preset.desc;
|
|
1252
|
-
|
|
1253
|
-
row.appendChild(chk);
|
|
1254
|
-
row.appendChild(nameEl);
|
|
1255
|
-
row.appendChild(descEl);
|
|
1256
|
-
presetsBox.appendChild(row);
|
|
1257
|
-
});
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
// Render active list (non-preset patterns only, or all if no presets box)
|
|
1261
|
-
const presetPatterns = new Set(
|
|
1262
|
-
CMD_PRESETS.map(function (p) {
|
|
1263
|
-
return p.pattern;
|
|
1264
|
-
}),
|
|
1265
|
-
);
|
|
1266
|
-
const customPatterns = presetsBox
|
|
1267
|
-
? list.filter(function (p) {
|
|
1268
|
-
return !presetPatterns.has(p);
|
|
1269
|
-
})
|
|
1270
|
-
: list;
|
|
1271
|
-
|
|
1272
|
-
box.innerHTML = "";
|
|
1273
|
-
if (!customPatterns.length) {
|
|
1274
|
-
box.innerHTML =
|
|
1275
|
-
'<div style="color:var(--text-3);font-size:12px;padding:4px 0;">' +
|
|
1276
|
-
(presetsBox ? "No custom patterns yet." : "No patterns yet.") +
|
|
1277
|
-
"</div>";
|
|
1278
|
-
return;
|
|
1279
|
-
}
|
|
1280
|
-
for (const pattern of customPatterns) {
|
|
1281
|
-
const row = document.createElement("div");
|
|
1282
|
-
row.style.cssText =
|
|
1283
|
-
"display:flex;align-items:center;gap:8px;padding:5px 0;border-bottom:1px solid var(--border);";
|
|
1284
|
-
const code = document.createElement("code");
|
|
1285
|
-
code.style.cssText = "flex:1;font-size:12px;color:var(--accent);";
|
|
1286
|
-
code.textContent = pattern;
|
|
1287
|
-
const del = document.createElement("button");
|
|
1288
|
-
del.textContent = "✕";
|
|
1289
|
-
del.style.cssText =
|
|
1290
|
-
"border:none;background:transparent;color:var(--text-3);cursor:pointer;font-size:14px;padding:0 4px;";
|
|
1291
|
-
del.title = "Remove";
|
|
1292
|
-
del.onclick = async function () {
|
|
1293
|
-
await fetch("/api/cmd-allowlist", {
|
|
1294
|
-
method: "DELETE",
|
|
1295
|
-
headers: { "content-type": "application/json" },
|
|
1296
|
-
body: JSON.stringify({ pattern }),
|
|
1297
|
-
}).catch((e) =>
|
|
1298
|
-
showNotification("Failed to delete pattern: " + e.message, true),
|
|
1299
|
-
);
|
|
1300
|
-
loadCmdAllowlist();
|
|
1301
|
-
};
|
|
1302
|
-
row.appendChild(code);
|
|
1303
|
-
row.appendChild(del);
|
|
1304
|
-
box.appendChild(row);
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1307
|
-
|
|
1308
|
-
async function addAllowlistPattern() {
|
|
1309
|
-
const inp = document.getElementById("cmdAllowlistInput");
|
|
1310
|
-
const pattern = inp ? inp.value.trim() : "";
|
|
1311
|
-
if (!pattern) return;
|
|
1312
|
-
await fetch("/api/cmd-allowlist", {
|
|
1313
|
-
method: "POST",
|
|
1314
|
-
headers: { "content-type": "application/json" },
|
|
1315
|
-
body: JSON.stringify({ pattern }),
|
|
1316
|
-
}).catch((e) =>
|
|
1317
|
-
showNotification("Failed to add pattern: " + e.message, true),
|
|
1318
|
-
);
|
|
1319
|
-
inp.value = "";
|
|
1320
|
-
loadCmdAllowlist();
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
// Token usage + Tool Matrix → tabs/usage-tab.js
|
|
1324
|
-
window._telemetryEvents = window._telemetryEvents || [];
|
|
1325
|
-
|
|
1326
|
-
const loadOcStats = () => loadOcStatsFromUsage(reportOcCost);
|
|
1327
|
-
|
|
1328
|
-
function appendRoadmapCard(box, { draftId, name, outputDir, roadmapMd }) {
|
|
1329
|
-
function countTasks(md) {
|
|
1330
|
-
return (md.match(/^- \[ \]/gm) || []).length;
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
const wrap = document.createElement("div");
|
|
1334
|
-
wrap.setAttribute("data-draft-id", draftId);
|
|
1335
|
-
wrap.style.cssText = "width:100%;display:flex;flex-direction:column;gap:4px;";
|
|
1336
|
-
|
|
1337
|
-
const lbl = document.createElement("div");
|
|
1338
|
-
lbl.style.cssText = "font-size:11px;color:var(--text-3);padding:0 6px;";
|
|
1339
|
-
lbl.textContent = "🗺️ Roadmap draft — review before building";
|
|
1340
|
-
|
|
1341
|
-
const card = document.createElement("div");
|
|
1342
|
-
card.style.cssText =
|
|
1343
|
-
"width:100%;border:1px solid var(--border);border-radius:12px;overflow:hidden;background:var(--bg-card);";
|
|
1344
|
-
|
|
1345
|
-
const header = document.createElement("div");
|
|
1346
|
-
header.style.cssText =
|
|
1347
|
-
"background:var(--bg-card2);padding:10px 14px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--border);";
|
|
1348
|
-
header.innerHTML =
|
|
1349
|
-
'<div><div style="font-size:13px;font-weight:600;color:var(--accent);">🚀 ' +
|
|
1350
|
-
name +
|
|
1351
|
-
'</div><div style="font-size:11px;color:var(--blue);margin-top:2px;">' +
|
|
1352
|
-
outputDir +
|
|
1353
|
-
"</div></div>" +
|
|
1354
|
-
'<span style="font-size:10px;color:var(--text-3);padding:2px 7px;background:var(--bg-card2);border-radius:10px;" class="task-count">' +
|
|
1355
|
-
countTasks(roadmapMd) +
|
|
1356
|
-
" tasks</span>";
|
|
1357
|
-
|
|
1358
|
-
const ta = document.createElement("textarea");
|
|
1359
|
-
ta.value = roadmapMd;
|
|
1360
|
-
ta.spellcheck = false;
|
|
1361
|
-
ta.style.cssText =
|
|
1362
|
-
"width:100%;background:var(--bg-card);border:none;outline:none;color:var(--text-1);font-size:11.5px;font-family:SF Mono,Monaco,Menlo,monospace;line-height:1.6;padding:12px 14px;resize:none;min-height:160px;max-height:320px;display:block;";
|
|
1363
|
-
setTimeout(() => {
|
|
1364
|
-
ta.style.height = "";
|
|
1365
|
-
ta.style.height = Math.min(ta.scrollHeight, 320) + "px";
|
|
1366
|
-
}, 50);
|
|
1367
|
-
ta.addEventListener("input", () => {
|
|
1368
|
-
ta.style.height = "";
|
|
1369
|
-
ta.style.height = Math.min(ta.scrollHeight, 320) + "px";
|
|
1370
|
-
header.querySelector(".task-count").textContent =
|
|
1371
|
-
countTasks(ta.value) + " tasks";
|
|
1372
|
-
});
|
|
1373
|
-
|
|
1374
|
-
const actions = document.createElement("div");
|
|
1375
|
-
actions.style.cssText =
|
|
1376
|
-
"display:flex;gap:8px;align-items:center;padding:10px 14px 12px;border-top:1px solid var(--border);background:var(--bg-card2);";
|
|
1377
|
-
|
|
1378
|
-
const startBtn = document.createElement("button");
|
|
1379
|
-
startBtn.textContent = "▶ Start Building";
|
|
1380
|
-
startBtn.style.cssText =
|
|
1381
|
-
"background:var(--green-hi);color:#000;border:none;border-radius:8px;padding:8px 16px;font-size:12px;font-weight:700;cursor:pointer;";
|
|
1382
|
-
startBtn.onclick = async () => {
|
|
1383
|
-
startBtn.disabled = true;
|
|
1384
|
-
startBtn.textContent = "⏳ Launching…";
|
|
1385
|
-
try {
|
|
1386
|
-
const r = await postJSON("/api/crew-lead/confirm-project", {
|
|
1387
|
-
draftId,
|
|
1388
|
-
roadmapMd: ta.value,
|
|
1389
|
-
});
|
|
1390
|
-
if (r.ok) {
|
|
1391
|
-
card.innerHTML =
|
|
1392
|
-
'<div style="padding:14px;color:var(--green-hi);font-size:13px;font-weight:600;">✅ ' +
|
|
1393
|
-
name +
|
|
1394
|
-
' — project created, PM loop running!<br><span style="color:var(--blue);font-size:11px;font-weight:400">' +
|
|
1395
|
-
(r.outputDir || outputDir) +
|
|
1396
|
-
"</span></div>";
|
|
1397
|
-
appendChatBubble(
|
|
1398
|
-
"assistant",
|
|
1399
|
-
"🚀 " +
|
|
1400
|
-
name +
|
|
1401
|
-
" is building. Check the Projects tab to watch progress.",
|
|
1402
|
-
);
|
|
1403
|
-
} else {
|
|
1404
|
-
startBtn.disabled = false;
|
|
1405
|
-
startBtn.textContent = "▶ Start Building";
|
|
1406
|
-
status.textContent = "⚠️ " + (r.error || "Launch failed");
|
|
1407
|
-
}
|
|
1408
|
-
} catch (e) {
|
|
1409
|
-
startBtn.disabled = false;
|
|
1410
|
-
startBtn.textContent = "▶ Start Building";
|
|
1411
|
-
status.textContent = "⚠️ " + e.message;
|
|
1412
|
-
}
|
|
1413
|
-
};
|
|
1414
|
-
|
|
1415
|
-
const discardBtn = document.createElement("button");
|
|
1416
|
-
discardBtn.textContent = "Discard";
|
|
1417
|
-
discardBtn.style.cssText =
|
|
1418
|
-
"background:none;border:1px solid var(--border);color:var(--text-3);border-radius:8px;padding:8px 14px;font-size:12px;cursor:pointer;";
|
|
1419
|
-
discardBtn.onclick = async () => {
|
|
1420
|
-
await postJSON("/api/crew-lead/discard-project", { draftId }).catch(
|
|
1421
|
-
() => {},
|
|
1422
|
-
);
|
|
1423
|
-
wrap.remove();
|
|
1424
|
-
};
|
|
1425
|
-
|
|
1426
|
-
const status = document.createElement("span");
|
|
1427
|
-
status.style.cssText = "font-size:11px;color:var(--blue);margin-left:auto;";
|
|
1428
|
-
status.textContent = "Edit above, then confirm";
|
|
1429
|
-
|
|
1430
|
-
actions.appendChild(startBtn);
|
|
1431
|
-
actions.appendChild(discardBtn);
|
|
1432
|
-
actions.appendChild(status);
|
|
1433
|
-
card.appendChild(header);
|
|
1434
|
-
card.appendChild(ta);
|
|
1435
|
-
card.appendChild(actions);
|
|
1436
|
-
wrap.appendChild(lbl);
|
|
1437
|
-
wrap.appendChild(card);
|
|
1438
|
-
box.appendChild(wrap);
|
|
1439
|
-
box.scrollTop = box.scrollHeight;
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
let lastAppendedAssistantContent = "";
|
|
1443
|
-
let lastAppendedUserContent = "";
|
|
1444
|
-
let lastSentContent = null;
|
|
1445
|
-
const {
|
|
1446
|
-
loadChatHistory,
|
|
1447
|
-
waitForChatHistoryIdle,
|
|
1448
|
-
chatAtAtInput,
|
|
1449
|
-
chatKeydown,
|
|
1450
|
-
sendChat,
|
|
1451
|
-
clearChatHistory,
|
|
1452
|
-
restorePassthroughLog,
|
|
1453
|
-
sendPassthrough,
|
|
1454
|
-
stopAll,
|
|
1455
|
-
killAll,
|
|
1456
|
-
killPassthrough,
|
|
1457
|
-
refreshSessionIndicator,
|
|
1458
|
-
clearPassthroughSession,
|
|
1459
|
-
resetSendButton,
|
|
1460
|
-
handleImageUpload,
|
|
1461
|
-
toggleVoiceRecording,
|
|
1462
|
-
} = initChatActions({
|
|
1463
|
-
postJSON,
|
|
1464
|
-
getJSON,
|
|
1465
|
-
appendChatBubble,
|
|
1466
|
-
showNotification,
|
|
1467
|
-
state,
|
|
1468
|
-
getChatSessionId: () => getChatSessionId(),
|
|
1469
|
-
getChatActiveProjectId: () => state.chatActiveProjectId,
|
|
1470
|
-
getCrewLeadInfo: () => window._crewLeadInfo,
|
|
1471
|
-
appendRoadmapCard,
|
|
1472
|
-
getLastAppendedAssistantContent: () => lastAppendedAssistantContent,
|
|
1473
|
-
setLastAppendedAssistantContent: (value) => {
|
|
1474
|
-
lastAppendedAssistantContent = value;
|
|
1475
|
-
},
|
|
1476
|
-
setLastAppendedUserContent: (value) => {
|
|
1477
|
-
lastAppendedUserContent = value;
|
|
1478
|
-
},
|
|
1479
|
-
setLastSentContent: (value) => {
|
|
1480
|
-
lastSentContent = value;
|
|
1481
|
-
},
|
|
1482
|
-
});
|
|
1483
|
-
|
|
1484
|
-
// Wire up multimodal buttons
|
|
1485
|
-
document.getElementById("attachImageBtn")?.addEventListener("click", () => {
|
|
1486
|
-
document.getElementById("imageUpload").click();
|
|
1487
|
-
});
|
|
1488
|
-
document
|
|
1489
|
-
.getElementById("imageUpload")
|
|
1490
|
-
?.addEventListener("change", handleImageUpload);
|
|
1491
|
-
document
|
|
1492
|
-
.getElementById("recordVoiceBtn")
|
|
1493
|
-
?.addEventListener("click", toggleVoiceRecording);
|
|
1494
|
-
|
|
1495
|
-
// Expose loadChatHistory globally for project tab switching
|
|
1496
|
-
window.loadChatHistory = loadChatHistory;
|
|
1497
|
-
|
|
1498
|
-
// Expose getChatSessionId for testing and debugging
|
|
1499
|
-
window.getChatSessionId = getChatSessionId;
|
|
1500
|
-
|
|
1501
|
-
// Expose selectProjectTab for General tab onclick in HTML
|
|
1502
|
-
window.selectProjectTab = (projectId) => {
|
|
1503
|
-
// Normalize: missing or invalid id => general (avoids #chat?project=undefined)
|
|
1504
|
-
const normalizedId =
|
|
1505
|
-
projectId && String(projectId).trim() && projectId !== "undefined"
|
|
1506
|
-
? projectId
|
|
1507
|
-
: "general";
|
|
1508
|
-
const currentProjectId = state.chatActiveProjectId;
|
|
1509
|
-
console.log(
|
|
1510
|
-
"🔵 [TAB CLICK] START",
|
|
1511
|
-
normalizedId,
|
|
1512
|
-
"- from:",
|
|
1513
|
-
currentProjectId,
|
|
1514
|
-
);
|
|
1515
|
-
|
|
1516
|
-
const tabsContainer = document.getElementById("chatProjectTabs");
|
|
1517
|
-
if (!tabsContainer) {
|
|
1518
|
-
console.error("🔵 [TAB CLICK] ERROR: chatProjectTabs container not found!");
|
|
1519
|
-
return;
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
if (currentProjectId === normalizedId) {
|
|
1523
|
-
console.log("🔵 [TAB CLICK] Already on this tab, skipping reload");
|
|
1524
|
-
return;
|
|
1525
|
-
}
|
|
1526
|
-
|
|
1527
|
-
setChatHashProject(normalizedId);
|
|
1528
|
-
|
|
1529
|
-
// Update UI: deactivate all, activate selected
|
|
1530
|
-
Array.from(tabsContainer.children).forEach((tab) => {
|
|
1531
|
-
tab.classList.remove("active");
|
|
1532
|
-
});
|
|
1533
|
-
|
|
1534
|
-
const selectedTab = Array.from(tabsContainer.children).find(
|
|
1535
|
-
(tab) => tab.dataset.projectId === normalizedId,
|
|
1536
|
-
);
|
|
1537
|
-
if (selectedTab) {
|
|
1538
|
-
selectedTab.classList.add("active");
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1541
|
-
state.chatActiveProjectId = normalizedId;
|
|
1542
|
-
try {
|
|
1543
|
-
localStorage.setItem("crewswarm_chat_active_project_id", normalizedId);
|
|
1544
|
-
} catch {}
|
|
1545
|
-
persistSharedActiveProjectId(normalizedId);
|
|
1546
|
-
|
|
1547
|
-
console.log("🔵 [TAB CLICK] Updated state:", {
|
|
1548
|
-
projectId: state.chatActiveProjectId,
|
|
1549
|
-
sessionId: getChatSessionId(),
|
|
1550
|
-
url: window.location.hash,
|
|
1551
|
-
});
|
|
1552
|
-
|
|
1553
|
-
console.log("🔵 [TAB CLICK] Calling loadChatHistory()...");
|
|
1554
|
-
|
|
1555
|
-
// Reload history
|
|
1556
|
-
loadChatHistory()
|
|
1557
|
-
.then(() => {
|
|
1558
|
-
console.log("🔵 [TAB CLICK] loadChatHistory() completed");
|
|
1559
|
-
const box = document.getElementById("chatMessages");
|
|
1560
|
-
console.log("🔵 [TAB CLICK] Messages in DOM:", box?.children.length || 0);
|
|
1561
|
-
})
|
|
1562
|
-
.catch((err) => {
|
|
1563
|
-
console.error("🔵 [TAB CLICK] loadChatHistory() ERROR:", err);
|
|
1564
|
-
});
|
|
1565
|
-
};
|
|
1566
|
-
|
|
1567
|
-
window.addEventListener("focus", () => {
|
|
1568
|
-
syncChatProjectFromSharedState().catch(() => {});
|
|
1569
|
-
});
|
|
1570
|
-
|
|
1571
|
-
/* services tab extracted to tabs/services-tab.js */
|
|
1572
|
-
async function loadFiles(forceRefresh) {
|
|
1573
|
-
const el = document.getElementById("filesContent");
|
|
1574
|
-
const dir =
|
|
1575
|
-
document.getElementById("filesDir").value.trim() ||
|
|
1576
|
-
window._crewCwd ||
|
|
1577
|
-
(window._crewHome ? window._crewHome + "/CrewSwarm" : "");
|
|
1578
|
-
showLoading(el, "Scanning " + dir + "...");
|
|
1579
|
-
try {
|
|
1580
|
-
const data = await getJSON("/api/files?dir=" + encodeURIComponent(dir));
|
|
1581
|
-
if (!data.files || !data.files.length) {
|
|
1582
|
-
showEmpty(el, "No files found in " + dir);
|
|
1583
|
-
return;
|
|
1584
|
-
}
|
|
1585
|
-
const grouped = {};
|
|
1586
|
-
data.files.forEach((f) => {
|
|
1587
|
-
const ext = f.path.split(".").pop().toLowerCase() || "other";
|
|
1588
|
-
if (!grouped[ext]) grouped[ext] = [];
|
|
1589
|
-
grouped[ext].push(f);
|
|
1590
|
-
});
|
|
1591
|
-
const extOrder = [
|
|
1592
|
-
"html",
|
|
1593
|
-
"css",
|
|
1594
|
-
"js",
|
|
1595
|
-
"mjs",
|
|
1596
|
-
"ts",
|
|
1597
|
-
"json",
|
|
1598
|
-
"md",
|
|
1599
|
-
"sh",
|
|
1600
|
-
"txt",
|
|
1601
|
-
"other",
|
|
1602
|
-
];
|
|
1603
|
-
const extEmoji = {
|
|
1604
|
-
html: "🌐",
|
|
1605
|
-
css: "🎨",
|
|
1606
|
-
js: "⚡",
|
|
1607
|
-
mjs: "⚡",
|
|
1608
|
-
ts: "🔷",
|
|
1609
|
-
json: "📋",
|
|
1610
|
-
md: "📝",
|
|
1611
|
-
sh: "🖥️",
|
|
1612
|
-
txt: "📄",
|
|
1613
|
-
other: "📁",
|
|
1614
|
-
};
|
|
1615
|
-
let html = '<div style="display:grid;gap:1rem;padding:4px 0;">';
|
|
1616
|
-
for (const ext of extOrder) {
|
|
1617
|
-
if (!grouped[ext]) continue;
|
|
1618
|
-
html += "<div>";
|
|
1619
|
-
html +=
|
|
1620
|
-
'<div style="font-size:11px;font-weight:600;color:var(--text-2);text-transform:uppercase;letter-spacing:0.08em;margin-bottom:8px;padding-left:2px;">' +
|
|
1621
|
-
(extEmoji[ext] || "📁") +
|
|
1622
|
-
" ." +
|
|
1623
|
-
ext +
|
|
1624
|
-
" — " +
|
|
1625
|
-
grouped[ext].length +
|
|
1626
|
-
" file" +
|
|
1627
|
-
(grouped[ext].length > 1 ? "s" : "") +
|
|
1628
|
-
"</div>";
|
|
1629
|
-
html += '<div style="display:grid;gap:6px;">';
|
|
1630
|
-
grouped[ext]
|
|
1631
|
-
.sort((a, b) => b.mtime - a.mtime)
|
|
1632
|
-
.forEach((f) => {
|
|
1633
|
-
const rel = f.path.replace(dir + "/", "");
|
|
1634
|
-
const age = formatAge(f.mtime);
|
|
1635
|
-
const sz = formatSize(f.size);
|
|
1636
|
-
html += '<div class="file-row">';
|
|
1637
|
-
html +=
|
|
1638
|
-
'<div class="file-info"><span class="file-name">' +
|
|
1639
|
-
rel +
|
|
1640
|
-
'</span><span class="file-meta">' +
|
|
1641
|
-
sz +
|
|
1642
|
-
" · " +
|
|
1643
|
-
age +
|
|
1644
|
-
"</span></div>";
|
|
1645
|
-
html += '<div class="file-actions">';
|
|
1646
|
-
html +=
|
|
1647
|
-
'<a href="cursor://file/' +
|
|
1648
|
-
f.path +
|
|
1649
|
-
'" class="file-btn file-btn-cursor" title="Open in Cursor">Cursor</a>';
|
|
1650
|
-
html +=
|
|
1651
|
-
'<a href="opencode://open?path=' +
|
|
1652
|
-
encodeURIComponent(f.path) +
|
|
1653
|
-
'" class="file-btn file-btn-opencode" title="Open in OpenCode">OpenCode</a>';
|
|
1654
|
-
html +=
|
|
1655
|
-
'<button data-action="previewFile" data-arg=\'' +
|
|
1656
|
-
f.path.replace(/'/g, "'") +
|
|
1657
|
-
'\' data-self="1" class="file-btn" title="Preview">👁</button>';
|
|
1658
|
-
html += "</div></div>";
|
|
1659
|
-
});
|
|
1660
|
-
html += "</div></div>";
|
|
1661
|
-
}
|
|
1662
|
-
html += "</div>";
|
|
1663
|
-
html +=
|
|
1664
|
-
'<div id="file-preview-pane" style="display:none;margin-top:1rem;background:#0d1117;border:1px solid var(--border);border-radius:8px;overflow:hidden;"><div id="file-preview-bar" style="display:flex;align-items:center;gap:8px;padding:8px 12px;background:#0d1420;border-bottom:1px solid var(--border);font-size:12px;color:var(--text-2);"><span id="file-preview-name"></span><button data-action="closePreviewPane" style="margin-left:auto;background:none;border:none;color:var(--text-2);cursor:pointer;">✕</button></div><pre id="file-preview-content" style="margin:0;padding:1rem;font-size:0.75rem;overflow:auto;max-height:400px;"></pre></div>';
|
|
1665
|
-
el.innerHTML = html;
|
|
1666
|
-
} catch (e) {
|
|
1667
|
-
showError(el, "Error: " + e.message);
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
1670
|
-
async function previewFile(filePath, btn) {
|
|
1671
|
-
const pane = document.getElementById("file-preview-pane");
|
|
1672
|
-
const content = document.getElementById("file-preview-content");
|
|
1673
|
-
const name = document.getElementById("file-preview-name");
|
|
1674
|
-
if (!pane) return;
|
|
1675
|
-
name.textContent = filePath.split("/").pop();
|
|
1676
|
-
content.textContent = "Loading...";
|
|
1677
|
-
pane.style.display = "block";
|
|
1678
|
-
pane.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
1679
|
-
try {
|
|
1680
|
-
const data = await getJSON(
|
|
1681
|
-
"/api/file-content?path=" + encodeURIComponent(filePath),
|
|
1682
|
-
);
|
|
1683
|
-
content.textContent = data.content || "(empty)";
|
|
1684
|
-
} catch (e) {
|
|
1685
|
-
content.textContent = "Error: " + e.message;
|
|
1686
|
-
}
|
|
1687
|
-
}
|
|
1688
|
-
function closePreviewPane() {
|
|
1689
|
-
const pane = document.getElementById("file-preview-pane");
|
|
1690
|
-
if (pane) pane.style.display = "none";
|
|
1691
|
-
}
|
|
1692
|
-
function formatAge(mtime) {
|
|
1693
|
-
const diff = Date.now() - mtime;
|
|
1694
|
-
const m = Math.floor(diff / 60000);
|
|
1695
|
-
if (m < 1) return "just now";
|
|
1696
|
-
if (m < 60) return m + "m ago";
|
|
1697
|
-
const h = Math.floor(m / 60);
|
|
1698
|
-
if (h < 24) return h + "h ago";
|
|
1699
|
-
return Math.floor(h / 24) + "d ago";
|
|
1700
|
-
}
|
|
1701
|
-
function formatSize(bytes) {
|
|
1702
|
-
if (bytes < 1024) return bytes + "B";
|
|
1703
|
-
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + "KB";
|
|
1704
|
-
return (bytes / 1024 / 1024).toFixed(1) + "MB";
|
|
1705
|
-
}
|
|
1706
|
-
function showSettings() {
|
|
1707
|
-
hideAllViews();
|
|
1708
|
-
document.getElementById("settingsView").classList.add("active");
|
|
1709
|
-
setNavActive("navSettings");
|
|
1710
|
-
state.activeTab = "settings";
|
|
1711
|
-
persistState();
|
|
1712
|
-
// Restore last active sub-tab from hash (e.g. #settings/telegram → telegram)
|
|
1713
|
-
const hashSubtab = (location.hash || "").replace("#settings/", "");
|
|
1714
|
-
// Support legacy deep-link aliases
|
|
1715
|
-
const TAB_ALIASES = {
|
|
1716
|
-
system: "engines",
|
|
1717
|
-
telegram: "comms",
|
|
1718
|
-
whatsapp: "comms",
|
|
1719
|
-
};
|
|
1720
|
-
const knownTabs = ["usage", "engines", "comms", "security", "webhooks"];
|
|
1721
|
-
const resolved = TAB_ALIASES[hashSubtab] || hashSubtab;
|
|
1722
|
-
showSettingsTab(knownTabs.includes(resolved) ? resolved : "usage");
|
|
1723
|
-
}
|
|
1724
|
-
function showSettingsTab(tab) {
|
|
1725
|
-
const knownTabs = ["usage", "engines", "comms", "security", "webhooks"];
|
|
1726
|
-
knownTabs.forEach((t) => {
|
|
1727
|
-
const panel = document.getElementById("stab-panel-" + t);
|
|
1728
|
-
const btn = document.getElementById("stab-" + t);
|
|
1729
|
-
if (!panel || !btn) return;
|
|
1730
|
-
panel.style.display =
|
|
1731
|
-
t === tab ? (t === "usage" ? "grid" : "block") : "none";
|
|
1732
|
-
btn.classList.toggle("active", t === tab);
|
|
1733
|
-
});
|
|
1734
|
-
if (tab === "usage") {
|
|
1735
|
-
loadTokenUsage();
|
|
1736
|
-
loadAllUsage();
|
|
1737
|
-
}
|
|
1738
|
-
if (tab === "engines") {
|
|
1739
|
-
loadOpencodeProject();
|
|
1740
|
-
loadBgConsciousness();
|
|
1741
|
-
loadGlobalFallback();
|
|
1742
|
-
loadConfigLockStatus();
|
|
1743
|
-
loadCursorWaves();
|
|
1744
|
-
loadTmuxBridge();
|
|
1745
|
-
loadAutonomousMentions();
|
|
1746
|
-
loadClaudeCode();
|
|
1747
|
-
loadCodexExecutor();
|
|
1748
|
-
loadGeminiCliExecutor();
|
|
1749
|
-
loadCrewCliExecutor();
|
|
1750
|
-
loadOpencodeExecutor();
|
|
1751
|
-
loadGlobalOcLoop();
|
|
1752
|
-
loadLoopBrain();
|
|
1753
|
-
loadPassthroughNotify();
|
|
1754
|
-
}
|
|
1755
|
-
if (tab === "comms") {
|
|
1756
|
-
loadCommsTabData();
|
|
1757
|
-
}
|
|
1758
|
-
if (tab === "security") {
|
|
1759
|
-
loadCmdAllowlist();
|
|
1760
|
-
loadEnvAdvanced();
|
|
1761
|
-
}
|
|
1762
|
-
if (tab === "webhooks") {
|
|
1763
|
-
/* static */
|
|
1764
|
-
}
|
|
1765
|
-
// Update URL hash for deep linking — e.g. #settings/telegram
|
|
1766
|
-
if (document.getElementById("settingsView")?.classList.contains("active")) {
|
|
1767
|
-
history.replaceState(null, "", "#settings/" + tab);
|
|
1768
|
-
}
|
|
1769
|
-
}
|
|
1770
|
-
|
|
1771
|
-
initCommsTab({ showSettings, showSettingsTab });
|
|
1772
|
-
initSettingsTab({ getModels: loadAgents_cfg, populateModelDropdown });
|
|
1773
|
-
initModelsTab({ hideAllViews, setNavActive, loadAgents: loadAgents_cfg });
|
|
1774
|
-
initAddProviderForm();
|
|
1775
|
-
initSwarmChatTab({ hideAllViews, setNavActive });
|
|
1776
|
-
|
|
1777
|
-
// ── Engines → engines-tab.js ─────────────────────────────────────────────────
|
|
1778
|
-
function showEngines() {
|
|
1779
|
-
hideAllViews();
|
|
1780
|
-
document.getElementById("enginesView").classList.add("active");
|
|
1781
|
-
setNavActive("navEngines");
|
|
1782
|
-
loadEngines();
|
|
1783
|
-
}
|
|
1784
|
-
|
|
1785
|
-
// showSkills / showRunSkills → skills-tab.js
|
|
1786
|
-
|
|
1787
|
-
const showBenchmarks = async () => {
|
|
1788
|
-
const { showBenchmarks: showBenchmarksTab } = await loadBenchmarksTabModule();
|
|
1789
|
-
showBenchmarksTab({ hideAllViews, setNavActive });
|
|
1790
|
-
};
|
|
1791
|
-
|
|
1792
|
-
function showMemoryView() {
|
|
1793
|
-
hideAllViews();
|
|
1794
|
-
document.getElementById("memoryView").classList.add("active");
|
|
1795
|
-
setNavActive("navMemory");
|
|
1796
|
-
showMemory();
|
|
1797
|
-
}
|
|
1798
|
-
|
|
1799
|
-
function showCLIProcess() {
|
|
1800
|
-
hideAllViews();
|
|
1801
|
-
document.getElementById("cliProcessView").classList.add("active");
|
|
1802
|
-
setNavActive("navCLI");
|
|
1803
|
-
// Initialize CLI process view
|
|
1804
|
-
if (window.initCLIProcess) window.initCLIProcess();
|
|
1805
|
-
}
|
|
1806
|
-
|
|
1807
|
-
function showToolMatrix() {
|
|
1808
|
-
hideAllViews();
|
|
1809
|
-
document.getElementById("toolMatrixView").classList.add("active");
|
|
1810
|
-
setNavActive("navToolMatrix");
|
|
1811
|
-
loadToolMatrix();
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
|
-
// keep old name working for any legacy calls
|
|
1815
|
-
function showIntegrations() {
|
|
1816
|
-
showSkills();
|
|
1817
|
-
}
|
|
1818
|
-
|
|
1819
|
-
// loadRunSkills / runSkillFromUI → skills-tab.js
|
|
1820
|
-
|
|
1821
|
-
// Tool Matrix → tabs/usage-tab.js
|
|
1822
|
-
|
|
1823
|
-
// Spending → tabs/spending-tab.js
|
|
1824
|
-
|
|
1825
|
-
// ── Webhooks ──────────────────────────────────────────────────────────────────
|
|
1826
|
-
async function sendTestWebhook() {
|
|
1827
|
-
const channel =
|
|
1828
|
-
document.getElementById("webhookChannel").value.trim() || "test";
|
|
1829
|
-
let payload = {};
|
|
1830
|
-
try {
|
|
1831
|
-
const v = document.getElementById("webhookPayload").value.trim();
|
|
1832
|
-
if (v) payload = JSON.parse(v);
|
|
1833
|
-
} catch {
|
|
1834
|
-
payload = { raw: document.getElementById("webhookPayload").value };
|
|
1835
|
-
}
|
|
1836
|
-
const el = document.getElementById("webhookTestResult");
|
|
1837
|
-
try {
|
|
1838
|
-
const res = await fetch("/proxy-webhook/" + channel, {
|
|
1839
|
-
method: "POST",
|
|
1840
|
-
headers: { "Content-Type": "application/json" },
|
|
1841
|
-
body: JSON.stringify(payload),
|
|
1842
|
-
});
|
|
1843
|
-
const d = await res.json();
|
|
1844
|
-
el.textContent = d.ok ? "✅ Sent to RT bus" : "❌ " + (d.error || "failed");
|
|
1845
|
-
el.style.color = d.ok ? "var(--green)" : "var(--red)";
|
|
1846
|
-
} catch (e) {
|
|
1847
|
-
el.textContent = "❌ " + e.message;
|
|
1848
|
-
el.style.color = "var(--red)";
|
|
1849
|
-
}
|
|
1850
|
-
}
|
|
1851
|
-
|
|
1852
|
-
// ── Pending Approvals ─────────────────────────────────────────────────────────
|
|
1853
|
-
async function loadPendingApprovals() {
|
|
1854
|
-
const el = document.getElementById("pendingApprovals");
|
|
1855
|
-
// pending-skills.json is at ~/.crewswarm/pending-skills.json — no direct API yet;
|
|
1856
|
-
// crew-lead should expose this but for now show instructions.
|
|
1857
|
-
el.innerHTML =
|
|
1858
|
-
'<div style="color:var(--text-3);font-size:12px;">Pending skill approvals appear here when an agent triggers a skill marked requiresApproval. You will also receive a Telegram notification with inline Approve/Reject buttons if Telegram is configured.</div>';
|
|
1859
|
-
}
|
|
1860
|
-
async function approveSkill(approvalId) {
|
|
1861
|
-
try {
|
|
1862
|
-
await fetch("/api/skills/approve", {
|
|
1863
|
-
method: "POST",
|
|
1864
|
-
headers: { "content-type": "application/json" },
|
|
1865
|
-
body: JSON.stringify({ approvalId }),
|
|
1866
|
-
});
|
|
1867
|
-
showNotification("Approved");
|
|
1868
|
-
loadPendingApprovals();
|
|
1869
|
-
} catch (e) {
|
|
1870
|
-
showNotification("Failed: " + e.message, "error");
|
|
1871
|
-
}
|
|
1872
|
-
}
|
|
1873
|
-
async function rejectSkill(approvalId) {
|
|
1874
|
-
try {
|
|
1875
|
-
await fetch("/api/skills/reject", {
|
|
1876
|
-
method: "POST",
|
|
1877
|
-
headers: { "content-type": "application/json" },
|
|
1878
|
-
body: JSON.stringify({ approvalId }),
|
|
1879
|
-
});
|
|
1880
|
-
showNotification("Rejected");
|
|
1881
|
-
loadPendingApprovals();
|
|
1882
|
-
} catch (e) {
|
|
1883
|
-
showNotification("Failed: " + e.message, "error");
|
|
1884
|
-
}
|
|
1885
|
-
}
|
|
1886
|
-
|
|
1887
|
-
/* agents tab extracted to tabs/agents-tab.js */
|
|
1888
|
-
function showBuild() {
|
|
1889
|
-
_showBuild({ hideAllViews, setNavActive });
|
|
1890
|
-
}
|
|
1891
|
-
function showProjects() {
|
|
1892
|
-
_showProjects({ hideAllViews, setNavActive });
|
|
1893
|
-
}
|
|
1894
|
-
|
|
1895
|
-
// ── Projects / Build → projects-tab.js ───────────────────────────────────────
|
|
1896
|
-
// Wire project list delegated click handler
|
|
1897
|
-
initProjectsList({ showChat, showBuild });
|
|
1898
|
-
|
|
1899
|
-
// Initial status check
|
|
1900
|
-
updateStatusBadges();
|
|
1901
|
-
|
|
1902
|
-
// Lightweight status badge updates only (DLQ count + status dot)
|
|
1903
|
-
// Content loads on-demand when switching tabs (event-driven, not polling)
|
|
1904
|
-
setInterval(updateStatusBadges, 30000); // Every 30s for badge/status only
|
|
1905
|
-
// Populate chat project dropdown on load; respect #projects deep link (e.g. from native app)
|
|
1906
|
-
(async () => {
|
|
1907
|
-
try {
|
|
1908
|
-
const data = await getJSON("/api/projects");
|
|
1909
|
-
const projects = data.projects || [];
|
|
1910
|
-
state.projectsData = {};
|
|
1911
|
-
projects.forEach((p) => {
|
|
1912
|
-
state.projectsData[p.id] = p;
|
|
1913
|
-
});
|
|
1914
|
-
populateChatProjectDropdown(projects);
|
|
1915
|
-
persistState();
|
|
1916
|
-
if (location.hash === "#projects") showProjects();
|
|
1917
|
-
} catch {}
|
|
1918
|
-
})();
|
|
1919
|
-
document.getElementById("refreshBtn").onclick = refreshAll;
|
|
1920
|
-
document.getElementById("runBuildBtn").onclick = runBuild;
|
|
1921
|
-
document.getElementById("continuousBuildBtn").onclick = continuousBuildRun;
|
|
1922
|
-
document.getElementById("stopBuildBtn").onclick = stopBuild;
|
|
1923
|
-
document.getElementById("stopContinuousBtn").onclick = stopContinuousBuild;
|
|
1924
|
-
document.getElementById("enhancePromptBtn").onclick = enhancePrompt;
|
|
1925
|
-
initPmLoopTab();
|
|
1926
|
-
document.getElementById("newProjectBtn").onclick = () => {
|
|
1927
|
-
const form = document.getElementById("newProjectForm");
|
|
1928
|
-
form.style.display = form.style.display === "none" ? "block" : "none";
|
|
1929
|
-
};
|
|
1930
|
-
document.getElementById("npCancelBtn").onclick = () => {
|
|
1931
|
-
document.getElementById("newProjectForm").style.display = "none";
|
|
1932
|
-
};
|
|
1933
|
-
document.getElementById("npCreateBtn").onclick = async () => {
|
|
1934
|
-
const name = document.getElementById("npName").value.trim();
|
|
1935
|
-
const desc = document.getElementById("npDesc").value.trim();
|
|
1936
|
-
const outputDir = document.getElementById("npOutputDir").value.trim();
|
|
1937
|
-
const featuresDoc = document.getElementById("npFeaturesDoc").value.trim();
|
|
1938
|
-
if (!name || !outputDir) {
|
|
1939
|
-
showNotification("Name and output directory required", true);
|
|
1940
|
-
return;
|
|
1941
|
-
}
|
|
1942
|
-
try {
|
|
1943
|
-
const r = await postJSON("/api/projects", {
|
|
1944
|
-
name,
|
|
1945
|
-
description: desc,
|
|
1946
|
-
outputDir,
|
|
1947
|
-
featuresDoc,
|
|
1948
|
-
});
|
|
1949
|
-
showNotification(`Project "${r.project.name}" created!`);
|
|
1950
|
-
document.getElementById("newProjectForm").style.display = "none";
|
|
1951
|
-
document.getElementById("npName").value = "";
|
|
1952
|
-
document.getElementById("npDesc").value = "";
|
|
1953
|
-
document.getElementById("npOutputDir").value = "";
|
|
1954
|
-
document.getElementById("npFeaturesDoc").value = "";
|
|
1955
|
-
loadProjects();
|
|
1956
|
-
} catch (e) {
|
|
1957
|
-
showNotification("Failed: " + e.message, true);
|
|
1958
|
-
}
|
|
1959
|
-
};
|
|
1960
|
-
// sendBtn / messageInput removed (replaced by crew-lead chat)
|
|
1961
|
-
|
|
1962
|
-
// PM Loop controls → tabs/pm-loop-tab.js
|
|
1963
|
-
// ── Hash routing — persist active view across refresh ────────────────────────
|
|
1964
|
-
// ── Hash routing ─────────────────────────────────────────────────────────────
|
|
1965
|
-
// Patch each top-level show* function so calling it (via onclick or code)
|
|
1966
|
-
// automatically updates location.hash. Refresh → restores the same tab.
|
|
1967
|
-
const VIEW_MAP = {
|
|
1968
|
-
chat: showChat,
|
|
1969
|
-
"swarm-chat": showSwarmChat,
|
|
1970
|
-
swarm: showSwarm,
|
|
1971
|
-
rt: showRT,
|
|
1972
|
-
dlq: showDLQ,
|
|
1973
|
-
files: showFiles,
|
|
1974
|
-
services: showServices,
|
|
1975
|
-
agents: showAgents,
|
|
1976
|
-
models: showModels,
|
|
1977
|
-
settings: showSettings,
|
|
1978
|
-
engines: showEngines,
|
|
1979
|
-
skills: showSkills,
|
|
1980
|
-
"run-skills": showRunSkills,
|
|
1981
|
-
benchmarks: showBenchmarks,
|
|
1982
|
-
"tool-matrix": showToolMatrix,
|
|
1983
|
-
build: showBuild,
|
|
1984
|
-
messaging: showMessaging,
|
|
1985
|
-
projects: showProjects,
|
|
1986
|
-
contacts: showContacts,
|
|
1987
|
-
memory: showMemoryView,
|
|
1988
|
-
workflows: showWorkflows,
|
|
1989
|
-
"cli-process": showCLIProcess,
|
|
1990
|
-
prompts: initPromptsTab,
|
|
1991
|
-
};
|
|
1992
|
-
|
|
1993
|
-
// Wrap each show* so it updates the hash when called from anywhere
|
|
1994
|
-
for (const [hash, fn] of Object.entries(VIEW_MAP)) {
|
|
1995
|
-
const original = fn;
|
|
1996
|
-
const wrapped = function (...args) {
|
|
1997
|
-
const cur = location.hash || "";
|
|
1998
|
-
if (hash === "chat") {
|
|
1999
|
-
// Never replaceState to bare #chat when already on #chat?project=… — that
|
|
2000
|
-
// fires hashchange in some browsers and doubles showChat + 500-msg reloads.
|
|
2001
|
-
if (!cur.startsWith("#chat")) {
|
|
2002
|
-
history.replaceState(null, "", "#chat");
|
|
2003
|
-
}
|
|
2004
|
-
} else {
|
|
2005
|
-
history.replaceState(null, "", "#" + hash);
|
|
2006
|
-
}
|
|
2007
|
-
return original(...args);
|
|
2008
|
-
};
|
|
2009
|
-
// Update the reference in the map and on window (for onclick= handlers)
|
|
2010
|
-
VIEW_MAP[hash] = wrapped;
|
|
2011
|
-
window[original.name] = wrapped;
|
|
2012
|
-
}
|
|
2013
|
-
|
|
2014
|
-
function navigateTo(view) {
|
|
2015
|
-
const base = String(view || "chat").split("?")[0].split("/")[0];
|
|
2016
|
-
const fn = VIEW_MAP[base] || VIEW_MAP["chat"];
|
|
2017
|
-
fn();
|
|
2018
|
-
}
|
|
2019
|
-
|
|
2020
|
-
// On load: check first-run status, then restore from hash or default to chat
|
|
2021
|
-
// Supports top-level (#chat, #services) and sub-tab deep links (#settings/telegram)
|
|
2022
|
-
(async () => {
|
|
2023
|
-
const wizardShown = await checkFirstRun();
|
|
2024
|
-
if (wizardShown) return; // wizard handles its own flow; reload on completion
|
|
2025
|
-
|
|
2026
|
-
const { view: startView, subtab: startSubtab } = parseRouteFromHash();
|
|
2027
|
-
const params = new URLSearchParams(window.location.search);
|
|
2028
|
-
if (params.get("focus") === "1") {
|
|
2029
|
-
setTimeout(() => {
|
|
2030
|
-
const ci = document.getElementById("chatInput");
|
|
2031
|
-
if (ci) {
|
|
2032
|
-
navigateTo("chat");
|
|
2033
|
-
ci.focus();
|
|
2034
|
-
}
|
|
2035
|
-
}, 500);
|
|
2036
|
-
} else {
|
|
2037
|
-
navigateTo(startView || "chat");
|
|
2038
|
-
if (startView === "settings" && startSubtab) {
|
|
2039
|
-
showSettingsTab(startSubtab);
|
|
2040
|
-
}
|
|
2041
|
-
}
|
|
2042
|
-
})();
|
|
2043
|
-
|
|
2044
|
-
// Handle browser back/forward buttons
|
|
2045
|
-
window.addEventListener("hashchange", () => {
|
|
2046
|
-
const { view, subtab } = parseRouteFromHash();
|
|
2047
|
-
|
|
2048
|
-
// Navigate to the view from hash
|
|
2049
|
-
const viewFn = NAV_VIEW_MAP[view];
|
|
2050
|
-
if (viewFn) {
|
|
2051
|
-
viewFn();
|
|
2052
|
-
// If it's a settings subtab, show that too
|
|
2053
|
-
if (view === "settings" && subtab) {
|
|
2054
|
-
showSettingsTab(subtab);
|
|
2055
|
-
}
|
|
2056
|
-
} else {
|
|
2057
|
-
// Fallback to chat if invalid hash
|
|
2058
|
-
showChat();
|
|
2059
|
-
}
|
|
2060
|
-
});
|
|
2061
|
-
// Resolve server-side env vars (HOME, cwd) once on boot
|
|
2062
|
-
fetch("/api/env")
|
|
2063
|
-
.then((r) => r.json())
|
|
2064
|
-
.then((env) => {
|
|
2065
|
-
window._crewHome = env.HOME || "";
|
|
2066
|
-
window._crewCwd = env.cwd || "";
|
|
2067
|
-
const filesDir = document.getElementById("filesDir");
|
|
2068
|
-
if (filesDir && !filesDir.value) filesDir.value = env.cwd || "";
|
|
2069
|
-
})
|
|
2070
|
-
.catch(() => {});
|
|
2071
|
-
|
|
2072
|
-
loadAgents_cfg().catch((e) => console.error("Initial agents-config load failed:", e));
|
|
2073
|
-
refreshAll();
|
|
2074
|
-
|
|
2075
|
-
// Wrap every type="password" input in a <form display:contents> so Chrome
|
|
2076
|
-
// stops emitting "Password field is not contained in a form" warnings.
|
|
2077
|
-
// Works for both static inputs and dynamically rendered provider key fields.
|
|
2078
|
-
(function () {
|
|
2079
|
-
function wrapOrphanPwd(inp) {
|
|
2080
|
-
if (inp.closest("form")) return;
|
|
2081
|
-
const form = document.createElement("form");
|
|
2082
|
-
form.autocomplete = "off";
|
|
2083
|
-
form.onsubmit = () => false;
|
|
2084
|
-
form.style.cssText = "margin:0;padding:0;display:contents;";
|
|
2085
|
-
// Hidden username field — satisfies Chrome's "password forms need a username" check
|
|
2086
|
-
const u = document.createElement("input");
|
|
2087
|
-
u.type = "text";
|
|
2088
|
-
u.autocomplete = "username";
|
|
2089
|
-
u.setAttribute("aria-hidden", "true");
|
|
2090
|
-
u.style.cssText =
|
|
2091
|
-
"display:none;position:absolute;width:0;height:0;opacity:0;";
|
|
2092
|
-
form.appendChild(u);
|
|
2093
|
-
inp.parentNode.insertBefore(form, inp);
|
|
2094
|
-
form.appendChild(inp);
|
|
2095
|
-
}
|
|
2096
|
-
function scanAndWrap(root) {
|
|
2097
|
-
(root || document)
|
|
2098
|
-
.querySelectorAll('input[type="password"]')
|
|
2099
|
-
.forEach(wrapOrphanPwd);
|
|
2100
|
-
}
|
|
2101
|
-
scanAndWrap();
|
|
2102
|
-
const obs = new MutationObserver((mutations) => {
|
|
2103
|
-
for (const m of mutations) {
|
|
2104
|
-
for (const node of m.addedNodes) {
|
|
2105
|
-
if (node.nodeType !== 1) continue;
|
|
2106
|
-
if (node.matches && node.matches('input[type="password"]'))
|
|
2107
|
-
wrapOrphanPwd(node);
|
|
2108
|
-
else scanAndWrap(node);
|
|
2109
|
-
}
|
|
2110
|
-
}
|
|
2111
|
-
});
|
|
2112
|
-
obs.observe(document.body, { childList: true, subtree: true });
|
|
2113
|
-
})();
|
|
2114
|
-
|
|
2115
|
-
// ── Expose functions to global scope for inline HTML event handlers ───────────
|
|
2116
|
-
// ── Global delegated click dispatcher ──────────────────────────────────────────
|
|
2117
|
-
// MetaMask's SES lockdown runs onclick handlers in an isolated Compartment where
|
|
2118
|
-
// neither globalThis.fn nor window.fn resolves. Using data-action + addEventListener
|
|
2119
|
-
// bypasses the Compartment entirely — the listener closure has full module scope.
|
|
2120
|
-
const ACTION_REGISTRY = {
|
|
2121
|
-
// Nav views
|
|
2122
|
-
showChat,
|
|
2123
|
-
showSwarm,
|
|
2124
|
-
showRT,
|
|
2125
|
-
showBuild,
|
|
2126
|
-
showFiles,
|
|
2127
|
-
showDLQ,
|
|
2128
|
-
showProjects,
|
|
2129
|
-
showAgents,
|
|
2130
|
-
showModels,
|
|
2131
|
-
showEngines,
|
|
2132
|
-
showSkills,
|
|
2133
|
-
showRunSkills,
|
|
2134
|
-
showBenchmarks,
|
|
2135
|
-
showToolMatrix,
|
|
2136
|
-
showServices,
|
|
2137
|
-
showSettings,
|
|
2138
|
-
// Static HTML actions (previously onclick="window.fn()")
|
|
2139
|
-
pickFolder: (id) => pickFolder(id),
|
|
2140
|
-
loadFiles: (force) => loadFiles(force === "true" || force === true),
|
|
2141
|
-
clearChatHistory,
|
|
2142
|
-
clearAgentChat: () => {
|
|
2143
|
-
const agentSelect = document.getElementById("agentChatSelector");
|
|
2144
|
-
const messages = document.getElementById("agentChatMessages");
|
|
2145
|
-
const input = document.getElementById("agentChatInput");
|
|
2146
|
-
if (messages)
|
|
2147
|
-
messages.innerHTML =
|
|
2148
|
-
'<div class="empty-state">No messages yet. Start chatting!</div>';
|
|
2149
|
-
if (input) input.value = "";
|
|
2150
|
-
if (agentSelect?.value) {
|
|
2151
|
-
// Clears visible chat; backend history retained in session files
|
|
2152
|
-
showNotification("Chat history cleared", "success");
|
|
2153
|
-
}
|
|
2154
|
-
},
|
|
2155
|
-
sendChat,
|
|
2156
|
-
stopAll,
|
|
2157
|
-
killAll,
|
|
2158
|
-
stopPassthrough: killPassthrough,
|
|
2159
|
-
clearPassthroughSession,
|
|
2160
|
-
loadServices,
|
|
2161
|
-
saveRTToken,
|
|
2162
|
-
lockConfig,
|
|
2163
|
-
unlockConfig,
|
|
2164
|
-
startCrew,
|
|
2165
|
-
toggleEmojiPicker: (id) => toggleEmojiPicker(id),
|
|
2166
|
-
bulkSetRoute: (route, model) => bulkSetRoute(route, model),
|
|
2167
|
-
loadSpending,
|
|
2168
|
-
resetSpending,
|
|
2169
|
-
saveGlobalCaps,
|
|
2170
|
-
loadOcStats,
|
|
2171
|
-
addAllowlistPattern,
|
|
2172
|
-
sendTestWebhook,
|
|
2173
|
-
startTgBridge,
|
|
2174
|
-
stopTgBridge,
|
|
2175
|
-
saveTgConfig,
|
|
2176
|
-
loadTelegramSessions,
|
|
2177
|
-
loadTgMessages,
|
|
2178
|
-
startWaBridge,
|
|
2179
|
-
stopWaBridge,
|
|
2180
|
-
saveWaConfig,
|
|
2181
|
-
loadWaMessages,
|
|
2182
|
-
saveOpencodeSettings,
|
|
2183
|
-
saveOpencodeModel,
|
|
2184
|
-
saveGlobalFallback,
|
|
2185
|
-
toggleBgConsciousness,
|
|
2186
|
-
toggleCursorWaves,
|
|
2187
|
-
toggleTmuxBridge,
|
|
2188
|
-
toggleAutonomousMentions,
|
|
2189
|
-
toggleClaudeCode,
|
|
2190
|
-
toggleCodexExecutor,
|
|
2191
|
-
toggleGeminiCliExecutor,
|
|
2192
|
-
toggleCrewCliExecutor,
|
|
2193
|
-
toggleOpencodeExecutor,
|
|
2194
|
-
saveGlobalOcLoop,
|
|
2195
|
-
saveGlobalOcLoopRounds,
|
|
2196
|
-
savePassthroughNotify,
|
|
2197
|
-
toggleAddSkill,
|
|
2198
|
-
toggleImportSkill,
|
|
2199
|
-
importSkillFromUrl,
|
|
2200
|
-
showSkills,
|
|
2201
|
-
saveSkill,
|
|
2202
|
-
cancelSkillForm,
|
|
2203
|
-
loadRunSkills,
|
|
2204
|
-
loadBenchmarks: async () => {
|
|
2205
|
-
const mod = await loadBenchmarksTabModule();
|
|
2206
|
-
return mod.loadBenchmarks();
|
|
2207
|
-
},
|
|
2208
|
-
loadBenchmarkLeaderboard: async () => {
|
|
2209
|
-
const mod = await loadBenchmarksTabModule();
|
|
2210
|
-
return mod.loadBenchmarkLeaderboard();
|
|
2211
|
-
},
|
|
2212
|
-
loadBenchmarkTasks: async () => {
|
|
2213
|
-
const mod = await loadBenchmarksTabModule();
|
|
2214
|
-
return mod.loadBenchmarkTasks();
|
|
2215
|
-
},
|
|
2216
|
-
onBenchmarkTaskSelect: async (taskId) => {
|
|
2217
|
-
const mod = await loadBenchmarksTabModule();
|
|
2218
|
-
return mod.onBenchmarkTaskSelect(taskId);
|
|
2219
|
-
},
|
|
2220
|
-
runBenchmarkTask: async () => {
|
|
2221
|
-
const mod = await loadBenchmarksTabModule();
|
|
2222
|
-
return mod.runBenchmarkTask();
|
|
2223
|
-
},
|
|
2224
|
-
stopBenchmarkRun: async () => {
|
|
2225
|
-
const mod = await loadBenchmarksTabModule();
|
|
2226
|
-
return mod.stopBenchmarkRun();
|
|
2227
|
-
},
|
|
2228
|
-
// Memory
|
|
2229
|
-
loadMemoryStats,
|
|
2230
|
-
searchMemory,
|
|
2231
|
-
migrateMemory,
|
|
2232
|
-
compactMemory,
|
|
2233
|
-
loadEngines,
|
|
2234
|
-
toggleImportEngine,
|
|
2235
|
-
importEngineFromUrl,
|
|
2236
|
-
deleteEngine: (id) => deleteEngine(id),
|
|
2237
|
-
loadToolMatrix,
|
|
2238
|
-
loadBuildProjectPicker,
|
|
2239
|
-
// RT scroll button
|
|
2240
|
-
scrollRTToBottom: () => {
|
|
2241
|
-
const v = document.getElementById("rtView");
|
|
2242
|
-
if (v) v.scrollTop = v.scrollHeight;
|
|
2243
|
-
},
|
|
2244
|
-
toggleRTPause,
|
|
2245
|
-
clearRTMessages,
|
|
2246
|
-
togglePmAdvanced: () => {
|
|
2247
|
-
const el = document.getElementById("pmAdvanced");
|
|
2248
|
-
if (el) el.style.display = el.style.display === "none" ? "block" : "none";
|
|
2249
|
-
},
|
|
2250
|
-
// RT token visibility toggle
|
|
2251
|
-
toggleRTTokenVis: () => {
|
|
2252
|
-
const i = document.getElementById("rtTokenInput");
|
|
2253
|
-
if (i) i.type = i.type === "password" ? "text" : "password";
|
|
2254
|
-
},
|
|
2255
|
-
// Services
|
|
2256
|
-
restartService: (id) => restartService(id),
|
|
2257
|
-
stopService: (id) => stopService(id),
|
|
2258
|
-
// Files
|
|
2259
|
-
closePreviewPane,
|
|
2260
|
-
previewFile: (path, el) => previewFile(path, el),
|
|
2261
|
-
// DLQ
|
|
2262
|
-
replayDLQ: (key) => replayDLQ(key),
|
|
2263
|
-
deleteDLQ: (key) => deleteDLQ(key),
|
|
2264
|
-
// Skills
|
|
2265
|
-
runSkillFromUI: (name) => runSkillFromUI(name),
|
|
2266
|
-
editSkill: (name) => editSkill(name),
|
|
2267
|
-
deleteSkill: (name) => deleteSkill(name),
|
|
2268
|
-
// Tool matrix
|
|
2269
|
-
restartAgentFromUI: (id) => restartAgentFromUI(id),
|
|
2270
|
-
// Models / providers
|
|
2271
|
-
saveSearchTool: (id) => saveSearchTool(id),
|
|
2272
|
-
testSearchTool: (id) => testSearchTool(id),
|
|
2273
|
-
saveBuiltinKey: (id) => saveBuiltinKey(id),
|
|
2274
|
-
testBuiltinProvider: (id) => testBuiltinProvider(id),
|
|
2275
|
-
fetchBuiltinModels: (id, el) => fetchBuiltinModels(id, el),
|
|
2276
|
-
saveKey: (id) => saveKey(id),
|
|
2277
|
-
testKey: (id) => testKey(id),
|
|
2278
|
-
fetchModels: (id, el) => fetchModels(id, el),
|
|
2279
|
-
toggleKeyVis: (inputId, el) => toggleKeyVis(inputId, el),
|
|
2280
|
-
// Agents
|
|
2281
|
-
toggleAgentBody: (id) => toggleAgentBody(id),
|
|
2282
|
-
deleteAgent: (id) => deleteAgent(id),
|
|
2283
|
-
saveAgentModel: (id) => saveAgentModel(id),
|
|
2284
|
-
saveAgentFallback: (id) => saveAgentFallback(id),
|
|
2285
|
-
saveAgentVoice: (id) => saveAgentVoice(id),
|
|
2286
|
-
toggleEmojiPicker: (id) => toggleEmojiPicker(id),
|
|
2287
|
-
saveAgentIdentity: (id) => saveAgentIdentity(id),
|
|
2288
|
-
saveAgentPrompt: (id) => saveAgentPrompt(id),
|
|
2289
|
-
resetAgentSession: (id) => resetAgentSession(id),
|
|
2290
|
-
saveAgentTools: (id) => saveAgentTools(id),
|
|
2291
|
-
applyToolPreset: (id) => applyToolPreset(id),
|
|
2292
|
-
setRoute: (id, route) => setRoute(id, route),
|
|
2293
|
-
saveOpenCodeConfig: (id) => saveOpenCodeConfig(id),
|
|
2294
|
-
saveOpenCodeFallback: (id) => saveOpenCodeFallback(id),
|
|
2295
|
-
saveCursorCliConfig: (id) => saveCursorCliConfig(id),
|
|
2296
|
-
saveClaudeCodeConfig: (id) => saveClaudeCodeConfig(id),
|
|
2297
|
-
saveCodexConfig: (id) => saveCodexConfig(id),
|
|
2298
|
-
saveGeminiCliConfig: (id) => saveGeminiCliConfig(id),
|
|
2299
|
-
saveCrewCLIConfig: (id) => saveCrewCLIConfig(id),
|
|
2300
|
-
// PM Loop / Projects
|
|
2301
|
-
"pm-toggle": (id) => {
|
|
2302
|
-
const proj = state.projects?.find((p) => p.id === id);
|
|
2303
|
-
proj && proj.running ? stopProjectPMLoop(id) : startProjectPMLoop(id);
|
|
2304
|
-
},
|
|
2305
|
-
"edit-roadmap": (id) => {
|
|
2306
|
-
const proj = state.projects?.find((p) => p.id === id);
|
|
2307
|
-
if (proj) openRoadmapEditor(id, proj.roadmapFile);
|
|
2308
|
-
},
|
|
2309
|
-
"retry-failed": (id) => {
|
|
2310
|
-
const proj = state.projects?.find((p) => p.id === id);
|
|
2311
|
-
if (proj) retryFailed(proj.roadmapFile);
|
|
2312
|
-
},
|
|
2313
|
-
"save-roadmap": (id) => saveRoadmap(id),
|
|
2314
|
-
"reset-failed": (id) => resetAllFailed(id),
|
|
2315
|
-
// Settings tabs
|
|
2316
|
-
showSettingsTab: (tab) => showSettingsTab(tab),
|
|
2317
|
-
};
|
|
2318
|
-
|
|
2319
|
-
// ── Touch and click event handling for mobile responsiveness ───────────────
|
|
2320
|
-
let touchHandled = false;
|
|
2321
|
-
|
|
2322
|
-
document.addEventListener("touchstart", (e) => {
|
|
2323
|
-
if (!(e.target instanceof Element)) return;
|
|
2324
|
-
const el = e.target.closest("[data-action]");
|
|
2325
|
-
if (!el) return;
|
|
2326
|
-
|
|
2327
|
-
// Mark that touch was handled to prevent duplicate click event
|
|
2328
|
-
touchHandled = true;
|
|
2329
|
-
setTimeout(() => { touchHandled = false; }, 500);
|
|
2330
|
-
}, { passive: true });
|
|
2331
|
-
|
|
2332
|
-
document.addEventListener("click", (e) => {
|
|
2333
|
-
if (!(e.target instanceof Element)) return;
|
|
2334
|
-
const el = e.target.closest("[data-action]");
|
|
2335
|
-
if (!el) return;
|
|
2336
|
-
|
|
2337
|
-
// Skip if already handled by touch
|
|
2338
|
-
if (touchHandled) {
|
|
2339
|
-
e.preventDefault();
|
|
2340
|
-
return;
|
|
2341
|
-
}
|
|
2342
|
-
|
|
2343
|
-
e.stopPropagation();
|
|
2344
|
-
const action = el.dataset.action;
|
|
2345
|
-
const fn = ACTION_REGISTRY[action];
|
|
2346
|
-
if (!fn) {
|
|
2347
|
-
console.warn("[crewswarm] unknown data-action:", action);
|
|
2348
|
-
return;
|
|
2349
|
-
}
|
|
2350
|
-
const arg = el.dataset.arg ?? null;
|
|
2351
|
-
const arg2 = el.dataset.arg2 ?? null;
|
|
2352
|
-
const needsEl = el.dataset.self === "1";
|
|
2353
|
-
if (arg !== null && arg2 !== null) fn(arg, arg2);
|
|
2354
|
-
else if (arg !== null && needsEl) fn(arg, el);
|
|
2355
|
-
else if (arg !== null) fn(arg);
|
|
2356
|
-
else if (needsEl) fn(el);
|
|
2357
|
-
else fn();
|
|
2358
|
-
});
|
|
2359
|
-
|
|
2360
|
-
// ── Delegated change listener (data-onchange) ────────────────────────────────
|
|
2361
|
-
document.addEventListener("change", (e) => {
|
|
2362
|
-
const el = e.target.closest("[data-onchange]");
|
|
2363
|
-
if (!el) return;
|
|
2364
|
-
const fn = ACTION_REGISTRY[el.dataset.onchange];
|
|
2365
|
-
if (!fn) return;
|
|
2366
|
-
// Pass element value if data-onchange-arg="this.value", otherwise no arg
|
|
2367
|
-
const arg = el.dataset.onchangeArg === "this.value" ? el.value : null;
|
|
2368
|
-
arg !== null ? fn(arg) : fn();
|
|
2369
|
-
});
|
|
2370
|
-
|
|
2371
|
-
// Wire chatInput keydown + oninput via addEventListener (SES-safe)
|
|
2372
|
-
document.addEventListener(
|
|
2373
|
-
"DOMContentLoaded",
|
|
2374
|
-
() => {
|
|
2375
|
-
// Initialize active tasks panel
|
|
2376
|
-
initActiveTasksPanel("activeTasksPanel");
|
|
2377
|
-
|
|
2378
|
-
// Initialize contacts tab event listeners
|
|
2379
|
-
initContactsList();
|
|
2380
|
-
|
|
2381
|
-
// Set sidebar self-link to actual origin (avoids hardcoded localhost:4319)
|
|
2382
|
-
const dashLink = document.getElementById("dashSelfLink");
|
|
2383
|
-
if (dashLink) {
|
|
2384
|
-
dashLink.href = window.location.origin;
|
|
2385
|
-
dashLink.textContent = window.location.host;
|
|
2386
|
-
}
|
|
2387
|
-
|
|
2388
|
-
// Wire nav buttons for all tabs
|
|
2389
|
-
document.querySelectorAll(".nav-item").forEach((btn) => {
|
|
2390
|
-
btn.addEventListener("click", (event) => {
|
|
2391
|
-
event.preventDefault();
|
|
2392
|
-
event.stopPropagation();
|
|
2393
|
-
const view = btn.dataset.view;
|
|
2394
|
-
if (!view) return;
|
|
2395
|
-
if (currentHashBaseView() !== view) {
|
|
2396
|
-
window.location.hash = view;
|
|
2397
|
-
return;
|
|
2398
|
-
}
|
|
2399
|
-
const fn = NAV_VIEW_MAP[view];
|
|
2400
|
-
if (fn) fn();
|
|
2401
|
-
});
|
|
2402
|
-
});
|
|
2403
|
-
|
|
2404
|
-
const chatInput = document.getElementById("chatInput");
|
|
2405
|
-
if (chatInput && !chatInput.dataset.boundChatComposer) {
|
|
2406
|
-
chatInput.dataset.boundChatComposer = "1";
|
|
2407
|
-
chatInput.addEventListener("keydown", chatKeydown);
|
|
2408
|
-
chatInput.addEventListener("input", chatAtAtInput);
|
|
2409
|
-
}
|
|
2410
|
-
const chatSendBtn =
|
|
2411
|
-
document.getElementById("chatSendBtn") ||
|
|
2412
|
-
document.querySelector('[data-action="sendChat"]');
|
|
2413
|
-
if (chatSendBtn && !chatSendBtn.dataset.boundChatComposer) {
|
|
2414
|
-
chatSendBtn.dataset.boundChatComposer = "1";
|
|
2415
|
-
chatSendBtn.addEventListener("click", (event) => {
|
|
2416
|
-
event.preventDefault();
|
|
2417
|
-
event.stopPropagation();
|
|
2418
|
-
sendChat();
|
|
2419
|
-
});
|
|
2420
|
-
}
|
|
2421
|
-
const cmdInput = document.getElementById("cmdAllowlistInput");
|
|
2422
|
-
if (cmdInput) {
|
|
2423
|
-
cmdInput.addEventListener("keydown", (e) => {
|
|
2424
|
-
if (e.key === "Enter") addAllowlistPattern();
|
|
2425
|
-
});
|
|
2426
|
-
}
|
|
2427
|
-
const waNumbers = document.getElementById("waAllowedNumbers");
|
|
2428
|
-
if (waNumbers) waNumbers.addEventListener("input", renderWaContactRows);
|
|
2429
|
-
const skillSearchInput = document.getElementById("skillSearch");
|
|
2430
|
-
if (skillSearchInput)
|
|
2431
|
-
skillSearchInput.addEventListener("input", (e) =>
|
|
2432
|
-
filterSkills(e.target.value),
|
|
2433
|
-
);
|
|
2434
|
-
|
|
2435
|
-
// Refresh session indicator when engine or project changes
|
|
2436
|
-
const engineSel = document.getElementById("passthroughEngine");
|
|
2437
|
-
if (engineSel) {
|
|
2438
|
-
engineSel.addEventListener("change", () => {
|
|
2439
|
-
refreshSessionIndicator();
|
|
2440
|
-
updatePassthroughModelDropdown();
|
|
2441
|
-
// Reset send button state when switching engines
|
|
2442
|
-
resetSendButton();
|
|
2443
|
-
});
|
|
2444
|
-
}
|
|
2445
|
-
const projSel = document.getElementById("chatProjectSelect");
|
|
2446
|
-
if (projSel) projSel.addEventListener("change", refreshSessionIndicator);
|
|
2447
|
-
|
|
2448
|
-
// Reset send button when model changes
|
|
2449
|
-
const modelSel = document.getElementById("passthroughModel");
|
|
2450
|
-
if (modelSel) {
|
|
2451
|
-
modelSel.addEventListener("change", () => {
|
|
2452
|
-
resetSendButton();
|
|
2453
|
-
});
|
|
2454
|
-
}
|
|
2455
|
-
},
|
|
2456
|
-
{ once: true },
|
|
2457
|
-
);
|
|
2458
|
-
|
|
2459
|
-
// Update passthrough model dropdown based on selected engine
|
|
2460
|
-
function updatePassthroughModelDropdown() {
|
|
2461
|
-
const engineSel = document.getElementById("passthroughEngine");
|
|
2462
|
-
const modelSel = document.getElementById("passthroughModel");
|
|
2463
|
-
if (!engineSel || !modelSel) return;
|
|
2464
|
-
|
|
2465
|
-
const engine = engineSel.value;
|
|
2466
|
-
const modelsByEngine = {
|
|
2467
|
-
cursor: [
|
|
2468
|
-
{ value: "", label: "— default (opus-4.6-thinking) —" },
|
|
2469
|
-
{ optgroup: "Recommended (No Rate Limits)" },
|
|
2470
|
-
{ value: "gemini-3-flash", label: "🟢 Gemini 3 Flash (fastest)" },
|
|
2471
|
-
{ value: "gemini-3-pro", label: "🟢 Gemini 3 Pro" },
|
|
2472
|
-
{ value: "gemini-3.1-pro", label: "🟢 Gemini 3.1 Pro" },
|
|
2473
|
-
{ value: "gpt-5.2-codex", label: "🟢 GPT-5.2 Codex" },
|
|
2474
|
-
{ value: "gpt-5.3-codex", label: "🟢 GPT-5.3 Codex" },
|
|
2475
|
-
{ optgroup: "Claude Models (May Hit Rate Limits)" },
|
|
2476
|
-
{ value: "sonnet-4.5", label: "🟡 Claude 4.5 Sonnet" },
|
|
2477
|
-
{ value: "sonnet-4.6", label: "🟡 Claude 4.6 Sonnet (current)" },
|
|
2478
|
-
{ value: "opus-4.5", label: "🟡 Claude 4.5 Opus" },
|
|
2479
|
-
{ value: "opus-4.6", label: "🟡 Claude 4.6 Opus" },
|
|
2480
|
-
{ optgroup: "Thinking Models (Slower)" },
|
|
2481
|
-
{ value: "sonnet-4.5-thinking", label: "Claude 4.5 Sonnet Thinking" },
|
|
2482
|
-
{ value: "opus-4.6-thinking", label: "Claude 4.6 Opus Thinking" },
|
|
2483
|
-
{ optgroup: "Other" },
|
|
2484
|
-
{ value: "grok", label: "xAI Grok" },
|
|
2485
|
-
{ value: "kimi-k2.5", label: "Moonshot Kimi K2.5" },
|
|
2486
|
-
],
|
|
2487
|
-
claude: [
|
|
2488
|
-
{ value: "", label: "— default (Sonnet 4.6) —" },
|
|
2489
|
-
{ optgroup: "Recommended" },
|
|
2490
|
-
{ value: "sonnet", label: "🟢 Sonnet (alias for latest)" },
|
|
2491
|
-
{ value: "Default", label: "🟢 Default (Sonnet 4.6)" },
|
|
2492
|
-
{ optgroup: "Specific Versions" },
|
|
2493
|
-
{
|
|
2494
|
-
value: "claude-sonnet-4-6",
|
|
2495
|
-
label: "Sonnet 4.6 · Best for everyday tasks",
|
|
2496
|
-
},
|
|
2497
|
-
{
|
|
2498
|
-
value: "Opus",
|
|
2499
|
-
label: "Opus (Opus 4.6) · Most capable for complex work",
|
|
2500
|
-
},
|
|
2501
|
-
{ value: "claude-opus-4-6", label: "Opus 4.6 · Most capable" },
|
|
2502
|
-
{
|
|
2503
|
-
value: "Haiku",
|
|
2504
|
-
label: "Haiku (Haiku 4.5) · Fastest for quick answers",
|
|
2505
|
-
},
|
|
2506
|
-
{ value: "claude-haiku-4-5", label: "Haiku 4.5 · Fastest" },
|
|
2507
|
-
{ optgroup: "Legacy" },
|
|
2508
|
-
{ value: "claude-sonnet-4-5", label: "Sonnet 4.5 (legacy)" },
|
|
2509
|
-
],
|
|
2510
|
-
codex: [
|
|
2511
|
-
{ value: "", label: "— default (gpt-5.3-codex) —" },
|
|
2512
|
-
{ optgroup: "Recommended" },
|
|
2513
|
-
{ value: "gpt-5.3-codex", label: "🟢 GPT-5.3 Codex (current)" },
|
|
2514
|
-
{ value: "gpt-5.2-codex", label: "🟢 GPT-5.2 Codex" },
|
|
2515
|
-
{ optgroup: "Specialized" },
|
|
2516
|
-
{
|
|
2517
|
-
value: "gpt-5.1-codex-max",
|
|
2518
|
-
label: "GPT-5.1 Codex Max (deep reasoning)",
|
|
2519
|
-
},
|
|
2520
|
-
{ value: "gpt-5.2", label: "GPT-5.2 (general purpose)" },
|
|
2521
|
-
{
|
|
2522
|
-
value: "gpt-5.1-codex-mini",
|
|
2523
|
-
label: "GPT-5.1 Codex Mini (fast & cheap)",
|
|
2524
|
-
},
|
|
2525
|
-
],
|
|
2526
|
-
opencode: [
|
|
2527
|
-
{ value: "", label: "— default —" },
|
|
2528
|
-
{ optgroup: "Free Models 🎁" },
|
|
2529
|
-
{ value: "opencode/big-pickle", label: "🆓 Big Pickle (Free)" },
|
|
2530
|
-
{ value: "opencode/minimax-m2.5-free", label: "🆓 MiniMax M2.5 Free" },
|
|
2531
|
-
{ value: "openai/gpt-5-nano", label: "🆓 GPT 5 Nano (Free)" },
|
|
2532
|
-
{ optgroup: "Budget Models 💰" },
|
|
2533
|
-
{
|
|
2534
|
-
value: "openai/gpt-5.1-codex-mini",
|
|
2535
|
-
label: "💰 GPT 5.1 Codex Mini ($0.25/$2)",
|
|
2536
|
-
},
|
|
2537
|
-
{ value: "google/gemini-3-flash", label: "💰 Gemini 3 Flash ($0.50/$3)" },
|
|
2538
|
-
{
|
|
2539
|
-
value: "anthropic/claude-haiku-4-5",
|
|
2540
|
-
label: "💰 Claude Haiku 4.5 ($1/$5)",
|
|
2541
|
-
},
|
|
2542
|
-
{ optgroup: "Interesting Models 🎯" },
|
|
2543
|
-
{ value: "moonshot/kimi-k2.5", label: "Kimi K2.5 ($0.60/$3)" },
|
|
2544
|
-
{
|
|
2545
|
-
value: "moonshot/kimi-k2-thinking",
|
|
2546
|
-
label: "Kimi K2 Thinking ($0.40/$2.50)",
|
|
2547
|
-
},
|
|
2548
|
-
{
|
|
2549
|
-
value: "alibaba/qwen3-coder-480b",
|
|
2550
|
-
label: "Qwen3 Coder 480B ($0.45/$1.50)",
|
|
2551
|
-
},
|
|
2552
|
-
{ value: "zhipu/glm-5", label: "GLM 5 ($1/$3.20)" },
|
|
2553
|
-
{ optgroup: "Premium Claude" },
|
|
2554
|
-
{
|
|
2555
|
-
value: "anthropic/claude-sonnet-4-6",
|
|
2556
|
-
label: "Claude Sonnet 4.6 ($3/$15)",
|
|
2557
|
-
},
|
|
2558
|
-
{ value: "anthropic/claude-opus-4-6", label: "Claude Opus 4.6 ($5/$25)" },
|
|
2559
|
-
{ optgroup: "Premium OpenAI" },
|
|
2560
|
-
{ value: "openai/gpt-5.3-codex", label: "GPT 5.3 Codex ($1.75/$14)" },
|
|
2561
|
-
{ value: "openai/gpt-5.2-codex", label: "GPT 5.2 Codex ($1.75/$14)" },
|
|
2562
|
-
{
|
|
2563
|
-
value: "openai/gpt-5.1-codex-max",
|
|
2564
|
-
label: "GPT 5.1 Codex Max ($1.25/$10)",
|
|
2565
|
-
},
|
|
2566
|
-
{ optgroup: "Premium Google" },
|
|
2567
|
-
{ value: "google/gemini-3.1-pro", label: "Gemini 3.1 Pro ($2/$12)" },
|
|
2568
|
-
{ value: "google/gemini-3-pro", label: "Gemini 3 Pro ($2/$12)" },
|
|
2569
|
-
],
|
|
2570
|
-
gemini: [
|
|
2571
|
-
{ value: "", label: "— default (gemini-3-flash-preview) —" },
|
|
2572
|
-
{ optgroup: "Recommended (Latest)" },
|
|
2573
|
-
{
|
|
2574
|
-
value: "gemini-3-flash-preview",
|
|
2575
|
-
label: "🟢 Gemini 3 Flash Preview (current)",
|
|
2576
|
-
},
|
|
2577
|
-
{ value: "gemini-3.1-pro-preview", label: "🟢 Gemini 3.1 Pro Preview" },
|
|
2578
|
-
{ optgroup: "Gemini 2.5 Series" },
|
|
2579
|
-
{ value: "gemini-2.5-pro", label: "Gemini 2.5 Pro" },
|
|
2580
|
-
{ value: "gemini-2.5-flash", label: "Gemini 2.5 Flash" },
|
|
2581
|
-
{
|
|
2582
|
-
value: "gemini-2.5-flash-lite",
|
|
2583
|
-
label: "Gemini 2.5 Flash Lite (fastest)",
|
|
2584
|
-
},
|
|
2585
|
-
],
|
|
2586
|
-
};
|
|
2587
|
-
|
|
2588
|
-
if (!engine || !modelsByEngine[engine]) {
|
|
2589
|
-
modelSel.style.display = "none";
|
|
2590
|
-
return;
|
|
2591
|
-
}
|
|
2592
|
-
|
|
2593
|
-
modelSel.style.display = "inline-block";
|
|
2594
|
-
modelSel.innerHTML = "";
|
|
2595
|
-
|
|
2596
|
-
let currentOptgroup = null;
|
|
2597
|
-
for (const item of modelsByEngine[engine]) {
|
|
2598
|
-
if (item.optgroup) {
|
|
2599
|
-
// Create optgroup
|
|
2600
|
-
currentOptgroup = document.createElement("optgroup");
|
|
2601
|
-
currentOptgroup.label = item.optgroup;
|
|
2602
|
-
modelSel.appendChild(currentOptgroup);
|
|
2603
|
-
} else {
|
|
2604
|
-
// Create option
|
|
2605
|
-
const opt = document.createElement("option");
|
|
2606
|
-
opt.value = item.value;
|
|
2607
|
-
opt.textContent = item.label;
|
|
2608
|
-
if (currentOptgroup) {
|
|
2609
|
-
currentOptgroup.appendChild(opt);
|
|
2610
|
-
} else {
|
|
2611
|
-
modelSel.appendChild(opt);
|
|
2612
|
-
}
|
|
2613
|
-
}
|
|
2614
|
-
}
|
|
2615
|
-
}
|
|
2616
|
-
|
|
2617
|
-
// Nav view delegation (data-view buttons in sidebar)
|
|
2618
|
-
const NAV_VIEW_MAP = {
|
|
2619
|
-
chat: showChat,
|
|
2620
|
-
"swarm-chat": showSwarmChat,
|
|
2621
|
-
swarm: showSwarm,
|
|
2622
|
-
rt: showRT,
|
|
2623
|
-
build: showBuild,
|
|
2624
|
-
files: showFiles,
|
|
2625
|
-
dlq: showDLQ,
|
|
2626
|
-
projects: showProjects,
|
|
2627
|
-
contacts: showContacts,
|
|
2628
|
-
agents: showAgents,
|
|
2629
|
-
models: showModels,
|
|
2630
|
-
engines: showEngines,
|
|
2631
|
-
skills: showSkills,
|
|
2632
|
-
"run-skills": showRunSkills,
|
|
2633
|
-
waves: () => {
|
|
2634
|
-
hideAllViews();
|
|
2635
|
-
document.getElementById("wavesView").style.display = "block";
|
|
2636
|
-
setNavActive("navWaves");
|
|
2637
|
-
},
|
|
2638
|
-
workflows: showWorkflows,
|
|
2639
|
-
benchmarks: showBenchmarks,
|
|
2640
|
-
"tool-matrix": showToolMatrix,
|
|
2641
|
-
memory: showMemoryView,
|
|
2642
|
-
"cli-process": showCLIProcess,
|
|
2643
|
-
services: showServices,
|
|
2644
|
-
prompts: initPromptsTab,
|
|
2645
|
-
settings: showSettings,
|
|
2646
|
-
};
|
|
2647
|
-
document.addEventListener("click", (e) => {
|
|
2648
|
-
const btn = e.target.closest("[data-view]");
|
|
2649
|
-
if (btn) {
|
|
2650
|
-
const viewName = btn.dataset.view;
|
|
2651
|
-
const fn = NAV_VIEW_MAP[viewName];
|
|
2652
|
-
if (fn) {
|
|
2653
|
-
// Let hashchange drive navigation to avoid double-render/jitter.
|
|
2654
|
-
if (currentHashBaseView() !== viewName) {
|
|
2655
|
-
window.location.hash = viewName;
|
|
2656
|
-
} else {
|
|
2657
|
-
fn();
|
|
2658
|
-
}
|
|
2659
|
-
}
|
|
2660
|
-
return;
|
|
2661
|
-
}
|
|
2662
|
-
const stab = e.target.closest("[data-stab]");
|
|
2663
|
-
if (stab) {
|
|
2664
|
-
const subtab = stab.dataset.stab;
|
|
2665
|
-
// Update URL hash for settings sub-tabs
|
|
2666
|
-
window.location.hash = `settings/${subtab}`;
|
|
2667
|
-
showSettingsTab(subtab);
|
|
2668
|
-
}
|
|
2669
|
-
// Collapse/expand panels with data-toggle-child
|
|
2670
|
-
const tog = e.target.closest("[data-toggle-child]");
|
|
2671
|
-
if (tog) {
|
|
2672
|
-
const sel = tog.dataset.toggleChild;
|
|
2673
|
-
const body = tog.parentElement && tog.parentElement.querySelector(sel);
|
|
2674
|
-
if (body)
|
|
2675
|
-
body.style.display = body.style.display === "none" ? "block" : "none";
|
|
2676
|
-
}
|
|
2677
|
-
// Collapse/expand next sibling with data-toggle-sibling (e.g. provider-header → provider-body)
|
|
2678
|
-
const togSib = e.target.closest("[data-toggle-sibling]");
|
|
2679
|
-
if (togSib && togSib.nextElementSibling) {
|
|
2680
|
-
togSib.nextElementSibling.classList.toggle(togSib.dataset.toggleSibling);
|
|
2681
|
-
}
|
|
2682
|
-
});
|
|
2683
|
-
|
|
2684
|
-
// Vite wraps modules in a closure; onclick="window.fn()" attrs in static + dynamic HTML need window.fn.
|
|
2685
|
-
Object.assign(window, {
|
|
2686
|
-
// ── Static HTML handlers ──
|
|
2687
|
-
addAllowlistPattern,
|
|
2688
|
-
applyNewAgentToolPreset,
|
|
2689
|
-
applyPromptPreset,
|
|
2690
|
-
bulkSetRoute,
|
|
2691
|
-
cancelSkillForm,
|
|
2692
|
-
chatAtAtInput,
|
|
2693
|
-
chatKeydown,
|
|
2694
|
-
clearChatHistory,
|
|
2695
|
-
filterSkills,
|
|
2696
|
-
loadAllUsage,
|
|
2697
|
-
loadBenchmarkLeaderboard: async () => {
|
|
2698
|
-
const mod = await loadBenchmarksTabModule();
|
|
2699
|
-
return mod.loadBenchmarkLeaderboard();
|
|
2700
|
-
},
|
|
2701
|
-
loadBenchmarks: async () => {
|
|
2702
|
-
const mod = await loadBenchmarksTabModule();
|
|
2703
|
-
return mod.loadBenchmarks();
|
|
2704
|
-
},
|
|
2705
|
-
loadBenchmarkTasks: async () => {
|
|
2706
|
-
const mod = await loadBenchmarksTabModule();
|
|
2707
|
-
return mod.loadBenchmarkTasks();
|
|
2708
|
-
},
|
|
2709
|
-
onBenchmarkTaskSelect: async (taskId) => {
|
|
2710
|
-
const mod = await loadBenchmarksTabModule();
|
|
2711
|
-
return mod.onBenchmarkTaskSelect(taskId);
|
|
2712
|
-
},
|
|
2713
|
-
runBenchmarkTask: async () => {
|
|
2714
|
-
const mod = await loadBenchmarksTabModule();
|
|
2715
|
-
return mod.runBenchmarkTask();
|
|
2716
|
-
},
|
|
2717
|
-
stopBenchmarkRun: async () => {
|
|
2718
|
-
const mod = await loadBenchmarksTabModule();
|
|
2719
|
-
return mod.stopBenchmarkRun();
|
|
2720
|
-
},
|
|
2721
|
-
loadMemoryStats,
|
|
2722
|
-
searchMemory,
|
|
2723
|
-
migrateMemory,
|
|
2724
|
-
compactMemory,
|
|
2725
|
-
loadBuildProjectPicker,
|
|
2726
|
-
loadFiles,
|
|
2727
|
-
loadOcStats,
|
|
2728
|
-
loadRunSkills,
|
|
2729
|
-
loadServices,
|
|
2730
|
-
loadSpending,
|
|
2731
|
-
loadTelegramSessions,
|
|
2732
|
-
loadTgMessages,
|
|
2733
|
-
loadToolMatrix,
|
|
2734
|
-
loadWaMessages,
|
|
2735
|
-
onBuildProjectChange,
|
|
2736
|
-
onChatProjectChange,
|
|
2737
|
-
pickFolder,
|
|
2738
|
-
renderWaContactRows,
|
|
2739
|
-
resetSpending,
|
|
2740
|
-
approveSkill,
|
|
2741
|
-
loadPendingApprovals,
|
|
2742
|
-
rejectSkill,
|
|
2743
|
-
saveGlobalCaps,
|
|
2744
|
-
saveGlobalFallback,
|
|
2745
|
-
saveBgConsciousnessModel,
|
|
2746
|
-
saveOpencodeSettings,
|
|
2747
|
-
saveRTToken,
|
|
2748
|
-
saveSkill,
|
|
2749
|
-
saveTgConfig,
|
|
2750
|
-
saveWaConfig,
|
|
2751
|
-
sendChat,
|
|
2752
|
-
sendTestWebhook,
|
|
2753
|
-
showAgents,
|
|
2754
|
-
showBenchmarks,
|
|
2755
|
-
showBuild,
|
|
2756
|
-
showChat,
|
|
2757
|
-
showContacts,
|
|
2758
|
-
showDLQ,
|
|
2759
|
-
showFiles,
|
|
2760
|
-
showModels,
|
|
2761
|
-
showProjects,
|
|
2762
|
-
showRT,
|
|
2763
|
-
showRunSkills,
|
|
2764
|
-
showServices,
|
|
2765
|
-
showSettings,
|
|
2766
|
-
showSettingsTab,
|
|
2767
|
-
showSkills,
|
|
2768
|
-
showSwarm,
|
|
2769
|
-
showToolMatrix,
|
|
2770
|
-
showMemoryView,
|
|
2771
|
-
startCrew,
|
|
2772
|
-
startTgBridge,
|
|
2773
|
-
startWaBridge,
|
|
2774
|
-
stopTgBridge,
|
|
2775
|
-
stopWaBridge,
|
|
2776
|
-
toggleAddSkill,
|
|
2777
|
-
toggleBgConsciousness,
|
|
2778
|
-
toggleCursorWaves,
|
|
2779
|
-
toggleTmuxBridge,
|
|
2780
|
-
toggleClaudeCode,
|
|
2781
|
-
toggleEmojiPicker,
|
|
2782
|
-
updateSkillAuthFields,
|
|
2783
|
-
navigateTo,
|
|
2784
|
-
renderStatusBadge,
|
|
2785
|
-
showLoading,
|
|
2786
|
-
showEmpty,
|
|
2787
|
-
showError,
|
|
2788
|
-
loadContacts,
|
|
2789
|
-
applyContactFilters,
|
|
2790
|
-
// ── Dynamic HTML handlers (innerHTML-rendered) ──
|
|
2791
|
-
applyToolPreset,
|
|
2792
|
-
closePreviewPane,
|
|
2793
|
-
deleteAgent,
|
|
2794
|
-
deleteSkill,
|
|
2795
|
-
editSkill,
|
|
2796
|
-
fetchBuiltinModels,
|
|
2797
|
-
fetchModels,
|
|
2798
|
-
previewFile,
|
|
2799
|
-
resetAgentSession,
|
|
2800
|
-
restartAgentFromUI,
|
|
2801
|
-
restartService,
|
|
2802
|
-
runSkillFromUI,
|
|
2803
|
-
saveAgentFallback,
|
|
2804
|
-
saveAgentVoice,
|
|
2805
|
-
saveAgentIdentity,
|
|
2806
|
-
saveAgentModel,
|
|
2807
|
-
saveAgentPrompt,
|
|
2808
|
-
saveAgentTools,
|
|
2809
|
-
saveBuiltinKey,
|
|
2810
|
-
saveCursorCliConfig,
|
|
2811
|
-
saveClaudeCodeConfig,
|
|
2812
|
-
saveGeminiCliConfig,
|
|
2813
|
-
saveKey,
|
|
2814
|
-
saveOpenCodeConfig,
|
|
2815
|
-
saveOpenCodeFallback,
|
|
2816
|
-
saveSearchTool,
|
|
2817
|
-
saveCodexConfig,
|
|
2818
|
-
setRoute,
|
|
2819
|
-
stopService,
|
|
2820
|
-
testBuiltinProvider,
|
|
2821
|
-
testKey,
|
|
2822
|
-
testSearchTool,
|
|
2823
|
-
toggleAgentBody,
|
|
2824
|
-
toggleKeyVis,
|
|
2825
|
-
});
|
|
2826
|
-
|
|
2827
|
-
// Project tabs: startup IIFE (near initProjectsList) already fetches /api/projects
|
|
2828
|
-
// and populates the dropdown — avoid a duplicate fetch here.
|