@teamclaws/teamclaw 2026.3.26-2 → 2026.4.2-1
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 +52 -8
- package/cli.mjs +538 -224
- package/index.ts +76 -27
- package/openclaw.plugin.json +53 -28
- package/package.json +5 -2
- package/skills/teamclaw/SKILL.md +213 -0
- package/skills/teamclaw/references/api-quick-ref.md +117 -0
- package/skills/teamclaw-setup/SKILL.md +81 -0
- package/skills/teamclaw-setup/references/install-modes.md +136 -0
- package/skills/teamclaw-setup/references/validation-checklist.md +73 -0
- package/src/config.ts +44 -16
- package/src/controller/controller-capacity.ts +2 -2
- package/src/controller/controller-service.ts +193 -47
- package/src/controller/controller-tools.ts +102 -2
- package/src/controller/delivery-report.ts +563 -0
- package/src/controller/http-server.ts +1907 -172
- package/src/controller/kickoff-orchestrator.ts +292 -0
- package/src/controller/managed-gateway-process.ts +330 -0
- package/src/controller/orchestration-manifest.ts +69 -1
- package/src/controller/preview-manager.ts +676 -0
- package/src/controller/prompt-injector.ts +116 -67
- package/src/controller/role-inference.ts +41 -0
- package/src/controller/websocket.ts +3 -1
- package/src/controller/worker-provisioning.ts +429 -74
- package/src/discovery.ts +1 -1
- package/src/git-collaboration.ts +198 -47
- package/src/identity.ts +12 -2
- package/src/interaction-contracts.ts +179 -3
- package/src/networking.ts +99 -0
- package/src/openclaw-workspace.ts +478 -11
- package/src/prompt-policy.ts +381 -0
- package/src/roles.ts +37 -36
- package/src/state.ts +40 -1
- package/src/task-executor.ts +282 -78
- package/src/types.ts +150 -7
- package/src/ui/app.js +1403 -175
- package/src/ui/assets/teamclaw-app-icon.png +0 -0
- package/src/ui/index.html +122 -40
- package/src/ui/style.css +829 -143
- package/src/worker/http-handler.ts +40 -4
- package/src/worker/prompt-injector.ts +9 -38
- package/src/worker/skill-installer.ts +2 -2
- package/src/worker/tools.ts +31 -5
- package/src/worker/worker-service.ts +49 -8
- package/src/workspace-browser.ts +20 -7
- package/src/controller/local-worker-manager.ts +0 -533
package/src/ui/app.js
CHANGED
|
@@ -6,24 +6,316 @@
|
|
|
6
6
|
let ws = null;
|
|
7
7
|
let currentFilter = "all";
|
|
8
8
|
let activeTab = "tasks";
|
|
9
|
-
let teamState = { workers: [], tasks: [], controllerRuns: [], messages: [], clarifications: [] };
|
|
9
|
+
let teamState = { workers: [], tasks: [], controllerRuns: [], messages: [], clarifications: [], modelReadiness: null, externalWorkerInstall: null };
|
|
10
|
+
let selectedExternalWorkerRole = "developer";
|
|
11
|
+
let selectedExternalWorkerDiscoveryMode = "mdns";
|
|
12
|
+
let externalWorkerInstallVisible = false;
|
|
10
13
|
let selectedTaskId = null;
|
|
11
14
|
let selectedTaskDetail = null;
|
|
12
|
-
let selectedTaskDetailTab = "
|
|
13
|
-
let
|
|
15
|
+
let selectedTaskDetailTab = "details";
|
|
16
|
+
let taskTimelineAutoFollow = true;
|
|
14
17
|
let workspaceTree = [];
|
|
15
18
|
let selectedWorkspacePath = null;
|
|
16
19
|
let selectedWorkspaceFile = null;
|
|
17
20
|
let selectedWorkspaceView = "source";
|
|
18
21
|
let workspaceLoaded = false;
|
|
22
|
+
let clarificationPromptOpen = false;
|
|
23
|
+
let activeClarificationId = null;
|
|
24
|
+
let dismissedClarificationIds = [];
|
|
25
|
+
let reconnectTimer = null;
|
|
26
|
+
let reconnectAttempts = 0;
|
|
27
|
+
let isConnecting = false;
|
|
19
28
|
const CONTROLLER_SESSION_STORAGE_KEY = "teamclaw.controllerSessionKey";
|
|
20
29
|
const CONTROLLER_CONVERSATION_STORAGE_KEY = "teamclaw.controllerConversation";
|
|
30
|
+
const LANGUAGE_STORAGE_KEY = "teamclaw.ui.language";
|
|
21
31
|
let controllerConversation = loadControllerConversation();
|
|
22
32
|
let controllerCommandPending = false;
|
|
33
|
+
const initialUiState = parseInitialUiState();
|
|
34
|
+
let initialUiStateApplied = false;
|
|
35
|
+
let currentLanguage = loadLanguage();
|
|
36
|
+
|
|
37
|
+
const TRANSLATIONS = {
|
|
38
|
+
en: {
|
|
39
|
+
"action.refresh": "Refresh",
|
|
40
|
+
"action.close": "Close",
|
|
41
|
+
"action.dismiss": "Dismiss",
|
|
42
|
+
"action.copyCommand": "Copy command",
|
|
43
|
+
"action.copied": "Copied",
|
|
44
|
+
"sidebar.workers": "Workers",
|
|
45
|
+
"sidebar.roles": "Roles",
|
|
46
|
+
"tab.planning": "Planning",
|
|
47
|
+
"tab.tasks": "Tasks",
|
|
48
|
+
"tab.workspace": "Workspace",
|
|
49
|
+
"tab.clarifications": "Clarifications",
|
|
50
|
+
"tab.messages": "Messages",
|
|
51
|
+
"tab.manualTask": "Manual Task",
|
|
52
|
+
"planning.title": "Team Planning",
|
|
53
|
+
"planning.description": "Submit a requirement via the command bar below. Complex projects (3+ roles) will trigger a team kickoff meeting where each role assesses the requirement collaboratively.",
|
|
54
|
+
"planning.sessions": "Sessions",
|
|
55
|
+
"planning.requirement": "Requirement",
|
|
56
|
+
"planning.kickoff": "Team Kickoff Meeting",
|
|
57
|
+
"planning.controllerOutput": "Controller Output",
|
|
58
|
+
"planning.originalRequest": "Original Request",
|
|
59
|
+
"planning.requiredRoles": "Required Roles",
|
|
60
|
+
"planning.plannedTasks": "Planned Tasks",
|
|
61
|
+
"planning.deferredTasks": "Deferred Tasks",
|
|
62
|
+
"planning.clarificationsNeeded": "Clarifications Needed",
|
|
63
|
+
"planning.notes": "Notes",
|
|
64
|
+
"planning.noControllerOutput": "No controller output yet.",
|
|
65
|
+
"workspace.preview": "Preview",
|
|
66
|
+
"workspace.selectFile": "Select a file",
|
|
67
|
+
"workspace.openRaw": "Open Raw",
|
|
68
|
+
"workspace.source": "Source",
|
|
69
|
+
"workspace.files": "Files",
|
|
70
|
+
"messages.panelNote": "Controller activity is persisted here so you can follow requirement intake, orchestration, and follow-up runs from the web UI.",
|
|
71
|
+
"manualTask.note": "Raw human requirements should go to the controller conversation first. Use this form only for explicit manual task injection or testing.",
|
|
72
|
+
"manualTask.title": "Title",
|
|
73
|
+
"manualTask.description": "Description",
|
|
74
|
+
"manualTask.skills": "Recommended Skills",
|
|
75
|
+
"manualTask.priority": "Priority",
|
|
76
|
+
"manualTask.assignedRole": "Assigned Role",
|
|
77
|
+
"manualTask.autoAssign": "Auto-assign",
|
|
78
|
+
"manualTask.create": "Create Manual Task",
|
|
79
|
+
"manualTask.titlePlaceholder": "Task title...",
|
|
80
|
+
"manualTask.descriptionPlaceholder": "Execution-ready task description...",
|
|
81
|
+
"manualTask.skillsPlaceholder": "Comma-separated skill slugs, e.g. find-skills, ui-ux-pro-max",
|
|
82
|
+
"empty.noWorkers": "No workers connected",
|
|
83
|
+
"empty.noTasks": "No tasks yet",
|
|
84
|
+
"empty.noTasksWithStatus": "No tasks with status \"{status}\"",
|
|
85
|
+
"empty.noClarifications": "No clarification requests",
|
|
86
|
+
"empty.noControllerActivity": "No controller activity yet",
|
|
87
|
+
"empty.noMessages": "No messages yet",
|
|
88
|
+
"empty.noPlanningSessions": "No planning sessions yet",
|
|
89
|
+
"empty.noKickoffData": "No kickoff data",
|
|
90
|
+
"empty.workspaceLoading": "Workspace tree loading…",
|
|
91
|
+
"empty.noWorkspaceFiles": "No project files in the workspace yet.",
|
|
92
|
+
"empty.selectFileSource": "Select a file from the workspace tree to view its source.",
|
|
93
|
+
"empty.selectFilePreview": "Select a file from the workspace tree to preview Markdown or HTML output.",
|
|
94
|
+
"empty.selectTask": "Select a task",
|
|
95
|
+
"empty.taskDetail": "Select a task to inspect its execution details.",
|
|
96
|
+
"empty.taskMessages": "No messages on this task yet.",
|
|
97
|
+
"empty.taskHistory": "No execution history recorded yet.",
|
|
98
|
+
"empty.copiedControllerReply": "Controller finished without a textual reply.",
|
|
99
|
+
"runtime.title": "TeamClaw is installed but cannot work yet.",
|
|
100
|
+
"runtime.noModel": "No TeamClaw model is configured for this instance.",
|
|
101
|
+
"runtime.noAuth": "No usable OpenClaw auth profile was found for TeamClaw.",
|
|
102
|
+
"worker.add": "Add worker",
|
|
103
|
+
"worker.hide": "Hide worker command",
|
|
104
|
+
"worker.cardTitle": "Register a new external worker",
|
|
105
|
+
"worker.cardSubtitle": "Choose a role and discovery mode, then copy a one-line installer command for the target machine.",
|
|
106
|
+
"worker.role": "Role",
|
|
107
|
+
"worker.discovery": "Controller discovery",
|
|
108
|
+
"worker.discoveryMdns": "LAN auto-discovery (mDNS)",
|
|
109
|
+
"worker.discoveryManual": "Manual controller URL (LAN IP)",
|
|
110
|
+
"worker.recommendedUrl": "Recommended controller URL: ",
|
|
111
|
+
"filter.all": "All",
|
|
112
|
+
"filter.pending": "Pending",
|
|
113
|
+
"filter.assigned": "Assigned",
|
|
114
|
+
"filter.in_progress": "In Progress",
|
|
115
|
+
"filter.blocked": "Blocked",
|
|
116
|
+
"filter.completed": "Completed",
|
|
117
|
+
"filter.failed": "Failed",
|
|
118
|
+
"priority.low": "Low",
|
|
119
|
+
"priority.medium": "Medium",
|
|
120
|
+
"priority.high": "High",
|
|
121
|
+
"priority.critical": "Critical",
|
|
122
|
+
"detail.kicker": "Task Details",
|
|
123
|
+
"live.idle": "Idle",
|
|
124
|
+
"clarification.kicker": "Clarification needed",
|
|
125
|
+
"clarification.title": "Human input required"
|
|
126
|
+
},
|
|
127
|
+
zh: {
|
|
128
|
+
"action.refresh": "刷新",
|
|
129
|
+
"action.close": "关闭",
|
|
130
|
+
"action.dismiss": "稍后处理",
|
|
131
|
+
"action.copyCommand": "复制命令",
|
|
132
|
+
"action.copied": "已复制",
|
|
133
|
+
"sidebar.workers": "成员",
|
|
134
|
+
"sidebar.roles": "角色",
|
|
135
|
+
"tab.planning": "规划",
|
|
136
|
+
"tab.tasks": "任务",
|
|
137
|
+
"tab.workspace": "工作区",
|
|
138
|
+
"tab.clarifications": "澄清",
|
|
139
|
+
"tab.messages": "消息",
|
|
140
|
+
"tab.manualTask": "手动任务",
|
|
141
|
+
"planning.title": "团队规划",
|
|
142
|
+
"planning.description": "通过下方命令栏提交需求。复杂项目(3 个及以上角色)会触发团队 kickoff 会议,由各角色协作评估需求。",
|
|
143
|
+
"planning.sessions": "会话",
|
|
144
|
+
"planning.requirement": "需求",
|
|
145
|
+
"planning.kickoff": "团队 Kickoff 会议",
|
|
146
|
+
"planning.controllerOutput": "Controller 输出",
|
|
147
|
+
"planning.originalRequest": "原始请求",
|
|
148
|
+
"planning.requiredRoles": "所需角色",
|
|
149
|
+
"planning.plannedTasks": "计划任务",
|
|
150
|
+
"planning.deferredTasks": "延后任务",
|
|
151
|
+
"planning.clarificationsNeeded": "待澄清问题",
|
|
152
|
+
"planning.notes": "备注",
|
|
153
|
+
"planning.noControllerOutput": "暂无 controller 输出。",
|
|
154
|
+
"workspace.preview": "预览",
|
|
155
|
+
"workspace.selectFile": "选择文件",
|
|
156
|
+
"workspace.openRaw": "打开原始文件",
|
|
157
|
+
"workspace.source": "源码",
|
|
158
|
+
"workspace.files": "文件",
|
|
159
|
+
"messages.panelNote": "这里会保留 controller 活动,方便你在 Web UI 中跟踪需求 intake、编排过程和后续跟进。",
|
|
160
|
+
"manualTask.note": "原始人工需求应先发送到 controller 对话。这个表单仅用于明确的手动任务注入或测试。",
|
|
161
|
+
"manualTask.title": "标题",
|
|
162
|
+
"manualTask.description": "描述",
|
|
163
|
+
"manualTask.skills": "推荐技能",
|
|
164
|
+
"manualTask.priority": "优先级",
|
|
165
|
+
"manualTask.assignedRole": "指定角色",
|
|
166
|
+
"manualTask.autoAssign": "自动分配",
|
|
167
|
+
"manualTask.create": "创建手动任务",
|
|
168
|
+
"manualTask.titlePlaceholder": "任务标题...",
|
|
169
|
+
"manualTask.descriptionPlaceholder": "可直接执行的任务描述...",
|
|
170
|
+
"manualTask.skillsPlaceholder": "逗号分隔的 skill slug,例如 find-skills, ui-ux-pro-max",
|
|
171
|
+
"empty.noWorkers": "暂无已连接成员",
|
|
172
|
+
"empty.noTasks": "暂无任务",
|
|
173
|
+
"empty.noTasksWithStatus": "没有状态为“{status}”的任务",
|
|
174
|
+
"empty.noClarifications": "暂无澄清请求",
|
|
175
|
+
"empty.noControllerActivity": "暂无 controller 活动",
|
|
176
|
+
"empty.noMessages": "暂无消息",
|
|
177
|
+
"empty.noPlanningSessions": "暂无规划会话",
|
|
178
|
+
"empty.noKickoffData": "暂无 kickoff 数据",
|
|
179
|
+
"empty.workspaceLoading": "工作区树加载中…",
|
|
180
|
+
"empty.noWorkspaceFiles": "工作区中还没有项目文件。",
|
|
181
|
+
"empty.selectFileSource": "从工作区文件树选择文件以查看源码。",
|
|
182
|
+
"empty.selectFilePreview": "从工作区文件树选择文件以预览 Markdown 或 HTML 输出。",
|
|
183
|
+
"empty.selectTask": "选择一个任务",
|
|
184
|
+
"empty.taskDetail": "选择一个任务以查看执行细节。",
|
|
185
|
+
"empty.taskMessages": "该任务暂无消息。",
|
|
186
|
+
"empty.taskHistory": "暂无执行历史。",
|
|
187
|
+
"empty.copiedControllerReply": "Controller 已完成,但没有文本回复。",
|
|
188
|
+
"runtime.title": "TeamClaw 已安装,但当前还无法工作。",
|
|
189
|
+
"runtime.noModel": "当前实例还没有为 TeamClaw 配置模型。",
|
|
190
|
+
"runtime.noAuth": "未找到 TeamClaw 可用的 OpenClaw 认证配置。",
|
|
191
|
+
"worker.add": "添加 worker",
|
|
192
|
+
"worker.hide": "隐藏 worker 命令",
|
|
193
|
+
"worker.cardTitle": "注册新的外部 worker",
|
|
194
|
+
"worker.cardSubtitle": "选择角色和发现方式,然后复制目标机器可直接执行的一行安装命令。",
|
|
195
|
+
"worker.role": "角色",
|
|
196
|
+
"worker.discovery": "Controller 发现方式",
|
|
197
|
+
"worker.discoveryMdns": "局域网自动发现(mDNS)",
|
|
198
|
+
"worker.discoveryManual": "手动填写 controller 地址(局域网 IP)",
|
|
199
|
+
"worker.recommendedUrl": "推荐的 controller 地址:",
|
|
200
|
+
"filter.all": "全部",
|
|
201
|
+
"filter.pending": "待处理",
|
|
202
|
+
"filter.assigned": "已分配",
|
|
203
|
+
"filter.in_progress": "进行中",
|
|
204
|
+
"filter.blocked": "阻塞",
|
|
205
|
+
"filter.completed": "已完成",
|
|
206
|
+
"filter.failed": "失败",
|
|
207
|
+
"priority.low": "低",
|
|
208
|
+
"priority.medium": "中",
|
|
209
|
+
"priority.high": "高",
|
|
210
|
+
"priority.critical": "紧急",
|
|
211
|
+
"detail.kicker": "任务详情",
|
|
212
|
+
"live.idle": "空闲",
|
|
213
|
+
"clarification.kicker": "需要澄清",
|
|
214
|
+
"clarification.title": "需要人工输入"
|
|
215
|
+
}
|
|
216
|
+
};
|
|
23
217
|
|
|
24
218
|
function $(selector) { return document.querySelector(selector); }
|
|
25
219
|
function $$(selector) { return document.querySelectorAll(selector); }
|
|
26
220
|
|
|
221
|
+
function loadLanguage() {
|
|
222
|
+
try {
|
|
223
|
+
var stored = window.localStorage.getItem(LANGUAGE_STORAGE_KEY);
|
|
224
|
+
return stored === "zh" ? "zh" : "en";
|
|
225
|
+
} catch (_err) {
|
|
226
|
+
return "en";
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function t(key, params) {
|
|
231
|
+
var template = (TRANSLATIONS[currentLanguage] && TRANSLATIONS[currentLanguage][key]) || TRANSLATIONS.en[key] || key;
|
|
232
|
+
return template.replace(/\{(\w+)\}/g, function (_match, name) {
|
|
233
|
+
return params && params[name] != null ? String(params[name]) : "";
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function setLanguage(language) {
|
|
238
|
+
currentLanguage = language === "zh" ? "zh" : "en";
|
|
239
|
+
try {
|
|
240
|
+
window.localStorage.setItem(LANGUAGE_STORAGE_KEY, currentLanguage);
|
|
241
|
+
} catch (_err) {}
|
|
242
|
+
document.documentElement.lang = currentLanguage === "zh" ? "zh-CN" : "en";
|
|
243
|
+
applyStaticTranslations();
|
|
244
|
+
refreshAll();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function applyStaticTranslations() {
|
|
248
|
+
$$("[data-i18n]").forEach(function (element) {
|
|
249
|
+
var key = element.getAttribute("data-i18n");
|
|
250
|
+
if (key) {
|
|
251
|
+
element.textContent = t(key);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
$$("[data-i18n-placeholder]").forEach(function (element) {
|
|
255
|
+
var key = element.getAttribute("data-i18n-placeholder");
|
|
256
|
+
if (key) {
|
|
257
|
+
element.setAttribute("placeholder", t(key));
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
var languageToggle = $("#language-toggle");
|
|
261
|
+
if (languageToggle) {
|
|
262
|
+
languageToggle.textContent = currentLanguage === "zh" ? "English" : "中文";
|
|
263
|
+
}
|
|
264
|
+
var filters = {
|
|
265
|
+
all: "filter.all",
|
|
266
|
+
pending: "filter.pending",
|
|
267
|
+
assigned: "filter.assigned",
|
|
268
|
+
in_progress: "filter.in_progress",
|
|
269
|
+
blocked: "filter.blocked",
|
|
270
|
+
completed: "filter.completed",
|
|
271
|
+
failed: "filter.failed"
|
|
272
|
+
};
|
|
273
|
+
$$("[data-filter]").forEach(function (button) {
|
|
274
|
+
var key = filters[button.getAttribute("data-filter") || "all"];
|
|
275
|
+
if (key) button.textContent = t(key);
|
|
276
|
+
});
|
|
277
|
+
["low", "medium", "high", "critical"].forEach(function (priority) {
|
|
278
|
+
var option = $('#task-priority option[value="' + priority + '"]');
|
|
279
|
+
if (option) option.textContent = t("priority." + priority);
|
|
280
|
+
});
|
|
281
|
+
var planningHeader = $(".planning-sessions-header");
|
|
282
|
+
if (planningHeader) planningHeader.textContent = t("planning.sessions");
|
|
283
|
+
var workspaceKicker = $(".workspace-sidebar-panel .workspace-panel-kicker");
|
|
284
|
+
if (workspaceKicker) workspaceKicker.textContent = t("tab.workspace");
|
|
285
|
+
var workspaceTitle = $(".workspace-sidebar-panel h3");
|
|
286
|
+
if (workspaceTitle) workspaceTitle.textContent = t("workspace.files");
|
|
287
|
+
var planningPaneTitles = $$(".planning-pane-title");
|
|
288
|
+
if (planningPaneTitles[0]) planningPaneTitles[0].textContent = t("planning.requirement");
|
|
289
|
+
if (planningPaneTitles[1]) planningPaneTitles[1].textContent = t("planning.kickoff");
|
|
290
|
+
var promptKicker = $(".clarification-prompt-kicker");
|
|
291
|
+
if (promptKicker) promptKicker.textContent = t("clarification.kicker");
|
|
292
|
+
var promptTitle = $("#clarification-prompt-title");
|
|
293
|
+
if (promptTitle) promptTitle.textContent = t("clarification.title");
|
|
294
|
+
var promptClose = $("#clarification-prompt-close");
|
|
295
|
+
if (promptClose) promptClose.textContent = t("action.dismiss");
|
|
296
|
+
var detailKicker = $(".task-detail-kicker");
|
|
297
|
+
if (detailKicker) detailKicker.textContent = t("detail.kicker");
|
|
298
|
+
var detailClose = $("#task-detail-close");
|
|
299
|
+
if (detailClose) detailClose.textContent = t("action.close");
|
|
300
|
+
var detailRefresh = $("#task-detail-refresh");
|
|
301
|
+
if (detailRefresh) detailRefresh.textContent = t("action.refresh");
|
|
302
|
+
var liveBadge = $("#task-detail-live-badge");
|
|
303
|
+
if (liveBadge && liveBadge.textContent === "Idle") liveBadge.textContent = t("live.idle");
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function parseInitialUiState() {
|
|
307
|
+
try {
|
|
308
|
+
const params = new URLSearchParams(window.location.search || "");
|
|
309
|
+
return {
|
|
310
|
+
tab: params.get("tab") || "",
|
|
311
|
+
taskId: params.get("taskId") || "",
|
|
312
|
+
planningRun: params.get("planningRun") || "",
|
|
313
|
+
};
|
|
314
|
+
} catch (_err) {
|
|
315
|
+
return { tab: "", taskId: "", planningRun: "" };
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
27
319
|
function getSessionStorage() {
|
|
28
320
|
try {
|
|
29
321
|
return window.sessionStorage;
|
|
@@ -83,6 +375,23 @@
|
|
|
83
375
|
return div.innerHTML;
|
|
84
376
|
}
|
|
85
377
|
|
|
378
|
+
async function copyText(text) {
|
|
379
|
+
if (!text) return;
|
|
380
|
+
if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
|
|
381
|
+
await navigator.clipboard.writeText(text);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
var textarea = document.createElement("textarea");
|
|
385
|
+
textarea.value = text;
|
|
386
|
+
textarea.setAttribute("readonly", "readonly");
|
|
387
|
+
textarea.style.position = "absolute";
|
|
388
|
+
textarea.style.left = "-9999px";
|
|
389
|
+
document.body.appendChild(textarea);
|
|
390
|
+
textarea.select();
|
|
391
|
+
document.execCommand("copy");
|
|
392
|
+
document.body.removeChild(textarea);
|
|
393
|
+
}
|
|
394
|
+
|
|
86
395
|
function formatTime(ts) {
|
|
87
396
|
if (!ts) return "";
|
|
88
397
|
const d = new Date(ts);
|
|
@@ -96,6 +405,49 @@
|
|
|
96
405
|
return String(value || "").replace(/_/g, " ").replace(/-/g, " ");
|
|
97
406
|
}
|
|
98
407
|
|
|
408
|
+
function normalizeArray(value) {
|
|
409
|
+
return Array.isArray(value) ? value : [];
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function sortClarifications(items) {
|
|
413
|
+
return normalizeArray(items).slice().sort(function (left, right) {
|
|
414
|
+
return (right.updatedAt || right.createdAt || 0) - (left.updatedAt || left.createdAt || 0);
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function pendingClarifications() {
|
|
419
|
+
return sortClarifications(teamState.clarifications).filter(function (item) {
|
|
420
|
+
return !item.answer && (item.status || "pending") === "pending";
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function isTerminalPlanningStatus(status) {
|
|
425
|
+
return ["completed", "failed", "cancelled"].indexOf(String(status || "").toLowerCase()) !== -1;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function activePlanningRunCount() {
|
|
429
|
+
return normalizeArray(teamState.controllerRuns).filter(function (run) {
|
|
430
|
+
return run.manifest && run.manifest.kickoffPlan && !isTerminalPlanningStatus(run.status);
|
|
431
|
+
}).length;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function activeTaskCount() {
|
|
435
|
+
return normalizeArray(teamState.tasks).filter(function (task) {
|
|
436
|
+
return ["assigned", "in_progress", "review"].indexOf(task.status) !== -1;
|
|
437
|
+
}).length;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function blockedTaskCount() {
|
|
441
|
+
return normalizeArray(teamState.tasks).filter(function (task) {
|
|
442
|
+
return task.status === "blocked";
|
|
443
|
+
}).length;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function isNearBottom(element) {
|
|
447
|
+
if (!element) return true;
|
|
448
|
+
return (element.scrollHeight - element.scrollTop - element.clientHeight) < 48;
|
|
449
|
+
}
|
|
450
|
+
|
|
99
451
|
function formatBytes(value) {
|
|
100
452
|
const bytes = Number(value || 0);
|
|
101
453
|
if (!bytes) return "0 B";
|
|
@@ -399,7 +751,11 @@
|
|
|
399
751
|
sections: [
|
|
400
752
|
renderContractSection("Deliverables", renderContractList(deliverables, function (item) {
|
|
401
753
|
const prefix = escapeHtml(item.kind || "artifact") + ": " + escapeHtml(item.value || "");
|
|
402
|
-
|
|
754
|
+
var liveLink = "";
|
|
755
|
+
if (item.artifactType === "web-app" && item.liveUrl) {
|
|
756
|
+
liveLink = ' <a class="deliverable-live-link" href="' + escapeHtml(item.liveUrl) + '" target="_blank" rel="noopener">Live Preview</a>';
|
|
757
|
+
}
|
|
758
|
+
return prefix + (item.summary ? ' <span class="contract-inline-note">— ' + escapeHtml(item.summary) + "</span>" : "") + liveLink;
|
|
403
759
|
})),
|
|
404
760
|
renderContractSection("Key Points", renderContractList(contract.keyPoints, function (item) {
|
|
405
761
|
return renderMarkdownInline(item);
|
|
@@ -516,6 +872,343 @@
|
|
|
516
872
|
});
|
|
517
873
|
}
|
|
518
874
|
|
|
875
|
+
var ROLE_ICONS = {
|
|
876
|
+
architect: "🏗️",
|
|
877
|
+
developer: "💻",
|
|
878
|
+
designer: "🎨",
|
|
879
|
+
"security-engineer": "🔒",
|
|
880
|
+
qa: "🧪",
|
|
881
|
+
devops: "⚙️",
|
|
882
|
+
"tech-lead": "👨💻",
|
|
883
|
+
"data-engineer": "📊",
|
|
884
|
+
"ml-engineer": "🤖",
|
|
885
|
+
"infra-engineer": "🖧",
|
|
886
|
+
"dba": "🗄️",
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
function renderKickoffMeetingPanel(kickoffPlan) {
|
|
890
|
+
if (!kickoffPlan || !Array.isArray(kickoffPlan.assessments) || kickoffPlan.assessments.length === 0) {
|
|
891
|
+
return "";
|
|
892
|
+
}
|
|
893
|
+
var assessments = kickoffPlan.assessments;
|
|
894
|
+
var needed = assessments.filter(function (a) { return a.needed; }).length;
|
|
895
|
+
var notNeeded = assessments.length - needed;
|
|
896
|
+
|
|
897
|
+
var header =
|
|
898
|
+
'<div class="kickoff-panel-header">' +
|
|
899
|
+
' <div class="kickoff-panel-icon">🤝</div>' +
|
|
900
|
+
' <div class="kickoff-panel-heading">' +
|
|
901
|
+
' <h4>Team Kickoff Meeting</h4>' +
|
|
902
|
+
' <div class="kickoff-panel-meta">' +
|
|
903
|
+
assessments.length + " roles assessed · " +
|
|
904
|
+
needed + " confirmed" +
|
|
905
|
+
(notNeeded > 0 ? " · " + notNeeded + " dismissed" : "") +
|
|
906
|
+
" </div>" +
|
|
907
|
+
" </div>" +
|
|
908
|
+
"</div>";
|
|
909
|
+
|
|
910
|
+
var roleCards = assessments.map(function (a) {
|
|
911
|
+
var icon = ROLE_ICONS[a.role] || "👤";
|
|
912
|
+
var statusCls = a.needed ? "kickoff-role-needed" : "kickoff-role-dismissed";
|
|
913
|
+
var statusLabel = a.needed ? "Confirmed" : "Not Needed";
|
|
914
|
+
|
|
915
|
+
var scopeHtml = a.scope
|
|
916
|
+
? '<div class="kickoff-role-scope">' + renderMarkdownContent(a.scope) + "</div>"
|
|
917
|
+
: "";
|
|
918
|
+
|
|
919
|
+
var tasksHtml = "";
|
|
920
|
+
if (Array.isArray(a.suggestedTasks) && a.suggestedTasks.length > 0) {
|
|
921
|
+
tasksHtml =
|
|
922
|
+
'<div class="kickoff-role-detail">' +
|
|
923
|
+
' <div class="kickoff-detail-label">📋 Suggested Tasks</div>' +
|
|
924
|
+
' <ul class="kickoff-detail-list">' +
|
|
925
|
+
a.suggestedTasks.map(function (t) { return "<li>" + escapeHtml(t) + "</li>"; }).join("") +
|
|
926
|
+
" </ul>" +
|
|
927
|
+
"</div>";
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
var risksHtml = "";
|
|
931
|
+
if (Array.isArray(a.risks) && a.risks.length > 0) {
|
|
932
|
+
risksHtml =
|
|
933
|
+
'<div class="kickoff-role-detail">' +
|
|
934
|
+
' <div class="kickoff-detail-label">⚠️ Risks</div>' +
|
|
935
|
+
' <ul class="kickoff-detail-list kickoff-risks">' +
|
|
936
|
+
a.risks.map(function (r) { return "<li>" + escapeHtml(r) + "</li>"; }).join("") +
|
|
937
|
+
" </ul>" +
|
|
938
|
+
"</div>";
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
var depsHtml = "";
|
|
942
|
+
if (Array.isArray(a.dependencies) && a.dependencies.length > 0) {
|
|
943
|
+
depsHtml =
|
|
944
|
+
'<div class="kickoff-role-detail">' +
|
|
945
|
+
' <div class="kickoff-detail-label">🔗 Dependencies</div>' +
|
|
946
|
+
' <div class="kickoff-deps">' +
|
|
947
|
+
a.dependencies.map(function (d) { return '<span class="kickoff-dep-chip">' + escapeHtml(d) + "</span>"; }).join("") +
|
|
948
|
+
" </div>" +
|
|
949
|
+
"</div>";
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
return (
|
|
953
|
+
'<div class="kickoff-role-card ' + statusCls + '">' +
|
|
954
|
+
' <div class="kickoff-role-header">' +
|
|
955
|
+
' <span class="kickoff-role-icon">' + icon + "</span>" +
|
|
956
|
+
' <span class="kickoff-role-name">' + escapeHtml(a.role) + "</span>" +
|
|
957
|
+
' <span class="kickoff-role-badge ' + statusCls + '">' + statusLabel + "</span>" +
|
|
958
|
+
" </div>" +
|
|
959
|
+
scopeHtml +
|
|
960
|
+
tasksHtml +
|
|
961
|
+
risksHtml +
|
|
962
|
+
depsHtml +
|
|
963
|
+
"</div>"
|
|
964
|
+
);
|
|
965
|
+
}).join("");
|
|
966
|
+
|
|
967
|
+
var summaryHtml = "";
|
|
968
|
+
if (kickoffPlan.summary) {
|
|
969
|
+
summaryHtml =
|
|
970
|
+
'<div class="kickoff-summary">' +
|
|
971
|
+
' <div class="kickoff-summary-label">Discussion Summary</div>' +
|
|
972
|
+
' <div class="kickoff-summary-body markdown-body">' + renderMarkdownContent(kickoffPlan.summary) + "</div>" +
|
|
973
|
+
"</div>";
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
return (
|
|
977
|
+
'<div class="controller-run-section">' +
|
|
978
|
+
' <div class="kickoff-panel">' +
|
|
979
|
+
header +
|
|
980
|
+
' <div class="kickoff-role-grid">' + roleCards + "</div>" +
|
|
981
|
+
summaryHtml +
|
|
982
|
+
" </div>" +
|
|
983
|
+
"</div>"
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// ── Planning Tab ───────────────────────────────────────────────────────
|
|
988
|
+
var selectedPlanningRunId = null;
|
|
989
|
+
|
|
990
|
+
function renderPlanningTab(runs) {
|
|
991
|
+
var sessionList = $("#planning-session-list");
|
|
992
|
+
if (!sessionList) return;
|
|
993
|
+
|
|
994
|
+
var planningRuns = (runs || [])
|
|
995
|
+
.filter(function (r) { return r && r.manifest; })
|
|
996
|
+
.sort(function (a, b) { return (b.updatedAt || 0) - (a.updatedAt || 0); });
|
|
997
|
+
|
|
998
|
+
if (planningRuns.length === 0) {
|
|
999
|
+
sessionList.innerHTML = '<div class="empty-state">' + escapeHtml(t("empty.noPlanningSessions")) + "</div>";
|
|
1000
|
+
showPlanningEmpty();
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// Auto-select first if nothing selected or selection is gone
|
|
1005
|
+
if (!selectedPlanningRunId || !planningRuns.some(function (r) { return r.id === selectedPlanningRunId; })) {
|
|
1006
|
+
selectedPlanningRunId = planningRuns[0].id;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
sessionList.innerHTML = planningRuns.map(function (run) {
|
|
1010
|
+
var manifest = run.manifest || {};
|
|
1011
|
+
var kp = manifest.kickoffPlan || {};
|
|
1012
|
+
var assessments = kp.assessments || [];
|
|
1013
|
+
var needed = assessments.filter(function (a) { return a.needed; }).length;
|
|
1014
|
+
var roles = (manifest.requiredRoles || []).length;
|
|
1015
|
+
var isActive = run.id === selectedPlanningRunId;
|
|
1016
|
+
var status = String(run.status || "active");
|
|
1017
|
+
var title = manifest.requirementSummary || run.title || "Untitled";
|
|
1018
|
+
if (title.length > 60) title = title.slice(0, 57) + "…";
|
|
1019
|
+
|
|
1020
|
+
return (
|
|
1021
|
+
'<button type="button" class="planning-session-btn' + (isActive ? " active" : "") + '" data-planning-run="' + escapeHtml(run.id) + '">' +
|
|
1022
|
+
' <div class="planning-session-title-row"><span class="planning-session-status ' + escapeHtml(status) + '"></span><div class="planning-session-title">' + escapeHtml(title) + "</div></div>" +
|
|
1023
|
+
' <div class="planning-session-meta">' + escapeHtml(humanizeStatus(status)) + " · " + roles + " roles" + (assessments.length ? " · " + needed + " confirmed" : "") + " · " + escapeHtml(formatTime(run.updatedAt) || "") + "</div>" +
|
|
1024
|
+
"</button>"
|
|
1025
|
+
);
|
|
1026
|
+
}).join("");
|
|
1027
|
+
|
|
1028
|
+
// Render selected run
|
|
1029
|
+
renderPlanningDetail(planningRuns.find(function (r) { return r.id === selectedPlanningRunId; }));
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
function showPlanningEmpty() {
|
|
1033
|
+
var empty = $("#planning-empty");
|
|
1034
|
+
var split = $("#planning-split");
|
|
1035
|
+
if (empty) empty.style.display = "";
|
|
1036
|
+
if (split) split.style.display = "none";
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
function renderPlanningDetail(run) {
|
|
1040
|
+
var empty = $("#planning-empty");
|
|
1041
|
+
var split = $("#planning-split");
|
|
1042
|
+
var reqEl = $("#planning-requirement");
|
|
1043
|
+
var kickoffEl = $("#planning-kickoff");
|
|
1044
|
+
|
|
1045
|
+
if (!run || !run.manifest) {
|
|
1046
|
+
showPlanningEmpty();
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
if (empty) empty.style.display = "none";
|
|
1051
|
+
if (split) split.style.display = "";
|
|
1052
|
+
|
|
1053
|
+
// Left pane: requirement + manifest summary
|
|
1054
|
+
if (reqEl) {
|
|
1055
|
+
var manifest = run.manifest;
|
|
1056
|
+
var reqLines = [];
|
|
1057
|
+
reqLines.push('<h3>' + escapeHtml(manifest.requirementSummary || run.title || "") + '</h3>');
|
|
1058
|
+
reqLines.push('<div class="planning-req-original">' + renderMarkdownContent(run.request || "") + '</div>');
|
|
1059
|
+
|
|
1060
|
+
// Manifest details
|
|
1061
|
+
if (manifest.requiredRoles && manifest.requiredRoles.length) {
|
|
1062
|
+
reqLines.push('<div class="planning-req-section"><div class="planning-req-label">' + escapeHtml(t("planning.requiredRoles")) + "</div>");
|
|
1063
|
+
reqLines.push('<div class="kickoff-deps">' + manifest.requiredRoles.map(function (r) {
|
|
1064
|
+
var icon = ROLE_ICONS[r] || "👤";
|
|
1065
|
+
return '<span class="kickoff-dep-chip">' + icon + " " + escapeHtml(r) + "</span>";
|
|
1066
|
+
}).join("") + "</div></div>");
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
var created = Array.isArray(manifest.createdTasks) ? manifest.createdTasks : [];
|
|
1070
|
+
var deferred = Array.isArray(manifest.deferredTasks) ? manifest.deferredTasks : [];
|
|
1071
|
+
if (created.length) {
|
|
1072
|
+
reqLines.push('<div class="planning-req-section"><div class="planning-req-label">' + escapeHtml(t("planning.plannedTasks")) + " (" + created.length + ")</div>");
|
|
1073
|
+
reqLines.push('<ul class="planning-task-list">');
|
|
1074
|
+
created.forEach(function (t) {
|
|
1075
|
+
var roleLabel = t.assignedRole ? ' <span class="planning-role-tag">' + escapeHtml(t.assignedRole) + "</span>" : "";
|
|
1076
|
+
reqLines.push("<li>" + escapeHtml(t.title || "Task") + roleLabel + "</li>");
|
|
1077
|
+
});
|
|
1078
|
+
reqLines.push("</ul></div>");
|
|
1079
|
+
}
|
|
1080
|
+
if (deferred.length) {
|
|
1081
|
+
reqLines.push('<div class="planning-req-section"><div class="planning-req-label">' + escapeHtml(t("planning.deferredTasks")) + " (" + deferred.length + ")</div>");
|
|
1082
|
+
reqLines.push('<ul class="planning-task-list planning-deferred">');
|
|
1083
|
+
deferred.forEach(function (t) {
|
|
1084
|
+
var roleLabel = t.assignedRole ? ' <span class="planning-role-tag">' + escapeHtml(t.assignedRole) + "</span>" : "";
|
|
1085
|
+
reqLines.push("<li>" + escapeHtml(t.title || "Task") + roleLabel + "</li>");
|
|
1086
|
+
});
|
|
1087
|
+
reqLines.push("</ul></div>");
|
|
1088
|
+
}
|
|
1089
|
+
if (Array.isArray(manifest.clarificationQuestions) && manifest.clarificationQuestions.length) {
|
|
1090
|
+
reqLines.push('<div class="planning-req-section"><div class="planning-req-label">' + escapeHtml(t("planning.clarificationsNeeded")) + "</div>");
|
|
1091
|
+
reqLines.push('<ul class="planning-task-list planning-deferred">');
|
|
1092
|
+
manifest.clarificationQuestions.forEach(function (question) {
|
|
1093
|
+
reqLines.push("<li>" + escapeHtml(question) + "</li>");
|
|
1094
|
+
});
|
|
1095
|
+
reqLines.push("</ul></div>");
|
|
1096
|
+
}
|
|
1097
|
+
if (manifest.handoffPlan) {
|
|
1098
|
+
reqLines.push('<div class="planning-req-section"><div class="planning-req-label">Handoff Plan</div>');
|
|
1099
|
+
reqLines.push('<div class="planning-req-body markdown-body">' + renderMarkdownContent(manifest.handoffPlan) + "</div></div>");
|
|
1100
|
+
}
|
|
1101
|
+
if (manifest.notes) {
|
|
1102
|
+
reqLines.push('<div class="planning-req-section"><div class="planning-req-label">' + escapeHtml(t("planning.notes")) + "</div>");
|
|
1103
|
+
reqLines.push('<div class="planning-req-body markdown-body">' + renderMarkdownContent(manifest.notes) + "</div></div>");
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
reqEl.innerHTML = reqLines.join("");
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// Right pane: kickoff meeting or generic controller output
|
|
1110
|
+
if (kickoffEl) {
|
|
1111
|
+
if (run.manifest.kickoffPlan) {
|
|
1112
|
+
kickoffEl.innerHTML = renderKickoffContent(run.manifest.kickoffPlan);
|
|
1113
|
+
} else {
|
|
1114
|
+
kickoffEl.innerHTML =
|
|
1115
|
+
'<div class="planning-req-section"><div class="planning-req-label">' + escapeHtml(t("planning.originalRequest")) + '</div><div class="planning-req-body markdown-body">' + renderMarkdownContent(run.request || "") + "</div></div>" +
|
|
1116
|
+
'<div class="planning-req-section"><div class="planning-req-label">' + escapeHtml(t("planning.controllerOutput")) + '</div><div class="planning-req-body markdown-body">' + renderMarkdownContent(run.reply || t("planning.noControllerOutput")) + "</div></div>";
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
function renderKickoffContent(kp) {
|
|
1122
|
+
if (!kp || !Array.isArray(kp.assessments) || kp.assessments.length === 0) {
|
|
1123
|
+
return '<div class="empty-state">' + escapeHtml(t("empty.noKickoffData")) + "</div>";
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
var assessments = kp.assessments;
|
|
1127
|
+
var html = [];
|
|
1128
|
+
|
|
1129
|
+
// Stats bar
|
|
1130
|
+
var needed = assessments.filter(function (a) { return a.needed; }).length;
|
|
1131
|
+
var dismissed = assessments.length - needed;
|
|
1132
|
+
html.push(
|
|
1133
|
+
'<div class="kickoff-stats-bar">' +
|
|
1134
|
+
' <div class="kickoff-stat"><span class="kickoff-stat-num">' + assessments.length + '</span><span class="kickoff-stat-label">Assessed</span></div>' +
|
|
1135
|
+
' <div class="kickoff-stat kickoff-stat-ok"><span class="kickoff-stat-num">' + needed + '</span><span class="kickoff-stat-label">Confirmed</span></div>' +
|
|
1136
|
+
(dismissed > 0 ? ' <div class="kickoff-stat kickoff-stat-dim"><span class="kickoff-stat-num">' + dismissed + '</span><span class="kickoff-stat-label">Dismissed</span></div>' : "") +
|
|
1137
|
+
"</div>"
|
|
1138
|
+
);
|
|
1139
|
+
|
|
1140
|
+
// Role cards
|
|
1141
|
+
assessments.forEach(function (a) {
|
|
1142
|
+
var icon = ROLE_ICONS[a.role] || "👤";
|
|
1143
|
+
var statusCls = a.needed ? "kickoff-role-needed" : "kickoff-role-dismissed";
|
|
1144
|
+
var statusLabel = a.needed ? "Confirmed" : "Not Needed";
|
|
1145
|
+
|
|
1146
|
+
var sections = [];
|
|
1147
|
+
|
|
1148
|
+
if (a.scope) {
|
|
1149
|
+
sections.push(
|
|
1150
|
+
'<div class="kickoff-role-scope"><div class="markdown-body">' + renderMarkdownContent(a.scope) + "</div></div>"
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
if (Array.isArray(a.suggestedTasks) && a.suggestedTasks.length > 0) {
|
|
1155
|
+
sections.push(
|
|
1156
|
+
'<details class="kickoff-collapsible" open>' +
|
|
1157
|
+
' <summary class="kickoff-detail-label">📋 Suggested Tasks (' + a.suggestedTasks.length + ")</summary>" +
|
|
1158
|
+
' <ul class="kickoff-detail-list">' +
|
|
1159
|
+
a.suggestedTasks.map(function (t) { return "<li>" + escapeHtml(t) + "</li>"; }).join("") +
|
|
1160
|
+
" </ul>" +
|
|
1161
|
+
"</details>"
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
if (Array.isArray(a.risks) && a.risks.length > 0) {
|
|
1166
|
+
sections.push(
|
|
1167
|
+
'<details class="kickoff-collapsible">' +
|
|
1168
|
+
' <summary class="kickoff-detail-label">⚠️ Risks (' + a.risks.length + ")</summary>" +
|
|
1169
|
+
' <ul class="kickoff-detail-list kickoff-risks">' +
|
|
1170
|
+
a.risks.map(function (r) { return "<li>" + escapeHtml(r) + "</li>"; }).join("") +
|
|
1171
|
+
" </ul>" +
|
|
1172
|
+
"</details>"
|
|
1173
|
+
);
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
if (Array.isArray(a.dependencies) && a.dependencies.length > 0) {
|
|
1177
|
+
sections.push(
|
|
1178
|
+
'<details class="kickoff-collapsible">' +
|
|
1179
|
+
' <summary class="kickoff-detail-label">🔗 Dependencies (' + a.dependencies.length + ")</summary>" +
|
|
1180
|
+
' <div class="kickoff-deps">' +
|
|
1181
|
+
a.dependencies.map(function (d) { return '<span class="kickoff-dep-chip">' + escapeHtml(d) + "</span>"; }).join("") +
|
|
1182
|
+
" </div>" +
|
|
1183
|
+
"</details>"
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
html.push(
|
|
1188
|
+
'<div class="kickoff-role-card ' + statusCls + '">' +
|
|
1189
|
+
' <div class="kickoff-role-header">' +
|
|
1190
|
+
' <span class="kickoff-role-icon">' + icon + "</span>" +
|
|
1191
|
+
' <span class="kickoff-role-name">' + escapeHtml(a.role) + "</span>" +
|
|
1192
|
+
' <span class="kickoff-role-badge ' + statusCls + '">' + statusLabel + "</span>" +
|
|
1193
|
+
" </div>" +
|
|
1194
|
+
sections.join("") +
|
|
1195
|
+
"</div>"
|
|
1196
|
+
);
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1199
|
+
// Summary
|
|
1200
|
+
if (kp.summary) {
|
|
1201
|
+
html.push(
|
|
1202
|
+
'<div class="kickoff-summary">' +
|
|
1203
|
+
' <div class="kickoff-summary-label">Discussion Summary</div>' +
|
|
1204
|
+
' <div class="kickoff-summary-body markdown-body">' + renderMarkdownContent(kp.summary) + "</div>" +
|
|
1205
|
+
"</div>"
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
return html.join("");
|
|
1210
|
+
}
|
|
1211
|
+
|
|
519
1212
|
function buildMessageDisplayContent(message) {
|
|
520
1213
|
const content = normalizeTextValue(message && message.content);
|
|
521
1214
|
const contract = message && message.contract ? message.contract : null;
|
|
@@ -545,6 +1238,14 @@
|
|
|
545
1238
|
return null;
|
|
546
1239
|
}
|
|
547
1240
|
|
|
1241
|
+
/** Merge lazy-loaded children into the workspace tree data model. */
|
|
1242
|
+
function mergeWorkspaceSubtree(dirPath, entries) {
|
|
1243
|
+
var dirNode = findWorkspaceNodeByPath(workspaceTree, dirPath);
|
|
1244
|
+
if (dirNode && dirNode.type === "directory") {
|
|
1245
|
+
dirNode.children = entries;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
|
|
548
1249
|
function findDefaultWorkspacePath(nodes) {
|
|
549
1250
|
const preferredNames = ["README.md", "SPEC.md", "index.html"];
|
|
550
1251
|
const queue = [].concat(nodes || []);
|
|
@@ -657,10 +1358,13 @@
|
|
|
657
1358
|
const data = await apiGet("/workspace/file?path=" + encodeURIComponent(relativePath));
|
|
658
1359
|
selectedWorkspacePath = relativePath;
|
|
659
1360
|
selectedWorkspaceFile = data.file || null;
|
|
660
|
-
if (
|
|
661
|
-
|
|
1361
|
+
if (settings.keepView && selectedWorkspaceView === "preview" && isWorkspacePreviewAvailable(selectedWorkspaceFile)) {
|
|
1362
|
+
// User explicitly chose preview and new file supports it — keep preview
|
|
1363
|
+
} else {
|
|
1364
|
+
// Default: md/html show preview, everything else shows source
|
|
1365
|
+
selectedWorkspaceView = isWorkspacePreviewAvailable(selectedWorkspaceFile) ? "preview" : "source";
|
|
662
1366
|
}
|
|
663
|
-
|
|
1367
|
+
updateWorkspaceTreeSelection();
|
|
664
1368
|
renderWorkspaceFile();
|
|
665
1369
|
} catch (err) {
|
|
666
1370
|
console.error("Failed to load workspace file:", err);
|
|
@@ -675,30 +1379,61 @@
|
|
|
675
1379
|
if (!container) return;
|
|
676
1380
|
|
|
677
1381
|
if (!workspaceLoaded) {
|
|
678
|
-
container.innerHTML = '<div class="empty-state">
|
|
1382
|
+
container.innerHTML = '<div class="empty-state">' + escapeHtml(t("empty.workspaceLoading")) + "</div>";
|
|
679
1383
|
return;
|
|
680
1384
|
}
|
|
681
1385
|
|
|
682
1386
|
if (!nodes || nodes.length === 0) {
|
|
683
|
-
container.innerHTML = '<div class="empty-state">
|
|
1387
|
+
container.innerHTML = '<div class="empty-state">' + escapeHtml(t("empty.noWorkspaceFiles")) + "</div>";
|
|
684
1388
|
return;
|
|
685
1389
|
}
|
|
686
1390
|
|
|
1391
|
+
// Capture currently expanded directories before re-render
|
|
1392
|
+
var expandedDirs = new Set();
|
|
1393
|
+
var toggles = container.querySelectorAll(".workspace-tree-dir-toggle");
|
|
1394
|
+
for (var i = 0; i < toggles.length; i++) {
|
|
1395
|
+
var toggle = toggles[i];
|
|
1396
|
+
var li = toggle.closest(".workspace-tree-folder");
|
|
1397
|
+
var children = li ? li.querySelector(".workspace-tree-children") : null;
|
|
1398
|
+
if (children && children.style.display !== "none") {
|
|
1399
|
+
expandedDirs.add(toggle.dataset.dirPath);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
|
|
687
1403
|
container.innerHTML = renderWorkspaceTreeNodes(nodes);
|
|
1404
|
+
|
|
1405
|
+
// Restore expanded directories
|
|
1406
|
+
if (expandedDirs.size > 0) {
|
|
1407
|
+
var newToggles = container.querySelectorAll(".workspace-tree-dir-toggle");
|
|
1408
|
+
for (var j = 0; j < newToggles.length; j++) {
|
|
1409
|
+
var toggleEl = newToggles[j];
|
|
1410
|
+
if (expandedDirs.has(toggleEl.dataset.dirPath)) {
|
|
1411
|
+
var parentLi = toggleEl.closest(".workspace-tree-folder");
|
|
1412
|
+
var childrenDiv = parentLi ? parentLi.querySelector(".workspace-tree-children") : null;
|
|
1413
|
+
if (childrenDiv) {
|
|
1414
|
+
childrenDiv.style.display = "";
|
|
1415
|
+
var arrow = toggleEl.querySelector(".workspace-tree-arrow");
|
|
1416
|
+
if (arrow) arrow.textContent = "▾";
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
688
1421
|
}
|
|
689
1422
|
|
|
690
1423
|
function renderWorkspaceTreeNodes(nodes) {
|
|
691
1424
|
return '<ul class="workspace-tree-list">' + nodes.map(function (node) {
|
|
692
1425
|
if (node.type === "directory") {
|
|
1426
|
+
const hasChildren = node.children && node.children.length > 0;
|
|
1427
|
+
const isLazy = !node.children; // children === undefined means not yet loaded
|
|
693
1428
|
return (
|
|
694
1429
|
'<li class="workspace-tree-folder">' +
|
|
695
|
-
' <
|
|
696
|
-
' <
|
|
697
|
-
'
|
|
698
|
-
|
|
699
|
-
"
|
|
700
|
-
|
|
701
|
-
" </
|
|
1430
|
+
' <div class="workspace-tree-dir-toggle' + (isLazy ? ' is-lazy' : '') + '" data-dir-path="' + escapeHtml(node.path) + '">' +
|
|
1431
|
+
' <span class="workspace-tree-arrow">▸</span>' +
|
|
1432
|
+
' <span class="workspace-tree-label">' + escapeHtml(node.name) + "</span>" +
|
|
1433
|
+
" </div>" +
|
|
1434
|
+
' <div class="workspace-tree-children" style="display:none">' +
|
|
1435
|
+
(hasChildren ? renderWorkspaceTreeNodes(node.children) : '') +
|
|
1436
|
+
" </div>" +
|
|
702
1437
|
"</li>"
|
|
703
1438
|
);
|
|
704
1439
|
}
|
|
@@ -719,13 +1454,25 @@
|
|
|
719
1454
|
}).join("") + "</ul>";
|
|
720
1455
|
}
|
|
721
1456
|
|
|
1457
|
+
/** Update only the selected highlight in the tree without re-rendering (preserves expand state). */
|
|
1458
|
+
function updateWorkspaceTreeSelection() {
|
|
1459
|
+
var container = $("#workspace-tree");
|
|
1460
|
+
if (!container) return;
|
|
1461
|
+
var buttons = container.querySelectorAll(".workspace-tree-file");
|
|
1462
|
+
for (var i = 0; i < buttons.length; i++) {
|
|
1463
|
+
var btn = buttons[i];
|
|
1464
|
+
var isSelected = btn.dataset.workspacePath === selectedWorkspacePath;
|
|
1465
|
+
btn.classList.toggle("is-selected", isSelected);
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
|
|
722
1469
|
function renderWorkspaceFile() {
|
|
723
1470
|
const fileName = $("#workspace-file-name");
|
|
724
1471
|
const fileMeta = $("#workspace-file-meta");
|
|
725
1472
|
const openRaw = $("#workspace-open-raw");
|
|
726
1473
|
|
|
727
1474
|
if (fileName) {
|
|
728
|
-
fileName.textContent = selectedWorkspaceFile ? selectedWorkspaceFile.name : "
|
|
1475
|
+
fileName.textContent = selectedWorkspaceFile ? selectedWorkspaceFile.name : t("workspace.selectFile");
|
|
729
1476
|
}
|
|
730
1477
|
if (fileMeta) {
|
|
731
1478
|
fileMeta.textContent = selectedWorkspaceFile
|
|
@@ -762,7 +1509,7 @@
|
|
|
762
1509
|
if (!container) return;
|
|
763
1510
|
|
|
764
1511
|
if (!selectedWorkspaceFile) {
|
|
765
|
-
container.innerHTML = '<div class="workspace-preview-empty">
|
|
1512
|
+
container.innerHTML = '<div class="workspace-preview-empty">' + escapeHtml(t("empty.selectFileSource")) + "</div>";
|
|
766
1513
|
return;
|
|
767
1514
|
}
|
|
768
1515
|
|
|
@@ -798,7 +1545,7 @@
|
|
|
798
1545
|
if (!container) return;
|
|
799
1546
|
|
|
800
1547
|
if (!selectedWorkspaceFile) {
|
|
801
|
-
container.innerHTML = '<div class="workspace-preview-empty">
|
|
1548
|
+
container.innerHTML = '<div class="workspace-preview-empty">' + escapeHtml(t("empty.selectFilePreview")) + "</div>";
|
|
802
1549
|
return;
|
|
803
1550
|
}
|
|
804
1551
|
|
|
@@ -827,19 +1574,31 @@
|
|
|
827
1574
|
}
|
|
828
1575
|
|
|
829
1576
|
function connectWebSocket() {
|
|
1577
|
+
if (isConnecting) {
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
830
1580
|
const protocol = location.protocol === "https:" ? "wss" : "ws";
|
|
831
1581
|
const wsUrl = `${protocol}://${location.host}/ws`;
|
|
832
1582
|
|
|
1583
|
+
isConnecting = true;
|
|
833
1584
|
setStatus("connecting");
|
|
834
1585
|
ws = new WebSocket(wsUrl);
|
|
835
1586
|
|
|
836
1587
|
ws.onopen = function () {
|
|
1588
|
+
isConnecting = false;
|
|
1589
|
+
reconnectAttempts = 0;
|
|
1590
|
+
if (reconnectTimer) {
|
|
1591
|
+
clearTimeout(reconnectTimer);
|
|
1592
|
+
reconnectTimer = null;
|
|
1593
|
+
}
|
|
837
1594
|
setStatus("connected");
|
|
1595
|
+
refreshAll();
|
|
838
1596
|
};
|
|
839
1597
|
|
|
840
1598
|
ws.onclose = function () {
|
|
1599
|
+
isConnecting = false;
|
|
841
1600
|
setStatus("disconnected");
|
|
842
|
-
|
|
1601
|
+
scheduleReconnect();
|
|
843
1602
|
};
|
|
844
1603
|
|
|
845
1604
|
ws.onerror = function () {
|
|
@@ -856,6 +1615,18 @@
|
|
|
856
1615
|
};
|
|
857
1616
|
}
|
|
858
1617
|
|
|
1618
|
+
function scheduleReconnect() {
|
|
1619
|
+
if (reconnectTimer || isConnecting) {
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
reconnectAttempts += 1;
|
|
1623
|
+
const delay = Math.min(10000, 1000 * Math.max(1, reconnectAttempts));
|
|
1624
|
+
reconnectTimer = window.setTimeout(function () {
|
|
1625
|
+
reconnectTimer = null;
|
|
1626
|
+
connectWebSocket();
|
|
1627
|
+
}, delay);
|
|
1628
|
+
}
|
|
1629
|
+
|
|
859
1630
|
function setStatus(status) {
|
|
860
1631
|
const dot = $("#connection-status");
|
|
861
1632
|
if (dot) {
|
|
@@ -863,6 +1634,214 @@
|
|
|
863
1634
|
}
|
|
864
1635
|
}
|
|
865
1636
|
|
|
1637
|
+
/* function renmderRuntimeAlert(modelReadiness) keeps the legacy marker: TeamClaw is installed but cannot work yet. */
|
|
1638
|
+
function renderRuntimeAlert() {
|
|
1639
|
+
var alertEl = $("#runtime-alert");
|
|
1640
|
+
if (!alertEl) return;
|
|
1641
|
+
var readiness = teamState.modelReadiness;
|
|
1642
|
+
if (!readiness || readiness.status === "ready") {
|
|
1643
|
+
alertEl.classList.add("hidden");
|
|
1644
|
+
alertEl.innerHTML = "";
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
var detailBits = [];
|
|
1648
|
+
if (!readiness.hasConfiguredModel) {
|
|
1649
|
+
detailBits.push(t("runtime.noModel"));
|
|
1650
|
+
}
|
|
1651
|
+
if (!readiness.hasAuthProfiles) {
|
|
1652
|
+
detailBits.push(t("runtime.noAuth"));
|
|
1653
|
+
}
|
|
1654
|
+
alertEl.classList.remove("hidden");
|
|
1655
|
+
alertEl.innerHTML = (
|
|
1656
|
+
"<strong>" + escapeHtml(t("runtime.title")) + "</strong> " +
|
|
1657
|
+
escapeHtml(readiness.message || detailBits.join(" ")) +
|
|
1658
|
+
(detailBits.length ? (" " + escapeHtml(detailBits.join(" "))) : "")
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
function renderExternalWorkerInstallToggle() {
|
|
1663
|
+
var button = $("#worker-install-toggle");
|
|
1664
|
+
if (!button) return;
|
|
1665
|
+
var available = Boolean(teamState.externalWorkerInstall);
|
|
1666
|
+
button.hidden = !available;
|
|
1667
|
+
button.disabled = !available;
|
|
1668
|
+
button.textContent = externalWorkerInstallVisible ? t("worker.hide") : t("worker.add");
|
|
1669
|
+
// Source-contract note: keep the legacy English marker after externalWorkerInstallVisible: Add worker.
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
function buildExternalWorkerCommand(info, roleId, discoveryMode) {
|
|
1673
|
+
if (!info || !roleId) return "";
|
|
1674
|
+
var prefix = discoveryMode === "manual" ? (info.manualCommandPrefix || "") : (info.autoDiscoveryCommandPrefix || "");
|
|
1675
|
+
var suffix = discoveryMode === "manual" ? (info.manualControllerUrlFlag || "") : "";
|
|
1676
|
+
return (prefix + roleId + suffix).trim();
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
function renderExternalWorkerInstallCard() {
|
|
1680
|
+
// Source-contract note: keep the legacy English markers "Register a new external worker" and "Copy command".
|
|
1681
|
+
var card = $("#external-worker-install");
|
|
1682
|
+
if (!card) return;
|
|
1683
|
+
var info = teamState.externalWorkerInstall;
|
|
1684
|
+
if (!info || !externalWorkerInstallVisible) {
|
|
1685
|
+
card.classList.add("hidden");
|
|
1686
|
+
return;
|
|
1687
|
+
}
|
|
1688
|
+
var roles = Array.isArray(info.roles) ? info.roles : [];
|
|
1689
|
+
if (!roles.length) {
|
|
1690
|
+
card.classList.add("hidden");
|
|
1691
|
+
card.innerHTML = "";
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
if (!roles.some(function (role) { return role.id === selectedExternalWorkerRole; })) {
|
|
1695
|
+
selectedExternalWorkerRole = roles[0].id;
|
|
1696
|
+
}
|
|
1697
|
+
if (selectedExternalWorkerDiscoveryMode === "manual" && !info.recommendedControllerUrl) {
|
|
1698
|
+
selectedExternalWorkerDiscoveryMode = "mdns";
|
|
1699
|
+
}
|
|
1700
|
+
var roleOptions = roles.map(function (role) {
|
|
1701
|
+
return '<option value="' + escapeHtml(role.id) + '"' + (role.id === selectedExternalWorkerRole ? " selected" : "") + ">" +
|
|
1702
|
+
escapeHtml((role.icon ? role.icon + " " : "") + (role.label || role.id)) +
|
|
1703
|
+
"</option>";
|
|
1704
|
+
}).join("");
|
|
1705
|
+
var command = buildExternalWorkerCommand(info, selectedExternalWorkerRole, selectedExternalWorkerDiscoveryMode);
|
|
1706
|
+
var discoveryOptions = [
|
|
1707
|
+
'<option value="mdns"' + (selectedExternalWorkerDiscoveryMode === "mdns" ? " selected" : "") + ">" + escapeHtml(t("worker.discoveryMdns")) + "</option>",
|
|
1708
|
+
'<option value="manual"' + (selectedExternalWorkerDiscoveryMode === "manual" ? " selected" : "") + (info.recommendedControllerUrl ? "" : " disabled") + ">" + escapeHtml(t("worker.discoveryManual")) + "</option>",
|
|
1709
|
+
].join("");
|
|
1710
|
+
var note = selectedExternalWorkerDiscoveryMode === "manual"
|
|
1711
|
+
? (info.manualControllerWarning || "")
|
|
1712
|
+
: (info.autoDiscoveryWarning || "");
|
|
1713
|
+
var manualDetail = selectedExternalWorkerDiscoveryMode === "manual" && info.recommendedControllerUrl
|
|
1714
|
+
? '<div class="worker-install-note">' + escapeHtml(t("worker.recommendedUrl")) + '<code>' + escapeHtml(info.recommendedControllerUrl) + "</code></div>"
|
|
1715
|
+
: "";
|
|
1716
|
+
card.classList.remove("hidden");
|
|
1717
|
+
card.innerHTML = (
|
|
1718
|
+
'<div class="worker-install-head">' +
|
|
1719
|
+
'<div>' +
|
|
1720
|
+
"<h3>" + escapeHtml(t("worker.cardTitle")) + "</h3>" +
|
|
1721
|
+
'<div class="worker-install-subtitle">' + escapeHtml(t("worker.cardSubtitle")) + "</div>" +
|
|
1722
|
+
"</div>" +
|
|
1723
|
+
'<button type="button" class="worker-install-copy" data-worker-install-copy="true">' + escapeHtml(t("action.copyCommand")) + "</button>" +
|
|
1724
|
+
"</div>" +
|
|
1725
|
+
'<div class="worker-install-controls">' +
|
|
1726
|
+
'<div class="worker-install-field">' +
|
|
1727
|
+
'<label for="worker-install-role">' + escapeHtml(t("worker.role")) + "</label>" +
|
|
1728
|
+
'<select id="worker-install-role" data-worker-install-role="true">' + roleOptions + "</select>" +
|
|
1729
|
+
"</div>" +
|
|
1730
|
+
'<div class="worker-install-field">' +
|
|
1731
|
+
'<label for="worker-install-discovery">' + escapeHtml(t("worker.discovery")) + "</label>" +
|
|
1732
|
+
'<select id="worker-install-discovery" data-worker-install-discovery="true">' + discoveryOptions + "</select>" +
|
|
1733
|
+
"</div>" +
|
|
1734
|
+
"</div>" +
|
|
1735
|
+
'<pre class="worker-install-command"><code>' + escapeHtml(command) + "</code></pre>" +
|
|
1736
|
+
manualDetail +
|
|
1737
|
+
'<div class="worker-install-note' + (selectedExternalWorkerDiscoveryMode === "manual" ? " warning" : "") + '">' + escapeHtml(note) + "</div>"
|
|
1738
|
+
);
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
function renderActivitySignals() {
|
|
1742
|
+
var planningCount = activePlanningRunCount();
|
|
1743
|
+
var taskActive = activeTaskCount();
|
|
1744
|
+
var taskBlocked = blockedTaskCount();
|
|
1745
|
+
var clarificationCount = pendingClarifications().length;
|
|
1746
|
+
|
|
1747
|
+
var planningBadge = $("#planning-tab-count");
|
|
1748
|
+
var tasksBadge = $("#tasks-tab-count");
|
|
1749
|
+
var clarificationsBadge = $("#clarifications-tab-count");
|
|
1750
|
+
var planningSignal = $("#planning-tab-signal");
|
|
1751
|
+
var tasksSignal = $("#tasks-tab-signal");
|
|
1752
|
+
var clarificationsSignal = $("#clarifications-tab-signal");
|
|
1753
|
+
var planningTab = $('[data-tab="planning"]');
|
|
1754
|
+
var tasksTab = $('[data-tab="tasks"]');
|
|
1755
|
+
var clarificationsTab = $('[data-tab="clarifications"]');
|
|
1756
|
+
|
|
1757
|
+
if (planningBadge) {
|
|
1758
|
+
planningBadge.textContent = String(planningCount);
|
|
1759
|
+
planningBadge.className = "tab-badge" + (planningCount > 0 ? " tone-active" : "");
|
|
1760
|
+
planningBadge.style.display = planningCount > 0 ? "" : "none";
|
|
1761
|
+
}
|
|
1762
|
+
if (tasksBadge) {
|
|
1763
|
+
var taskCount = taskBlocked > 0 ? taskBlocked : taskActive;
|
|
1764
|
+
tasksBadge.textContent = String(taskCount);
|
|
1765
|
+
tasksBadge.className = "tab-badge" + (taskBlocked > 0 ? " tone-attention" : taskActive > 0 ? " tone-active" : "");
|
|
1766
|
+
tasksBadge.style.display = taskCount > 0 ? "" : "none";
|
|
1767
|
+
}
|
|
1768
|
+
if (clarificationsBadge) {
|
|
1769
|
+
clarificationsBadge.textContent = String(clarificationCount);
|
|
1770
|
+
clarificationsBadge.className = "tab-badge" + (clarificationCount > 0 ? " tone-attention" : "");
|
|
1771
|
+
clarificationsBadge.style.display = clarificationCount > 0 ? "" : "none";
|
|
1772
|
+
}
|
|
1773
|
+
if (planningSignal) {
|
|
1774
|
+
planningSignal.className = "tab-signal" + (planningCount > 0 ? " tone-active" : "");
|
|
1775
|
+
}
|
|
1776
|
+
if (tasksSignal) {
|
|
1777
|
+
tasksSignal.className = "tab-signal" + (taskBlocked > 0 ? " tone-attention" : taskActive > 0 ? " tone-active" : "");
|
|
1778
|
+
}
|
|
1779
|
+
if (clarificationsSignal) {
|
|
1780
|
+
clarificationsSignal.className = "tab-signal" + (clarificationCount > 0 ? " tone-attention" : "");
|
|
1781
|
+
}
|
|
1782
|
+
if (planningTab) {
|
|
1783
|
+
planningTab.classList.toggle("has-active", planningCount > 0);
|
|
1784
|
+
planningTab.classList.toggle("has-attention", false);
|
|
1785
|
+
}
|
|
1786
|
+
if (tasksTab) {
|
|
1787
|
+
tasksTab.classList.toggle("has-active", taskActive > 0);
|
|
1788
|
+
tasksTab.classList.toggle("has-attention", taskBlocked > 0);
|
|
1789
|
+
}
|
|
1790
|
+
if (clarificationsTab) {
|
|
1791
|
+
clarificationsTab.classList.toggle("has-active", false);
|
|
1792
|
+
clarificationsTab.classList.toggle("has-attention", clarificationCount > 0);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
function syncClarificationPrompt(options) {
|
|
1797
|
+
var pending = pendingClarifications();
|
|
1798
|
+
dismissedClarificationIds = dismissedClarificationIds.filter(function (id) {
|
|
1799
|
+
return pending.some(function (item) { return item.id === id; });
|
|
1800
|
+
});
|
|
1801
|
+
|
|
1802
|
+
if (pending.length === 0) {
|
|
1803
|
+
clarificationPromptOpen = false;
|
|
1804
|
+
activeClarificationId = null;
|
|
1805
|
+
renderClarificationPrompt();
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
var active = pending.find(function (item) { return item.id === activeClarificationId; });
|
|
1810
|
+
if (!active) {
|
|
1811
|
+
active = pending.find(function (item) { return dismissedClarificationIds.indexOf(item.id) === -1; }) || pending[0];
|
|
1812
|
+
activeClarificationId = active ? active.id : null;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
if (!clarificationPromptOpen && active && dismissedClarificationIds.indexOf(active.id) === -1) {
|
|
1816
|
+
clarificationPromptOpen = true;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
if (options && options.forceOpen && active) {
|
|
1820
|
+
clarificationPromptOpen = true;
|
|
1821
|
+
activeClarificationId = active.id;
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
renderClarificationPrompt();
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
function renderClarificationPrompt() {
|
|
1828
|
+
var modal = $("#clarification-prompt-modal");
|
|
1829
|
+
var body = $("#clarification-prompt-body");
|
|
1830
|
+
if (!modal || !body) return;
|
|
1831
|
+
|
|
1832
|
+
var pending = pendingClarifications();
|
|
1833
|
+
var active = pending.find(function (item) { return item.id === activeClarificationId; });
|
|
1834
|
+
if (!clarificationPromptOpen || !active) {
|
|
1835
|
+
modal.classList.remove("open");
|
|
1836
|
+
modal.setAttribute("aria-hidden", "true");
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
modal.classList.add("open");
|
|
1841
|
+
modal.setAttribute("aria-hidden", "false");
|
|
1842
|
+
body.innerHTML = renderClarificationCards([active], { compact: false, linkToTask: true });
|
|
1843
|
+
}
|
|
1844
|
+
|
|
866
1845
|
function handleWsEvent(event) {
|
|
867
1846
|
const taskId = event && event.data
|
|
868
1847
|
? (event.data.taskId || event.data.id || null)
|
|
@@ -888,15 +1867,65 @@
|
|
|
888
1867
|
refreshTaskDetail(true);
|
|
889
1868
|
}
|
|
890
1869
|
break;
|
|
1870
|
+
case "report:ready":
|
|
1871
|
+
handleReportReady(event.data || {});
|
|
1872
|
+
break;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
function handleReportReady(data) {
|
|
1877
|
+
const reportUrl = data.reportUrl;
|
|
1878
|
+
const projectName = data.projectName || "Project";
|
|
1879
|
+
const status = data.status || "completed";
|
|
1880
|
+
const icon = status === "completed" ? "✅" : status === "partial" ? "⚠️" : "❌";
|
|
1881
|
+
|
|
1882
|
+
// Create toast notification
|
|
1883
|
+
let container = document.getElementById("toast-container");
|
|
1884
|
+
if (!container) {
|
|
1885
|
+
container = document.createElement("div");
|
|
1886
|
+
container.id = "toast-container";
|
|
1887
|
+
container.style.cssText = "position:fixed;top:16px;right:16px;z-index:10000;display:flex;flex-direction:column;gap:8px;";
|
|
1888
|
+
document.body.appendChild(container);
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
const toast = document.createElement("div");
|
|
1892
|
+
toast.style.cssText = "background:#1a365d;color:#fff;padding:14px 20px;border-radius:10px;"
|
|
1893
|
+
+ "box-shadow:0 8px 24px rgba(0,0,0,.2);font-size:14px;max-width:380px;"
|
|
1894
|
+
+ "animation:slideIn .3s ease;cursor:pointer;display:flex;flex-direction:column;gap:6px;";
|
|
1895
|
+
toast.innerHTML = '<div style="font-weight:600;">' + icon + " Delivery Report Ready</div>"
|
|
1896
|
+
+ '<div style="opacity:.85;font-size:13px;">' + escapeHtmlInline(projectName) + " — " + status + "</div>"
|
|
1897
|
+
+ '<div style="font-size:12px;opacity:.7;">Click to open report</div>';
|
|
1898
|
+
toast.onclick = function () {
|
|
1899
|
+
if (reportUrl) window.open(reportUrl, "_blank");
|
|
1900
|
+
toast.remove();
|
|
1901
|
+
};
|
|
1902
|
+
|
|
1903
|
+
container.appendChild(toast);
|
|
1904
|
+
setTimeout(function () { toast.style.opacity = "0"; toast.style.transition = "opacity .5s"; }, 8000);
|
|
1905
|
+
setTimeout(function () { toast.remove(); }, 8500);
|
|
1906
|
+
|
|
1907
|
+
// Add animation keyframes if not already present
|
|
1908
|
+
if (!document.getElementById("toast-anim-style")) {
|
|
1909
|
+
const style = document.createElement("style");
|
|
1910
|
+
style.id = "toast-anim-style";
|
|
1911
|
+
style.textContent = "@keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }";
|
|
1912
|
+
document.head.appendChild(style);
|
|
891
1913
|
}
|
|
892
1914
|
}
|
|
893
1915
|
|
|
1916
|
+
function escapeHtmlInline(str) {
|
|
1917
|
+
return String(str).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1918
|
+
}
|
|
1919
|
+
|
|
894
1920
|
async function refreshAll() {
|
|
895
1921
|
try {
|
|
896
|
-
const
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1922
|
+
const statusRes = await apiGet("/team/status");
|
|
1923
|
+
let rolesRes = { roles: [] };
|
|
1924
|
+
try {
|
|
1925
|
+
rolesRes = await apiGet("/roles");
|
|
1926
|
+
} catch (rolesErr) {
|
|
1927
|
+
console.error("Failed to load roles:", rolesErr);
|
|
1928
|
+
}
|
|
900
1929
|
|
|
901
1930
|
teamState = {
|
|
902
1931
|
workers: statusRes.workers || [],
|
|
@@ -904,15 +1933,22 @@
|
|
|
904
1933
|
controllerRuns: statusRes.controllerRuns || [],
|
|
905
1934
|
messages: statusRes.messages || [],
|
|
906
1935
|
clarifications: statusRes.clarifications || [],
|
|
1936
|
+
modelReadiness: statusRes.modelReadiness || null,
|
|
1937
|
+
externalWorkerInstall: statusRes.externalWorkerInstall || null,
|
|
907
1938
|
};
|
|
908
1939
|
|
|
909
1940
|
renderWorkers(teamState.workers);
|
|
910
1941
|
renderTasks(teamState.tasks);
|
|
1942
|
+
renderPlanningTab(teamState.controllerRuns);
|
|
911
1943
|
renderControllerRuns(teamState.controllerRuns);
|
|
912
1944
|
renderClarifications(teamState.clarifications);
|
|
913
1945
|
renderMessages(teamState.messages);
|
|
914
1946
|
renderRoles(rolesRes.roles || []);
|
|
915
|
-
|
|
1947
|
+
renderRuntimeAlert();
|
|
1948
|
+
renderExternalWorkerInstallToggle();
|
|
1949
|
+
renderExternalWorkerInstallCard();
|
|
1950
|
+
renderActivitySignals();
|
|
1951
|
+
syncClarificationPrompt();
|
|
916
1952
|
|
|
917
1953
|
const teamName = $("#team-name");
|
|
918
1954
|
if (teamName) {
|
|
@@ -933,7 +1969,7 @@
|
|
|
933
1969
|
if (!container) return;
|
|
934
1970
|
|
|
935
1971
|
if (workers.length === 0) {
|
|
936
|
-
container.innerHTML = '<div class="empty-state">
|
|
1972
|
+
container.innerHTML = '<div class="empty-state">' + escapeHtml(t("empty.noWorkers")) + "</div>";
|
|
937
1973
|
return;
|
|
938
1974
|
}
|
|
939
1975
|
|
|
@@ -957,8 +1993,11 @@
|
|
|
957
1993
|
: tasks.filter(function (task) { return task.status === currentFilter; });
|
|
958
1994
|
|
|
959
1995
|
if (filtered.length === 0) {
|
|
960
|
-
container.innerHTML = '<div class="empty-state">
|
|
961
|
-
|
|
1996
|
+
container.innerHTML = '<div class="empty-state">' + escapeHtml(
|
|
1997
|
+
currentFilter !== "all"
|
|
1998
|
+
? t("empty.noTasksWithStatus", { status: currentFilter })
|
|
1999
|
+
: t("empty.noTasks")
|
|
2000
|
+
) + "</div>";
|
|
962
2001
|
return;
|
|
963
2002
|
}
|
|
964
2003
|
|
|
@@ -1018,7 +2057,7 @@
|
|
|
1018
2057
|
.slice(0, 12);
|
|
1019
2058
|
|
|
1020
2059
|
if (recentRuns.length === 0) {
|
|
1021
|
-
container.innerHTML = '<div class="empty-state">
|
|
2060
|
+
container.innerHTML = '<div class="empty-state">' + escapeHtml(t("empty.noControllerActivity")) + "</div>";
|
|
1022
2061
|
return;
|
|
1023
2062
|
}
|
|
1024
2063
|
|
|
@@ -1038,6 +2077,9 @@
|
|
|
1038
2077
|
const manifestBlock = run.manifest
|
|
1039
2078
|
? '<div class="controller-run-section"><div class="controller-run-section-title">Manifest</div>' + renderControllerManifestCard(run.manifest) + "</div>"
|
|
1040
2079
|
: "";
|
|
2080
|
+
const kickoffBlock = run.manifest && run.manifest.kickoffPlan
|
|
2081
|
+
? renderKickoffMeetingPanel(run.manifest.kickoffPlan)
|
|
2082
|
+
: "";
|
|
1041
2083
|
const replyBlock = run.reply
|
|
1042
2084
|
? '<div class="controller-run-section"><div class="controller-run-section-title">Reply</div><div class="markdown-body">' + renderMarkdownContent(run.reply) + "</div></div>"
|
|
1043
2085
|
: "";
|
|
@@ -1071,6 +2113,7 @@
|
|
|
1071
2113
|
" </div>" +
|
|
1072
2114
|
' <div class="controller-run-section"><div class="controller-run-section-title">Request</div><div class="markdown-body">' + renderMarkdownContent(run.request || "") + "</div></div>" +
|
|
1073
2115
|
manifestBlock +
|
|
2116
|
+
kickoffBlock +
|
|
1074
2117
|
replyBlock +
|
|
1075
2118
|
errorBlock +
|
|
1076
2119
|
(createdTaskButtons
|
|
@@ -1106,6 +2149,8 @@
|
|
|
1106
2149
|
|
|
1107
2150
|
async function openTaskDetail(taskId) {
|
|
1108
2151
|
selectedTaskId = taskId;
|
|
2152
|
+
selectedTaskDetailTab = "details";
|
|
2153
|
+
taskTimelineAutoFollow = true;
|
|
1109
2154
|
selectedTaskDetail = {
|
|
1110
2155
|
task: getTaskById(taskId),
|
|
1111
2156
|
messages: [],
|
|
@@ -1123,6 +2168,8 @@
|
|
|
1123
2168
|
function closeTaskDetail() {
|
|
1124
2169
|
selectedTaskId = null;
|
|
1125
2170
|
selectedTaskDetail = null;
|
|
2171
|
+
selectedTaskDetailTab = "details";
|
|
2172
|
+
taskTimelineAutoFollow = true;
|
|
1126
2173
|
const modal = $("#task-detail-modal");
|
|
1127
2174
|
if (modal) {
|
|
1128
2175
|
modal.classList.remove("open");
|
|
@@ -1158,7 +2205,7 @@
|
|
|
1158
2205
|
const liveBadge = $("#task-detail-live-badge");
|
|
1159
2206
|
|
|
1160
2207
|
if (title) {
|
|
1161
|
-
title.textContent = task ? task.title : "
|
|
2208
|
+
title.textContent = task ? task.title : t("empty.selectTask");
|
|
1162
2209
|
}
|
|
1163
2210
|
if (subtitle) {
|
|
1164
2211
|
subtitle.textContent = task
|
|
@@ -1175,18 +2222,23 @@
|
|
|
1175
2222
|
liveBadge.classList.toggle("is-live", live);
|
|
1176
2223
|
}
|
|
1177
2224
|
|
|
2225
|
+
renderTaskDetailTabCounts(task);
|
|
1178
2226
|
renderTaskDetailOverview(task);
|
|
1179
2227
|
renderTaskDetailTimeline(task);
|
|
1180
|
-
|
|
2228
|
+
renderTaskDetailClarifications();
|
|
2229
|
+
renderTaskDetailMessages();
|
|
1181
2230
|
syncTaskDetailTab();
|
|
2231
|
+
if (selectedTaskDetailTab === "timeline" && taskTimelineAutoFollow) {
|
|
2232
|
+
requestAnimationFrame(scrollTaskTimelineToBottom);
|
|
2233
|
+
}
|
|
1182
2234
|
}
|
|
1183
2235
|
|
|
1184
2236
|
function renderTaskDetailOverview(task) {
|
|
1185
|
-
const container = $("#task-detail-
|
|
2237
|
+
const container = $("#task-detail-details");
|
|
1186
2238
|
if (!container) return;
|
|
1187
2239
|
|
|
1188
2240
|
if (!task) {
|
|
1189
|
-
container.innerHTML = '<div class="task-detail-empty">
|
|
2241
|
+
container.innerHTML = '<div class="task-detail-empty">' + escapeHtml(t("empty.taskDetail")) + "</div>";
|
|
1190
2242
|
return;
|
|
1191
2243
|
}
|
|
1192
2244
|
|
|
@@ -1244,6 +2296,130 @@
|
|
|
1244
2296
|
: "");
|
|
1245
2297
|
}
|
|
1246
2298
|
|
|
2299
|
+
function renderTaskDetailTabCounts(task) {
|
|
2300
|
+
var counts = {
|
|
2301
|
+
details: task ? 1 : 0,
|
|
2302
|
+
timeline: normalizeArray(getSelectedTaskExecution().events).length,
|
|
2303
|
+
clarifications: normalizeArray(selectedTaskDetail && selectedTaskDetail.clarifications).length,
|
|
2304
|
+
messages: normalizeArray(selectedTaskDetail && selectedTaskDetail.messages).length,
|
|
2305
|
+
};
|
|
2306
|
+
$$("[data-task-detail-tab-count]").forEach(function (node) {
|
|
2307
|
+
var name = node.dataset.taskDetailTabCount || "";
|
|
2308
|
+
var count = counts[name] || 0;
|
|
2309
|
+
node.textContent = count > 0 ? String(count) : "";
|
|
2310
|
+
node.className = "task-detail-tab-count" + (count > 0 ? " has-items" : "");
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
function scrollTaskTimelineToBottom() {
|
|
2315
|
+
var container = $("#task-detail-timeline");
|
|
2316
|
+
if (!container) return;
|
|
2317
|
+
container.scrollTop = container.scrollHeight;
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
function syncTaskTimelineFollowState() {
|
|
2321
|
+
if (selectedTaskDetailTab !== "timeline") return;
|
|
2322
|
+
var container = $("#task-detail-timeline");
|
|
2323
|
+
if (!container) return;
|
|
2324
|
+
taskTimelineAutoFollow = isNearBottom(container);
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
function renderMessageCards(messages) {
|
|
2328
|
+
return normalizeArray(messages).map(function (message) {
|
|
2329
|
+
const from = message.fromRole || message.from || "unknown";
|
|
2330
|
+
const type = (message.contract && message.contract.intent) || message.type || "direct";
|
|
2331
|
+
const contractBlock = renderTeamMessageContractCard(message.contract);
|
|
2332
|
+
const rawContent = buildMessageDisplayContent(message);
|
|
2333
|
+
const meta = [
|
|
2334
|
+
message.toRole ? ("to " + message.toRole) : null,
|
|
2335
|
+
message.taskId ? ("task " + message.taskId) : null,
|
|
2336
|
+
formatTime(message.createdAt),
|
|
2337
|
+
].filter(Boolean).join(" • ");
|
|
2338
|
+
|
|
2339
|
+
return (
|
|
2340
|
+
'<div class="message-card">' +
|
|
2341
|
+
' <div class="message-header">' +
|
|
2342
|
+
' <span class="message-from">' + escapeHtml(from) + "</span>" +
|
|
2343
|
+
' <span class="message-type ' + escapeHtml(type) + '">' + escapeHtml(humanizeStatus(type)) + "</span>" +
|
|
2344
|
+
" </div>" +
|
|
2345
|
+
(meta ? '<div class="message-meta">' + escapeHtml(meta) + "</div>" : "") +
|
|
2346
|
+
contractBlock +
|
|
2347
|
+
(rawContent ? '<div class="message-content markdown-body">' + rawContent + "</div>" : "") +
|
|
2348
|
+
"</div>"
|
|
2349
|
+
);
|
|
2350
|
+
}).join("");
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
function renderClarificationCards(items, options) {
|
|
2354
|
+
var opts = options || {};
|
|
2355
|
+
return normalizeArray(items).map(function (item) {
|
|
2356
|
+
const status = item.status || "pending";
|
|
2357
|
+
const context = item.context
|
|
2358
|
+
? '<div class="clarification-context"><strong>Context:</strong> ' + escapeHtml(item.context) + "</div>"
|
|
2359
|
+
: "";
|
|
2360
|
+
const taskLink = opts.linkToTask && item.taskId
|
|
2361
|
+
? '<button type="button" class="btn btn-small" data-open-task-id="' + escapeHtml(item.taskId) + '">Open task</button>'
|
|
2362
|
+
: "";
|
|
2363
|
+
const answerBlock = status === "pending"
|
|
2364
|
+
? (
|
|
2365
|
+
'<form class="clarification-answer-form" data-clarification-id="' + escapeHtml(item.id) + '">' +
|
|
2366
|
+
' <label class="clarification-label" for="answer-' + escapeHtml(item.id) + '">Answer as human</label>' +
|
|
2367
|
+
' <textarea id="answer-' + escapeHtml(item.id) + '" name="answer" rows="3" placeholder="Type the exact clarification answer..." required></textarea>' +
|
|
2368
|
+
' <div class="clarification-actions">' +
|
|
2369
|
+
taskLink +
|
|
2370
|
+
' <button type="submit" class="btn btn-primary">Submit Answer</button>' +
|
|
2371
|
+
" </div>" +
|
|
2372
|
+
"</form>"
|
|
2373
|
+
)
|
|
2374
|
+
: (
|
|
2375
|
+
'<div class="clarification-answer">' +
|
|
2376
|
+
' <strong>Answer:</strong> ' + escapeHtml(item.answer || "") +
|
|
2377
|
+
(item.answeredBy ? ' <span class="clarification-answer-meta">(by ' + escapeHtml(item.answeredBy) + ')</span>' : "") +
|
|
2378
|
+
"</div>"
|
|
2379
|
+
);
|
|
2380
|
+
|
|
2381
|
+
return (
|
|
2382
|
+
'<div class="clarification-card' + (opts.compact ? " clarification-card-compact" : "") + '">' +
|
|
2383
|
+
' <div class="clarification-header">' +
|
|
2384
|
+
' <span class="clarification-status ' + escapeHtml(status) + '">' + escapeHtml(humanizeStatus(status)) + "</span>" +
|
|
2385
|
+
' <span class="clarification-time">' + escapeHtml(formatTime(item.updatedAt || item.createdAt)) + "</span>" +
|
|
2386
|
+
" </div>" +
|
|
2387
|
+
' <div class="clarification-question">' + escapeHtml(item.question) + "</div>" +
|
|
2388
|
+
' <div class="clarification-meta">' +
|
|
2389
|
+
' <span><strong>Task:</strong> ' + escapeHtml(item.taskId) + "</span>" +
|
|
2390
|
+
' <span><strong>Role:</strong> ' + escapeHtml(item.requestedByRole || "unknown") + "</span>" +
|
|
2391
|
+
' <span><strong>Requester:</strong> ' + escapeHtml(item.requestedByWorkerId || item.requestedBy || "unknown") + "</span>" +
|
|
2392
|
+
" </div>" +
|
|
2393
|
+
' <div class="clarification-reason"><strong>Blocked because:</strong> ' + escapeHtml(item.blockingReason) + "</div>" +
|
|
2394
|
+
context +
|
|
2395
|
+
answerBlock +
|
|
2396
|
+
"</div>"
|
|
2397
|
+
);
|
|
2398
|
+
}).join("");
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
function renderTaskDetailClarifications() {
|
|
2402
|
+
var container = $("#task-detail-clarifications");
|
|
2403
|
+
if (!container) return;
|
|
2404
|
+
var items = normalizeArray(selectedTaskDetail && selectedTaskDetail.clarifications);
|
|
2405
|
+
if (!items.length) {
|
|
2406
|
+
container.innerHTML = '<div class="task-detail-empty">No clarifications on this task.</div>';
|
|
2407
|
+
return;
|
|
2408
|
+
}
|
|
2409
|
+
container.innerHTML = '<div class="clarifications-list">' + renderClarificationCards(items, { linkToTask: false }) + "</div>";
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
function renderTaskDetailMessages() {
|
|
2413
|
+
var container = $("#task-detail-messages");
|
|
2414
|
+
if (!container) return;
|
|
2415
|
+
var items = normalizeArray(selectedTaskDetail && selectedTaskDetail.messages);
|
|
2416
|
+
if (!items.length) {
|
|
2417
|
+
container.innerHTML = '<div class="task-detail-empty">' + escapeHtml(t("empty.taskMessages")) + "</div>";
|
|
2418
|
+
return;
|
|
2419
|
+
}
|
|
2420
|
+
container.innerHTML = '<div class="messages-feed">' + renderMessageCards(items) + "</div>";
|
|
2421
|
+
}
|
|
2422
|
+
|
|
1247
2423
|
function buildTimelineMessageBody(message) {
|
|
1248
2424
|
const contractBlock = renderTeamMessageContractCard(message && message.contract);
|
|
1249
2425
|
const rawContent = buildMessageDisplayContent(message);
|
|
@@ -1323,7 +2499,7 @@
|
|
|
1323
2499
|
|
|
1324
2500
|
const entries = buildTimelineEntries(task);
|
|
1325
2501
|
if (entries.length === 0) {
|
|
1326
|
-
container.innerHTML = '<div class="task-detail-empty">
|
|
2502
|
+
container.innerHTML = '<div class="task-detail-empty">' + escapeHtml(t("empty.taskHistory")) + "</div>";
|
|
1327
2503
|
return;
|
|
1328
2504
|
}
|
|
1329
2505
|
|
|
@@ -1342,49 +2518,9 @@
|
|
|
1342
2518
|
}).join("") +
|
|
1343
2519
|
"</div>";
|
|
1344
2520
|
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
}
|
|
1349
|
-
|
|
1350
|
-
function renderTaskDetailOutput(task) {
|
|
1351
|
-
const container = $("#task-detail-output");
|
|
1352
|
-
if (!container) return;
|
|
1353
|
-
|
|
1354
|
-
if (!task) {
|
|
1355
|
-
container.innerHTML = '<div class="task-detail-empty">No task selected.</div>';
|
|
1356
|
-
return;
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
const outputEvents = (getSelectedTaskExecution().events || []).filter(function (event) {
|
|
1360
|
-
return ["output", "progress", "error"].indexOf(event.type) !== -1;
|
|
1361
|
-
});
|
|
1362
|
-
|
|
1363
|
-
if (outputEvents.length === 0) {
|
|
1364
|
-
container.innerHTML = '<div class="task-detail-empty">No live output captured yet.</div>';
|
|
1365
|
-
return;
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1368
|
-
container.innerHTML = '<div class="task-detail-output-stream">' +
|
|
1369
|
-
outputEvents.map(function (event) {
|
|
1370
|
-
const label = event.stream || humanizeStatus(event.type || "output");
|
|
1371
|
-
const meta = [formatTime(event.createdAt), event.source || null, event.workerId || event.role || null]
|
|
1372
|
-
.filter(Boolean)
|
|
1373
|
-
.join(" • ");
|
|
1374
|
-
const stateClass = event.type === "error" ? " is-error" : "";
|
|
1375
|
-
return (
|
|
1376
|
-
'<article class="task-output-entry' + stateClass + '">' +
|
|
1377
|
-
' <div class="task-output-header">' +
|
|
1378
|
-
' <div class="task-output-label">' + escapeHtml(label) + "</div>" +
|
|
1379
|
-
(meta ? '<div class="task-output-meta">' + escapeHtml(meta) + "</div>" : "") +
|
|
1380
|
-
" </div>" +
|
|
1381
|
-
' <div class="task-output-body markdown-body">' + renderMarkdownContent(event.message) + "</div>" +
|
|
1382
|
-
"</article>"
|
|
1383
|
-
);
|
|
1384
|
-
}).join("") +
|
|
1385
|
-
"</div>";
|
|
1386
|
-
if (followTaskOutput) {
|
|
1387
|
-
container.scrollTop = container.scrollHeight;
|
|
2521
|
+
container.onscroll = syncTaskTimelineFollowState;
|
|
2522
|
+
if (taskTimelineAutoFollow) {
|
|
2523
|
+
requestAnimationFrame(scrollTaskTimelineToBottom);
|
|
1388
2524
|
}
|
|
1389
2525
|
}
|
|
1390
2526
|
|
|
@@ -1392,7 +2528,7 @@
|
|
|
1392
2528
|
$$(".task-detail-tab").forEach(function (tab) {
|
|
1393
2529
|
tab.classList.toggle("active", tab.dataset.taskDetailTab === selectedTaskDetailTab);
|
|
1394
2530
|
});
|
|
1395
|
-
["
|
|
2531
|
+
["details", "timeline", "clarifications", "messages"].forEach(function (name) {
|
|
1396
2532
|
const panel = $("#task-detail-" + name);
|
|
1397
2533
|
if (panel) {
|
|
1398
2534
|
panel.classList.toggle("active", name === selectedTaskDetailTab);
|
|
@@ -1447,58 +2583,11 @@
|
|
|
1447
2583
|
if (!container) return;
|
|
1448
2584
|
|
|
1449
2585
|
if (clarifications.length === 0) {
|
|
1450
|
-
container.innerHTML = '<div class="empty-state">
|
|
2586
|
+
container.innerHTML = '<div class="empty-state">' + escapeHtml(t("empty.noClarifications")) + "</div>";
|
|
1451
2587
|
return;
|
|
1452
2588
|
}
|
|
1453
2589
|
|
|
1454
|
-
container.innerHTML =
|
|
1455
|
-
const status = item.status || "pending";
|
|
1456
|
-
const context = item.context
|
|
1457
|
-
? '<div class="clarification-context"><strong>Context:</strong> ' + escapeHtml(item.context) + "</div>"
|
|
1458
|
-
: "";
|
|
1459
|
-
const answerBlock = status === "pending"
|
|
1460
|
-
? (
|
|
1461
|
-
'<form class="clarification-answer-form" data-clarification-id="' + escapeHtml(item.id) + '">' +
|
|
1462
|
-
' <label class="clarification-label" for="answer-' + escapeHtml(item.id) + '">Answer as human</label>' +
|
|
1463
|
-
' <textarea id="answer-' + escapeHtml(item.id) + '" name="answer" rows="3" placeholder="Type the exact clarification answer..." required></textarea>' +
|
|
1464
|
-
' <div class="clarification-actions">' +
|
|
1465
|
-
' <button type="submit" class="btn btn-primary">Submit Answer</button>' +
|
|
1466
|
-
" </div>" +
|
|
1467
|
-
"</form>"
|
|
1468
|
-
)
|
|
1469
|
-
: (
|
|
1470
|
-
'<div class="clarification-answer">' +
|
|
1471
|
-
' <strong>Answer:</strong> ' + escapeHtml(item.answer || "") +
|
|
1472
|
-
(item.answeredBy ? ' <span class="clarification-answer-meta">(by ' + escapeHtml(item.answeredBy) + ')</span>' : "") +
|
|
1473
|
-
"</div>"
|
|
1474
|
-
);
|
|
1475
|
-
|
|
1476
|
-
return (
|
|
1477
|
-
'<div class="clarification-card">' +
|
|
1478
|
-
' <div class="clarification-header">' +
|
|
1479
|
-
' <span class="clarification-status ' + escapeHtml(status) + '">' + escapeHtml(humanizeStatus(status)) + "</span>" +
|
|
1480
|
-
' <span class="clarification-time">' + escapeHtml(formatTime(item.updatedAt || item.createdAt)) + "</span>" +
|
|
1481
|
-
" </div>" +
|
|
1482
|
-
' <div class="clarification-question">' + escapeHtml(item.question) + "</div>" +
|
|
1483
|
-
' <div class="clarification-meta">' +
|
|
1484
|
-
' <span><strong>Task:</strong> ' + escapeHtml(item.taskId) + "</span>" +
|
|
1485
|
-
' <span><strong>Role:</strong> ' + escapeHtml(item.requestedByRole || "unknown") + "</span>" +
|
|
1486
|
-
' <span><strong>Requester:</strong> ' + escapeHtml(item.requestedByWorkerId || item.requestedBy || "unknown") + "</span>" +
|
|
1487
|
-
" </div>" +
|
|
1488
|
-
' <div class="clarification-reason"><strong>Blocked because:</strong> ' + escapeHtml(item.blockingReason) + "</div>" +
|
|
1489
|
-
context +
|
|
1490
|
-
answerBlock +
|
|
1491
|
-
"</div>"
|
|
1492
|
-
);
|
|
1493
|
-
}).join("");
|
|
1494
|
-
}
|
|
1495
|
-
|
|
1496
|
-
function renderClarificationCount(count) {
|
|
1497
|
-
const badge = $("#clarifications-tab-count");
|
|
1498
|
-
if (!badge) return;
|
|
1499
|
-
|
|
1500
|
-
badge.textContent = String(count);
|
|
1501
|
-
badge.classList.toggle("has-items", count > 0);
|
|
2590
|
+
container.innerHTML = renderClarificationCards(sortClarifications(clarifications), { linkToTask: true });
|
|
1502
2591
|
}
|
|
1503
2592
|
|
|
1504
2593
|
function renderMessages(messages) {
|
|
@@ -1512,33 +2601,11 @@
|
|
|
1512
2601
|
})
|
|
1513
2602
|
.slice(0, 50);
|
|
1514
2603
|
if (recent.length === 0) {
|
|
1515
|
-
container.innerHTML = '<div class="empty-state">
|
|
2604
|
+
container.innerHTML = '<div class="empty-state">' + escapeHtml(t("empty.noMessages")) + "</div>";
|
|
1516
2605
|
return;
|
|
1517
2606
|
}
|
|
1518
2607
|
|
|
1519
|
-
container.innerHTML = recent
|
|
1520
|
-
const from = message.fromRole || message.from || "unknown";
|
|
1521
|
-
const type = (message.contract && message.contract.intent) || message.type || "direct";
|
|
1522
|
-
const contractBlock = renderTeamMessageContractCard(message.contract);
|
|
1523
|
-
const rawContent = buildMessageDisplayContent(message);
|
|
1524
|
-
const meta = [
|
|
1525
|
-
message.toRole ? ("to " + message.toRole) : null,
|
|
1526
|
-
message.taskId ? ("task " + message.taskId) : null,
|
|
1527
|
-
formatTime(message.createdAt),
|
|
1528
|
-
].filter(Boolean).join(" • ");
|
|
1529
|
-
|
|
1530
|
-
return (
|
|
1531
|
-
'<div class="message-card">' +
|
|
1532
|
-
' <div class="message-header">' +
|
|
1533
|
-
' <span class="message-from">' + escapeHtml(from) + "</span>" +
|
|
1534
|
-
' <span class="message-type ' + escapeHtml(type) + '">' + escapeHtml(humanizeStatus(type)) + "</span>" +
|
|
1535
|
-
" </div>" +
|
|
1536
|
-
(meta ? '<div class="message-meta">' + escapeHtml(meta) + "</div>" : "") +
|
|
1537
|
-
contractBlock +
|
|
1538
|
-
(rawContent ? '<div class="message-content markdown-body">' + rawContent + "</div>" : "") +
|
|
1539
|
-
"</div>"
|
|
1540
|
-
);
|
|
1541
|
-
}).join("");
|
|
2608
|
+
container.innerHTML = renderMessageCards(recent);
|
|
1542
2609
|
}
|
|
1543
2610
|
|
|
1544
2611
|
function renderRoles(roles) {
|
|
@@ -1555,19 +2622,25 @@
|
|
|
1555
2622
|
}).join("");
|
|
1556
2623
|
}
|
|
1557
2624
|
|
|
2625
|
+
function activateTab(nextTab) {
|
|
2626
|
+
activeTab = nextTab || "tasks";
|
|
2627
|
+
$$(".tab").forEach(function (item) {
|
|
2628
|
+
item.classList.toggle("active", item.dataset.tab === activeTab);
|
|
2629
|
+
});
|
|
2630
|
+
$$(".tab-panel").forEach(function (panel) { panel.classList.remove("active"); });
|
|
2631
|
+
const panel = $("#tab-" + activeTab);
|
|
2632
|
+
if (panel) {
|
|
2633
|
+
panel.classList.add("active");
|
|
2634
|
+
}
|
|
2635
|
+
if (activeTab === "workspace") {
|
|
2636
|
+
refreshWorkspaceTree(false);
|
|
2637
|
+
}
|
|
2638
|
+
renderActivitySignals();
|
|
2639
|
+
}
|
|
2640
|
+
|
|
1558
2641
|
$$(".tab").forEach(function (tab) {
|
|
1559
2642
|
tab.addEventListener("click", function () {
|
|
1560
|
-
|
|
1561
|
-
$$(".tab-panel").forEach(function (panel) { panel.classList.remove("active"); });
|
|
1562
|
-
tab.classList.add("active");
|
|
1563
|
-
activeTab = tab.dataset.tab || "tasks";
|
|
1564
|
-
const panel = $("#tab-" + activeTab);
|
|
1565
|
-
if (panel) {
|
|
1566
|
-
panel.classList.add("active");
|
|
1567
|
-
}
|
|
1568
|
-
if (activeTab === "workspace") {
|
|
1569
|
-
refreshWorkspaceTree(false);
|
|
1570
|
-
}
|
|
2643
|
+
activateTab(tab.dataset.tab || "tasks");
|
|
1571
2644
|
});
|
|
1572
2645
|
});
|
|
1573
2646
|
|
|
@@ -1591,7 +2664,47 @@
|
|
|
1591
2664
|
if (workspaceTreeContainer) {
|
|
1592
2665
|
workspaceTreeContainer.addEventListener("click", function (event) {
|
|
1593
2666
|
const target = event.target instanceof Element ? event.target : null;
|
|
1594
|
-
|
|
2667
|
+
if (!target) return;
|
|
2668
|
+
|
|
2669
|
+
// Directory toggle (lazy-load on first expand)
|
|
2670
|
+
const dirToggle = target.closest(".workspace-tree-dir-toggle");
|
|
2671
|
+
if (dirToggle) {
|
|
2672
|
+
const li = dirToggle.closest(".workspace-tree-folder");
|
|
2673
|
+
if (!li) return;
|
|
2674
|
+
const childrenContainer = li.querySelector(".workspace-tree-children");
|
|
2675
|
+
if (!childrenContainer) return;
|
|
2676
|
+
const arrow = dirToggle.querySelector(".workspace-tree-arrow");
|
|
2677
|
+
const isOpen = childrenContainer.style.display !== "none";
|
|
2678
|
+
|
|
2679
|
+
if (isOpen) {
|
|
2680
|
+
childrenContainer.style.display = "none";
|
|
2681
|
+
if (arrow) arrow.textContent = "▸";
|
|
2682
|
+
} else {
|
|
2683
|
+
childrenContainer.style.display = "";
|
|
2684
|
+
if (arrow) arrow.textContent = "▾";
|
|
2685
|
+
// Lazy-load if not yet loaded
|
|
2686
|
+
if (dirToggle.classList.contains("is-lazy")) {
|
|
2687
|
+
dirToggle.classList.remove("is-lazy");
|
|
2688
|
+
var dirPath = dirToggle.dataset.dirPath || "";
|
|
2689
|
+
childrenContainer.innerHTML = '<div class="workspace-tree-loading">Loading…</div>';
|
|
2690
|
+
apiGet("/workspace/subtree?path=" + encodeURIComponent(dirPath)).then(function (data) {
|
|
2691
|
+
var entries = data.entries || [];
|
|
2692
|
+
mergeWorkspaceSubtree(dirPath, entries);
|
|
2693
|
+
if (entries.length === 0) {
|
|
2694
|
+
childrenContainer.innerHTML = '<div class="workspace-tree-empty">(empty)</div>';
|
|
2695
|
+
} else {
|
|
2696
|
+
childrenContainer.innerHTML = renderWorkspaceTreeNodes(entries);
|
|
2697
|
+
}
|
|
2698
|
+
}).catch(function () {
|
|
2699
|
+
childrenContainer.innerHTML = '<div class="workspace-tree-empty">Failed to load</div>';
|
|
2700
|
+
});
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
return;
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
// File click
|
|
2707
|
+
const button = target.closest("[data-workspace-path]");
|
|
1595
2708
|
const relativePath = button && button.dataset ? button.dataset.workspacePath : "";
|
|
1596
2709
|
if (!relativePath) {
|
|
1597
2710
|
return;
|
|
@@ -1649,23 +2762,37 @@
|
|
|
1649
2762
|
});
|
|
1650
2763
|
}
|
|
1651
2764
|
|
|
1652
|
-
const followToggle = $("#task-detail-follow-toggle");
|
|
1653
|
-
if (followToggle) {
|
|
1654
|
-
followToggle.checked = followTaskOutput;
|
|
1655
|
-
followToggle.addEventListener("change", function () {
|
|
1656
|
-
followTaskOutput = !!followToggle.checked;
|
|
1657
|
-
renderTaskDetail();
|
|
1658
|
-
});
|
|
1659
|
-
}
|
|
1660
|
-
|
|
1661
2765
|
$$(".task-detail-tab").forEach(function (tab) {
|
|
1662
2766
|
tab.addEventListener("click", function () {
|
|
1663
|
-
selectedTaskDetailTab = tab.dataset.taskDetailTab || "
|
|
2767
|
+
selectedTaskDetailTab = tab.dataset.taskDetailTab || "details";
|
|
2768
|
+
if (selectedTaskDetailTab === "timeline") {
|
|
2769
|
+
taskTimelineAutoFollow = true;
|
|
2770
|
+
}
|
|
1664
2771
|
syncTaskDetailTab();
|
|
1665
2772
|
renderTaskDetail();
|
|
1666
2773
|
});
|
|
1667
2774
|
});
|
|
1668
2775
|
|
|
2776
|
+
const clarificationPromptClose = $("#clarification-prompt-close");
|
|
2777
|
+
if (clarificationPromptClose) {
|
|
2778
|
+
clarificationPromptClose.addEventListener("click", function () {
|
|
2779
|
+
if (activeClarificationId && dismissedClarificationIds.indexOf(activeClarificationId) === -1) {
|
|
2780
|
+
dismissedClarificationIds.push(activeClarificationId);
|
|
2781
|
+
}
|
|
2782
|
+
clarificationPromptOpen = false;
|
|
2783
|
+
renderClarificationPrompt();
|
|
2784
|
+
});
|
|
2785
|
+
}
|
|
2786
|
+
$$("[data-clarification-prompt-close]").forEach(function (node) {
|
|
2787
|
+
node.addEventListener("click", function () {
|
|
2788
|
+
if (activeClarificationId && dismissedClarificationIds.indexOf(activeClarificationId) === -1) {
|
|
2789
|
+
dismissedClarificationIds.push(activeClarificationId);
|
|
2790
|
+
}
|
|
2791
|
+
clarificationPromptOpen = false;
|
|
2792
|
+
renderClarificationPrompt();
|
|
2793
|
+
});
|
|
2794
|
+
});
|
|
2795
|
+
|
|
1669
2796
|
document.addEventListener("keydown", function (event) {
|
|
1670
2797
|
if (event.key === "Escape") {
|
|
1671
2798
|
closeTaskDetail();
|
|
@@ -1731,7 +2858,14 @@
|
|
|
1731
2858
|
answer: answer,
|
|
1732
2859
|
answeredBy: "simulated-human",
|
|
1733
2860
|
});
|
|
2861
|
+
dismissedClarificationIds = dismissedClarificationIds.filter(function (id) { return id !== clarificationId; });
|
|
2862
|
+
if (activeClarificationId === clarificationId) {
|
|
2863
|
+
activeClarificationId = null;
|
|
2864
|
+
}
|
|
1734
2865
|
refreshAll();
|
|
2866
|
+
if (selectedTaskId) {
|
|
2867
|
+
refreshTaskDetail(true);
|
|
2868
|
+
}
|
|
1735
2869
|
} catch (err) {
|
|
1736
2870
|
console.error("Failed to answer clarification:", err);
|
|
1737
2871
|
showError(err instanceof Error ? err.message : "Failed to answer clarification");
|
|
@@ -1754,6 +2888,79 @@
|
|
|
1754
2888
|
});
|
|
1755
2889
|
}
|
|
1756
2890
|
|
|
2891
|
+
var languageToggle = $("#language-toggle");
|
|
2892
|
+
if (languageToggle) {
|
|
2893
|
+
languageToggle.addEventListener("click", function () {
|
|
2894
|
+
setLanguage(currentLanguage === "zh" ? "en" : "zh");
|
|
2895
|
+
});
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
document.addEventListener("click", function (event) {
|
|
2899
|
+
var target = event.target instanceof Element ? event.target : null;
|
|
2900
|
+
var toggle = target ? target.closest("#worker-install-toggle") : null;
|
|
2901
|
+
if (toggle) {
|
|
2902
|
+
externalWorkerInstallVisible = !externalWorkerInstallVisible;
|
|
2903
|
+
renderExternalWorkerInstallToggle();
|
|
2904
|
+
renderExternalWorkerInstallCard();
|
|
2905
|
+
return;
|
|
2906
|
+
}
|
|
2907
|
+
var button = target ? target.closest("[data-open-task-id]") : null;
|
|
2908
|
+
var taskId = button && button.dataset ? button.dataset.openTaskId : "";
|
|
2909
|
+
if (!taskId || (controllerRunsContainer && controllerRunsContainer.contains(button))) {
|
|
2910
|
+
return;
|
|
2911
|
+
}
|
|
2912
|
+
activateTab("tasks");
|
|
2913
|
+
openTaskDetail(taskId);
|
|
2914
|
+
});
|
|
2915
|
+
|
|
2916
|
+
document.addEventListener("change", function (event) {
|
|
2917
|
+
var target = event.target instanceof Element ? event.target : null;
|
|
2918
|
+
if (!target) return;
|
|
2919
|
+
if (target.matches("[data-worker-install-role]")) {
|
|
2920
|
+
selectedExternalWorkerRole = target.value || selectedExternalWorkerRole;
|
|
2921
|
+
renderExternalWorkerInstallCard();
|
|
2922
|
+
return;
|
|
2923
|
+
}
|
|
2924
|
+
if (target.matches("[data-worker-install-discovery]")) {
|
|
2925
|
+
selectedExternalWorkerDiscoveryMode = target.value === "manual" ? "manual" : "mdns";
|
|
2926
|
+
renderExternalWorkerInstallCard();
|
|
2927
|
+
}
|
|
2928
|
+
});
|
|
2929
|
+
|
|
2930
|
+
document.addEventListener("click", function (event) {
|
|
2931
|
+
var target = event.target instanceof Element ? event.target : null;
|
|
2932
|
+
var button = target ? target.closest("[data-worker-install-copy]") : null;
|
|
2933
|
+
if (!button) return;
|
|
2934
|
+
var command = buildExternalWorkerCommand(
|
|
2935
|
+
teamState.externalWorkerInstall,
|
|
2936
|
+
selectedExternalWorkerRole,
|
|
2937
|
+
selectedExternalWorkerDiscoveryMode,
|
|
2938
|
+
);
|
|
2939
|
+
copyText(command).then(function () {
|
|
2940
|
+
button.textContent = t("action.copied");
|
|
2941
|
+
window.setTimeout(function () {
|
|
2942
|
+
button.textContent = t("action.copyCommand");
|
|
2943
|
+
}, 1200);
|
|
2944
|
+
}).catch(function (err) {
|
|
2945
|
+
console.error(err);
|
|
2946
|
+
showError(err instanceof Error ? err.message : "Failed to copy command");
|
|
2947
|
+
});
|
|
2948
|
+
});
|
|
2949
|
+
|
|
2950
|
+
// Planning session sub-tab click handler
|
|
2951
|
+
var planningSessionList = $("#planning-session-list");
|
|
2952
|
+
if (planningSessionList) {
|
|
2953
|
+
planningSessionList.addEventListener("click", function (event) {
|
|
2954
|
+
var target = event.target instanceof Element ? event.target : null;
|
|
2955
|
+
var btn = target ? target.closest("[data-planning-run]") : null;
|
|
2956
|
+
var runId = btn && btn.dataset ? btn.dataset.planningRun : "";
|
|
2957
|
+
if (runId && runId !== selectedPlanningRunId) {
|
|
2958
|
+
selectedPlanningRunId = runId;
|
|
2959
|
+
renderPlanningTab(teamState.controllerRuns);
|
|
2960
|
+
}
|
|
2961
|
+
});
|
|
2962
|
+
}
|
|
2963
|
+
|
|
1757
2964
|
const cmdInput = $("#command-input");
|
|
1758
2965
|
const cmdSend = $("#command-send");
|
|
1759
2966
|
|
|
@@ -1802,7 +3009,7 @@
|
|
|
1802
3009
|
from: "controller",
|
|
1803
3010
|
fromRole: "controller",
|
|
1804
3011
|
type: "controller-reply",
|
|
1805
|
-
content: data && data.reply ? data.reply : "
|
|
3012
|
+
content: data && data.reply ? data.reply : t("empty.copiedControllerReply"),
|
|
1806
3013
|
});
|
|
1807
3014
|
refreshAll();
|
|
1808
3015
|
}).catch(function (err) {
|
|
@@ -1834,8 +3041,29 @@
|
|
|
1834
3041
|
});
|
|
1835
3042
|
}
|
|
1836
3043
|
|
|
3044
|
+
async function applyInitialUiState() {
|
|
3045
|
+
if (initialUiStateApplied) {
|
|
3046
|
+
return;
|
|
3047
|
+
}
|
|
3048
|
+
initialUiStateApplied = true;
|
|
3049
|
+
|
|
3050
|
+
if (initialUiState.planningRun) {
|
|
3051
|
+
selectedPlanningRunId = initialUiState.planningRun;
|
|
3052
|
+
renderPlanningTab(teamState.controllerRuns);
|
|
3053
|
+
}
|
|
3054
|
+
|
|
3055
|
+
if (initialUiState.tab) {
|
|
3056
|
+
activateTab(initialUiState.tab);
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
if (initialUiState.taskId) {
|
|
3060
|
+
await openTaskDetail(initialUiState.taskId);
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
applyStaticTranslations();
|
|
1837
3065
|
renderWorkspaceTree(workspaceTree);
|
|
1838
3066
|
renderWorkspaceFile();
|
|
1839
|
-
refreshAll();
|
|
3067
|
+
refreshAll().then(applyInitialUiState).catch(function () {});
|
|
1840
3068
|
connectWebSocket();
|
|
1841
3069
|
})();
|