cli-jaw 2.1.2 → 2.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/dist/bin/cli-jaw.js +14 -2
- package/dist/bin/cli-jaw.js.map +1 -1
- package/dist/bin/commands/chat-search.js +71 -0
- package/dist/bin/commands/chat-search.js.map +1 -0
- package/dist/bin/commands/goal.js +48 -3
- package/dist/bin/commands/goal.js.map +1 -1
- package/dist/bin/commands/history.js +61 -0
- package/dist/bin/commands/history.js.map +1 -0
- package/dist/bin/commands/lock.js +104 -0
- package/dist/bin/commands/lock.js.map +1 -0
- package/dist/bin/commands/memory.js +23 -2
- package/dist/bin/commands/memory.js.map +1 -1
- package/dist/bin/commands/project.js +4 -1
- package/dist/bin/commands/project.js.map +1 -1
- package/dist/bin/commands/tui/simple-mode.js +0 -1
- package/dist/bin/commands/tui/simple-mode.js.map +1 -1
- package/dist/lib/upload.js +7 -0
- package/dist/lib/upload.js.map +1 -1
- package/dist/server.js +127 -19
- package/dist/server.js.map +1 -1
- package/dist/src/agent/agy-runtime.js +1 -0
- package/dist/src/agent/agy-runtime.js.map +1 -1
- package/dist/src/agent/agy-transcript-watcher.js +78 -0
- package/dist/src/agent/agy-transcript-watcher.js.map +1 -0
- package/dist/src/agent/agy-transcript.js +158 -0
- package/dist/src/agent/agy-transcript.js.map +1 -0
- package/dist/src/agent/args.js +18 -4
- package/dist/src/agent/args.js.map +1 -1
- package/dist/src/agent/codex-app-client.js +1 -1
- package/dist/src/agent/codex-app-client.js.map +1 -1
- package/dist/src/agent/cursor-runtime.js +1 -0
- package/dist/src/agent/cursor-runtime.js.map +1 -1
- package/dist/src/agent/kiro-runtime.js +11 -3
- package/dist/src/agent/kiro-runtime.js.map +1 -1
- package/dist/src/agent/lifecycle-handler.js +18 -11
- package/dist/src/agent/lifecycle-handler.js.map +1 -1
- package/dist/src/agent/memory-flush-controller.js +2 -1
- package/dist/src/agent/memory-flush-controller.js.map +1 -1
- package/dist/src/agent/session-persistence.js +2 -2
- package/dist/src/agent/session-persistence.js.map +1 -1
- package/dist/src/agent/spawn/queue.js +1 -1
- package/dist/src/agent/spawn/queue.js.map +1 -1
- package/dist/src/agent/spawn-env.js +1 -1
- package/dist/src/agent/spawn-env.js.map +1 -1
- package/dist/src/agent/spawn.js +76 -48
- package/dist/src/agent/spawn.js.map +1 -1
- package/dist/src/agent/watchdog.js +1 -1
- package/dist/src/agent/watchdog.js.map +1 -1
- package/dist/src/browser/actions.js +0 -8
- package/dist/src/browser/actions.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/browser-escalation.js +5 -29
- package/dist/src/browser/adaptive-fetch/browser-escalation.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/browser-runtime.js +5 -14
- package/dist/src/browser/adaptive-fetch/browser-runtime.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/browser-session.js +14 -28
- package/dist/src/browser/adaptive-fetch/browser-session.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/challenge-detector.js +4 -26
- package/dist/src/browser/adaptive-fetch/challenge-detector.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/content-scorer.js +5 -29
- package/dist/src/browser/adaptive-fetch/content-scorer.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/endpoint-resolvers.js +0 -64
- package/dist/src/browser/adaptive-fetch/endpoint-resolvers.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/fetcher.js +4 -19
- package/dist/src/browser/adaptive-fetch/fetcher.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/human-loop.js +0 -26
- package/dist/src/browser/adaptive-fetch/human-loop.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/index.js +74 -145
- package/dist/src/browser/adaptive-fetch/index.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/metadata.js +1 -41
- package/dist/src/browser/adaptive-fetch/metadata.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/output.js +1 -18
- package/dist/src/browser/adaptive-fetch/output.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/reader-adapters.js +43 -75
- package/dist/src/browser/adaptive-fetch/reader-adapters.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/safety.js +5 -48
- package/dist/src/browser/adaptive-fetch/safety.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/third-party-readers.js +0 -11
- package/dist/src/browser/adaptive-fetch/third-party-readers.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/trace.js +3 -18
- package/dist/src/browser/adaptive-fetch/trace.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/transforms.js +0 -23
- package/dist/src/browser/adaptive-fetch/transforms.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/types.js +3 -0
- package/dist/src/browser/adaptive-fetch/types.js.map +1 -0
- package/dist/src/browser/adaptive-fetch/validators.js +0 -17
- package/dist/src/browser/adaptive-fetch/validators.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/waf-profiles.js +0 -10
- package/dist/src/browser/adaptive-fetch/waf-profiles.js.map +1 -1
- package/dist/src/browser/connection.js +1 -1
- package/dist/src/browser/connection.js.map +1 -1
- package/dist/src/browser/web-ai/chatgpt-response.js +0 -1
- package/dist/src/browser/web-ai/chatgpt-response.js.map +1 -1
- package/dist/src/browser/web-ai/chatgpt.js +0 -33
- package/dist/src/browser/web-ai/chatgpt.js.map +1 -1
- package/dist/src/browser/web-ai/diagnostics.js +0 -16
- package/dist/src/browser/web-ai/diagnostics.js.map +1 -1
- package/dist/src/browser/web-ai/doctor.js.map +1 -1
- package/dist/src/browser/web-ai/errors.js +1 -1
- package/dist/src/browser/web-ai/errors.js.map +1 -1
- package/dist/src/cli/command-context.js.map +1 -1
- package/dist/src/cli/commands.js +13 -2
- package/dist/src/cli/commands.js.map +1 -1
- package/dist/src/cli/compact.js +2 -1
- package/dist/src/cli/compact.js.map +1 -1
- package/dist/src/cli/handlers/session-handlers.js +34 -0
- package/dist/src/cli/handlers/session-handlers.js.map +1 -0
- package/dist/src/cli/handlers-project.js.map +1 -1
- package/dist/src/cli/handlers-workflows.js +76 -13
- package/dist/src/cli/handlers-workflows.js.map +1 -1
- package/dist/src/cli/handlers.js +37 -36
- package/dist/src/cli/handlers.js.map +1 -1
- package/dist/src/cli/registry.js +7 -7
- package/dist/src/cli/registry.js.map +1 -1
- package/dist/src/core/chat-sessions.js +63 -0
- package/dist/src/core/chat-sessions.js.map +1 -0
- package/dist/src/core/compact.js +43 -11
- package/dist/src/core/compact.js.map +1 -1
- package/dist/src/core/config.js +9 -1
- package/dist/src/core/config.js.map +1 -1
- package/dist/src/core/db.js +48 -15
- package/dist/src/core/db.js.map +1 -1
- package/dist/src/core/main-session.js +3 -2
- package/dist/src/core/main-session.js.map +1 -1
- package/dist/src/goal/heartbeat.js +88 -5
- package/dist/src/goal/heartbeat.js.map +1 -1
- package/dist/src/goal/store.js +38 -3
- package/dist/src/goal/store.js.map +1 -1
- package/dist/src/manager/lifecycle-store.js.map +1 -1
- package/dist/src/manager/lifecycle.js +45 -1
- package/dist/src/manager/lifecycle.js.map +1 -1
- package/dist/src/manager/preview-link-policy.js +11 -4
- package/dist/src/manager/preview-link-policy.js.map +1 -1
- package/dist/src/manager/server.js +25 -9
- package/dist/src/manager/server.js.map +1 -1
- package/dist/src/manager/shutdown.js +8 -3
- package/dist/src/manager/shutdown.js.map +1 -1
- package/dist/src/memory/indexing.js +49 -1
- package/dist/src/memory/indexing.js.map +1 -1
- package/dist/src/memory/runtime.js +2 -1
- package/dist/src/memory/runtime.js.map +1 -1
- package/dist/src/messaging/send.js +66 -7
- package/dist/src/messaging/send.js.map +1 -1
- package/dist/src/orchestrator/distribute.js +2 -2
- package/dist/src/orchestrator/gateway.js +4 -4
- package/dist/src/orchestrator/gateway.js.map +1 -1
- package/dist/src/orchestrator/pipeline.js +2 -1
- package/dist/src/orchestrator/pipeline.js.map +1 -1
- package/dist/src/orchestrator/sanitize.js +57 -7
- package/dist/src/orchestrator/sanitize.js.map +1 -1
- package/dist/src/orchestrator/state-machine.js +15 -8
- package/dist/src/orchestrator/state-machine.js.map +1 -1
- package/dist/src/prompt/builder.js +171 -86
- package/dist/src/prompt/builder.js.map +1 -1
- package/dist/src/prompt/templates/a1-system.md +94 -66
- package/dist/src/prompt/templates/employee.md +41 -59
- package/dist/src/prompt/templates/skills.md +6 -0
- package/dist/src/routes/browser.js +3 -3
- package/dist/src/routes/browser.js.map +1 -1
- package/dist/src/routes/goal.js +38 -2
- package/dist/src/routes/goal.js.map +1 -1
- package/dist/src/routes/messaging.js +8 -3
- package/dist/src/routes/messaging.js.map +1 -1
- package/dist/src/routes/orchestrate.js +3 -2
- package/dist/src/routes/orchestrate.js.map +1 -1
- package/dist/src/routes/quota-agy-reverse.js +6 -0
- package/dist/src/routes/quota-agy-reverse.js.map +1 -1
- package/dist/src/routes/quota.js +68 -6
- package/dist/src/routes/quota.js.map +1 -1
- package/dist/src/routes/settings.js +6 -6
- package/dist/src/routes/settings.js.map +1 -1
- package/dist/src/security/path-guards.js +0 -3
- package/dist/src/security/path-guards.js.map +1 -1
- package/dist/src/workflows/scope-sandbox.js +17 -5
- package/dist/src/workflows/scope-sandbox.js.map +1 -1
- package/package.json +1 -1
- package/public/css/chat.css +27 -0
- package/public/css/orc-state.css +10 -6
- package/public/css/variables.css +4 -0
- package/public/dist/assets/{Agent-DkH7eoHd.js → Agent-DBKNQ6tp.js} +1 -1
- package/public/dist/assets/{FolderPanel-DSkanaGN.js → FolderPanel-DT0fU8f2.js} +1 -1
- package/public/dist/assets/{Heartbeat-C3JS6gkF.js → Heartbeat-fsuLzY9c.js} +1 -1
- package/public/dist/assets/{Memory-C-EQubN2.js → Memory-CRR8kotI.js} +1 -1
- package/public/dist/assets/{ModelProvider-BD2KrypI.js → ModelProvider-DQgISbKw.js} +1 -1
- package/public/dist/assets/agent-meta-C4mauPL5.js +1 -0
- package/public/dist/assets/app-0OQhPpTG.css +1 -0
- package/public/dist/assets/app-CSqIyg9A.js +55 -0
- package/public/dist/assets/constants-BHMkzpN_.js +1 -0
- package/public/dist/assets/{employees-_A-p_bZg.js → employees-CZdWHH-r.js} +1 -1
- package/public/dist/assets/manager-CGTQ5EIm.js +12 -0
- package/public/dist/assets/manager-UBunDqMH.css +1 -0
- package/public/dist/assets/{memory-D9AUn8fG.js → memory-B3QSmj_e.js} +1 -1
- package/public/dist/assets/memory-DOBqiuPc.js +1 -0
- package/public/dist/assets/render-J11oxfnl.js +28 -0
- package/public/dist/assets/settings-BV_2Bh0_.js +1 -0
- package/public/dist/assets/settings-m_cim0ov.js +151 -0
- package/public/dist/assets/sidebar-ZBXdBgF7.js +49 -0
- package/public/dist/assets/{skills-BDVLIrVT.js → skills-CjaBmIPV.js} +1 -1
- package/public/dist/assets/skills-Xm3fl1zr.js +1 -0
- package/public/dist/assets/{slash-commands-C5da3q1p.js → slash-commands-71jc07Wf.js} +1 -1
- package/public/dist/assets/slash-commands-BO6ssnAz.js +1 -0
- package/public/dist/assets/{trace-drawer-5kqBzFMk.js → trace-drawer-Czvf2P2o.js} +1 -1
- package/public/dist/assets/ui-CverZJnd.js +1 -0
- package/public/dist/assets/ui-qKxy-4p9.js +142 -0
- package/public/dist/index.html +2 -2
- package/public/dist/manager/index.html +2 -2
- package/public/js/constants.ts +4 -4
- package/public/js/features/chat-messages.ts +13 -2
- package/public/js/features/chat.ts +86 -44
- package/public/js/features/media-lightbox.ts +40 -0
- package/public/js/features/pending-queue.ts +20 -2
- package/public/js/features/settings-cli-status-render.ts +2 -4
- package/public/js/features/settings-cli-status.ts +6 -2
- package/public/js/features/settings-types.ts +1 -0
- package/public/js/features/ui-status.ts +5 -1
- package/public/js/main.ts +2 -0
- package/public/js/render/markdown.ts +16 -0
- package/public/js/ui.ts +8 -4
- package/public/js/ws.ts +27 -16
- package/public/locales/en.json +1 -0
- package/public/locales/ja.json +1 -0
- package/public/locales/ko.json +1 -0
- package/public/locales/zh.json +1 -0
- package/public/manager/src/InstancePreview.tsx +21 -1
- package/public/manager/src/SidebarRailRouter.tsx +32 -4
- package/public/manager/src/components/InstanceRow.tsx +0 -1
- package/public/manager/src/components/WorkbenchHeader.tsx +17 -7
- package/public/manager/src/folder-panel/FolderPanel.tsx +13 -2
- package/public/manager/src/hooks/useElectronDroppedPaths.ts +89 -0
- package/public/manager/src/manager-components.css +34 -0
- package/public/manager/src/manager-polish.css +10 -0
- package/public/manager/src/panels/desktop-bridge.ts +11 -0
- package/public/manager/src/settings/pages/components/agent/agent-meta.ts +4 -4
- package/scripts/smoke/agy-transcript-tail-smoke.mjs +89 -0
- package/public/dist/assets/agent-meta-B1098B_a.js +0 -1
- package/public/dist/assets/app-ByHYOMZE.js +0 -48
- package/public/dist/assets/app-CYdhP6Vh.css +0 -1
- package/public/dist/assets/constants-s2UJodER.js +0 -1
- package/public/dist/assets/manager-B2qEQRxN.css +0 -1
- package/public/dist/assets/manager-CR9BA-wO.js +0 -12
- package/public/dist/assets/memory-D1RKYvyu.js +0 -1
- package/public/dist/assets/render-DFBujF8n.js +0 -28
- package/public/dist/assets/settings-B4ZkeaU-.js +0 -1
- package/public/dist/assets/settings-D9jTceN0.js +0 -151
- package/public/dist/assets/sidebar-DPPRNiTc.js +0 -48
- package/public/dist/assets/skills-8nHJkv-r.js +0 -1
- package/public/dist/assets/slash-commands-RJWO8wJZ.js +0 -1
- package/public/dist/assets/ui-Crnp79bG.js +0 -142
- package/public/dist/assets/ui-HtSKByR3.js +0 -1
- /package/public/dist/assets/{locale-DT1WRaeJ.js → locale-BHMJIzyw.js} +0 -0
- /package/public/dist/assets/{provider-icons-CVVK5xUP.js → provider-icons-Crv6yvlG.js} +0 -0
|
@@ -12,6 +12,7 @@ import { apiJson } from '../api.js';
|
|
|
12
12
|
import { t } from './i18n.js';
|
|
13
13
|
import { setStatus } from './ui-status.js';
|
|
14
14
|
import { syncOrchestrateSnapshot } from '../ws.js';
|
|
15
|
+
import { copyText } from './copy-text.js';
|
|
15
16
|
|
|
16
17
|
export interface PendingItem {
|
|
17
18
|
id: string;
|
|
@@ -57,11 +58,13 @@ function renderRow(item: PendingItem): string {
|
|
|
57
58
|
const preview = (item.prompt || '').replace(/\s+/g, ' ').trim();
|
|
58
59
|
const truncated = preview.length > 140 ? preview.slice(0, 140) + '…' : preview;
|
|
59
60
|
const source = item.source ? `<span class="pending-row-source">${escapeHtml(item.source)}</span>` : '';
|
|
61
|
+
const copyLabel = escapeHtml(t('queue.copy'));
|
|
60
62
|
const steerLabel = escapeHtml(t('queue.steer'));
|
|
61
63
|
const deleteLabel = escapeHtml(t('queue.delete'));
|
|
62
64
|
return `<div class="pending-row" data-pending-id="${escapeHtml(item.id)}" title="${escapeHtml(preview)}">
|
|
63
65
|
<span class="pending-row-text">${escapeHtml(truncated)}</span>
|
|
64
66
|
${source}
|
|
67
|
+
<button class="pending-row-btn pending-row-copy" data-pending-action="copy" title="${copyLabel}" aria-label="${copyLabel}"><span class="pending-btn-content">${ICONS.copy}</span></button>
|
|
65
68
|
<button class="pending-row-btn pending-row-steer" data-pending-action="steer" data-i18n-title="queue.steer" title="${steerLabel}" aria-label="${steerLabel}"><span class="pending-arm-fill" aria-hidden="true"></span><span class="pending-btn-content"><span class="pending-steer-arrow" aria-hidden="true">↳</span><span class="pending-steer-label">${steerLabel}</span></span></button>
|
|
66
69
|
<button class="pending-row-btn pending-row-delete" data-pending-action="delete" data-i18n-title="queue.delete" title="${deleteLabel}" aria-label="${deleteLabel}"><span class="pending-arm-fill" aria-hidden="true"></span><span class="pending-btn-content">${ICONS.trash}</span></button>
|
|
67
70
|
</div>`;
|
|
@@ -169,13 +172,28 @@ function handleClick(id: string, action: Action): void {
|
|
|
169
172
|
export function initPendingQueue(): void {
|
|
170
173
|
const host = document.getElementById('pendingQueue');
|
|
171
174
|
if (!host) return;
|
|
172
|
-
host.addEventListener('click', (e) => {
|
|
175
|
+
host.addEventListener('click', async (e) => {
|
|
173
176
|
const btn = (e.target as HTMLElement)?.closest('[data-pending-action]') as HTMLElement | null;
|
|
174
177
|
if (!btn) return;
|
|
175
178
|
const row = btn.closest('.pending-row') as HTMLElement | null;
|
|
176
179
|
const id = row?.dataset['pendingId'];
|
|
177
180
|
if (!id) return;
|
|
178
|
-
const
|
|
181
|
+
const actionStr = btn.dataset['pendingAction'];
|
|
182
|
+
if (actionStr === 'copy') {
|
|
183
|
+
const item = lastItems.find(it => it.id === id);
|
|
184
|
+
if (item?.prompt) {
|
|
185
|
+
const result = await copyText(item.prompt);
|
|
186
|
+
if (result.ok) {
|
|
187
|
+
const content = btn.querySelector('.pending-btn-content');
|
|
188
|
+
if (content) {
|
|
189
|
+
content.innerHTML = ICONS.checkSimple;
|
|
190
|
+
setTimeout(() => { content.innerHTML = ICONS.copy; }, 600);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const action: Action = actionStr === 'steer' ? 'steer' : 'delete';
|
|
179
197
|
handleClick(id, action);
|
|
180
198
|
});
|
|
181
199
|
}
|
|
@@ -31,12 +31,10 @@ export const QUOTA_SETUP_HINTS: Record<string, QuotaSetupHint> = {
|
|
|
31
31
|
],
|
|
32
32
|
},
|
|
33
33
|
grok: {
|
|
34
|
-
title: 'Grok
|
|
34
|
+
title: 'Grok login',
|
|
35
35
|
commands: [
|
|
36
|
-
'
|
|
37
|
-
'grok models # verify auth',
|
|
36
|
+
'progrok login',
|
|
38
37
|
],
|
|
39
|
-
note: 'Subscription remaining quota: xAI console only. No official grok quota subcommand.',
|
|
40
38
|
},
|
|
41
39
|
opencode: {
|
|
42
40
|
title: 'OpenCode auth + optional plan quota plugin',
|
|
@@ -391,12 +391,16 @@ function renderCliStatus(data: { cliStatus: Record<string, { available: boolean
|
|
|
391
391
|
? renderSetupHelpMark(name, q)
|
|
392
392
|
: '';
|
|
393
393
|
|
|
394
|
+
const billingLabel = q?.billing?.usedUsd != null && q?.billing?.limitUsd
|
|
395
|
+
? `<span style="margin-left:auto;font-size:10px;color:var(--text-dim);white-space:nowrap">$${q.billing.usedUsd.toFixed(1)}/$${q.billing.limitUsd}</span>`
|
|
396
|
+
: '';
|
|
397
|
+
|
|
394
398
|
html += `
|
|
395
399
|
<div class="settings-group" style="margin-bottom:6px;padding:8px 10px">
|
|
396
|
-
<div class="cli-status-row">
|
|
400
|
+
<div class="cli-status-row" style="display:flex;align-items:center">
|
|
397
401
|
<span class="cli-dot ${dotClass}"></span>
|
|
398
402
|
<span class="cli-provider-icon" aria-hidden="true">${providerIcon(name) || ''}</span>
|
|
399
|
-
<span class="cli-name" style="font-weight:600">${escapeHtml(providerLabel(name))}${quotaHelpMark}</span>${name === 'copilot' ? `<button id="copilotKeychainBtn" style="font-size:9px;margin-left:6px;padding:1px 5px;background:var(--border);color:var(--text-dim);border:1px solid var(--text-dim);border-radius:3px;cursor:pointer;vertical-align:middle;line-height:1" title="${t('copilot.keychainHint')}">${ICONS.key}</button>` : ''}
|
|
403
|
+
<span class="cli-name" style="font-weight:600">${escapeHtml(providerLabel(name))}${quotaHelpMark}</span>${name === 'copilot' ? `<button id="copilotKeychainBtn" style="font-size:9px;margin-left:6px;padding:1px 5px;background:var(--border);color:var(--text-dim);border:1px solid var(--text-dim);border-radius:3px;cursor:pointer;vertical-align:middle;line-height:1" title="${t('copilot.keychainHint')}">${ICONS.key}</button>` : ''}${billingLabel}
|
|
400
404
|
</div>
|
|
401
405
|
${accountLine}
|
|
402
406
|
${authHint}
|
|
@@ -15,6 +15,7 @@ export interface QuotaEntry {
|
|
|
15
15
|
sessionUsageCapable?: boolean;
|
|
16
16
|
displayTier?: string;
|
|
17
17
|
delegatedProvider?: string;
|
|
18
|
+
billing?: { usedUsd?: number; limitUsd?: number; percent?: number; periodEnd?: string };
|
|
18
19
|
sessionUsage?: {
|
|
19
20
|
contextTokensUsed?: number | null;
|
|
20
21
|
contextWindowTokens?: number | null;
|
|
@@ -9,13 +9,17 @@ export function setStatus(s: string): void {
|
|
|
9
9
|
const badge = document.getElementById('statusBadge');
|
|
10
10
|
const btn = document.getElementById('btnSend');
|
|
11
11
|
const label = document.getElementById('typingIndicator')?.querySelector('.label') as HTMLElement | null;
|
|
12
|
-
state.agentBusy = s === 'running';
|
|
12
|
+
state.agentBusy = s === 'running' || s === 'steering';
|
|
13
13
|
document.getElementById('typingIndicator')?.classList.toggle('active', state.agentBusy);
|
|
14
14
|
if (s === 'running') {
|
|
15
15
|
if (badge) { badge.className = 'status-badge status-running'; badge.textContent = 'running'; }
|
|
16
16
|
if (btn) { btn.innerHTML = ICONS.stop; btn.title = t('btn.stop'); btn.classList.add('stop-mode'); }
|
|
17
17
|
if (label) label.textContent = t('status.responding');
|
|
18
18
|
showSkeleton();
|
|
19
|
+
} else if (s === 'steering') {
|
|
20
|
+
if (badge) { badge.className = 'status-badge status-steering'; badge.textContent = 'steering'; }
|
|
21
|
+
if (btn) { btn.innerHTML = ICONS.stop; btn.title = 'Steering...'; btn.classList.add('stop-mode'); }
|
|
22
|
+
if (label) label.textContent = 'Steering...';
|
|
19
23
|
} else {
|
|
20
24
|
if (badge) { badge.className = 'status-badge status-idle'; badge.textContent = 'idle'; }
|
|
21
25
|
if (btn) { btn.innerHTML = ICONS.send; btn.title = 'Send'; btn.classList.remove('stop-mode'); }
|
package/public/js/main.ts
CHANGED
|
@@ -90,6 +90,7 @@ import { initPendingQueue } from './features/pending-queue.js';
|
|
|
90
90
|
import { initAttentionBadge } from './features/attention-badge.js';
|
|
91
91
|
import { initHelpDialog } from './features/help-dialog.js';
|
|
92
92
|
import { initChatSearch, toggleChatSearch, closeChatSearch } from './features/chat-search.js';
|
|
93
|
+
import { initMediaLightbox } from './features/media-lightbox.js';
|
|
93
94
|
|
|
94
95
|
function isLocalPreviewOrigin(origin: string): boolean {
|
|
95
96
|
if (origin === window.location.origin) return true;
|
|
@@ -519,6 +520,7 @@ async function bootstrap(): Promise<void> {
|
|
|
519
520
|
bindPerCliControlEvents();
|
|
520
521
|
initHelpDialog();
|
|
521
522
|
initChatSearch();
|
|
523
|
+
initMediaLightbox();
|
|
522
524
|
document.getElementById('chatSearchTrigger')?.addEventListener('click', toggleChatSearch);
|
|
523
525
|
initAttentionBadge();
|
|
524
526
|
connect();
|
|
@@ -10,6 +10,7 @@ import { unshieldSvgBlocks } from './svg-actions.js';
|
|
|
10
10
|
import { highlightCode, ensureHighlightLanguages } from './highlight.js';
|
|
11
11
|
import { schedulePostRender } from './post-render.js';
|
|
12
12
|
import { ensureRenderDelegations } from './delegations.js';
|
|
13
|
+
import { API_BASE } from '../api.js';
|
|
13
14
|
|
|
14
15
|
// ── marked.js configuration (ES module — always available) ──
|
|
15
16
|
let markedReady = false;
|
|
@@ -46,6 +47,21 @@ function ensureMarked(): boolean {
|
|
|
46
47
|
return `<div class="code-block"><div class="code-header"><span class="code-lang">${langDisplay}</span><button class="code-copy-btn" type="button" aria-label="${escapeHtml(copyLabel)}">${escapeHtml(copyLabel)}</button></div><pre><code class="hljs${lang ? ` language-${escapeHtml(lang)}` : ''}">${highlighted}</code></pre></div>`;
|
|
47
48
|
};
|
|
48
49
|
|
|
50
|
+
// Inline media: rewrite absolute paths to /media/ URL, detect video
|
|
51
|
+
renderer.image = function ({ href, title, text }: { href: string; title?: string | null; text: string }) {
|
|
52
|
+
if (!href) return '';
|
|
53
|
+
const src = (href.startsWith('/') || href.includes('/uploads/'))
|
|
54
|
+
? `${API_BASE}/media/${encodeURIComponent(href.split('/').pop()!)}`
|
|
55
|
+
: escapeHtml(href);
|
|
56
|
+
const alt = escapeHtml(text || '');
|
|
57
|
+
const titleAttr = title ? ` title="${escapeHtml(title)}"` : '';
|
|
58
|
+
const ext = (src.split('.').pop() || '').toLowerCase().split('?')[0] || '';
|
|
59
|
+
if (['mp4', 'webm', 'mov', 'ogg'].includes(ext)) {
|
|
60
|
+
return `<video src="${src}" controls class="chat-inline-video" preload="metadata"${titleAttr}></video>`;
|
|
61
|
+
}
|
|
62
|
+
return `<img src="${src}" alt="${alt}" class="chat-inline-img" loading="lazy"${titleAttr} />`;
|
|
63
|
+
};
|
|
64
|
+
|
|
49
65
|
renderer.link = function ({ href, title, text }: { href: string; title?: string | null; text: string }) {
|
|
50
66
|
const safeHref = escapeHtml(href || '');
|
|
51
67
|
const titleAttr = title ? ` title="${escapeHtml(title)}"` : '';
|
package/public/js/ui.ts
CHANGED
|
@@ -76,6 +76,12 @@ export function cleanupToolActivity(): void {
|
|
|
76
76
|
currentStream = null;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/** Timestamp of last steer — used to suppress late agent_done after steer. */
|
|
80
|
+
let lastSteerTs = 0;
|
|
81
|
+
export function markSteered(): void { lastSteerTs = Date.now(); }
|
|
82
|
+
export function clearSteer(): void { lastSteerTs = 0; }
|
|
83
|
+
export function isRecentSteer(): boolean { return Date.now() - lastSteerTs < 8000; }
|
|
84
|
+
|
|
79
85
|
export function showLiveToolActivity(label: string): void {
|
|
80
86
|
removeSkeleton();
|
|
81
87
|
if (!state.currentAgentDiv || !state.currentAgentDiv.isConnected) {
|
|
@@ -307,11 +313,9 @@ export function finalizeAgent(text: string, toolLog?: ToolLogEntry[]): void {
|
|
|
307
313
|
}
|
|
308
314
|
state.currentAgentDiv.removeAttribute(ACTIVE_RUN_HYDRATED_ATTR);
|
|
309
315
|
const content = (state.currentAgentDiv as HTMLElement)?.querySelector('.msg-content');
|
|
310
|
-
// Live stream is preview-only; agent_done text
|
|
316
|
+
// Live stream is preview-only; agent_done text is always authoritative.
|
|
311
317
|
const streamedText = currentStream ? finalizeStream(currentStream, true) : '';
|
|
312
|
-
const finalText = text
|
|
313
|
-
? streamedText
|
|
314
|
-
: (text || streamedText);
|
|
318
|
+
const finalText = text || streamedText;
|
|
315
319
|
currentStream = null;
|
|
316
320
|
if (content) content.innerHTML = renderMarkdown(finalText);
|
|
317
321
|
if (hasTools && state.currentAgentDiv && !hadProcessBlock && !hasAgentToolBlock(state.currentAgentDiv)) {
|
package/public/js/ws.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// ── WebSocket Connection ──
|
|
2
2
|
import { state } from './state.js';
|
|
3
3
|
import { API_BASE } from './api.js';
|
|
4
|
-
import { setStatus, updateQueueBadge, addSystemMsg, appendAgentText, finalizeAgent, addMessage, showProcessStep, cleanupToolActivity,
|
|
4
|
+
import { setStatus, updateQueueBadge, addSystemMsg, appendAgentText, finalizeAgent, addMessage, showProcessStep, cleanupToolActivity, applyQueuedOverlay, hydrateActiveRun, reconcileChatBottomAfterRestore, showChatRestoreIndicator, markSteered, clearSteer, isRecentSteer } from './ui.js';
|
|
5
5
|
import { renderPendingQueue } from './features/pending-queue.js';
|
|
6
6
|
import { t, getLang } from './features/i18n.js';
|
|
7
7
|
import { getVirtualScroll } from './virtual-scroll.js';
|
|
@@ -107,6 +107,8 @@ interface WsMessage {
|
|
|
107
107
|
exitCode?: number;
|
|
108
108
|
error?: string;
|
|
109
109
|
message?: string;
|
|
110
|
+
steered?: boolean;
|
|
111
|
+
steerWaitMs?: number;
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
// Agent phase state (populated by agent_status events from orchestrator)
|
|
@@ -134,9 +136,10 @@ async function refreshRuntimeSnapshot(options: { hydrateRun?: boolean } = {}): P
|
|
|
134
136
|
renderPendingQueue(snap.queued || []);
|
|
135
137
|
if (options.hydrateRun) hydrateActiveRun(snap.activeRun);
|
|
136
138
|
hydrateGoalState();
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
139
|
+
if (snap.runtime.busy) {
|
|
140
|
+
setStatus('running');
|
|
141
|
+
} else if (!isRecentSteer()) {
|
|
142
|
+
setStatus('idle');
|
|
140
143
|
}
|
|
141
144
|
import('./features/employees.js').then(m => {
|
|
142
145
|
if (typeof m.renderEmployees === 'function') m.renderEmployees();
|
|
@@ -379,9 +382,9 @@ function renderInterviewPanel(interview: { known: (string | InterviewEvidenceVie
|
|
|
379
382
|
<span class="iv-summary">Interview · <span class="iv-known-count">Known ${interview.known.length}</span> · <span class="iv-unknown-count">Unknown ${interview.unknown.length}</span>${interview.known.filter(k => typeof k === 'object' && k && 'source' in k && (k as InterviewEvidenceView).source === 'assumption').length > 0 ? ` · <span class="iv-assumption-count">⚠️ ${interview.known.filter(k => typeof k === 'object' && k && 'source' in k && (k as InterviewEvidenceView).source === 'assumption').length} assumptions</span>` : ''} · Round ${interview.round}</span>
|
|
380
383
|
</button>
|
|
381
384
|
<div class="iv-body" id="interviewBody">
|
|
382
|
-
${interview.assessment ? renderDimensionBars(interview.assessment) : ''}
|
|
383
|
-
<div class="iv-section"><strong>Known (${interview.known.length})</strong><ul>${knownHtml}</ul></div>
|
|
384
|
-
<div class="iv-section"><strong>Unknown (${interview.unknown.length})</strong><ul>${unknownHtml}</ul></div>
|
|
385
|
+
${interview.assessment ? `<div class="iv-section iv-section-ambiguity">${renderDimensionBars(interview.assessment)}</div>` : ''}
|
|
386
|
+
<div class="iv-section iv-section-known"><strong>Known (${interview.known.length})</strong><ul>${knownHtml}</ul></div>
|
|
387
|
+
<div class="iv-section iv-section-unknown"><strong>Unknown (${interview.unknown.length})</strong><ul>${unknownHtml}</ul></div>
|
|
385
388
|
</div>
|
|
386
389
|
`;
|
|
387
390
|
if (!panel.dataset['toggleBound']) {
|
|
@@ -580,16 +583,12 @@ export function connect(): void {
|
|
|
580
583
|
return;
|
|
581
584
|
}
|
|
582
585
|
if (msg.type === 'agent_status') {
|
|
586
|
+
if (!msg.running && isRecentSteer()) return;
|
|
587
|
+
if (msg.running && isRecentSteer()) clearSteer();
|
|
583
588
|
if (msg.running !== undefined) {
|
|
584
589
|
setStatus(msg.running ? 'running' : 'idle');
|
|
585
|
-
if (msg.running && (msg.cli === 'agy' || msg.cli === 'kiro-code')) {
|
|
586
|
-
showLiveToolActivity(`${providerLabel(msg.cli)} working...`);
|
|
587
|
-
}
|
|
588
590
|
} else {
|
|
589
591
|
setStatus(msg.status || 'idle');
|
|
590
|
-
if (msg.status === 'running' && (msg.cli === 'agy' || msg.cli === 'kiro-code')) {
|
|
591
|
-
showLiveToolActivity(`${providerLabel(msg.cli)} working...`);
|
|
592
|
-
}
|
|
593
592
|
}
|
|
594
593
|
// Track per-agent phase for badge rendering
|
|
595
594
|
if (msg.agentId && msg.phase) {
|
|
@@ -617,6 +616,7 @@ export function connect(): void {
|
|
|
617
616
|
addSystemMsg(t('ws.roundRetry', { round: msg.round || 0 }));
|
|
618
617
|
}
|
|
619
618
|
} else if (msg.type === 'agent_tool') {
|
|
619
|
+
if (isRecentSteer()) return;
|
|
620
620
|
const stepType = msg.toolType === 'thinking' ? 'thinking'
|
|
621
621
|
: msg.toolType === 'search' ? 'search'
|
|
622
622
|
: msg.toolType === 'subagent' ? 'subagent' : 'tool';
|
|
@@ -638,6 +638,7 @@ export function connect(): void {
|
|
|
638
638
|
startTime: Date.now(),
|
|
639
639
|
});
|
|
640
640
|
} else if (msg.type === 'agent_output' || msg.type === 'agent_chunk') {
|
|
641
|
+
if (isRecentSteer()) return;
|
|
641
642
|
appendAgentText(msg.text || '');
|
|
642
643
|
} else if (msg.type === 'agent_retry') {
|
|
643
644
|
const retryDelay = Number(msg.delay ?? 0);
|
|
@@ -667,8 +668,13 @@ export function connect(): void {
|
|
|
667
668
|
const evt = msg.event;
|
|
668
669
|
if (evt) applyWorkflowEvent(evt);
|
|
669
670
|
} else if (msg.type === 'agent_done') {
|
|
670
|
-
|
|
671
|
-
|
|
671
|
+
if (msg.steered || isRecentSteer()) {
|
|
672
|
+
// Suppress agent_done from steered (killed) process.
|
|
673
|
+
// Server sets steered:true; isRecentSteer is fallback for edge cases.
|
|
674
|
+
} else {
|
|
675
|
+
finalizeAgent(msg.text || '', msg.toolLog);
|
|
676
|
+
notifyUnreadResponse();
|
|
677
|
+
}
|
|
672
678
|
} else if (msg.type === 'orchestrate_done') {
|
|
673
679
|
finalizeAgent(msg.text || '');
|
|
674
680
|
notifyUnreadResponse();
|
|
@@ -706,7 +712,9 @@ export function connect(): void {
|
|
|
706
712
|
} else if (msg.type === 'memory_status') {
|
|
707
713
|
import('./features/memory.js').then(m => m.refreshMemorySidebar());
|
|
708
714
|
} else if (msg.type === 'steer_started') {
|
|
709
|
-
|
|
715
|
+
markSteered();
|
|
716
|
+
finalizeAgent('');
|
|
717
|
+
setStatus('steering');
|
|
710
718
|
} else if (msg.type === 'new_message' && (msg.source === 'telegram' || msg.source === 'discord' || msg.fromQueue === true)) {
|
|
711
719
|
addMessage(msg.role === 'assistant' ? 'agent' : (msg.role || 'user'), msg.content || '', msg.cli);
|
|
712
720
|
} else if (msg.type === 'system_notice') {
|
|
@@ -725,6 +733,9 @@ export function connect(): void {
|
|
|
725
733
|
addSystemMsg(`⚠️ Goal continuation failed: ${escapeHtml(msg.error || '')}`, 'tool-activity');
|
|
726
734
|
} else if (msg.type === 'settings_change') {
|
|
727
735
|
syncOrchestrateSnapshot('settings_change').catch(() => {});
|
|
736
|
+
} else if (msg.type === 'session_switched' || msg.type === 'session_created') {
|
|
737
|
+
// Reload messages for the new active session
|
|
738
|
+
window.location.reload();
|
|
728
739
|
}
|
|
729
740
|
};
|
|
730
741
|
state.ws.onopen = () => {
|
package/public/locales/en.json
CHANGED
|
@@ -267,6 +267,7 @@
|
|
|
267
267
|
"btn.attach": "Attach file",
|
|
268
268
|
"input.placeholder": "Type a message...",
|
|
269
269
|
"queue.pendingTitle": "Pending",
|
|
270
|
+
"queue.copy": "Copy prompt",
|
|
270
271
|
"queue.delete": "Remove",
|
|
271
272
|
"queue.steer": "Run now (steer)",
|
|
272
273
|
"queue.cancelArm": "Click again to cancel",
|
package/public/locales/ja.json
CHANGED
package/public/locales/ko.json
CHANGED
package/public/locales/zh.json
CHANGED
|
@@ -13,6 +13,7 @@ type InstancePreviewProps = {
|
|
|
13
13
|
theme: PreviewTheme;
|
|
14
14
|
onOpenNotesFromPreview?: (path: string) => void;
|
|
15
15
|
onOpenDocFromPreview?: (absolutePath: string) => void;
|
|
16
|
+
onPreviewDroppedFiles?: (files: File[]) => void;
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
type PreviewOpenNotesMessage = {
|
|
@@ -26,6 +27,11 @@ type PreviewSendMessage = {
|
|
|
26
27
|
prompt?: unknown;
|
|
27
28
|
};
|
|
28
29
|
|
|
30
|
+
type PreviewDroppedFilesMessage = {
|
|
31
|
+
type?: unknown;
|
|
32
|
+
files?: unknown;
|
|
33
|
+
};
|
|
34
|
+
|
|
29
35
|
function normalizeLoopbackHostname(hostname: string): string {
|
|
30
36
|
return hostname.replace(/^\[|\]$/g, '').toLowerCase();
|
|
31
37
|
}
|
|
@@ -144,6 +150,11 @@ function isPreviewSttShortcut(event: KeyboardEvent): boolean {
|
|
|
144
150
|
return primarySpace || fallbackMic;
|
|
145
151
|
}
|
|
146
152
|
|
|
153
|
+
function extractDroppedFiles(data: PreviewDroppedFilesMessage | null): File[] {
|
|
154
|
+
if (!data || data.type !== 'jaw-preview-dropped-files' || !Array.isArray(data.files)) return [];
|
|
155
|
+
return data.files.filter((item): item is File => item instanceof File);
|
|
156
|
+
}
|
|
157
|
+
|
|
147
158
|
export function InstancePreview(props: InstancePreviewProps) {
|
|
148
159
|
const iframeRef = useRef<HTMLIFrameElement | null>(null);
|
|
149
160
|
const loadedSrcRef = useRef<string | null>(null);
|
|
@@ -224,12 +235,21 @@ export function InstancePreview(props: InstancePreviewProps) {
|
|
|
224
235
|
props.onOpenDocFromPreview?.(path);
|
|
225
236
|
}
|
|
226
237
|
window.addEventListener('message', onPreviewOpenDoc);
|
|
238
|
+
function onPreviewDroppedFiles(event: MessageEvent): void {
|
|
239
|
+
if (event.source !== iframeRef.current?.contentWindow) return;
|
|
240
|
+
if (!state.src || !previewFrameOriginMatches(event.origin, state.src, iframeRef.current)) return;
|
|
241
|
+
const files = extractDroppedFiles(event.data as PreviewDroppedFilesMessage | null);
|
|
242
|
+
if (files.length === 0) return;
|
|
243
|
+
props.onPreviewDroppedFiles?.(files);
|
|
244
|
+
}
|
|
245
|
+
window.addEventListener('message', onPreviewDroppedFiles);
|
|
227
246
|
return () => {
|
|
228
247
|
window.removeEventListener('message', onPreviewSend);
|
|
229
248
|
window.removeEventListener('message', onPreviewOpenNotes);
|
|
230
249
|
window.removeEventListener('message', onPreviewOpenDoc);
|
|
250
|
+
window.removeEventListener('message', onPreviewDroppedFiles);
|
|
231
251
|
};
|
|
232
|
-
}, [props.enabled, props.instance, props.onOpenNotesFromPreview, props.onOpenDocFromPreview, state.canPreview, state.src]);
|
|
252
|
+
}, [props.enabled, props.instance, props.onOpenNotesFromPreview, props.onOpenDocFromPreview, props.onPreviewDroppedFiles, state.canPreview, state.src]);
|
|
233
253
|
|
|
234
254
|
useEffect(() => {
|
|
235
255
|
if (!props.active || !props.enabled || !state.canPreview || !state.src) return undefined;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, type ReactNode, Suspense } from 'react';
|
|
1
|
+
import { useCallback, useState, type ReactNode, Suspense } from 'react';
|
|
2
2
|
import { ActivityDock } from './components/ActivityDock';
|
|
3
3
|
import { InstanceDrawer } from './components/InstanceDrawer';
|
|
4
4
|
import { InstanceNavigator } from './components/InstanceNavigator';
|
|
@@ -31,6 +31,12 @@ import { DashboardScheduleWorkspace } from './dashboard-schedule/DashboardSchedu
|
|
|
31
31
|
import { DashboardRemindersSidebar, type RemindersView } from './dashboard-reminders/DashboardRemindersSidebar';
|
|
32
32
|
import { DashboardRemindersWorkspace } from './dashboard-reminders/DashboardRemindersWorkspace';
|
|
33
33
|
import { useRemindersFeed } from './dashboard-reminders/useRemindersFeed';
|
|
34
|
+
import {
|
|
35
|
+
firstDirectory,
|
|
36
|
+
firstFile,
|
|
37
|
+
useElectronDroppedPaths,
|
|
38
|
+
type ElectronDroppedPathsEvent,
|
|
39
|
+
} from './hooks/useElectronDroppedPaths';
|
|
34
40
|
import type { HelpTopicId } from './help/helpContent';
|
|
35
41
|
import { NotesCommandPalette } from './notes/NotesCommandPalette';
|
|
36
42
|
import { NotesCommandProvider } from './notes/notes-command-registry';
|
|
@@ -140,6 +146,7 @@ type Props = {
|
|
|
140
146
|
function renderRightPanelContent(
|
|
141
147
|
mode: RightPanelMode,
|
|
142
148
|
previewFilePath: string | null,
|
|
149
|
+
folderRootPath: string | null,
|
|
143
150
|
onPreviewFile: (path: string) => void,
|
|
144
151
|
selectedInstance: DashboardInstance | null,
|
|
145
152
|
dashboardSettingsUi: DashboardRegistryUi,
|
|
@@ -149,7 +156,7 @@ function renderRightPanelContent(
|
|
|
149
156
|
const fallback = <div style={{ padding: '12px', color: 'var(--text-dim)', fontSize: '12px' }}>Loading...</div>;
|
|
150
157
|
switch (mode) {
|
|
151
158
|
case 'diff': return <Suspense fallback={fallback}><DiffPanel selectedInstance={selectedInstance} settings={dashboardSettingsUi} onSettingsPatch={onDashboardSettingsPatch} /></Suspense>;
|
|
152
|
-
case 'folder': return <Suspense fallback={fallback}><FolderPanel selectedFilePath={previewFilePath} notesTree={notesModel.tree} notesRoot={notesModel.notesRoot} onPreviewFile={onPreviewFile} /></Suspense>;
|
|
159
|
+
case 'folder': return <Suspense fallback={fallback}><FolderPanel selectedFilePath={previewFilePath} externalRootPath={folderRootPath} notesTree={notesModel.tree} notesRoot={notesModel.notesRoot} onPreviewFile={onPreviewFile} /></Suspense>;
|
|
153
160
|
case 'doc': return <Suspense fallback={fallback}><DocPanel filePath={previewFilePath ?? undefined} /></Suspense>;
|
|
154
161
|
case 'browser': return <Suspense fallback={fallback}><BrowserPanel /></Suspense>;
|
|
155
162
|
default: return null;
|
|
@@ -168,6 +175,8 @@ function renderBottomTabContent(tab: BottomPanelTab, controls: BottomPanelRender
|
|
|
168
175
|
export function SidebarRailRouter(props: Props) {
|
|
169
176
|
const panelLayout = usePanelLayout();
|
|
170
177
|
const [rightPreviewFilePath, setRightPreviewFilePath] = useState<string | null>(null);
|
|
178
|
+
const [rightFolderRootPath, setRightFolderRootPath] = useState<string | null>(null);
|
|
179
|
+
const [, setRecentDroppedPaths] = useState<ElectronDroppedPathsEvent | null>(null);
|
|
171
180
|
const [remindersView, setRemindersView] = useState<RemindersView>('matrix');
|
|
172
181
|
const remindersFeed = useRemindersFeed({ active: props.sidebarMode === 'reminders' });
|
|
173
182
|
const desktopPanelsAvailable = currentManagerSurface() === 'electron';
|
|
@@ -184,6 +193,25 @@ export function SidebarRailRouter(props: Props) {
|
|
|
184
193
|
panelLayout.dispatch({ type: 'OPEN_RIGHT_PANEL', mode: 'doc', slot: 'bottom' });
|
|
185
194
|
}
|
|
186
195
|
|
|
196
|
+
const handleDroppedPaths = useCallback((event: ElectronDroppedPathsEvent): void => {
|
|
197
|
+
setRecentDroppedPaths(event);
|
|
198
|
+
if (event.source === 'preview') return;
|
|
199
|
+
const directory = firstDirectory(event.entries);
|
|
200
|
+
if (directory) {
|
|
201
|
+
setRightFolderRootPath(directory.path);
|
|
202
|
+
panelLayout.dispatch({ type: 'OPEN_RIGHT_PANEL', mode: 'folder', slot: 'top' });
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const file = firstFile(event.entries);
|
|
206
|
+
if (file) handleRightPreviewFile(file.path);
|
|
207
|
+
}, [panelLayout]);
|
|
208
|
+
|
|
209
|
+
const electronDrop = useElectronDroppedPaths({ onDroppedPaths: handleDroppedPaths });
|
|
210
|
+
|
|
211
|
+
const handlePreviewDroppedFiles = useCallback((files: File[]): void => {
|
|
212
|
+
void electronDrop.resolveDroppedFiles(files, 'preview');
|
|
213
|
+
}, [electronDrop]);
|
|
214
|
+
|
|
187
215
|
return (
|
|
188
216
|
<NotesCommandProvider>
|
|
189
217
|
{props.jawCeoVoiceOverlay}
|
|
@@ -195,7 +223,7 @@ export function SidebarRailRouter(props: Props) {
|
|
|
195
223
|
onCloseDrawer={props.onCloseDrawer}
|
|
196
224
|
rightPanelOpen={rightPanelOpen}
|
|
197
225
|
rightPanelWidth={panelLayout.state.rightPanel.width}
|
|
198
|
-
rightPanelContent={rightPanelOpen ? <RightSidebar renderPanel={mode => renderRightPanelContent(mode, rightPreviewFilePath, handleRightPreviewFile, props.selectedInstance, props.dashboardSettingsUi, props.onDashboardSettingsPatch, props.notesModel)} /> : undefined}
|
|
226
|
+
rightPanelContent={rightPanelOpen ? <RightSidebar renderPanel={mode => renderRightPanelContent(mode, rightPreviewFilePath, rightFolderRootPath, handleRightPreviewFile, props.selectedInstance, props.dashboardSettingsUi, props.onDashboardSettingsPatch, props.notesModel)} /> : undefined}
|
|
199
227
|
bottomPanelOpen={bottomPanelOpen}
|
|
200
228
|
bottomPanelHeight={panelLayout.state.bottomPanel.height}
|
|
201
229
|
bottomPanelContent={bottomPanelOpen && panelLayout.state.bottomPanel.tabs.length > 0 ? <BottomPanel renderTab={renderBottomTabContent} /> : undefined}
|
|
@@ -243,7 +271,7 @@ export function SidebarRailRouter(props: Props) {
|
|
|
243
271
|
<div className="workspace-surface-layer">
|
|
244
272
|
<WorkspaceSurface active={props.sidebarMode === 'instances'}>
|
|
245
273
|
<Workbench mode={props.activeDetailTab} onModeChange={props.onDetailTabChange} header={props.workbenchHeader} modeActions={props.jawCeoWorkbenchButton} overview={props.detailContent('overview')} preview={(
|
|
246
|
-
<InstancePreview instance={props.selectedInstance} data={props.data} enabled={props.previewEnabled} active={props.sidebarMode === 'instances' && props.activeDetailTab === 'preview'} refreshKey={props.previewRefreshKey} theme={props.previewTheme} {...(props.onOpenNotesFromPreview ? { onOpenNotesFromPreview: props.onOpenNotesFromPreview } : {})} onOpenDocFromPreview={handleRightPreviewFile} />
|
|
274
|
+
<InstancePreview instance={props.selectedInstance} data={props.data} enabled={props.previewEnabled} active={props.sidebarMode === 'instances' && props.activeDetailTab === 'preview'} refreshKey={props.previewRefreshKey} theme={props.previewTheme} {...(props.onOpenNotesFromPreview ? { onOpenNotesFromPreview: props.onOpenNotesFromPreview } : {})} onOpenDocFromPreview={handleRightPreviewFile} onPreviewDroppedFiles={handlePreviewDroppedFiles} />
|
|
247
275
|
)} logs={props.detailContent('logs')} settings={props.detailContent('settings')} />
|
|
248
276
|
</WorkspaceSurface>
|
|
249
277
|
<WorkspaceSurface active={props.sidebarMode === 'notes'}>
|
|
@@ -67,7 +67,6 @@ export function InstanceRow(props: InstanceRowProps) {
|
|
|
67
67
|
const transitionLabel = props.transitioning ? TRANSITION_LABELS[props.transitioning] : null;
|
|
68
68
|
const dotClass = `${statusClass(props.instance.status)}${transitionLabel ? ' is-transitioning' : ''}${props.agentBusy ? ' is-busy' : ''}`;
|
|
69
69
|
const primaryLabel = props.instance.label || props.profile?.label || props.label;
|
|
70
|
-
|
|
71
70
|
async function submitLabel(event: FormEvent<HTMLFormElement>): Promise<void> {
|
|
72
71
|
event.preventDefault();
|
|
73
72
|
event.stopPropagation();
|
|
@@ -11,27 +11,37 @@ type WorkbenchHeaderProps = {
|
|
|
11
11
|
onOpenHelpTopic?: (topic: HelpTopicId) => void;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
+
function compactPath(path: string): string {
|
|
15
|
+
const parts = path.split(/[/\\]/).filter(Boolean);
|
|
16
|
+
if (parts.length <= 2) return path;
|
|
17
|
+
return `…/${parts.slice(-2).join('/')}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
14
20
|
export function WorkbenchHeader(props: WorkbenchHeaderProps) {
|
|
15
21
|
const instance = props.instance;
|
|
16
22
|
const canPreview = Boolean(instance?.ok);
|
|
17
23
|
const previewLabel = props.previewEnabled ? 'Preview on' : 'Preview off';
|
|
18
24
|
const hasActions = Boolean(instance || props.onOpenHelpTopic);
|
|
25
|
+
const projectDirs = instance?.projectDirs?.filter(Boolean) ?? [];
|
|
19
26
|
|
|
20
27
|
return (
|
|
21
28
|
<div className="detail-header">
|
|
22
29
|
<div>
|
|
23
30
|
<p className="eyebrow">Selected instance</p>
|
|
24
31
|
<h2>{instance ? instanceLabel(instance) : 'No instance selected'}</h2>
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
{instance.projectDirs.map((dir, i) => (
|
|
32
|
+
{instance ? (
|
|
33
|
+
<div className={`project-dirs${projectDirs.length ? '' : ' is-empty'}`}>
|
|
34
|
+
<span className="label">Project</span>
|
|
35
|
+
{projectDirs.length ? projectDirs.map((dir, i) => (
|
|
30
36
|
<span key={i} className="project-dir" title={dir}>
|
|
31
|
-
{dir
|
|
37
|
+
{compactPath(dir)}
|
|
32
38
|
</span>
|
|
33
|
-
))
|
|
39
|
+
)) : (
|
|
40
|
+
<span className="project-dir">Not set</span>
|
|
41
|
+
)}
|
|
34
42
|
</div>
|
|
43
|
+
) : (
|
|
44
|
+
<span>Select an online instance to inspect it.</span>
|
|
35
45
|
)}
|
|
36
46
|
</div>
|
|
37
47
|
{hasActions && (
|
|
@@ -10,6 +10,7 @@ function getFolderBridge(): FolderBridgeApi | null {
|
|
|
10
10
|
|
|
11
11
|
type FolderPanelProps = {
|
|
12
12
|
selectedFilePath?: string | null | undefined;
|
|
13
|
+
externalRootPath?: string | null | undefined;
|
|
13
14
|
notesTree?: NotesTreeEntry[] | undefined;
|
|
14
15
|
notesRoot?: string | null | undefined;
|
|
15
16
|
onPreviewFile?: ((path: string) => void) | undefined;
|
|
@@ -73,7 +74,7 @@ export function FolderPanel(props: FolderPanelProps) {
|
|
|
73
74
|
}, [loadDir, rootPath, source]);
|
|
74
75
|
|
|
75
76
|
useEffect(() => {
|
|
76
|
-
if (rootPath !== null) return;
|
|
77
|
+
if (rootPath !== null || props.externalRootPath) return;
|
|
77
78
|
let cancelled = false;
|
|
78
79
|
void (async () => {
|
|
79
80
|
const nextRoot = await source.getDefaultRoot();
|
|
@@ -82,7 +83,17 @@ export function FolderPanel(props: FolderPanelProps) {
|
|
|
82
83
|
await loadDir(nextRoot);
|
|
83
84
|
})();
|
|
84
85
|
return () => { cancelled = true; };
|
|
85
|
-
}, [loadDir, rootPath, source]);
|
|
86
|
+
}, [loadDir, props.externalRootPath, rootPath, source]);
|
|
87
|
+
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
const externalRoot = props.externalRootPath;
|
|
90
|
+
if (!externalRoot || externalRoot === rootPath) return;
|
|
91
|
+
if (rootPath && source.unwatchDir) void source.unwatchDir(rootPath);
|
|
92
|
+
setRootPath(externalRoot);
|
|
93
|
+
setExpanded(new Set());
|
|
94
|
+
setChildrenCache(new Map());
|
|
95
|
+
void loadDir(externalRoot);
|
|
96
|
+
}, [loadDir, props.externalRootPath, rootPath, source]);
|
|
86
97
|
|
|
87
98
|
useEffect(() => {
|
|
88
99
|
if (!source.watchDir || !source.onDirChange || rootPath === null) return;
|