bosun 0.40.21 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/agent-custom-tools.mjs +23 -5
- package/agent/agent-pool.mjs +6 -2
- package/agent/primary-agent.mjs +81 -7
- package/bench/swebench/bosun-swebench.mjs +5 -0
- package/cli.mjs +208 -3
- package/config/config-doctor.mjs +51 -2
- package/config/config.mjs +103 -3
- package/github/github-auth-manager.mjs +70 -19
- package/infra/library-manager.mjs +894 -60
- package/infra/monitor.mjs +8 -2
- package/infra/session-tracker.mjs +13 -3
- package/infra/test-runtime.mjs +267 -0
- package/package.json +8 -5
- package/server/setup-web-server.mjs +4 -1
- package/server/ui-server.mjs +1323 -20
- package/task/task-claims.mjs +6 -10
- package/ui/components/chat-view.js +18 -1
- package/ui/components/workspace-switcher.js +321 -9
- package/ui/demo-defaults.js +11746 -9470
- package/ui/demo.html +9 -1
- package/ui/modules/router.js +1 -1
- package/ui/modules/voice-client-sdk.js +1 -1
- package/ui/modules/voice-client.js +33 -2
- package/ui/styles/components.css +514 -1
- package/ui/tabs/library.js +410 -55
- package/ui/tabs/tasks.js +1052 -506
- package/ui/tabs/workflow-canvas-utils.mjs +30 -0
- package/ui/tabs/workflows.js +914 -298
- package/voice/voice-agents-sdk.mjs +1 -1
- package/voice/voice-relay.mjs +24 -16
- package/workflow/project-detection.mjs +559 -0
- package/workflow/workflow-contract.mjs +433 -232
- package/workflow/workflow-engine.mjs +181 -30
- package/workflow/workflow-nodes.mjs +304 -6
- package/workflow/workflow-templates.mjs +92 -16
- package/workflow-templates/agents.mjs +20 -19
- package/workflow-templates/code-quality.mjs +20 -14
- package/workflow-templates/task-batch.mjs +3 -2
- package/workflow-templates/task-execution.mjs +752 -0
- package/workflow-templates/task-lifecycle.mjs +34 -8
- package/workspace/workspace-manager.mjs +151 -0
package/ui/tabs/tasks.js
CHANGED
|
@@ -2406,6 +2406,43 @@ export function TaskDetailModal({ task, onClose, onStart, presentation = "modal"
|
|
|
2406
2406
|
task?.workflowHistory,
|
|
2407
2407
|
task?.workflows,
|
|
2408
2408
|
]);
|
|
2409
|
+
|
|
2410
|
+
// ── Execution Plan state ──────────────────────────────────────────────────
|
|
2411
|
+
const [executionPlan, setExecutionPlan] = useState(null);
|
|
2412
|
+
const [executionPlanLoading, setExecutionPlanLoading] = useState(false);
|
|
2413
|
+
const [expandedNodes, setExpandedNodes] = useState({});
|
|
2414
|
+
const [expandedStages, setExpandedStages] = useState({});
|
|
2415
|
+
const [dryRunLoading, setDryRunLoading] = useState(false);
|
|
2416
|
+
const [dryRunResults, setDryRunResults] = useState(null);
|
|
2417
|
+
const [fullScreen, setFullScreen] = useState(false);
|
|
2418
|
+
const [activeTab, setActiveTab] = useState("details");
|
|
2419
|
+
|
|
2420
|
+
const fetchExecutionPlan = useCallback((mode = "resolve") => {
|
|
2421
|
+
if (!task?.id) return;
|
|
2422
|
+
if (mode === "resolve") { setExecutionPlan(null); setExecutionPlanLoading(true); }
|
|
2423
|
+
else { setDryRunLoading(true); }
|
|
2424
|
+
const wsParam = typeof window !== "undefined" && window.__bosunWorkspaceId ? `&workspace=${encodeURIComponent(window.__bosunWorkspaceId)}` : "";
|
|
2425
|
+
fetch(`/api/tasks/execution-plan?taskId=${encodeURIComponent(task.id)}${wsParam}&mode=${mode}`)
|
|
2426
|
+
.then((r) => r.json())
|
|
2427
|
+
.then((data) => {
|
|
2428
|
+
if (data?.ok) {
|
|
2429
|
+
if (mode === "resolve") setExecutionPlan(data);
|
|
2430
|
+
else { setExecutionPlan(data); setDryRunResults(data.dryRunResults || null); }
|
|
2431
|
+
}
|
|
2432
|
+
})
|
|
2433
|
+
.catch(() => {})
|
|
2434
|
+
.finally(() => { setExecutionPlanLoading(false); setDryRunLoading(false); });
|
|
2435
|
+
}, [task?.id]);
|
|
2436
|
+
|
|
2437
|
+
useEffect(() => { fetchExecutionPlan("resolve"); }, [task?.id]);
|
|
2438
|
+
|
|
2439
|
+
const toggleNodeExpand = useCallback((stageIdx, nodeId) => {
|
|
2440
|
+
setExpandedNodes((prev) => ({ ...prev, [`${stageIdx}-${nodeId}`]: !prev[`${stageIdx}-${nodeId}`] }));
|
|
2441
|
+
}, []);
|
|
2442
|
+
const toggleStageExpand = useCallback((stageIdx) => {
|
|
2443
|
+
setExpandedStages((prev) => ({ ...prev, [stageIdx]: !prev[stageIdx] }));
|
|
2444
|
+
}, []);
|
|
2445
|
+
|
|
2409
2446
|
const relatedLinks = useMemo(() => buildTaskRelatedLinks(task), [
|
|
2410
2447
|
task?.id,
|
|
2411
2448
|
task?.branch,
|
|
@@ -3055,9 +3092,11 @@ export function TaskDetailModal({ task, onClose, onStart, presentation = "modal"
|
|
|
3055
3092
|
<${Modal}
|
|
3056
3093
|
title=${task?.title || "Task Detail"}
|
|
3057
3094
|
onClose=${onClose}
|
|
3058
|
-
contentClassName=${
|
|
3059
|
-
|
|
3060
|
-
|
|
3095
|
+
contentClassName=${fullScreen
|
|
3096
|
+
? "task-detail-fullscreen"
|
|
3097
|
+
: "modal-content-wide task-detail-modal-jira" + (presentation === "side-sheet" ? " task-detail-side-sheet" : "")}
|
|
3098
|
+
layout=${fullScreen ? "sheet" : (presentation === "side-sheet" ? "side-sheet" : "sheet")}
|
|
3099
|
+
resizable=${!fullScreen && presentation === "side-sheet"}
|
|
3061
3100
|
widthStorageKey="tasks.task-detail.width"
|
|
3062
3101
|
defaultWidth=${900}
|
|
3063
3102
|
unsavedChanges=${changeCount}
|
|
@@ -3068,577 +3107,674 @@ export function TaskDetailModal({ task, onClose, onStart, presentation = "modal"
|
|
|
3068
3107
|
}}
|
|
3069
3108
|
activeOperationLabel=${activeOperationLabel}
|
|
3070
3109
|
>
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
<
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3110
|
+
${/* ── Breadcrumb ── */ ""}
|
|
3111
|
+
<div class="task-detail-breadcrumb">
|
|
3112
|
+
<span>Tasks</span>
|
|
3113
|
+
<span>/</span>
|
|
3114
|
+
<span style="color:var(--color-text);font-weight:500;user-select:all;">${task?.id?.slice(0, 8) || "New"}</span>
|
|
3115
|
+
${task?.priority && html`<span class="task-priority-dot" data-priority=${task.priority}></span>`}
|
|
3116
|
+
${manualOverride && html`<span class="exec-plan-badge" style="background:#fbbf2420;color:#fbbf24;">MANUAL</span>`}
|
|
3117
|
+
</div>
|
|
3118
|
+
|
|
3119
|
+
${/* ── Title + Actions ── */ ""}
|
|
3120
|
+
<div class="task-detail-title-area" style="display:flex;gap:12px;align-items:flex-start;">
|
|
3121
|
+
<div style="flex:1;min-width:0;">
|
|
3122
|
+
<input class="task-detail-title-input" value=${title} onInput=${(e) => setTitle(e.target.value)} placeholder="Task title" />
|
|
3078
3123
|
</div>
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3124
|
+
<div style="display:flex;gap:6px;align-items:center;padding-top:6px;flex-shrink:0;">
|
|
3125
|
+
<button class="task-status-btn" data-status=${status}>
|
|
3126
|
+
${(status || "todo").toUpperCase()}
|
|
3127
|
+
</button>
|
|
3128
|
+
${canDispatch && html`
|
|
3082
3129
|
<${Button} variant="contained" size="small" onClick=${handleStart}>
|
|
3083
|
-
${iconText(":play: Dispatch
|
|
3130
|
+
${iconText(":play: Dispatch")}
|
|
3084
3131
|
<//>
|
|
3085
|
-
|
|
3086
|
-
|
|
3132
|
+
`}
|
|
3133
|
+
<button class="task-action-icon-btn"
|
|
3134
|
+
onClick=${() => setFullScreen(!fullScreen)}
|
|
3135
|
+
title=${fullScreen ? "Exit fullscreen" : "Fullscreen"}>
|
|
3136
|
+
${fullScreen ? resolveIcon("minimize") || "⊟" : resolveIcon("maximize") || "⊞"}
|
|
3137
|
+
</button>
|
|
3138
|
+
</div>
|
|
3087
3139
|
</div>
|
|
3088
3140
|
|
|
3141
|
+
${/* ── Tab Bar (Jira style) ── */ ""}
|
|
3142
|
+
<div class="task-tab-bar">
|
|
3143
|
+
<button class="task-tab-btn" data-active=${activeTab === "details"} onClick=${() => setActiveTab("details")}>
|
|
3144
|
+
${resolveIcon("edit") || "✎"} Details
|
|
3145
|
+
</button>
|
|
3146
|
+
<button class="task-tab-btn" data-active=${activeTab === "execution"} onClick=${() => setActiveTab("execution")}>
|
|
3147
|
+
${resolveIcon("play")} Execution Plan
|
|
3148
|
+
${executionPlan?.stageCount > 0 && html`<span class="task-tab-count">${executionPlan.stageCount}</span>`}
|
|
3149
|
+
</button>
|
|
3150
|
+
<button class="task-tab-btn" data-active=${activeTab === "history"} onClick=${() => setActiveTab("history")}>
|
|
3151
|
+
${resolveIcon("clock") || "⏱"} History
|
|
3152
|
+
${historyEntries.length > 0 && html`<span class="task-tab-count">${historyEntries.length}</span>`}
|
|
3153
|
+
</button>
|
|
3154
|
+
</div>
|
|
3089
3155
|
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3156
|
+
${/* ── Content Body ───────────────────────────────────────────── */ ""}
|
|
3157
|
+
<div style="padding:${fullScreen ? '20px 24px' : '0'};overflow-y:auto;max-height:${fullScreen ? 'calc(100dvh - 140px)' : 'auto'};">
|
|
3158
|
+
|
|
3159
|
+
${/* ── DETAILS TAB — Two-column Jira layout ─────────────────── */ ""}
|
|
3160
|
+
${activeTab === "details" && html`<div class="task-detail-columns" style="max-height:${fullScreen ? 'calc(100dvh - 160px)' : '65vh'};overflow:hidden;">
|
|
3161
|
+
|
|
3162
|
+
${/* ── LEFT: Main Content ── */ ""}
|
|
3163
|
+
<div class="task-detail-main">
|
|
3164
|
+
|
|
3165
|
+
${/* Description */ ""}
|
|
3166
|
+
<div class="task-section">
|
|
3167
|
+
<div class="task-section-title">Description</div>
|
|
3168
|
+
<div class="task-section-body">
|
|
3169
|
+
<div class="textarea-with-mic" style="position:relative">
|
|
3170
|
+
<${TextField} multiline rows=${4} size="small" placeholder="Add a description..." value=${description} onInput=${(e) => setDescription(e.target.value)} style=${{ paddingRight: "36px" }} fullWidth />
|
|
3171
|
+
<${VoiceMicButton}
|
|
3172
|
+
onTranscript=${(t) => setDescription((prev) => (prev ? prev + " " + t : t))}
|
|
3173
|
+
disabled=${saving || rewriting}
|
|
3174
|
+
size="sm"
|
|
3175
|
+
className="textarea-mic-btn"
|
|
3176
|
+
/>
|
|
3105
3177
|
</div>
|
|
3106
|
-
<div
|
|
3107
|
-
|
|
3108
|
-
|
|
3178
|
+
<div style="display:flex;gap:6px;margin-top:8px;">
|
|
3179
|
+
<${Tooltip} title="Use AI to expand and improve this task description">
|
|
3180
|
+
<${Button}
|
|
3181
|
+
variant="text" size="small"
|
|
3182
|
+
style=${{ display: "flex", alignItems: "center", gap: "6px", fontSize: "12px", padding: "5px 10px", opacity: !title.trim() ? 0.45 : 1 }}
|
|
3183
|
+
disabled=${!title.trim() || rewriting || saving}
|
|
3184
|
+
onClick=${async () => {
|
|
3185
|
+
if (!title.trim() || rewriting) return;
|
|
3186
|
+
setRewriting(true);
|
|
3187
|
+
haptic("medium");
|
|
3188
|
+
try {
|
|
3189
|
+
const res = await apiFetch("/api/tasks/rewrite", {
|
|
3190
|
+
method: "POST",
|
|
3191
|
+
body: JSON.stringify({ title: title.trim(), description: description.trim() }),
|
|
3192
|
+
});
|
|
3193
|
+
if (res?.data) {
|
|
3194
|
+
if (res.data.title) setTitle(res.data.title);
|
|
3195
|
+
if (res.data.description) setDescription(res.data.description);
|
|
3196
|
+
showToast("Task description improved", "success");
|
|
3197
|
+
haptic("medium");
|
|
3198
|
+
}
|
|
3199
|
+
} catch { /* toast via apiFetch */ }
|
|
3200
|
+
setRewriting(false);
|
|
3201
|
+
}}
|
|
3202
|
+
>
|
|
3203
|
+
${rewriting
|
|
3204
|
+
? html`<span style="display:inline-block;animation:spin 0.8s linear infinite">${resolveIcon(":clock:")}</span> Improving…`
|
|
3205
|
+
: html`${iconText(":star: Improve with AI")}`
|
|
3206
|
+
}
|
|
3207
|
+
<//>
|
|
3208
|
+
<//>
|
|
3109
3209
|
</div>
|
|
3110
3210
|
</div>
|
|
3111
3211
|
</div>
|
|
3112
|
-
</div>
|
|
3113
3212
|
|
|
3114
|
-
|
|
3115
|
-
<div class="task-
|
|
3116
|
-
<div class="task-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3213
|
+
${/* Attachments */ ""}
|
|
3214
|
+
<div class="task-section" onPaste=${handleAttachmentPaste}>
|
|
3215
|
+
<div class="task-section-title">
|
|
3216
|
+
Attachments
|
|
3217
|
+
<span class="task-tab-count">${attachments.length}</span>
|
|
3218
|
+
<span style="margin-left:auto;">
|
|
3219
|
+
<${Button}
|
|
3220
|
+
variant="text" size="small"
|
|
3221
|
+
type="button"
|
|
3222
|
+
onClick=${() => attachmentInputRef.current && attachmentInputRef.current.click()}
|
|
3223
|
+
disabled=${uploadingAttachment}
|
|
3224
|
+
>
|
|
3225
|
+
Upload
|
|
3226
|
+
<//>
|
|
3227
|
+
</span>
|
|
3228
|
+
</div>
|
|
3229
|
+
<div class="task-section-body">
|
|
3230
|
+
<input
|
|
3231
|
+
ref=${attachmentInputRef}
|
|
3232
|
+
type="file"
|
|
3233
|
+
multiple
|
|
3234
|
+
style="display:none"
|
|
3235
|
+
onChange=${handleAttachmentPick}
|
|
3236
|
+
/>
|
|
3237
|
+
${attachments.length === 0 && !uploadingAttachment && html`
|
|
3238
|
+
<div class="meta-text">No attachments uploaded.</div>
|
|
3239
|
+
`}
|
|
3240
|
+
${uploadingAttachment && html`
|
|
3241
|
+
<div class="meta-text">Uploading attachments...</div>
|
|
3242
|
+
`}
|
|
3243
|
+
${attachments.length > 0 && html`
|
|
3244
|
+
<div class="task-attachments-list">
|
|
3245
|
+
${attachments.map((att, index) => {
|
|
3246
|
+
const name = att.name || att.filename || "attachment";
|
|
3247
|
+
const url = att.url || att.filePath || att.path || "";
|
|
3248
|
+
const size = att.size ? formatBytes(att.size) : "";
|
|
3249
|
+
const isImage = isImageAttachment(att);
|
|
3250
|
+
return html`
|
|
3251
|
+
<div class="task-attachment-item" key=${att.id || `${name}-${index}`}>
|
|
3252
|
+
${isImage && url
|
|
3253
|
+
? html`<img class="task-attachment-thumb" src=${url} alt=${name} />`
|
|
3254
|
+
: html`<span class="task-attachment-icon">${resolveIcon(":link:")}</span>`}
|
|
3255
|
+
<div class="task-attachment-meta">
|
|
3256
|
+
${url
|
|
3257
|
+
? html`<a class="task-attachment-name" href=${url} target="_blank" rel="noopener">${name}</a>`
|
|
3258
|
+
: html`<span class="task-attachment-name">${name}</span>`}
|
|
3259
|
+
<div class="task-attachment-sub">
|
|
3260
|
+
${(att.kind || "file")}${size ? ` · ${size}` : ""}
|
|
3261
|
+
</div>
|
|
3262
|
+
</div>
|
|
3263
|
+
</div>
|
|
3264
|
+
`;
|
|
3265
|
+
})}
|
|
3129
3266
|
</div>
|
|
3130
|
-
`
|
|
3267
|
+
`}
|
|
3131
3268
|
</div>
|
|
3132
3269
|
</div>
|
|
3133
|
-
`}
|
|
3134
3270
|
|
|
3135
|
-
|
|
3136
|
-
<div class="task-
|
|
3137
|
-
<div class="task-
|
|
3138
|
-
|
|
3139
|
-
${
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3271
|
+
${/* Subtasks */ ""}
|
|
3272
|
+
<div class="task-section">
|
|
3273
|
+
<div class="task-section-title">
|
|
3274
|
+
Subtasks
|
|
3275
|
+
${subtasks.length > 0 && html`<span class="task-tab-count">${subtasks.length}</span>`}
|
|
3276
|
+
<span style="margin-left:auto;">
|
|
3277
|
+
<${Button}
|
|
3278
|
+
variant="text"
|
|
3279
|
+
size="small"
|
|
3280
|
+
onClick=${loadSubtasks}
|
|
3281
|
+
disabled=${subtasksLoading || creatingSubtask}
|
|
3282
|
+
>
|
|
3283
|
+
${subtasksLoading ? "Refreshing…" : "Refresh"}
|
|
3284
|
+
<//>
|
|
3285
|
+
</span>
|
|
3286
|
+
</div>
|
|
3287
|
+
<div class="task-section-body">
|
|
3288
|
+
<div class="task-comment-composer" style=${{ marginBottom: "8px" }}>
|
|
3289
|
+
<${TextField}
|
|
3290
|
+
size="small"
|
|
3291
|
+
placeholder="Create subtask summary"
|
|
3292
|
+
value=${subtaskTitle}
|
|
3293
|
+
onInput=${(e) => setSubtaskTitle(e.target.value)}
|
|
3294
|
+
fullWidth
|
|
3295
|
+
/>
|
|
3296
|
+
<${Button}
|
|
3297
|
+
variant="contained"
|
|
3298
|
+
size="small"
|
|
3299
|
+
disabled=${creatingSubtask || !sanitizeTaskText(subtaskTitle || "").trim()}
|
|
3300
|
+
onClick=${handleCreateSubtask}
|
|
3301
|
+
>
|
|
3302
|
+
${creatingSubtask ? "Creating…" : "Add"}
|
|
3303
|
+
<//>
|
|
3304
|
+
</div>
|
|
3305
|
+
<div class="task-comments-list">
|
|
3306
|
+
${!subtasksLoading && !subtasks.length && html`<div class="meta-text">No subtasks yet.</div>`}
|
|
3307
|
+
${subtasks.map((subtask) => html`
|
|
3308
|
+
<div class="task-comment-item" key=${subtask.id}>
|
|
3309
|
+
<div class="task-comment-meta">
|
|
3310
|
+
<span style="user-select:all">${subtask.id}</span>
|
|
3311
|
+
${subtask.status ? ` · ${subtask.status}` : ""}
|
|
3312
|
+
${subtask.storyPoints ? ` · ${subtask.storyPoints} pts` : ""}
|
|
3313
|
+
</div>
|
|
3314
|
+
<div class="task-comment-body">${subtask.title}</div>
|
|
3315
|
+
${subtask.assignee && html`<div class="task-comment-meta">Assignee: ${subtask.assignee}</div>`}
|
|
3144
3316
|
</div>
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
`)}
|
|
3317
|
+
`)}
|
|
3318
|
+
</div>
|
|
3148
3319
|
</div>
|
|
3149
3320
|
</div>
|
|
3150
|
-
`}
|
|
3151
3321
|
|
|
3152
|
-
|
|
3153
|
-
<div class="task-
|
|
3154
|
-
<div class="task-
|
|
3155
|
-
|
|
3156
|
-
${
|
|
3157
|
-
<div class="task-comment-item" key=${`link-${index}`}>
|
|
3158
|
-
<div class="task-comment-meta">${item.kind}</div>
|
|
3159
|
-
<div class="task-comment-body">
|
|
3160
|
-
${item.url
|
|
3161
|
-
? html`<a href=${item.url} target="_blank" rel="noopener">${item.value}</a>`
|
|
3162
|
-
: item.value}
|
|
3163
|
-
</div>
|
|
3164
|
-
</div>
|
|
3165
|
-
`)}
|
|
3322
|
+
${/* Comments & Updates */ ""}
|
|
3323
|
+
<div class="task-section">
|
|
3324
|
+
<div class="task-section-title">
|
|
3325
|
+
Comments & Updates
|
|
3326
|
+
${comments.length > 0 && html`<span class="task-tab-count">${comments.length}</span>`}
|
|
3166
3327
|
</div>
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
<div class="task-attachments-actions">
|
|
3328
|
+
<div class="task-section-body">
|
|
3329
|
+
<div class="task-comments-list">
|
|
3330
|
+
${comments.length > 0
|
|
3331
|
+
? comments.map((comment, index) => html`
|
|
3332
|
+
<div class="task-comment-item" key=${comment.id || `comment-${index}`}>
|
|
3333
|
+
<div class="task-comment-meta">
|
|
3334
|
+
${comment.author ? `@${comment.author}` : "comment"}
|
|
3335
|
+
${comment.createdAt ? ` · ${formatRelative(comment.createdAt)}` : ""}
|
|
3336
|
+
</div>
|
|
3337
|
+
<div class="task-comment-body">${comment.body}</div>
|
|
3338
|
+
</div>
|
|
3339
|
+
`)
|
|
3340
|
+
: html`<div class="meta-text">No comments yet. Add one below.</div>`}
|
|
3341
|
+
</div>
|
|
3342
|
+
<div class="task-comment-composer" style=${{ marginTop: "10px" }}>
|
|
3343
|
+
<${TextField}
|
|
3344
|
+
multiline
|
|
3345
|
+
rows=${2}
|
|
3346
|
+
size="small"
|
|
3347
|
+
placeholder="Add a comment or status update..."
|
|
3348
|
+
value=${commentDraft}
|
|
3349
|
+
onInput=${(e) => setCommentDraft(e.target.value)}
|
|
3350
|
+
fullWidth
|
|
3351
|
+
/>
|
|
3192
3352
|
<${Button}
|
|
3193
|
-
variant="
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3353
|
+
variant="contained"
|
|
3354
|
+
size="small"
|
|
3355
|
+
disabled=${postingComment || !sanitizeTaskText(commentDraft || "").trim()}
|
|
3356
|
+
onClick=${handlePostComment}
|
|
3197
3357
|
>
|
|
3198
|
-
|
|
3358
|
+
${postingComment ? "Posting…" : "Post Comment"}
|
|
3199
3359
|
<//>
|
|
3200
3360
|
</div>
|
|
3201
3361
|
</div>
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3362
|
+
</div>
|
|
3363
|
+
|
|
3364
|
+
${/* Tracking Overview */ ""}
|
|
3365
|
+
<div class="task-section">
|
|
3366
|
+
<div class="task-section-title">Tracking Overview</div>
|
|
3367
|
+
<div class="task-section-body">
|
|
3368
|
+
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;">
|
|
3369
|
+
<div class="task-comment-item">
|
|
3370
|
+
<div class="task-comment-meta">Assigned Agents</div>
|
|
3371
|
+
<div class="task-comment-body">${taskAgents.length ? taskAgents.join(" · ") : "No agent assignment recorded."}</div>
|
|
3372
|
+
</div>
|
|
3373
|
+
<div class="task-comment-item">
|
|
3374
|
+
<div class="task-comment-meta">Workflow Runs</div>
|
|
3375
|
+
<div class="task-comment-body">${workflowRuns.length ? `${workflowRuns.length} linked runs` : "No workflow runs linked yet."}</div>
|
|
3376
|
+
</div>
|
|
3377
|
+
<div class="task-comment-item">
|
|
3378
|
+
<div class="task-comment-meta">Timeline Events</div>
|
|
3379
|
+
<div class="task-comment-body">${historyEntries.length ? `${historyEntries.length} recorded entries` : "No timeline history yet."}</div>
|
|
3380
|
+
</div>
|
|
3381
|
+
<div class="task-comment-item">
|
|
3382
|
+
<div class="task-comment-meta">Branch / PR</div>
|
|
3383
|
+
<div class="task-comment-body">${relatedLinks.length ? relatedLinks.map((item) => `${item.kind}: ${item.value}`).join(" · ") : "No branch or PR links recorded."}</div>
|
|
3384
|
+
</div>
|
|
3385
|
+
</div>
|
|
3386
|
+
</div>
|
|
3387
|
+
</div>
|
|
3388
|
+
|
|
3389
|
+
${/* Workflow Activity */ ""}
|
|
3390
|
+
${workflowRuns.length > 0 && html`
|
|
3391
|
+
<div class="task-section">
|
|
3392
|
+
<div class="task-section-title">Workflow Activity</div>
|
|
3393
|
+
<div class="task-section-body">
|
|
3394
|
+
<div class="task-comments-list">
|
|
3395
|
+
${workflowRuns.map((run, index) => html`
|
|
3396
|
+
<div class="task-comment-item" key=${`workflow-${index}`}>
|
|
3397
|
+
<div class="task-comment-meta">
|
|
3398
|
+
${run.workflowId || "workflow"}
|
|
3399
|
+
${run.runId ? ` · run ${run.runId}` : ""}
|
|
3400
|
+
${run.timestamp ? ` · ${formatRelative(run.timestamp)}` : ""}
|
|
3234
3401
|
</div>
|
|
3402
|
+
<div class="task-comment-body">${run.status || run.result || "No status summary"}</div>
|
|
3403
|
+
${run.result && run.status && run.result !== run.status && html`
|
|
3404
|
+
<div class="task-comment-body">${run.result}</div>
|
|
3405
|
+
`}
|
|
3235
3406
|
</div>
|
|
3236
|
-
|
|
3237
|
-
|
|
3407
|
+
`)}
|
|
3408
|
+
</div>
|
|
3238
3409
|
</div>
|
|
3239
|
-
|
|
3410
|
+
</div>
|
|
3411
|
+
`}
|
|
3412
|
+
|
|
3413
|
+
</div>
|
|
3414
|
+
|
|
3415
|
+
${/* ── RIGHT: Sidebar ── */ ""}
|
|
3416
|
+
<div class="task-detail-sidebar">
|
|
3417
|
+
|
|
3418
|
+
${/* Status */ ""}
|
|
3419
|
+
<div class="task-sidebar-field">
|
|
3420
|
+
<div class="task-sidebar-label">Status</div>
|
|
3421
|
+
<div class="task-sidebar-value">
|
|
3422
|
+
<${Select}
|
|
3423
|
+
size="small"
|
|
3424
|
+
value=${status}
|
|
3425
|
+
onChange=${(e) => {
|
|
3426
|
+
const next = e.target.value;
|
|
3427
|
+
setStatus(next);
|
|
3428
|
+
if (next === "draft") setDraft(true);
|
|
3429
|
+
else if (draft) setDraft(false);
|
|
3430
|
+
}}
|
|
3431
|
+
fullWidth
|
|
3432
|
+
>
|
|
3433
|
+
${["draft", "todo", "inprogress", "inreview", "done", "cancelled"].map(
|
|
3434
|
+
(s) => html`<${MenuItem} value=${s}>${s}</${MenuItem}>`,
|
|
3435
|
+
)}
|
|
3436
|
+
</${Select}>
|
|
3437
|
+
</div>
|
|
3240
3438
|
</div>
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3439
|
+
|
|
3440
|
+
${/* Priority */ ""}
|
|
3441
|
+
<div class="task-sidebar-field">
|
|
3442
|
+
<div class="task-sidebar-label">Priority</div>
|
|
3443
|
+
<div class="task-sidebar-value">
|
|
3444
|
+
<${Select}
|
|
3445
|
+
size="small"
|
|
3446
|
+
value=${priority}
|
|
3447
|
+
onChange=${(e) => setPriority(e.target.value)}
|
|
3448
|
+
fullWidth
|
|
3449
|
+
>
|
|
3450
|
+
<${MenuItem} value="">No priority</${MenuItem}>
|
|
3451
|
+
${["low", "medium", "high", "critical"].map(
|
|
3452
|
+
(p) => html`<${MenuItem} value=${p}>${p}</${MenuItem}>`,
|
|
3453
|
+
)}
|
|
3454
|
+
</${Select}>
|
|
3255
3455
|
</div>
|
|
3256
|
-
|
|
3456
|
+
</div>
|
|
3457
|
+
|
|
3458
|
+
${/* Assignee */ ""}
|
|
3459
|
+
<div class="task-sidebar-field">
|
|
3460
|
+
<div class="task-sidebar-label">Assignee</div>
|
|
3461
|
+
<div class="task-sidebar-value">
|
|
3257
3462
|
<${TextField}
|
|
3258
|
-
multiline
|
|
3259
|
-
rows=${2}
|
|
3260
3463
|
size="small"
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3464
|
+
variant="outlined"
|
|
3465
|
+
placeholder="Assignee"
|
|
3466
|
+
value=${assignee}
|
|
3467
|
+
onInput=${(e) => setAssignee(e.target.value)}
|
|
3264
3468
|
fullWidth
|
|
3265
3469
|
/>
|
|
3266
|
-
|
|
3267
|
-
|
|
3470
|
+
</div>
|
|
3471
|
+
</div>
|
|
3472
|
+
|
|
3473
|
+
${/* Assignees */ ""}
|
|
3474
|
+
<div class="task-sidebar-field">
|
|
3475
|
+
<div class="task-sidebar-label">Assignees</div>
|
|
3476
|
+
<div class="task-sidebar-value">
|
|
3477
|
+
<${TextField}
|
|
3268
3478
|
size="small"
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3479
|
+
variant="outlined"
|
|
3480
|
+
placeholder="alice, bob"
|
|
3481
|
+
value=${assigneesInput}
|
|
3482
|
+
onInput=${(e) => setAssigneesInput(e.target.value)}
|
|
3483
|
+
fullWidth
|
|
3484
|
+
/>
|
|
3274
3485
|
</div>
|
|
3275
3486
|
</div>
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3487
|
+
|
|
3488
|
+
${/* Sprint */ ""}
|
|
3489
|
+
<div class="task-sidebar-field">
|
|
3490
|
+
<div class="task-sidebar-label">Sprint</div>
|
|
3491
|
+
<div class="task-sidebar-value">
|
|
3492
|
+
<div style="display:flex;gap:6px;">
|
|
3493
|
+
<${Select}
|
|
3282
3494
|
size="small"
|
|
3283
|
-
|
|
3284
|
-
|
|
3495
|
+
value=${selectedSprintId}
|
|
3496
|
+
onChange=${(e) => setSelectedSprintId(e.target.value)}
|
|
3497
|
+
style=${{ flex: 1 }}
|
|
3285
3498
|
>
|
|
3286
|
-
|
|
3287
|
-
|
|
3499
|
+
<${MenuItem} value="">No sprint</${MenuItem}>
|
|
3500
|
+
${sprintOptions.map((sprint) => html`
|
|
3501
|
+
<${MenuItem} key=${sprint.id} value=${sprint.id}>${sprint.label}</${MenuItem}>
|
|
3502
|
+
`)}
|
|
3503
|
+
</${Select}>
|
|
3504
|
+
<${TextField}
|
|
3505
|
+
size="small"
|
|
3506
|
+
type="number"
|
|
3507
|
+
placeholder="#"
|
|
3508
|
+
value=${sprintOrderInput}
|
|
3509
|
+
onInput=${(e) => setSprintOrderInput(e.target.value)}
|
|
3510
|
+
inputProps=${{ min: 1, step: 1 }}
|
|
3511
|
+
style=${{ width: "60px" }}
|
|
3512
|
+
/>
|
|
3288
3513
|
</div>
|
|
3289
3514
|
</div>
|
|
3290
|
-
|
|
3515
|
+
</div>
|
|
3516
|
+
|
|
3517
|
+
${/* Story Points */ ""}
|
|
3518
|
+
<div class="task-sidebar-field">
|
|
3519
|
+
<div class="task-sidebar-label">Story Points</div>
|
|
3520
|
+
<div class="task-sidebar-value">
|
|
3291
3521
|
<${TextField}
|
|
3292
3522
|
size="small"
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3523
|
+
variant="outlined"
|
|
3524
|
+
type="number"
|
|
3525
|
+
placeholder="Points"
|
|
3526
|
+
value=${storyPoints}
|
|
3527
|
+
onInput=${(e) => setStoryPoints(e.target.value)}
|
|
3296
3528
|
fullWidth
|
|
3297
3529
|
/>
|
|
3298
|
-
|
|
3299
|
-
|
|
3530
|
+
</div>
|
|
3531
|
+
</div>
|
|
3532
|
+
|
|
3533
|
+
${/* Due Date */ ""}
|
|
3534
|
+
<div class="task-sidebar-field">
|
|
3535
|
+
<div class="task-sidebar-label">Due Date</div>
|
|
3536
|
+
<div class="task-sidebar-value">
|
|
3537
|
+
<${TextField}
|
|
3300
3538
|
size="small"
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3539
|
+
variant="outlined"
|
|
3540
|
+
type="date"
|
|
3541
|
+
value=${dueDate}
|
|
3542
|
+
onInput=${(e) => setDueDate(e.target.value)}
|
|
3543
|
+
InputLabelProps=${{ shrink: true }}
|
|
3544
|
+
fullWidth
|
|
3545
|
+
/>
|
|
3306
3546
|
</div>
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3547
|
+
</div>
|
|
3548
|
+
|
|
3549
|
+
${/* Epic */ ""}
|
|
3550
|
+
<div class="task-sidebar-field">
|
|
3551
|
+
<div class="task-sidebar-label">Epic</div>
|
|
3552
|
+
<div class="task-sidebar-value">
|
|
3553
|
+
<${TextField}
|
|
3554
|
+
size="small"
|
|
3555
|
+
variant="outlined"
|
|
3556
|
+
placeholder="Epic"
|
|
3557
|
+
value=${epicId}
|
|
3558
|
+
onInput=${(e) => setEpicId(e.target.value)}
|
|
3559
|
+
fullWidth
|
|
3560
|
+
/>
|
|
3561
|
+
${epicCatalog.length > 0 && html`
|
|
3562
|
+
<div class="tag-row" style=${{ marginTop: "6px" }}>
|
|
3563
|
+
${epicCatalog.slice(0, 6).map((entry) => html`<button type="button" class="tag-chip task-structure-chip ${epicId === entry.id ? "task-structure-chip-active" : ""}" style="font-size:10px;" onClick=${() => setEpicId(entry.id)}>${entry.label}</button>`)}
|
|
3318
3564
|
</div>
|
|
3319
|
-
`
|
|
3565
|
+
`}
|
|
3320
3566
|
</div>
|
|
3321
3567
|
</div>
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
rows=${2}
|
|
3328
|
-
size="small"
|
|
3329
|
-
placeholder="Dependency task IDs (comma or newline separated)"
|
|
3330
|
-
value=${dependenciesInput}
|
|
3331
|
-
onInput=${(e) => setDependenciesInput(e.target.value)}
|
|
3332
|
-
fullWidth
|
|
3333
|
-
/>
|
|
3334
|
-
${currentDependencyIds.length > 0 && html`
|
|
3335
|
-
<div class="tag-row" style=${{ marginTop: "8px" }}>
|
|
3336
|
-
${currentDependencyIds.map((depId) => html`<button type="button" class="tag-chip task-structure-chip" onClick=${() => handleDependencyChipRemove(depId)} title="Remove dependency">${depId} ×</button>`)}
|
|
3337
|
-
</div>
|
|
3338
|
-
`}
|
|
3339
|
-
${dependencySuggestions.length > 0 && html`
|
|
3340
|
-
<div style=${{ marginTop: "8px" }}>
|
|
3341
|
-
<div class="meta-text" style=${{ marginBottom: "6px" }}>Quick add dependencies</div>
|
|
3342
|
-
<div class="tag-row">
|
|
3343
|
-
${dependencySuggestions.map((entry) => html`<button type="button" class="tag-chip task-structure-chip task-structure-chip-muted" onClick=${() => handleDependencyChipAdd(entry.id)}>${entry.id}: ${truncate(entry.title || entry.id, 26)}</button>`)}
|
|
3344
|
-
</div>
|
|
3345
|
-
</div>
|
|
3346
|
-
`}
|
|
3347
|
-
<div class="input-row" style=${{ marginTop: "8px" }}>
|
|
3348
|
-
<${Select}
|
|
3349
|
-
size="small"
|
|
3350
|
-
value=${selectedSprintId}
|
|
3351
|
-
onChange=${(e) => setSelectedSprintId(e.target.value)}
|
|
3352
|
-
>
|
|
3353
|
-
<${MenuItem} value="">No sprint</${MenuItem}>
|
|
3354
|
-
${sprintOptions.map((sprint) => html`
|
|
3355
|
-
<${MenuItem} key=${sprint.id} value=${sprint.id}>${sprint.label}</${MenuItem}>
|
|
3356
|
-
`)}
|
|
3357
|
-
</${Select}>
|
|
3568
|
+
|
|
3569
|
+
${/* Parent Task */ ""}
|
|
3570
|
+
<div class="task-sidebar-field">
|
|
3571
|
+
<div class="task-sidebar-label">Parent Task</div>
|
|
3572
|
+
<div class="task-sidebar-value">
|
|
3358
3573
|
<${TextField}
|
|
3359
3574
|
size="small"
|
|
3360
|
-
|
|
3361
|
-
placeholder="
|
|
3362
|
-
value=${
|
|
3363
|
-
onInput=${(e) =>
|
|
3364
|
-
|
|
3575
|
+
variant="outlined"
|
|
3576
|
+
placeholder="Parent task ID"
|
|
3577
|
+
value=${parentTaskId}
|
|
3578
|
+
onInput=${(e) => setParentTaskId(e.target.value)}
|
|
3579
|
+
fullWidth
|
|
3365
3580
|
/>
|
|
3366
3581
|
</div>
|
|
3367
|
-
<div class="btn-row" style=${{ marginTop: "8px", flexWrap: "wrap" }}>
|
|
3368
|
-
<${Button} variant="outlined" size="small" disabled=${savingDependencies} onClick=${handleSaveDependencies}>
|
|
3369
|
-
${savingDependencies ? "Saving…" : "Save Dependencies"}
|
|
3370
|
-
<//>
|
|
3371
|
-
<${Button} variant="outlined" size="small" disabled=${savingSprint || !selectedSprintId} onClick=${handleSaveSprintAssignment}>
|
|
3372
|
-
${savingSprint ? "Saving…" : "Save Sprint Assignment"}
|
|
3373
|
-
<//>
|
|
3374
|
-
<${Button} variant="text" size="small" disabled=${savingSprint} onClick=${() => handleSprintOrderNudge(-1)}>↑ Earlier</${Button}>
|
|
3375
|
-
<${Button} variant="text" size="small" disabled=${savingSprint} onClick=${() => handleSprintOrderNudge(1)}>↓ Later</${Button}>
|
|
3376
|
-
${dependencyFeedback && html`<span class="meta-text">${dependencyFeedback}</span>`}
|
|
3377
|
-
</div>
|
|
3378
|
-
${epicCatalog.length > 0 && html`
|
|
3379
|
-
<div style=${{ marginTop: "10px" }}>
|
|
3380
|
-
<div class="meta-text" style=${{ marginBottom: "6px" }}>Epic shortcuts</div>
|
|
3381
|
-
<div class="tag-row">
|
|
3382
|
-
${epicCatalog.slice(0, 12).map((entry) => html`<button type="button" class="tag-chip task-structure-chip ${epicId === entry.id ? "task-structure-chip-active" : ""}" onClick=${() => setEpicId(entry.id)}>${entry.label}</button>`)}
|
|
3383
|
-
</div>
|
|
3384
|
-
</div>
|
|
3385
|
-
`}
|
|
3386
3582
|
</div>
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
if (res.data.title) setTitle(res.data.title);
|
|
3402
|
-
if (res.data.description) setDescription(res.data.description);
|
|
3403
|
-
showToast("Task description improved", "success");
|
|
3404
|
-
haptic("medium");
|
|
3405
|
-
}
|
|
3406
|
-
} catch { /* toast via apiFetch */ }
|
|
3407
|
-
setRewriting(false);
|
|
3408
|
-
}}
|
|
3409
|
-
>
|
|
3410
|
-
${rewriting
|
|
3411
|
-
? html`<span style="display:inline-block;animation:spin 0.8s linear infinite">${resolveIcon(":clock:")}</span> Improving…`
|
|
3412
|
-
: html`${iconText(":star: Improve with AI")}`
|
|
3413
|
-
}
|
|
3414
|
-
<//><//>
|
|
3415
|
-
<${TextField} size="small" variant="outlined" className="modal-form-span" placeholder="Base branch (optional, e.g. feature/xyz)" value=${baseBranch} onInput=${(e) => setBaseBranch(e.target.value)} fullWidth /> <div class="modal-form-span jira-meta-grid">
|
|
3416
|
-
<${TextField}
|
|
3417
|
-
size="small"
|
|
3418
|
-
variant="outlined"
|
|
3419
|
-
label="Assignee"
|
|
3420
|
-
value=${assignee}
|
|
3421
|
-
onInput=${(e) => setAssignee(e.target.value)}
|
|
3422
|
-
fullWidth
|
|
3423
|
-
/>
|
|
3424
|
-
<${TextField}
|
|
3425
|
-
size="small"
|
|
3426
|
-
variant="outlined"
|
|
3427
|
-
label="Assignees"
|
|
3428
|
-
placeholder="alice, bob"
|
|
3429
|
-
value=${assigneesInput}
|
|
3430
|
-
onInput=${(e) => setAssigneesInput(e.target.value)}
|
|
3431
|
-
fullWidth
|
|
3432
|
-
/>
|
|
3433
|
-
<${TextField}
|
|
3434
|
-
size="small"
|
|
3435
|
-
variant="outlined"
|
|
3436
|
-
label="Epic"
|
|
3437
|
-
value=${epicId}
|
|
3438
|
-
onInput=${(e) => setEpicId(e.target.value)}
|
|
3439
|
-
fullWidth
|
|
3440
|
-
/>
|
|
3441
|
-
<${TextField}
|
|
3442
|
-
size="small"
|
|
3443
|
-
variant="outlined"
|
|
3444
|
-
type="number"
|
|
3445
|
-
label="Story Points"
|
|
3446
|
-
value=${storyPoints}
|
|
3447
|
-
onInput=${(e) => setStoryPoints(e.target.value)}
|
|
3448
|
-
fullWidth
|
|
3449
|
-
/>
|
|
3450
|
-
<${TextField}
|
|
3451
|
-
size="small"
|
|
3452
|
-
variant="outlined"
|
|
3453
|
-
type="date"
|
|
3454
|
-
label="Due Date"
|
|
3455
|
-
value=${dueDate}
|
|
3456
|
-
onInput=${(e) => setDueDate(e.target.value)}
|
|
3457
|
-
InputLabelProps=${{ shrink: true }}
|
|
3458
|
-
fullWidth
|
|
3459
|
-
/>
|
|
3460
|
-
<${TextField}
|
|
3461
|
-
size="small"
|
|
3462
|
-
variant="outlined"
|
|
3463
|
-
label="Parent Task"
|
|
3464
|
-
value=${parentTaskId}
|
|
3465
|
-
onInput=${(e) => setParentTaskId(e.target.value)}
|
|
3466
|
-
fullWidth
|
|
3467
|
-
/>
|
|
3583
|
+
|
|
3584
|
+
${/* Tags */ ""}
|
|
3585
|
+
<div class="task-sidebar-field">
|
|
3586
|
+
<div class="task-sidebar-label">Tags</div>
|
|
3587
|
+
<div class="task-sidebar-value">
|
|
3588
|
+
<${TextField} size="small" variant="outlined" placeholder="Tags (comma-separated)" value=${tagsInput} onInput=${(e) => setTagsInput(e.target.value)} fullWidth />
|
|
3589
|
+
${normalizeTagInput(tagsInput).length > 0 && html`
|
|
3590
|
+
<div class="tag-row" style=${{ marginTop: "4px" }}>
|
|
3591
|
+
${normalizeTagInput(tagsInput).map(
|
|
3592
|
+
(tag) => html`<span class="tag-chip">#${tag}</span>`,
|
|
3593
|
+
)}
|
|
3594
|
+
</div>
|
|
3595
|
+
`}
|
|
3596
|
+
</div>
|
|
3468
3597
|
</div>
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
(
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
>
|
|
3486
|
-
<${MenuItem} value="">
|
|
3487
|
-
${repositoryOptions.length ? "Auto repository" : "No repos in workspace"}
|
|
3488
|
-
</${MenuItem}>
|
|
3489
|
-
${repositoryOptions.map(
|
|
3490
|
-
(repo) =>
|
|
3491
|
-
html`<${MenuItem} value=${repo.slug}>${repo.name}${repo.primary ? " (Primary)" : ""}</${MenuItem}>`,
|
|
3492
|
-
)}
|
|
3493
|
-
</${Select}>
|
|
3598
|
+
|
|
3599
|
+
${/* Workspace & Repository */ ""}
|
|
3600
|
+
<div class="task-sidebar-field">
|
|
3601
|
+
<div class="task-sidebar-label">Workspace</div>
|
|
3602
|
+
<div class="task-sidebar-value">
|
|
3603
|
+
<${Select}
|
|
3604
|
+
size="small"
|
|
3605
|
+
value=${workspaceId}
|
|
3606
|
+
onChange=${(e) => setWorkspaceId(e.target.value)}
|
|
3607
|
+
fullWidth
|
|
3608
|
+
>
|
|
3609
|
+
<${MenuItem} value="">Active workspace</${MenuItem}>
|
|
3610
|
+
${workspaceOptions.map(
|
|
3611
|
+
(ws) => html`<${MenuItem} value=${ws.id}>${ws.name || ws.id}</${MenuItem}>`,
|
|
3612
|
+
)}
|
|
3613
|
+
</${Select}>
|
|
3614
|
+
</div>
|
|
3494
3615
|
</div>
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
<div class="
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3616
|
+
|
|
3617
|
+
<div class="task-sidebar-field">
|
|
3618
|
+
<div class="task-sidebar-label">Repository</div>
|
|
3619
|
+
<div class="task-sidebar-value">
|
|
3620
|
+
<${Select}
|
|
3621
|
+
size="small"
|
|
3622
|
+
value=${repository}
|
|
3623
|
+
onChange=${(e) => setRepository(e.target.value)}
|
|
3624
|
+
disabled=${!repositoryOptions.length}
|
|
3625
|
+
fullWidth
|
|
3626
|
+
>
|
|
3627
|
+
<${MenuItem} value="">
|
|
3628
|
+
${repositoryOptions.length ? "Auto repository" : "No repos in workspace"}
|
|
3629
|
+
</${MenuItem}>
|
|
3630
|
+
${repositoryOptions.map(
|
|
3631
|
+
(repo) =>
|
|
3632
|
+
html`<${MenuItem} value=${repo.slug}>${repo.name}${repo.primary ? " (Primary)" : ""}</${MenuItem}>`,
|
|
3633
|
+
)}
|
|
3634
|
+
</${Select}>
|
|
3502
3635
|
</div>
|
|
3503
|
-
|
|
3636
|
+
</div>
|
|
3504
3637
|
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
setStatus(next);
|
|
3512
|
-
if (next === "draft") setDraft(true);
|
|
3513
|
-
else if (draft) setDraft(false);
|
|
3514
|
-
}}
|
|
3515
|
-
>
|
|
3516
|
-
${["draft", "todo", "inprogress", "inreview", "done", "cancelled"].map(
|
|
3517
|
-
(s) => html`<${MenuItem} value=${s}>${s}</${MenuItem}>`,
|
|
3518
|
-
)}
|
|
3519
|
-
</${Select}>
|
|
3520
|
-
<${Select}
|
|
3521
|
-
size="small"
|
|
3522
|
-
value=${priority}
|
|
3523
|
-
onChange=${(e) => setPriority(e.target.value)}
|
|
3524
|
-
>
|
|
3525
|
-
<${MenuItem} value="">No priority</${MenuItem}>
|
|
3526
|
-
${["low", "medium", "high", "critical"].map(
|
|
3527
|
-
(p) => html`<${MenuItem} value=${p}>${p}</${MenuItem}>`,
|
|
3528
|
-
)}
|
|
3529
|
-
</${Select}>
|
|
3638
|
+
${/* Base Branch */ ""}
|
|
3639
|
+
<div class="task-sidebar-field">
|
|
3640
|
+
<div class="task-sidebar-label">Base Branch</div>
|
|
3641
|
+
<div class="task-sidebar-value">
|
|
3642
|
+
<${TextField} size="small" variant="outlined" placeholder="e.g. feature/xyz" value=${baseBranch} onInput=${(e) => setBaseBranch(e.target.value)} fullWidth />
|
|
3643
|
+
</div>
|
|
3530
3644
|
</div>
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3645
|
+
|
|
3646
|
+
${/* Draft toggle */ ""}
|
|
3647
|
+
<div class="task-sidebar-field">
|
|
3648
|
+
<div class="task-sidebar-label">Draft</div>
|
|
3649
|
+
<div class="task-sidebar-value">
|
|
3650
|
+
<${Toggle}
|
|
3651
|
+
label="Keep in backlog"
|
|
3652
|
+
checked=${draft}
|
|
3653
|
+
onChange=${(next) => {
|
|
3654
|
+
setDraft(next);
|
|
3655
|
+
if (next) setStatus("draft");
|
|
3656
|
+
else if (status === "draft") setStatus("todo");
|
|
3657
|
+
}}
|
|
3658
|
+
/>
|
|
3659
|
+
</div>
|
|
3541
3660
|
</div>
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3661
|
+
|
|
3662
|
+
${/* Manual Override toggle */ ""}
|
|
3663
|
+
<div class="task-sidebar-field">
|
|
3664
|
+
<div class="task-sidebar-label">Manual</div>
|
|
3665
|
+
<div class="task-sidebar-value">
|
|
3666
|
+
<${Toggle}
|
|
3667
|
+
label="Exclude from automation"
|
|
3668
|
+
checked=${manualOverride}
|
|
3669
|
+
disabled=${manualBusy || !task?.id}
|
|
3670
|
+
onChange=${handleManualToggle}
|
|
3671
|
+
/>
|
|
3672
|
+
${manualOverride && html`
|
|
3673
|
+
<${TextField} size="small" variant="outlined" placeholder="Reason (optional)" value=${manualReason} disabled=${manualBusy} onInput=${(e) => setManualReason(e.target.value)} fullWidth style=${{ marginTop: "6px" }} />
|
|
3674
|
+
<div class="meta-text" style=${{ marginTop: "4px" }}>Bosun will skip this task until cleared.</div>
|
|
3675
|
+
`}
|
|
3676
|
+
</div>
|
|
3549
3677
|
</div>
|
|
3550
|
-
|
|
3551
|
-
${
|
|
3552
|
-
|
|
3553
|
-
<div class="
|
|
3554
|
-
|
|
3555
|
-
|
|
3678
|
+
|
|
3679
|
+
${/* Dependencies */ ""}
|
|
3680
|
+
<div class="task-sidebar-field" style="flex-direction:column;gap:6px;">
|
|
3681
|
+
<div class="task-sidebar-label" style="width:auto;">Dependencies</div>
|
|
3682
|
+
<div class="task-sidebar-value">
|
|
3683
|
+
${currentEpicEntry && html`<div class="meta-text" style=${{ marginBottom: "6px" }}>Epic: ${currentEpicEntry.label} · ${currentEpicEntry.taskCount} tasks</div>`}
|
|
3684
|
+
<${TextField}
|
|
3685
|
+
multiline
|
|
3686
|
+
rows=${2}
|
|
3687
|
+
size="small"
|
|
3688
|
+
placeholder="Dependency task IDs (comma or newline separated)"
|
|
3689
|
+
value=${dependenciesInput}
|
|
3690
|
+
onInput=${(e) => setDependenciesInput(e.target.value)}
|
|
3691
|
+
fullWidth
|
|
3692
|
+
/>
|
|
3693
|
+
${currentDependencyIds.length > 0 && html`
|
|
3694
|
+
<div class="tag-row" style=${{ marginTop: "6px" }}>
|
|
3695
|
+
${currentDependencyIds.map((depId) => html`<button type="button" class="tag-chip task-structure-chip" onClick=${() => handleDependencyChipRemove(depId)} title="Remove dependency">${depId} ×</button>`)}
|
|
3696
|
+
</div>
|
|
3697
|
+
`}
|
|
3698
|
+
${dependencySuggestions.length > 0 && html`
|
|
3699
|
+
<div style=${{ marginTop: "6px" }}>
|
|
3700
|
+
<div class="meta-text" style=${{ marginBottom: "4px" }}>Quick add</div>
|
|
3701
|
+
<div class="tag-row">
|
|
3702
|
+
${dependencySuggestions.map((entry) => html`<button type="button" class="tag-chip task-structure-chip task-structure-chip-muted" onClick=${() => handleDependencyChipAdd(entry.id)}>${entry.id}: ${truncate(entry.title || entry.id, 20)}</button>`)}
|
|
3703
|
+
</div>
|
|
3704
|
+
</div>
|
|
3705
|
+
`}
|
|
3706
|
+
<div style="display:flex;gap:4px;margin-top:6px;flex-wrap:wrap;">
|
|
3707
|
+
<${Button} variant="outlined" size="small" disabled=${savingDependencies} onClick=${handleSaveDependencies}>
|
|
3708
|
+
${savingDependencies ? "Saving…" : "Save Deps"}
|
|
3709
|
+
<//>
|
|
3710
|
+
<${Button} variant="outlined" size="small" disabled=${savingSprint || !selectedSprintId} onClick=${handleSaveSprintAssignment}>
|
|
3711
|
+
${savingSprint ? "Saving…" : "Save Sprint"}
|
|
3712
|
+
<//>
|
|
3713
|
+
<${Button} variant="text" size="small" disabled=${savingSprint} onClick=${() => handleSprintOrderNudge(-1)}>↑<//>
|
|
3714
|
+
<${Button} variant="text" size="small" disabled=${savingSprint} onClick=${() => handleSprintOrderNudge(1)}>↓<//>
|
|
3556
3715
|
</div>
|
|
3557
|
-
${
|
|
3558
|
-
html`<div class="meta-text">Reason: ${manualReason}</div>`}
|
|
3716
|
+
${dependencyFeedback && html`<span class="meta-text">${dependencyFeedback}</span>`}
|
|
3559
3717
|
</div>
|
|
3560
|
-
|
|
3718
|
+
</div>
|
|
3561
3719
|
|
|
3562
|
-
${
|
|
3563
|
-
html`
|
|
3564
|
-
<div class="
|
|
3565
|
-
|
|
3720
|
+
${/* Meta info */ ""}
|
|
3721
|
+
${task?.meta?.triggerTemplate?.id && html`
|
|
3722
|
+
<div class="task-sidebar-field">
|
|
3723
|
+
<div class="task-sidebar-label">Trigger</div>
|
|
3724
|
+
<div class="task-sidebar-value" style="font-size:11px;opacity:0.7;">${task.meta.triggerTemplate.id}</div>
|
|
3566
3725
|
</div>
|
|
3567
3726
|
`}
|
|
3568
|
-
${task?.
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
${task?.meta?.triggerTemplate?.id &&
|
|
3575
|
-
html`
|
|
3576
|
-
<div class="meta-text modal-form-span">
|
|
3577
|
-
Trigger Template: ${task.meta.triggerTemplate.id}
|
|
3727
|
+
${(task?.meta?.execution?.sdk || task?.meta?.execution?.model) && html`
|
|
3728
|
+
<div class="task-sidebar-field">
|
|
3729
|
+
<div class="task-sidebar-label">Exec Override</div>
|
|
3730
|
+
<div class="task-sidebar-value" style="font-size:11px;opacity:0.7;">
|
|
3731
|
+
${task?.meta?.execution?.sdk || "auto"}${task?.meta?.execution?.model ? ` · ${task.meta.execution.model}` : ""}
|
|
3732
|
+
</div>
|
|
3578
3733
|
</div>
|
|
3579
3734
|
`}
|
|
3580
|
-
${
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
${task?.meta?.execution?.sdk || "auto"}
|
|
3585
|
-
${task?.meta?.execution?.model
|
|
3586
|
-
? html` · ${task.meta.execution.model}`
|
|
3587
|
-
: ""}
|
|
3588
|
-
</div>
|
|
3589
|
-
`}
|
|
3590
|
-
${task?.assignee &&
|
|
3591
|
-
html` <div class="meta-text modal-form-span">Assignee: ${task.assignee}</div> `}
|
|
3592
|
-
${task?.branch &&
|
|
3593
|
-
html`
|
|
3594
|
-
<div class="meta-text modal-form-span" style="user-select:all">
|
|
3595
|
-
Branch: ${task.branch}
|
|
3735
|
+
${task?.branch && html`
|
|
3736
|
+
<div class="task-sidebar-field">
|
|
3737
|
+
<div class="task-sidebar-label">Branch</div>
|
|
3738
|
+
<div class="task-sidebar-value" style="font-size:11px;user-select:all;word-break:break-all;">${task.branch}</div>
|
|
3596
3739
|
</div>
|
|
3597
3740
|
`}
|
|
3598
3741
|
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
onSave=${() => {
|
|
3605
|
-
void handleSave({ closeAfterSave: false });
|
|
3606
|
-
}}
|
|
3607
|
-
onDiscard=${handleDiscardChanges}
|
|
3608
|
-
saving=${saving}
|
|
3609
|
-
disabled=${Boolean(activeOperationLabel && !saving)}
|
|
3610
|
-
/>
|
|
3742
|
+
${/* Timestamps */ ""}
|
|
3743
|
+
<div class="task-timestamps">
|
|
3744
|
+
${task?.created_at && html`<div class="task-timestamp-row">Created ${new Date(task.created_at).toLocaleString()}</div>`}
|
|
3745
|
+
${task?.updated_at && html`<div class="task-timestamp-row">Updated ${formatRelative(task.updated_at)}</div>`}
|
|
3746
|
+
</div>
|
|
3611
3747
|
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3748
|
+
${/* Save bar */ ""}
|
|
3749
|
+
<div class="task-save-bar">
|
|
3750
|
+
<${SaveDiscardBar}
|
|
3751
|
+
dirty=${hasUnsaved}
|
|
3752
|
+
message=${unsavedChangesMessage(changeCount)}
|
|
3753
|
+
saveLabel="Save Changes"
|
|
3754
|
+
discardLabel="Discard"
|
|
3755
|
+
onSave=${() => {
|
|
3756
|
+
void handleSave({ closeAfterSave: false });
|
|
3757
|
+
}}
|
|
3758
|
+
onDiscard=${handleDiscardChanges}
|
|
3759
|
+
saving=${saving}
|
|
3760
|
+
disabled=${Boolean(activeOperationLabel && !saving)}
|
|
3761
|
+
/>
|
|
3762
|
+
</div>
|
|
3763
|
+
|
|
3764
|
+
<div style="display:flex;gap:4px;flex-wrap:wrap;">
|
|
3765
|
+
${(task?.status === "error" || task?.status === "cancelled") && html`
|
|
3766
|
+
<${Button} variant="contained" size="small" onClick=${handleRetry}>↻ Retry<//>
|
|
3618
3767
|
`}
|
|
3619
3768
|
<${Button}
|
|
3620
3769
|
variant="outlined" size="small"
|
|
3621
|
-
onClick=${() => {
|
|
3622
|
-
void handleSave({ closeAfterSave: true });
|
|
3623
|
-
}}
|
|
3770
|
+
onClick=${() => { void handleSave({ closeAfterSave: true }); }}
|
|
3624
3771
|
disabled=${saving}
|
|
3625
3772
|
>
|
|
3626
3773
|
${saving ? "Saving…" : iconText(":save: Save")}
|
|
3627
3774
|
<//>
|
|
3628
|
-
<${Button}
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
>
|
|
3632
|
-
→ Review
|
|
3633
|
-
<//>
|
|
3634
|
-
<${Button}
|
|
3635
|
-
variant="text" size="small"
|
|
3636
|
-
onClick=${() => handleStatusUpdate("done")}
|
|
3637
|
-
>
|
|
3638
|
-
${iconText("✓ Done")}
|
|
3639
|
-
<//>
|
|
3640
|
-
${task?.status !== "cancelled" &&
|
|
3641
|
-
html`
|
|
3775
|
+
<${Button} variant="text" size="small" onClick=${() => handleStatusUpdate("inreview")}>→ Review<//>
|
|
3776
|
+
<${Button} variant="text" size="small" onClick=${() => handleStatusUpdate("done")}>${iconText("✓ Done")}<//>
|
|
3777
|
+
${task?.status !== "cancelled" && html`
|
|
3642
3778
|
<${Button}
|
|
3643
3779
|
variant="text" size="small"
|
|
3644
3780
|
style=${{ color: "var(--color-error)" }}
|
|
@@ -3647,20 +3783,430 @@ export function TaskDetailModal({ task, onClose, onStart, presentation = "modal"
|
|
|
3647
3783
|
${iconText("✕ Cancel")}
|
|
3648
3784
|
<//>
|
|
3649
3785
|
`}
|
|
3786
|
+
${task?.id && html`
|
|
3787
|
+
<${Button}
|
|
3788
|
+
variant="text" size="small"
|
|
3789
|
+
onClick=${() => {
|
|
3790
|
+
haptic();
|
|
3791
|
+
sendCommandToChat("/logs " + task.id);
|
|
3792
|
+
}}
|
|
3793
|
+
>
|
|
3794
|
+
${iconText(":file: Logs")}
|
|
3795
|
+
<//>
|
|
3796
|
+
`}
|
|
3650
3797
|
</div>
|
|
3651
3798
|
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3799
|
+
</div>
|
|
3800
|
+
|
|
3801
|
+
</div>`}
|
|
3802
|
+
|
|
3803
|
+
${/* ── EXECUTION TAB ───────────────────────────────────────────── */ ""}
|
|
3804
|
+
${activeTab === "execution" && html`<div style="display:contents;">
|
|
3805
|
+
|
|
3806
|
+
${/* ── Execution Plan Visualization (Premium) ─────────────────── */ ""}
|
|
3807
|
+
<div class="exec-plan-stage" style="margin:0 0 14px;">
|
|
3808
|
+
<div class="exec-plan-stage-header" style="background:transparent;border-bottom:none;padding:12px 16px;">
|
|
3809
|
+
<span style="font-weight:700;font-size:0.9em;flex:1;">${resolveIcon("play")} Execution Plan</span>
|
|
3810
|
+
${executionPlanLoading && html`<span style="font-size:0.8em;opacity:0.6;">Loading…</span>`}
|
|
3811
|
+
${executionPlan && html`<span style="font-size:0.8em;opacity:0.6;">${executionPlan.stageCount || 0} workflows · ${executionPlan.agentRunTotal || 0} agent runs</span>`}
|
|
3812
|
+
${executionPlan?.validationIssues?.length > 0 && html`
|
|
3813
|
+
<span class="exec-plan-badge" style="background:#ef444420;color:#f87171;">
|
|
3814
|
+
${executionPlan.validationIssues.filter((v) => v.level === "error").length} errors
|
|
3815
|
+
</span>
|
|
3816
|
+
`}
|
|
3817
|
+
</div>
|
|
3818
|
+
<div class="exec-plan-stage-body">
|
|
3819
|
+
${/* ── Action buttons ── */ ""}
|
|
3820
|
+
<div style="display:flex;gap:8px;margin-bottom:14px;align-items:center;">
|
|
3821
|
+
<button style="padding:6px 14px;border-radius:6px;border:1px solid var(--border-color,#444);background:var(--color-bg-secondary,#1a1f2e);color:var(--color-text,#e0e0e0);font-size:0.8em;cursor:pointer;"
|
|
3822
|
+
onClick=${() => fetchExecutionPlan("resolve")} disabled=${executionPlanLoading}>
|
|
3823
|
+
${resolveIcon("refresh")} Refresh Plan
|
|
3824
|
+
</button>
|
|
3825
|
+
<button style="padding:6px 14px;border-radius:6px;border:1px solid #3b82f660;background:#3b82f620;color:#60a5fa;font-size:0.8em;cursor:pointer;font-weight:600;"
|
|
3826
|
+
onClick=${() => fetchExecutionPlan("dry-run")} disabled=${dryRunLoading || executionPlanLoading}>
|
|
3827
|
+
${dryRunLoading ? "Simulating…" : `${resolveIcon("play")} Dry Run Simulation`}
|
|
3828
|
+
</button>
|
|
3829
|
+
${executionPlan?.mode === "dry-run" && html`
|
|
3830
|
+
<span style="font-size:0.8em;color:#10b981;font-weight:600;">${resolveIcon("check") || "✓"} Dry-run complete</span>
|
|
3831
|
+
`}
|
|
3832
|
+
</div>
|
|
3833
|
+
|
|
3834
|
+
${/* ── Validation Issues ── */ ""}
|
|
3835
|
+
${executionPlan?.validationIssues?.length > 0 && html`
|
|
3836
|
+
<div style="margin-bottom:10px;border:1px solid #ef444440;border-radius:6px;padding:8px;background:#ef444410;">
|
|
3837
|
+
<div style="font-weight:600;font-size:0.8em;color:#f87171;margin-bottom:4px;">${resolveIcon("warning")} Validation Issues</div>
|
|
3838
|
+
${executionPlan.validationIssues.map((issue, ii) => html`
|
|
3839
|
+
<div key=${`vi-${ii}`} style="font-size:0.75em;padding:2px 0;display:flex;gap:4px;align-items:start;">
|
|
3840
|
+
<span style="color:${issue.level === 'error' ? '#f87171' : '#fbbf24'};flex-shrink:0;">${issue.level === "error" ? "✗" : "⚠"}</span>
|
|
3841
|
+
<span><strong>${issue.workflowName}:</strong> ${issue.message}</span>
|
|
3842
|
+
</div>
|
|
3843
|
+
`)}
|
|
3844
|
+
</div>
|
|
3845
|
+
`}
|
|
3846
|
+
|
|
3847
|
+
${!executionPlan && !executionPlanLoading && html`
|
|
3848
|
+
<div style="opacity:0.6;font-size:0.85em;padding:8px;">No execution plan data available.</div>
|
|
3849
|
+
`}
|
|
3850
|
+
|
|
3851
|
+
${/* ── Workflow Stages (Enhanced) ── */ ""}
|
|
3852
|
+
${executionPlan?.stages?.map((stage, si) => {
|
|
3853
|
+
const matchColors = {
|
|
3854
|
+
task_assigned: { bg: "#3b82f620", color: "#60a5fa", label: "Task Match" },
|
|
3855
|
+
polling: { bg: "#6b728020", color: "#9ca3af", label: "Lifecycle" },
|
|
3856
|
+
pr_event: { bg: "#8b5cf620", color: "#a78bfa", label: "PR Event" },
|
|
3857
|
+
schedule: { bg: "#06b6d420", color: "#22d3ee", label: "Scheduled" },
|
|
3858
|
+
event: { bg: "#f5932020", color: "#fb923c", label: "Event" },
|
|
3859
|
+
anomaly: { bg: "#ef444420", color: "#f87171", label: "Anomaly" },
|
|
3860
|
+
webhook: { bg: "#84cc1620", color: "#a3e635", label: "Webhook" },
|
|
3861
|
+
manual: { bg: "#6b728020", color: "#d1d5db", label: "Manual" },
|
|
3862
|
+
workflow_call: { bg: "#14b8a620", color: "#2dd4bf", label: "Sub-call" },
|
|
3863
|
+
};
|
|
3864
|
+
const mc = matchColors[stage.matchType] || { bg: "#33333320", color: "#888", label: stage.matchType };
|
|
3865
|
+
|
|
3866
|
+
return html`
|
|
3867
|
+
<div key=${`stage-${si}`} class="exec-plan-stage">
|
|
3868
|
+
<div class="exec-plan-stage-header" onClick=${() => toggleStageExpand(si)}>
|
|
3869
|
+
<span style="font-size:0.8em;opacity:0.5;">${expandedStages[si] !== false ? "▾" : "▸"}</span>
|
|
3870
|
+
${stage.core ? html`<span class="exec-plan-badge" style="background:#8b5cf620;color:#a78bfa;">CORE</span>` : ""}
|
|
3871
|
+
<strong style="font-size:0.9em;flex:1;">${stage.workflowName}</strong>
|
|
3872
|
+
<span class="exec-plan-badge" style="background:${mc.bg};color:${mc.color};">${mc.label}</span>
|
|
3873
|
+
<span style="font-size:0.75em;opacity:0.5;">${stage.nodeCount} nodes · ${stage.agentRunCount} agents</span>
|
|
3874
|
+
<span style="font-size:0.65em;opacity:0.35;text-transform:uppercase;">${stage.category || ""}</span>
|
|
3875
|
+
</div>
|
|
3876
|
+
|
|
3877
|
+
${expandedStages[si] !== false && html`
|
|
3878
|
+
<div class="exec-plan-stage-body">
|
|
3879
|
+
${stage.description ? html`<div style="font-size:0.8em;opacity:0.6;margin-bottom:10px;">${stage.description}</div>` : ""}
|
|
3880
|
+
|
|
3881
|
+
${/* ── Node Pipeline ── */ ""}
|
|
3882
|
+
<div style="display:flex;flex-direction:column;gap:0;">
|
|
3883
|
+
${(stage.nodes || []).map((nd, ni) => {
|
|
3884
|
+
const isExpanded = expandedNodes[`${si}-${nd.id}`];
|
|
3885
|
+
const nodeColor = nd.isAgentRun ? "#3b82f6"
|
|
3886
|
+
: nd.isTrigger ? "#eab308"
|
|
3887
|
+
: nd.isCondition ? "#8b5cf6"
|
|
3888
|
+
: nd.isCommand || nd.isValidation ? "#22c55e"
|
|
3889
|
+
: nd.isStatusUpdate ? "#ef4444"
|
|
3890
|
+
: nd.isPromptBuilder ? "#f59e0b"
|
|
3891
|
+
: nd.isCreatePr || nd.isPushBranch ? "#06b6d4"
|
|
3892
|
+
: nd.isSubWorkflow ? "#14b8a6"
|
|
3893
|
+
: nd.isNotify ? "#64748b"
|
|
3894
|
+
: "#6b7280";
|
|
3895
|
+
const hasIssue = nd.expressionValid === false || !nd.typeRegistered || (nd.unresolvedVars?.length > 0);
|
|
3896
|
+
const dryRunNode = dryRunResults?.find((dr) => dr.workflowId === stage.workflowId)?.nodes?.find((dn) => dn.id === nd.id);
|
|
3897
|
+
|
|
3898
|
+
return html`
|
|
3899
|
+
<div key=${`n-${ni}`}>
|
|
3900
|
+
${ni > 0 && html`<div class="exec-plan-connector"></div>`}
|
|
3901
|
+
<div class="exec-plan-node" style="border-color:${hasIssue ? '#ef4444' : nodeColor + '40'};">
|
|
3902
|
+
<div class="exec-plan-node-header" onClick=${() => toggleNodeExpand(si, nd.id)}>
|
|
3903
|
+
<span style="width:22px;height:22px;border-radius:6px;background:${nodeColor}20;color:${nodeColor};display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;flex-shrink:0;">${ni + 1}</span>
|
|
3904
|
+
<span style="font-size:0.7em;color:${nodeColor};opacity:0.8;min-width:70px;font-weight:500;">${nd.type.split(".").pop()}</span>
|
|
3905
|
+
<strong style="flex:1;font-size:0.85em;">${nd.label}</strong>
|
|
3906
|
+
${hasIssue ? html`<span style="color:#ef4444;font-size:0.7em;">✗</span>` : ""}
|
|
3907
|
+
|
|
3908
|
+
${/* Agent badges */ ""}
|
|
3909
|
+
${nd.isAgentRun && nd.resolvedAgent ? html`
|
|
3910
|
+
<span class="exec-plan-skill-tag" style="background:${nodeColor}15;color:${nodeColor};border-color:${nodeColor}30;">
|
|
3911
|
+
${resolveIcon("bot")} ${nd.resolvedAgent} ${nd.confidence ? `(${Math.round(nd.confidence * 100)}%)` : ""}
|
|
3912
|
+
</span>
|
|
3913
|
+
` : ""}
|
|
3914
|
+
${nd.isAgentRun && !nd.resolvedAgent && nd.resolveMode === "library" ? html`
|
|
3915
|
+
<span class="exec-plan-badge" style="background:#f59e0b20;color:#fbbf24;">Auto-Resolve</span>
|
|
3916
|
+
` : ""}
|
|
3917
|
+
|
|
3918
|
+
${/* Context flow chips */ ""}
|
|
3919
|
+
${nd.contextPreview?.hasTaskPrompt ? html`<span class="exec-plan-context-chip">TaskPrompt</span>` : ""}
|
|
3920
|
+
${nd.contextPreview?.hasPreviousOutput ? html`<span class="exec-plan-context-chip" style="background:#8b5cf615;color:#a78bfa;border-color:#8b5cf630;">PrevOutput</span>` : ""}
|
|
3921
|
+
|
|
3922
|
+
${/* Status indicators */ ""}
|
|
3923
|
+
${nd.isStatusUpdate ? html`<span class="exec-plan-badge" style="background:#ef444420;color:#fca5a5;">→ ${nd.targetStatus}</span>` : ""}
|
|
3924
|
+
${nd.isCommand ? html`<span style="font-size:0.65em;font-family:monospace;opacity:0.5;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${nd.commandResolved || nd.commandRaw}</span>` : ""}
|
|
3925
|
+
${nd.isPromptBuilder ? html`<span class="exec-plan-badge" style="background:#f59e0b20;color:#fbbf24;">Builds TaskPrompt</span>` : ""}
|
|
3926
|
+
${nd.isCreatePr ? html`<span class="exec-plan-badge" style="background:#06b6d420;color:#22d3ee;">Creates PR</span>` : ""}
|
|
3927
|
+
|
|
3928
|
+
${dryRunNode ? html`<span style="font-size:0.65em;color:${dryRunNode.status === 'simulated' || dryRunNode.status === 'COMPLETED' ? '#10b981' : '#fbbf24'};">● ${dryRunNode.status}</span>` : ""}
|
|
3929
|
+
<span style="font-size:0.65em;opacity:0.3;">${isExpanded ? "▾" : "▸"}</span>
|
|
3930
|
+
</div>
|
|
3931
|
+
|
|
3932
|
+
${isExpanded && html`
|
|
3933
|
+
<div class="exec-plan-node-body">
|
|
3934
|
+
${/* ── Inputs from previous nodes ── */ ""}
|
|
3935
|
+
${nd.inputsFrom?.length > 0 ? html`
|
|
3936
|
+
<div style="margin-bottom:8px;padding:6px 8px;background:var(--bg-surface,#0d1117);border-radius:6px;border:1px solid var(--border-color,#333);">
|
|
3937
|
+
<div style="font-size:0.85em;opacity:0.6;margin-bottom:4px;font-weight:600;">Inputs From:</div>
|
|
3938
|
+
<div style="display:flex;flex-wrap:wrap;gap:4px;">
|
|
3939
|
+
${nd.inputsFrom.map((inp) => html`
|
|
3940
|
+
<span class="exec-plan-context-chip" style="background:#3b82f610;color:#60a5fa;border-color:#3b82f625;">
|
|
3941
|
+
${inp.nodeLabel} ${inp.port ? `[${inp.port}]` : ""} ${inp.condition ? `if: ${inp.condition.slice(0, 30)}` : ""}
|
|
3942
|
+
</span>
|
|
3943
|
+
`)}
|
|
3944
|
+
</div>
|
|
3945
|
+
</div>
|
|
3946
|
+
` : ""}
|
|
3947
|
+
|
|
3948
|
+
<div style="display:grid;grid-template-columns:auto 1fr;gap:3px 12px;align-items:start;">
|
|
3949
|
+
<span style="opacity:0.5;">Type:</span>
|
|
3950
|
+
<span style="font-family:monospace;">${nd.type}${!nd.typeRegistered ? html` <span style="color:#ef4444;">✗ unregistered</span>` : ""}</span>
|
|
3951
|
+
|
|
3952
|
+
${nd.isTrigger && nd.taskPattern ? html`
|
|
3953
|
+
<span style="opacity:0.5;">Pattern:</span>
|
|
3954
|
+
<span style="font-family:monospace;">${nd.taskPattern} ${nd.patternMatches === true ? html`<span style="color:#10b981;">✓ matches</span>` : nd.patternMatches === false ? html`<span style="color:#ef4444;">✗ no match</span>` : ""}</span>
|
|
3955
|
+
` : ""}
|
|
3956
|
+
|
|
3957
|
+
${nd.isTrigger && nd.prEvents ? html`
|
|
3958
|
+
<span style="opacity:0.5;">PR Events:</span>
|
|
3959
|
+
<span>${nd.prEvents.join(", ")}</span>
|
|
3960
|
+
` : ""}
|
|
3961
|
+
|
|
3962
|
+
${nd.isTrigger && nd.eventTypes?.length > 0 ? html`
|
|
3963
|
+
<span style="opacity:0.5;">Event Types:</span>
|
|
3964
|
+
<span>${nd.eventTypes.join(", ")}</span>
|
|
3965
|
+
` : ""}
|
|
3966
|
+
|
|
3967
|
+
${nd.isTrigger && nd.intervalMs ? html`
|
|
3968
|
+
<span style="opacity:0.5;">Interval:</span>
|
|
3969
|
+
<span>${Math.round(nd.intervalMs / 60000)}min</span>
|
|
3970
|
+
` : ""}
|
|
3971
|
+
|
|
3972
|
+
${nd.isCondition && nd.expression ? html`
|
|
3973
|
+
<span style="opacity:0.5;">Expression:</span>
|
|
3974
|
+
<span style="font-family:monospace;word-break:break-all;">${nd.expression}${nd.expressionValid === false ? html` <span style="color:#ef4444;">✗ ${nd.expressionError}</span>` : html` <span style="color:#10b981;">✓</span>`}</span>
|
|
3975
|
+
` : ""}
|
|
3976
|
+
${nd.isCondition && nd.cases ? html`
|
|
3977
|
+
<span style="opacity:0.5;">Cases:</span>
|
|
3978
|
+
<span>${nd.cases.join(", ")}</span>
|
|
3979
|
+
` : ""}
|
|
3980
|
+
|
|
3981
|
+
${nd.isAgentRun ? html`
|
|
3982
|
+
<span style="opacity:0.5;">SDK:</span><span>${nd.sdk || "auto"}</span>
|
|
3983
|
+
<span style="opacity:0.5;">Model:</span><span>${nd.model || "auto"}</span>
|
|
3984
|
+
<span style="opacity:0.5;">Timeout:</span><span>${Math.round((nd.timeoutMs || 3600000) / 60000)}min</span>
|
|
3985
|
+
<span style="opacity:0.5;">Retries:</span><span>${nd.maxRetries ?? 2} retries, ${nd.maxContinues ?? 2} continues</span>
|
|
3986
|
+
<span style="opacity:0.5;">Resolve:</span><span style="font-weight:500;color:${nd.resolveMode === 'library' ? '#f59e0b' : '#60a5fa'};">${nd.resolveMode || "manual"}${nd.resolveMode === "library" ? " (auto)" : ""}</span>
|
|
3987
|
+
<span style="opacity:0.5;">CWD:</span><span style="font-family:monospace;">${nd.cwd || "auto"}</span>
|
|
3988
|
+
` : ""}
|
|
3989
|
+
|
|
3990
|
+
${nd.isCommand ? html`
|
|
3991
|
+
<span style="opacity:0.5;">Command:</span>
|
|
3992
|
+
<span style="font-family:monospace;word-break:break-all;">${nd.commandResolved || nd.commandRaw}</span>
|
|
3993
|
+
<span style="opacity:0.5;">CWD:</span><span style="font-family:monospace;">${nd.commandCwd}</span>
|
|
3994
|
+
<span style="opacity:0.5;">Timeout:</span><span>${Math.round((nd.commandTimeout || 300000) / 1000)}s</span>
|
|
3995
|
+
<span style="opacity:0.5;">Fail on error:</span><span>${nd.failOnError ? "Yes" : "No"}</span>
|
|
3996
|
+
` : ""}
|
|
3997
|
+
|
|
3998
|
+
${nd.isResolveExecutor ? html`
|
|
3999
|
+
<span style="opacity:0.5;">SDK Override:</span><span>${nd.sdkOverride || "auto"}</span>
|
|
4000
|
+
<span style="opacity:0.5;">Model Override:</span><span>${nd.modelOverride || "auto"}</span>
|
|
4001
|
+
` : ""}
|
|
4002
|
+
|
|
4003
|
+
${nd.isSubWorkflow ? html`
|
|
4004
|
+
<span style="opacity:0.5;">Sub-workflow:</span><span style="font-family:monospace;">${nd.targetWorkflowId || "—"}</span>
|
|
4005
|
+
<span style="opacity:0.5;">Inherit ctx:</span><span>${nd.inheritContext ? "Yes" : "No"}</span>
|
|
4006
|
+
` : ""}
|
|
4007
|
+
|
|
4008
|
+
${nd.isValidation ? html`
|
|
4009
|
+
<span style="opacity:0.5;">${nd.validationType} cmd:</span>
|
|
4010
|
+
<span style="font-family:monospace;">${nd.commandResolved || nd.commandRaw || "auto-detect"}</span>
|
|
4011
|
+
` : ""}
|
|
4012
|
+
|
|
4013
|
+
${nd.isPromptBuilder ? html`
|
|
4014
|
+
<span style="opacity:0.5;">Output:</span><span>${"{{TaskPrompt}}"}</span>
|
|
4015
|
+
<span style="opacity:0.5;">Include Skills:</span><span>${nd.includeSkills !== false ? "Yes" : "No"}</span>
|
|
4016
|
+
<span style="opacity:0.5;">Include Agent Instructions:</span><span>${nd.includeAgentInstructions !== false ? "Yes" : "No"}</span>
|
|
4017
|
+
` : ""}
|
|
4018
|
+
|
|
4019
|
+
${nd.isCreatePr ? html`
|
|
4020
|
+
<span style="opacity:0.5;">PR Title:</span><span>${nd.prTitle || "auto"}</span>
|
|
4021
|
+
<span style="opacity:0.5;">Base Branch:</span><span>${nd.prBaseBranch || "main"}</span>
|
|
4022
|
+
` : ""}
|
|
4023
|
+
|
|
4024
|
+
${nd.joinMode ? html`
|
|
4025
|
+
<span style="opacity:0.5;">Join mode:</span><span>${nd.joinMode}</span>
|
|
4026
|
+
` : ""}
|
|
4027
|
+
|
|
4028
|
+
${nd.isNotify && nd.logMessage ? html`
|
|
4029
|
+
<span style="opacity:0.5;">Message:</span><span>${nd.logMessage}</span>
|
|
4030
|
+
` : ""}
|
|
4031
|
+
|
|
4032
|
+
${nd.unresolvedVars?.length > 0 ? html`
|
|
4033
|
+
<span style="opacity:0.5;color:#fbbf24;">Unresolved:</span>
|
|
4034
|
+
<span style="color:#fbbf24;">${nd.unresolvedVars.map((v) => `{{${v}}}`).join(", ")}</span>
|
|
4035
|
+
` : ""}
|
|
4036
|
+
</div>
|
|
4037
|
+
|
|
4038
|
+
${/* ── Agent: Context Preview ── */ ""}
|
|
4039
|
+
${nd.isAgentRun && nd.contextPreview ? html`
|
|
4040
|
+
<div style="margin-top:8px;padding:6px 8px;background:#1a1a2e;border-radius:6px;border:1px solid #333;">
|
|
4041
|
+
<div style="font-size:0.85em;opacity:0.6;margin-bottom:4px;font-weight:600;">${resolveIcon("link") || "🔗"} Context Injected:</div>
|
|
4042
|
+
<div style="display:flex;flex-wrap:wrap;gap:4px;">
|
|
4043
|
+
${nd.contextPreview.hasTaskPrompt ? html`<span class="exec-plan-context-chip">Task Prompt (built by build_task_prompt)</span>` : ""}
|
|
4044
|
+
${nd.contextPreview.hasPreviousOutput ? html`<span class="exec-plan-context-chip" style="background:#8b5cf615;color:#a78bfa;border-color:#8b5cf630;">Previous Agent Output</span>` : ""}
|
|
4045
|
+
${nd.contextPreview.hasWorktreePath ? html`<span class="exec-plan-context-chip" style="background:#22c55e15;color:#4ade80;border-color:#22c55e30;">Worktree Path</span>` : ""}
|
|
4046
|
+
${nd.contextPreview.hasBranchName ? html`<span class="exec-plan-context-chip" style="background:#06b6d415;color:#22d3ee;border-color:#06b6d430;">Branch Name</span>` : ""}
|
|
4047
|
+
${nd.contextPreview.hasPrUrl ? html`<span class="exec-plan-context-chip" style="background:#8b5cf615;color:#c4b5fd;border-color:#8b5cf630;">PR Link</span>` : ""}
|
|
4048
|
+
${nd.includeTaskContext ? html`<span class="exec-plan-context-chip" style="background:#f59e0b15;color:#fbbf24;border-color:#f59e0b30;">Full Task Context</span>` : ""}
|
|
4049
|
+
${(nd.contextPreview.injectedVariables || []).map((v) => html`
|
|
4050
|
+
<span class="exec-plan-context-chip" style="background:#64748b15;color:#94a3b8;border-color:#64748b30;">${"{{" + v + "}}"}</span>
|
|
4051
|
+
`)}
|
|
4052
|
+
</div>
|
|
4053
|
+
</div>
|
|
4054
|
+
` : ""}
|
|
4055
|
+
|
|
4056
|
+
${/* ── Agent: resolved skills with descriptions ── */ ""}
|
|
4057
|
+
${nd.isAgentRun && nd.resolvedSkills?.length > 0 ? html`
|
|
4058
|
+
<div style="margin-top:8px;padding:8px;background:var(--bg-surface,#0d1117);border-radius:6px;border:1px solid var(--border-color,#333);">
|
|
4059
|
+
<div style="font-size:0.85em;opacity:0.6;margin-bottom:6px;font-weight:600;">${resolveIcon("star")} Resolved Skills (${nd.resolvedSkills.length}):</div>
|
|
4060
|
+
${nd.resolvedSkills.map((sk) => html`
|
|
4061
|
+
<div style="display:flex;gap:8px;padding:4px 0;align-items:start;border-bottom:1px solid var(--border-color,#222);">
|
|
4062
|
+
<span class="exec-plan-skill-tag">${sk.name} ${sk.score ? `${Math.round(sk.score * 100)}%` : ""}</span>
|
|
4063
|
+
${sk.source ? html`<span style="opacity:0.35;font-size:0.85em;">(${sk.source})</span>` : ""}
|
|
4064
|
+
${sk.description ? html`<span style="opacity:0.5;font-size:0.85em;flex:1;">${sk.description.slice(0, 120)}${sk.description.length > 120 ? "…" : ""}</span>` : ""}
|
|
4065
|
+
</div>
|
|
4066
|
+
`)}
|
|
4067
|
+
</div>
|
|
4068
|
+
` : ""}
|
|
4069
|
+
|
|
4070
|
+
${/* ── Agent: resolved tools ── */ ""}
|
|
4071
|
+
${nd.isAgentRun && nd.resolvedTools && (nd.resolvedTools.builtin?.length > 0 || nd.resolvedTools.mcp?.length > 0) ? html`
|
|
4072
|
+
<div style="margin-top:6px;display:flex;flex-wrap:wrap;gap:4px;align-items:center;">
|
|
4073
|
+
<span style="opacity:0.6;font-size:0.85em;">${resolveIcon("tool")} Tools:</span>
|
|
4074
|
+
${[...(nd.resolvedTools.builtin || []), ...(nd.resolvedTools.mcp || [])].map((t) => html`
|
|
4075
|
+
<span style="padding:1px 6px;border-radius:4px;font-size:0.75em;background:#22c55e10;color:#4ade80;border:1px solid #22c55e25;">${t}</span>
|
|
4076
|
+
`)}
|
|
4077
|
+
</div>
|
|
4078
|
+
` : ""}
|
|
4079
|
+
|
|
4080
|
+
${/* ── Agent: alternatives ── */ ""}
|
|
4081
|
+
${nd.isAgentRun && nd.alternatives?.length > 0 ? html`
|
|
4082
|
+
<div style="margin-top:6px;opacity:0.5;font-size:0.85em;">
|
|
4083
|
+
<span>Alternatives: ${nd.alternatives.map((a) => `${a.name} (${Math.round((a.confidence || 0) * 100)}%)`).join(", ")}</span>
|
|
4084
|
+
</div>
|
|
4085
|
+
` : ""}
|
|
4086
|
+
|
|
4087
|
+
${/* ── Agent: prompt preview ── */ ""}
|
|
4088
|
+
${nd.isAgentRun && nd.promptResolved ? html`
|
|
4089
|
+
<details style="margin-top:8px;">
|
|
4090
|
+
<summary style="cursor:pointer;opacity:0.6;font-size:0.85em;font-weight:500;">Prompt Preview (${nd.promptResolved.length} chars)</summary>
|
|
4091
|
+
<pre style="margin-top:4px;padding:8px;background:#00000030;border-radius:6px;white-space:pre-wrap;word-break:break-word;max-height:300px;overflow-y:auto;font-size:0.8em;line-height:1.5;">${nd.promptResolved.slice(0, 3000)}${nd.promptResolved.length > 3000 ? "\n…(truncated)" : ""}</pre>
|
|
4092
|
+
</details>
|
|
4093
|
+
` : ""}
|
|
4094
|
+
</div>
|
|
4095
|
+
`}
|
|
4096
|
+
</div>
|
|
4097
|
+
</div>
|
|
4098
|
+
`;
|
|
4099
|
+
})}
|
|
4100
|
+
</div>
|
|
4101
|
+
|
|
4102
|
+
${/* ── Edge routing ── */ ""}
|
|
4103
|
+
${stage.edges?.some((e) => e.condition || e.sourcePort || e.isBackEdge) && html`
|
|
4104
|
+
<details style="margin-top:10px;">
|
|
4105
|
+
<summary style="cursor:pointer;font-size:0.8em;opacity:0.5;font-weight:500;">Edge Routing (${stage.edges.length} edges)</summary>
|
|
4106
|
+
<div style="margin-top:6px;font-size:0.75em;font-family:monospace;">
|
|
4107
|
+
${stage.edges.filter((e) => e.condition || e.sourcePort || e.isBackEdge).map((e) => html`
|
|
4108
|
+
<div style="padding:3px 0;display:flex;gap:6px;align-items:center;">
|
|
4109
|
+
<span>${e.source}</span>
|
|
4110
|
+
<span style="opacity:0.3;">→</span>
|
|
4111
|
+
<span>${e.target}</span>
|
|
4112
|
+
${e.sourcePort ? html`<span style="color:#a78bfa;">[${e.sourcePort}]</span>` : ""}
|
|
4113
|
+
${e.condition ? html`<span style="opacity:0.5;color:${e.conditionValid === false ? '#ef4444' : '#4ade80'};">${e.condition.length > 60 ? e.condition.slice(0, 60) + "…" : e.condition}</span>` : ""}
|
|
4114
|
+
${e.isBackEdge ? html`<span style="color:#fbbf24;">↩ loop</span>` : ""}
|
|
4115
|
+
${e.conditionValid === false ? html`<span style="color:#ef4444;">✗ ${e.conditionError}</span>` : ""}
|
|
4116
|
+
</div>
|
|
4117
|
+
`)}
|
|
4118
|
+
</div>
|
|
4119
|
+
</details>
|
|
4120
|
+
`}
|
|
4121
|
+
</div>
|
|
4122
|
+
`}
|
|
4123
|
+
</div>
|
|
4124
|
+
`;})}
|
|
4125
|
+
|
|
4126
|
+
${/* ── Dry-run results summary ── */ ""}
|
|
4127
|
+
${dryRunResults && html`
|
|
4128
|
+
<div style="margin-top:10px;border:1px solid #10b98140;border-radius:8px;padding:10px;background:#10b98110;">
|
|
4129
|
+
<div style="font-weight:600;font-size:0.85em;color:#10b981;margin-bottom:6px;">${resolveIcon("check")} Dry-Run Simulation Results</div>
|
|
4130
|
+
${dryRunResults.map((dr) => html`
|
|
4131
|
+
<div style="font-size:0.8em;padding:3px 0;display:flex;gap:8px;align-items:center;">
|
|
4132
|
+
<span style="font-weight:500;">${dr.workflowName}</span>
|
|
4133
|
+
<span class="exec-plan-badge" style="background:${dr.status === 'completed' ? '#10b98120' : dr.status === 'error' ? '#ef444420' : '#fbbf2420'};color:${dr.status === 'completed' ? '#10b981' : dr.status === 'error' ? '#ef4444' : '#fbbf24'};">
|
|
4134
|
+
${dr.status}
|
|
4135
|
+
</span>
|
|
4136
|
+
${dr.error ? html`<span style="color:#ef4444;font-size:0.9em;">${dr.error}</span>` : ""}
|
|
4137
|
+
${dr.nodes?.length > 0 ? html`<span style="opacity:0.5;">(${dr.nodes.length} nodes simulated)</span>` : ""}
|
|
4138
|
+
</div>
|
|
4139
|
+
`)}
|
|
4140
|
+
</div>
|
|
4141
|
+
`}
|
|
4142
|
+
</div>
|
|
4143
|
+
</div>
|
|
4144
|
+
|
|
4145
|
+
</div>`}
|
|
4146
|
+
|
|
4147
|
+
${/* ── HISTORY TAB ─────────────────────────────────────────────── */ ""}
|
|
4148
|
+
${activeTab === "history" && html`<div style="display:contents;">
|
|
4149
|
+
|
|
4150
|
+
${historyEntries.length > 0 ? html`
|
|
4151
|
+
<div class="task-comments-block modal-form-span jira-panel">
|
|
4152
|
+
<div class="task-attachments-title">History Timeline</div>
|
|
4153
|
+
<div class="task-comments-list">
|
|
4154
|
+
${historyEntries.map((entry, index) => html`
|
|
4155
|
+
<div class="task-comment-item" key=${`history-${index}`}>
|
|
4156
|
+
<div class="task-comment-meta">
|
|
4157
|
+
${entry.timestamp ? formatRelative(entry.timestamp) : "Time unknown"}
|
|
4158
|
+
${entry.source ? ` · ${entry.source}` : ""}
|
|
4159
|
+
</div>
|
|
4160
|
+
<div class="task-comment-body">${entry.label}</div>
|
|
4161
|
+
</div>
|
|
4162
|
+
`)}
|
|
4163
|
+
</div>
|
|
4164
|
+
</div>
|
|
4165
|
+
` : ""}
|
|
4166
|
+
|
|
4167
|
+
${relatedLinks.length > 0 && html`
|
|
4168
|
+
<div class="task-comments-block modal-form-span jira-panel">
|
|
4169
|
+
<div class="task-attachments-title">Branch and PR Links</div>
|
|
4170
|
+
<div class="task-comments-list">
|
|
4171
|
+
${relatedLinks.map((item, index) => html`
|
|
4172
|
+
<div class="task-comment-item" key=${`link-${index}`}>
|
|
4173
|
+
<div class="task-comment-meta">${item.kind}</div>
|
|
4174
|
+
<div class="task-comment-body">
|
|
4175
|
+
${item.url
|
|
4176
|
+
? html`<a href=${item.url} target="_blank" rel="noopener">${item.value}</a>`
|
|
4177
|
+
: item.value}
|
|
4178
|
+
</div>
|
|
4179
|
+
</div>
|
|
4180
|
+
`)}
|
|
4181
|
+
</div>
|
|
4182
|
+
</div>
|
|
4183
|
+
`}
|
|
4184
|
+
|
|
4185
|
+
${/* workflow activity in history tab too */ ""}
|
|
4186
|
+
${workflowRuns.length > 0 && html`
|
|
4187
|
+
<div class="task-comments-block modal-form-span jira-panel">
|
|
4188
|
+
<div class="task-attachments-title">Workflow Activity</div>
|
|
4189
|
+
<div class="task-comments-list">
|
|
4190
|
+
${workflowRuns.map((run, index) => html`
|
|
4191
|
+
<div class="task-comment-item" key=${`wf-hist-${index}`}>
|
|
4192
|
+
<div class="task-comment-meta">
|
|
4193
|
+
${run.workflowId || "workflow"}
|
|
4194
|
+
${run.runId ? ` · run ${run.runId}` : ""}
|
|
4195
|
+
${run.timestamp ? ` · ${formatRelative(run.timestamp)}` : ""}
|
|
4196
|
+
</div>
|
|
4197
|
+
<div class="task-comment-body">${run.status || run.result || "No status summary"}</div>
|
|
4198
|
+
</div>
|
|
4199
|
+
`)}
|
|
4200
|
+
</div>
|
|
4201
|
+
</div>
|
|
4202
|
+
`}
|
|
4203
|
+
|
|
4204
|
+
${historyEntries.length === 0 && workflowRuns.length === 0 && relatedLinks.length === 0 ? html`
|
|
4205
|
+
<div style="padding:24px;text-align:center;opacity:0.5;font-size:0.9em;">No history, workflow runs, or links recorded yet.</div>
|
|
4206
|
+
` : ""}
|
|
4207
|
+
|
|
4208
|
+
</div>`}
|
|
4209
|
+
|
|
3664
4210
|
</div>
|
|
3665
4211
|
<//>
|
|
3666
4212
|
`;
|