dsh-fps-workbench 0.1.77 → 0.1.79
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/CHANGELOG.md +36 -0
- package/README.md +106 -46
- package/README.zh.md +106 -46
- package/client.js +474 -53
- package/docs/screenshots/en/01-workspace.jpg +0 -0
- package/docs/screenshots/en/02-tasks.jpg +0 -0
- package/docs/screenshots/en/03-preview.jpg +0 -0
- package/docs/screenshots/en/04-drafts.jpg +0 -0
- package/docs/screenshots/en/05-image-tools.jpg +0 -0
- package/docs/screenshots/en/06-compatibility.jpg +0 -0
- package/docs/screenshots/zh/01-workspace.jpg +0 -0
- package/docs/screenshots/zh/02-tasks.jpg +0 -0
- package/docs/screenshots/zh/03-preview.jpg +0 -0
- package/docs/screenshots/zh/04-drafts.jpg +0 -0
- package/docs/screenshots/zh/05-image-tools.jpg +0 -0
- package/docs/screenshots/zh/06-compatibility.jpg +0 -0
- package/locale/detail.en.md +9 -5
- package/locale/detail.zh.md +9 -5
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +11 -4
- package/provider-usage.js +65 -1
- package/screenshots.json +10 -0
package/client.js
CHANGED
|
@@ -51,9 +51,46 @@ window.__ModuleLoader__.load({
|
|
|
51
51
|
|
|
52
52
|
function reportDiagnostic(message) {
|
|
53
53
|
console.error(`[claude-workbench] ${message}`);
|
|
54
|
+
runtimeFailure = true;
|
|
55
|
+
compatibilityChanged();
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
const en = {
|
|
59
|
+
draft_loading: "Restoring attachments…",
|
|
60
|
+
draft_saving: "Saving attachments on this device…",
|
|
61
|
+
draft_saved: "Attachments saved on this device · kept for 30 days",
|
|
62
|
+
draft_error: "Attachments could not be saved or restored. Keep this window open and retry.",
|
|
63
|
+
draft_memory: "Attachments are available in this window only.",
|
|
64
|
+
tasksTitle: "Task status",
|
|
65
|
+
tasksPlan: "Plan",
|
|
66
|
+
tasksNoPlan: "No plan has been recorded in this session.",
|
|
67
|
+
tasksBackground: "Background tasks",
|
|
68
|
+
tasksEmpty: "No background tasks are reported by DSH.",
|
|
69
|
+
tasksUnavailable: "Background task status is unavailable. Use the native session header.",
|
|
70
|
+
tasksSettled: "Finished",
|
|
71
|
+
tasksNative: "Open native details and controls",
|
|
72
|
+
task_running: "Running",
|
|
73
|
+
task_stopping: "Stopping",
|
|
74
|
+
task_completed: "Completed",
|
|
75
|
+
task_failed: "Failed",
|
|
76
|
+
task_killed: "Stopped",
|
|
77
|
+
task_pending: "Pending",
|
|
78
|
+
task_in_progress: "In progress",
|
|
79
|
+
task_unknown: "Unknown",
|
|
80
|
+
compatTitle: "Compatibility check",
|
|
81
|
+
compatIntro: "Checks the interfaces available in this client. This does not test a model or execute tools. Missing optional components keep their native entry points.",
|
|
82
|
+
compatComposer: "Conversation composer",
|
|
83
|
+
compatFiles: "File attachments and metadata",
|
|
84
|
+
compatPreview: "Native file preview",
|
|
85
|
+
compatJobs: "Native background tasks",
|
|
86
|
+
compatStorage: "Attachment persistence",
|
|
87
|
+
compatReady: "Available",
|
|
88
|
+
compatUnavailable: "Unavailable / native fallback",
|
|
89
|
+
compatRefresh: "Check again",
|
|
90
|
+
compatCopy: "Copy diagnostic summary",
|
|
91
|
+
compatCopied: "Copied",
|
|
92
|
+
compatRuntimeFailure: "A component error was observed. A failed FPS composer falls back to DSH’s native input.",
|
|
93
|
+
|
|
57
94
|
greetingMorning: 'Good morning',
|
|
58
95
|
greetingAfternoon: 'Good afternoon',
|
|
59
96
|
greetingEvening: 'Good evening',
|
|
@@ -140,6 +177,8 @@ window.__ModuleLoader__.load({
|
|
|
140
177
|
usageDreaminaBalance: 'Credit balance',
|
|
141
178
|
usageKeySpent: 'Key spend',
|
|
142
179
|
usageKeyRemaining: 'Key spending limit remaining',
|
|
180
|
+
usageOrganizationSpend: 'Org spend · 7 days',
|
|
181
|
+
usageOrganizationTokens: 'Org tokens · 7 days',
|
|
143
182
|
usageDreaminaReset: 'Estimated subscription credit reset',
|
|
144
183
|
usageExtraBalance: 'Extra usage balance',
|
|
145
184
|
usageFiveHours: '5-hour limit',
|
|
@@ -235,6 +274,41 @@ window.__ModuleLoader__.load({
|
|
|
235
274
|
permissionEnable: 'Enable',
|
|
236
275
|
};
|
|
237
276
|
const zh = {
|
|
277
|
+
draft_loading: "正在恢复附件…",
|
|
278
|
+
draft_saving: "正在将附件保存到本机…",
|
|
279
|
+
draft_saved: "附件已保存到本机 · 保留 30 天",
|
|
280
|
+
draft_error: "附件保存或恢复失败,请保留当前窗口并重试。",
|
|
281
|
+
draft_memory: "附件仅保留在当前窗口。",
|
|
282
|
+
tasksTitle: "任务状态",
|
|
283
|
+
tasksPlan: "计划步骤",
|
|
284
|
+
tasksNoPlan: "当前会话尚未记录计划步骤。",
|
|
285
|
+
tasksBackground: "后台任务",
|
|
286
|
+
tasksEmpty: "DSH 暂未报告后台任务。",
|
|
287
|
+
tasksUnavailable: "暂时无法读取后台任务,请使用会话顶栏的原生入口。",
|
|
288
|
+
tasksSettled: "已结束",
|
|
289
|
+
tasksNative: "打开原生详情与操作",
|
|
290
|
+
task_running: "运行中",
|
|
291
|
+
task_stopping: "正在停止",
|
|
292
|
+
task_completed: "已完成",
|
|
293
|
+
task_failed: "失败",
|
|
294
|
+
task_killed: "已停止",
|
|
295
|
+
task_pending: "待处理",
|
|
296
|
+
task_in_progress: "进行中",
|
|
297
|
+
task_unknown: "未知",
|
|
298
|
+
compatTitle: "兼容性自检",
|
|
299
|
+
compatIntro: "检查当前客户端可用接口,不调用模型或执行工具。可选组件缺失时保留 DSH 原生入口。",
|
|
300
|
+
compatComposer: "会话输入框",
|
|
301
|
+
compatFiles: "文件附件与信息",
|
|
302
|
+
compatPreview: "原生文件预览",
|
|
303
|
+
compatJobs: "原生后台任务",
|
|
304
|
+
compatStorage: "附件持久化",
|
|
305
|
+
compatReady: "可用",
|
|
306
|
+
compatUnavailable: "不可用 / 使用原生入口",
|
|
307
|
+
compatRefresh: "重新检查",
|
|
308
|
+
compatCopy: "复制诊断摘要",
|
|
309
|
+
compatCopied: "已复制",
|
|
310
|
+
compatRuntimeFailure: "检测到组件异常。FPS 输入框渲染失败时会回退到 DSH 原生输入框。",
|
|
311
|
+
|
|
238
312
|
greetingMorning: '早上好',
|
|
239
313
|
greetingAfternoon: '下午好',
|
|
240
314
|
greetingEvening: '晚上好',
|
|
@@ -321,6 +395,8 @@ window.__ModuleLoader__.load({
|
|
|
321
395
|
usageDreaminaBalance: '积分余额',
|
|
322
396
|
usageKeySpent: 'Key 已用金额',
|
|
323
397
|
usageKeyRemaining: 'Key 剩余支出额度',
|
|
398
|
+
usageOrganizationSpend: '组织花费 · 近 7 天',
|
|
399
|
+
usageOrganizationTokens: '组织 Token · 近 7 天',
|
|
324
400
|
usageDreaminaReset: '订阅积分重置(推算)',
|
|
325
401
|
usageExtraBalance: '加量余额',
|
|
326
402
|
usageFiveHours: '5 小时限额',
|
|
@@ -472,6 +548,24 @@ window.__ModuleLoader__.load({
|
|
|
472
548
|
}
|
|
473
549
|
|
|
474
550
|
const CSS = `
|
|
551
|
+
.cw-draft-status { padding: 3px 12px 8px; color: var(--cw-text-muted); font-size: 11px; }
|
|
552
|
+
.cw-draft-status button, .cw-compatibility button { border: 1px solid var(--cw-border); border-radius: 6px; padding: 4px 8px; margin: 5px; background: transparent; color: inherit; cursor: pointer; }
|
|
553
|
+
.cw-compatibility { padding: 12px 0 20px; margin-bottom: 20px; border-bottom: 1px solid var(--cw-border); font-size: 12px; }
|
|
554
|
+
.cw-compatibility summary { font-size: 14px; cursor: pointer; }
|
|
555
|
+
.cw-long-tasks h3 { font-size: 12px; font-weight: 500; color: var(--cw-text-secondary); margin: 16px 0 8px; }
|
|
556
|
+
.cw-long-tasks summary { cursor: pointer; font-size: 12px; color: var(--cw-text-secondary); padding: 6px 0; }
|
|
557
|
+
.cw-task-list, .cw-task-plan { list-style: none; padding: 0; margin: 4px 0; }
|
|
558
|
+
.cw-task-row, .cw-task-plan li { display: flex; gap: 9px; padding: 7px 0; font-size: 12px; }
|
|
559
|
+
.cw-task-dot { flex: 0 0 12px; width: 12px; height: 12px; margin-top: 2px; border: 1px solid currentColor; border-radius: 50%; color: var(--cw-text-muted); font-size: 9px; line-height: 11px; text-align: center; }
|
|
560
|
+
[data-status="pending"] > .cw-task-dot { border-style: dashed; }
|
|
561
|
+
[data-status="running"] > .cw-task-dot, [data-status="in_progress"] > .cw-task-dot { color: var(--cw-accent); border-width: 2px; }
|
|
562
|
+
[data-status="failed"] > .cw-task-dot { color: var(--cw-danger); }
|
|
563
|
+
.cw-task-row[data-status="completed"], .cw-task-plan li[data-status="completed"] { color: var(--cw-text-muted); }
|
|
564
|
+
.cw-task-main { display: flex; flex: 1; flex-direction: column; gap: 3px; min-width: 0; }
|
|
565
|
+
.cw-task-main > span, .cw-task-main small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
566
|
+
.cw-task-main small, .cw-native-jobs > small { color: var(--cw-text-muted); font-size: 11px; }
|
|
567
|
+
.cw-native-jobs { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 9px; }
|
|
568
|
+
|
|
475
569
|
.cw-image-settings { padding: 4px 0 16px; color: var(--dsw-alias-label-primary); }
|
|
476
570
|
.cw-image-settings h2 { margin: 0 0 5px; font-size: 15px; font-weight: 600; }
|
|
477
571
|
.cw-image-settings p { margin: 0; color: var(--dsw-alias-label-secondary); font-size: 13px; line-height: 1.55; }
|
|
@@ -1557,6 +1651,7 @@ window.__ModuleLoader__.load({
|
|
|
1557
1651
|
|
|
1558
1652
|
componentDidCatch(error) {
|
|
1559
1653
|
reportDiagnostic(`slot render failed: ${error?.message || error}`);
|
|
1654
|
+
this.props.onFailure?.();
|
|
1560
1655
|
}
|
|
1561
1656
|
|
|
1562
1657
|
render() {
|
|
@@ -1677,6 +1772,7 @@ window.__ModuleLoader__.load({
|
|
|
1677
1772
|
}
|
|
1678
1773
|
|
|
1679
1774
|
return h('section', { className: 'cw-image-settings', 'aria-labelledby': 'cw-image-heading' },
|
|
1775
|
+
h(CompatibilityPanel, { t }),
|
|
1680
1776
|
h('h2', { id: 'cw-image-heading' }, t('imageSettings')),
|
|
1681
1777
|
h('p', null, t('imageSettingsIntro')),
|
|
1682
1778
|
loading ? h('p', { role: 'status' }, t('imageSettingsLoad'))
|
|
@@ -2339,44 +2435,164 @@ window.__ModuleLoader__.load({
|
|
|
2339
2435
|
}
|
|
2340
2436
|
}
|
|
2341
2437
|
|
|
2342
|
-
|
|
2438
|
+
// Browser/profile-local storage; file bytes never leave the device until Send.
|
|
2439
|
+
function createAttachmentStorage(indexedDB = globalThis.indexedDB, timeoutMs = 10000) {
|
|
2440
|
+
let database;
|
|
2441
|
+
function open() {
|
|
2442
|
+
if (database) return database;
|
|
2443
|
+
const pending = new Promise((resolve, reject) => {
|
|
2444
|
+
if (!indexedDB) return reject(new Error('Storage unavailable'));
|
|
2445
|
+
let settled = false;
|
|
2446
|
+
const fail = (error) => {
|
|
2447
|
+
if (settled) return;
|
|
2448
|
+
settled = true; clearTimeout(timer); reject(error);
|
|
2449
|
+
};
|
|
2450
|
+
const timer = setTimeout(() => fail(new Error('Storage open timed out')), timeoutMs);
|
|
2451
|
+
let request;
|
|
2452
|
+
try { request = indexedDB.open('fps-attachment-drafts', 1); }
|
|
2453
|
+
catch (error) { fail(error); return; }
|
|
2454
|
+
request.onupgradeneeded = () => {
|
|
2455
|
+
if (settled) { request.transaction?.abort(); return; }
|
|
2456
|
+
request.result.createObjectStore('drafts', { keyPath: 'id' });
|
|
2457
|
+
};
|
|
2458
|
+
request.onerror = () => fail(request.error);
|
|
2459
|
+
request.onblocked = () => fail(new Error('Storage blocked'));
|
|
2460
|
+
request.onsuccess = () => {
|
|
2461
|
+
const db = request.result;
|
|
2462
|
+
if (settled) { db.close(); return; }
|
|
2463
|
+
settled = true; clearTimeout(timer);
|
|
2464
|
+
db.onversionchange = () => { db.close(); database = undefined; };
|
|
2465
|
+
resolve(db);
|
|
2466
|
+
};
|
|
2467
|
+
});
|
|
2468
|
+
database = pending;
|
|
2469
|
+
pending.catch(() => { if (database === pending) database = undefined; });
|
|
2470
|
+
return pending;
|
|
2471
|
+
}
|
|
2472
|
+
async function transaction(mode, action) {
|
|
2473
|
+
const db = await open();
|
|
2474
|
+
return new Promise((resolve, reject) => {
|
|
2475
|
+
const tx = db.transaction('drafts', mode);
|
|
2476
|
+
let value, settled = false;
|
|
2477
|
+
function finish(error) {
|
|
2478
|
+
if (settled) return;
|
|
2479
|
+
settled = true; clearTimeout(timer);
|
|
2480
|
+
if (error) reject(error); else resolve(value);
|
|
2481
|
+
}
|
|
2482
|
+
const timer = setTimeout(() => {
|
|
2483
|
+
// Abort before allowing a retry: an old write must never commit later.
|
|
2484
|
+
try { tx.abort(); } catch { /* Transaction already settled. */ }
|
|
2485
|
+
finish(new Error('Draft storage timed out'));
|
|
2486
|
+
}, timeoutMs);
|
|
2487
|
+
tx.oncomplete = () => finish();
|
|
2488
|
+
tx.onabort = tx.onerror = () => finish(tx.error || new Error('Draft transaction failed'));
|
|
2489
|
+
try {
|
|
2490
|
+
const request = action(tx.objectStore('drafts'));
|
|
2491
|
+
if (request) request.onsuccess = () => { value = request.result; };
|
|
2492
|
+
} catch (error) {
|
|
2493
|
+
try { tx.abort(); } catch { /* Transaction already settled. */ }
|
|
2494
|
+
finish(error);
|
|
2495
|
+
}
|
|
2496
|
+
});
|
|
2497
|
+
}
|
|
2498
|
+
const encode = (file) => ({ blob: file, name: file.name, type: file.type, lastModified: file.lastModified });
|
|
2499
|
+
const decode = (file) => {
|
|
2500
|
+
if (!(file?.blob instanceof Blob) || typeof file.name !== 'string') throw new Error('Invalid saved attachment');
|
|
2501
|
+
return new File([file.blob], file.name, { type: file.type || '', lastModified: file.lastModified });
|
|
2502
|
+
};
|
|
2503
|
+
return {
|
|
2504
|
+
async load() {
|
|
2505
|
+
const rows = await transaction('readonly', (store) => store.getAll());
|
|
2506
|
+
const valid = [];
|
|
2507
|
+
for (const row of rows) {
|
|
2508
|
+
if (Date.now() - row.updatedAt > 30 * 86400_000) {
|
|
2509
|
+
await transaction('readwrite', (store) => store.delete(row.id));
|
|
2510
|
+
continue;
|
|
2511
|
+
}
|
|
2512
|
+
valid.push({ id: row.id, images: row.images.map(decode), files: row.files.map(decode) });
|
|
2513
|
+
}
|
|
2514
|
+
return valid;
|
|
2515
|
+
},
|
|
2516
|
+
save(id, draft) {
|
|
2517
|
+
return transaction('readwrite', (store) => draft.images.length || draft.files.length
|
|
2518
|
+
? store.put({ id, updatedAt: Date.now(), images: draft.images.map(encode), files: draft.files.map(encode) })
|
|
2519
|
+
: store.delete(id));
|
|
2520
|
+
},
|
|
2521
|
+
clear: () => transaction('readwrite', (store) => store.clear()),
|
|
2522
|
+
};
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
function createAttachmentDrafts(storage) {
|
|
2343
2526
|
const drafts = new Map();
|
|
2344
2527
|
const listeners = new Set();
|
|
2345
2528
|
const empty = { images: [], files: [], submission: null };
|
|
2346
2529
|
const read = (id) => drafts.get(id) ?? empty;
|
|
2347
|
-
|
|
2348
|
-
|
|
2530
|
+
let status = storage ? 'loading' : 'memory';
|
|
2531
|
+
let chain = Promise.resolve();
|
|
2532
|
+
let revision = 0;
|
|
2533
|
+
let hydrationFailed = false;
|
|
2534
|
+
const dirty = new Map();
|
|
2535
|
+
const touched = new Set();
|
|
2536
|
+
const notify = () => { for (const listener of listeners) listener(); };
|
|
2537
|
+
async function hydrate() {
|
|
2538
|
+
if (!storage) return;
|
|
2539
|
+
return storage.load().then((rows) => {
|
|
2540
|
+
for (const row of rows) if (!touched.has(row.id)) drafts.set(row.id,
|
|
2541
|
+
{ images: row.images, files: row.files, submission: null });
|
|
2542
|
+
hydrationFailed = false; status = dirty.size ? 'saving' : 'saved'; notify();
|
|
2543
|
+
}).catch(() => { hydrationFailed = true; status = 'error'; notify(); });
|
|
2544
|
+
}
|
|
2545
|
+
const ready = hydrate();
|
|
2546
|
+
chain = ready;
|
|
2547
|
+
function persist(id, draft) {
|
|
2548
|
+
if (!storage) return;
|
|
2549
|
+
const current = ++revision;
|
|
2550
|
+
dirty.set(id, { draft, revision: current });
|
|
2551
|
+
status = 'saving';
|
|
2552
|
+
chain = chain.then(() => storage.save(id, draft)).then(() => {
|
|
2553
|
+
if (dirty.get(id)?.revision === current) dirty.delete(id);
|
|
2554
|
+
status = dirty.size || hydrationFailed ? 'error' : 'saved'; notify();
|
|
2555
|
+
}, () => { status = 'error'; notify(); });
|
|
2556
|
+
}
|
|
2557
|
+
function write(id, draft, durable = true) {
|
|
2558
|
+
touched.add(id);
|
|
2349
2559
|
if (draft.images.length || draft.files.length || draft.submission) drafts.set(id, draft);
|
|
2350
2560
|
else drafts.delete(id);
|
|
2351
|
-
|
|
2561
|
+
if (durable) persist(id, draft);
|
|
2562
|
+
notify();
|
|
2352
2563
|
}
|
|
2353
|
-
|
|
2354
2564
|
return {
|
|
2355
|
-
read,
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
return () => listeners.delete(listener);
|
|
2359
|
-
},
|
|
2565
|
+
read, ready, flush: () => chain,
|
|
2566
|
+
status: () => status,
|
|
2567
|
+
subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener); },
|
|
2360
2568
|
replace(id, kind, change) {
|
|
2361
2569
|
const draft = read(id);
|
|
2362
|
-
if (!id || draft.submission) return;
|
|
2570
|
+
if (!id || draft.submission || !['images', 'files'].includes(kind)) return;
|
|
2363
2571
|
write(id, { ...draft, [kind]: change(draft[kind]) });
|
|
2364
2572
|
},
|
|
2365
2573
|
begin(id) {
|
|
2366
2574
|
const draft = read(id);
|
|
2367
|
-
if (!id || draft.submission) return null;
|
|
2575
|
+
if (!id || draft.submission || status === 'loading') return null;
|
|
2368
2576
|
const submission = Symbol();
|
|
2369
|
-
write(id, { ...draft, submission });
|
|
2577
|
+
write(id, { ...draft, submission }, false);
|
|
2370
2578
|
return submission;
|
|
2371
2579
|
},
|
|
2372
2580
|
finish(id, submission, accepted = false) {
|
|
2373
2581
|
const draft = read(id);
|
|
2374
2582
|
if (draft.submission !== submission) return;
|
|
2375
|
-
write(id, accepted ? empty : { ...draft, submission: null });
|
|
2583
|
+
write(id, accepted ? empty : { ...draft, submission: null }, accepted);
|
|
2584
|
+
},
|
|
2585
|
+
async retry() {
|
|
2586
|
+
await chain;
|
|
2587
|
+
if (hydrationFailed) await hydrate();
|
|
2588
|
+
for (const [id, { draft }] of [...dirty]) persist(id, draft);
|
|
2589
|
+
notify();
|
|
2590
|
+
await chain;
|
|
2376
2591
|
},
|
|
2377
2592
|
};
|
|
2378
2593
|
}
|
|
2379
|
-
const attachmentDrafts = createAttachmentDrafts(
|
|
2594
|
+
const attachmentDrafts = createAttachmentDrafts(
|
|
2595
|
+
typeof window !== 'undefined' && !window.__CLAUDE_WORKBENCH_TEST__ ? createAttachmentStorage() : undefined);
|
|
2380
2596
|
|
|
2381
2597
|
function DraftImage({ file, fallback }) {
|
|
2382
2598
|
const [previewUrl, setPreviewUrl] = React.useState('');
|
|
@@ -3050,6 +3266,167 @@ window.__ModuleLoader__.load({
|
|
|
3050
3266
|
);
|
|
3051
3267
|
}
|
|
3052
3268
|
|
|
3269
|
+
const EMPTY_JOBS = { rows: {}, observed: {} };
|
|
3270
|
+
const noopSubscribe = () => () => {};
|
|
3271
|
+
const compatibilityListeners = new Set();
|
|
3272
|
+
let compatibilityRevision = 0;
|
|
3273
|
+
let runtimeFailure = false;
|
|
3274
|
+
function compatibilityChanged() {
|
|
3275
|
+
compatibilityRevision += 1;
|
|
3276
|
+
for (const listener of compatibilityListeners) listener();
|
|
3277
|
+
}
|
|
3278
|
+
function subscribeCompatibility(listener) {
|
|
3279
|
+
compatibilityListeners.add(listener);
|
|
3280
|
+
return () => compatibilityListeners.delete(listener);
|
|
3281
|
+
}
|
|
3282
|
+
const readCompatibilityRevision = () => compatibilityRevision;
|
|
3283
|
+
function compatibilityChecks(services, slots, storageStatus) {
|
|
3284
|
+
const method = (object, key) => typeof object?.[key] === 'function';
|
|
3285
|
+
return [
|
|
3286
|
+
['compatComposer', ['register', 'inject', 'subscribe', 'entriesOfSlot'].every((key) => method(slots, key)) && !services.composerFallback],
|
|
3287
|
+
['compatFiles', method(services.fileUploads, 'upload') && method(services.workspaceFiles, 'stat')],
|
|
3288
|
+
['compatPreview', method(services.sidebarRight, 'openResource')],
|
|
3289
|
+
['compatJobs', method(services.jobs?.state, 'subscribe') && method(services.jobs?.state, 'getSnapshot') && method(services.jobs, 'watchRows')],
|
|
3290
|
+
['compatStorage', ['saved', 'saving'].includes(storageStatus)],
|
|
3291
|
+
];
|
|
3292
|
+
}
|
|
3293
|
+
function CompatibilityPanel({ t }) {
|
|
3294
|
+
React.useSyncExternalStore(subscribeCompatibility, readCompatibilityRevision);
|
|
3295
|
+
const storageStatus = React.useSyncExternalStore(attachmentDrafts.subscribe, attachmentDrafts.status);
|
|
3296
|
+
const [copied, setCopied] = React.useState(false);
|
|
3297
|
+
const checks = compatibilityChecks(runtime, runtime.ctx?.slots, storageStatus);
|
|
3298
|
+
async function copy() {
|
|
3299
|
+
const report = { plugin: 'FPS', checks: Object.fromEntries(checks),
|
|
3300
|
+
runtimeErrorObserved: runtimeFailure, nativeComposerFallback: !!runtime.composerFallback };
|
|
3301
|
+
try { await navigator.clipboard.writeText(JSON.stringify(report, null, 2)); setCopied(true); }
|
|
3302
|
+
catch { setCopied(false); }
|
|
3303
|
+
}
|
|
3304
|
+
return h('details', { className: 'cw-compatibility' },
|
|
3305
|
+
h('summary', null, t('compatTitle')),
|
|
3306
|
+
h('p', { className: 'cw-context-subtle' }, t('compatIntro')),
|
|
3307
|
+
...checks.map(([key, available]) => h('div', { key, className: 'cw-context-row' },
|
|
3308
|
+
h('span', null, t(key)), h('span', { 'data-ready': available }, t(available ? 'compatReady' : 'compatUnavailable')))),
|
|
3309
|
+
runtimeFailure ? h('p', { role: 'status' }, t('compatRuntimeFailure')) : null,
|
|
3310
|
+
h('button', { type: 'button', onClick: () => { setCopied(false); compatibilityChanged(); } }, t('compatRefresh')),
|
|
3311
|
+
h('button', { type: 'button', onClick: copy }, t(copied ? 'compatCopied' : 'compatCopy')),
|
|
3312
|
+
);
|
|
3313
|
+
}
|
|
3314
|
+
function orderJobs(rows) {
|
|
3315
|
+
const live = (job) => ['running', 'stopping'].includes(job.status);
|
|
3316
|
+
return (Array.isArray(rows) ? rows : []).filter((job) => job && typeof job.id === 'string')
|
|
3317
|
+
.slice().sort((a, b) => Number(live(b)) - Number(live(a)) ||
|
|
3318
|
+
(live(a) ? a.startedAt - b.startedAt : (b.finishedAt ?? b.startedAt) - (a.finishedAt ?? a.startedAt)));
|
|
3319
|
+
}
|
|
3320
|
+
function jobElapsed(job, now) {
|
|
3321
|
+
const end = ['running', 'stopping'].includes(job.status) ? now : job.finishedAt;
|
|
3322
|
+
if (!Number.isFinite(end) || !Number.isFinite(job.startedAt)) return '—';
|
|
3323
|
+
const seconds = Math.max(0, Math.floor((end - job.startedAt) / 1000));
|
|
3324
|
+
return seconds < 60 ? `${seconds}s` : `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
|
|
3325
|
+
}
|
|
3326
|
+
function TaskPlan({ useProjection, t }) {
|
|
3327
|
+
const todos = useProjection('todos');
|
|
3328
|
+
if (!Array.isArray(todos) || !todos.length) return h('p', { className: 'cw-context-subtle' }, t('tasksNoPlan'));
|
|
3329
|
+
return h('ul', { className: 'cw-task-plan' }, todos.map((todo, index) => h('li', {
|
|
3330
|
+
key: todo.id ?? index, 'data-status': todo.status,
|
|
3331
|
+
}, h('span', { className: 'cw-task-dot', 'aria-label': t(`task_${todo.status}`) },
|
|
3332
|
+
todo.status === 'completed' ? '✓' : todo.status === 'in_progress' ? '·' : ''),
|
|
3333
|
+
h('span', null, todo.content))));
|
|
3334
|
+
}
|
|
3335
|
+
function LongTasks(props) {
|
|
3336
|
+
const t = (key) => translate(props, key);
|
|
3337
|
+
React.useSyncExternalStore(subscribeCompatibility, readCompatibilityRevision);
|
|
3338
|
+
const jobs = runtime.jobs;
|
|
3339
|
+
const subscribe = React.useCallback((listener) => jobs?.state?.subscribe?.(listener) ?? (() => {}), [jobs]);
|
|
3340
|
+
const snapshot = React.useCallback(() => jobs?.state?.getSnapshot?.() ?? EMPTY_JOBS, [jobs]);
|
|
3341
|
+
const state = React.useSyncExternalStore(subscribe, snapshot);
|
|
3342
|
+
const [watchFailed, setWatchFailed] = React.useState(false);
|
|
3343
|
+
React.useEffect(() => {
|
|
3344
|
+
setWatchFailed(false);
|
|
3345
|
+
if (!jobs?.watchRows || !props.sessionId) return;
|
|
3346
|
+
try { return jobs.watchRows(props.sessionId); }
|
|
3347
|
+
catch { setWatchFailed(true); }
|
|
3348
|
+
}, [jobs, props.sessionId]);
|
|
3349
|
+
const rows = orderJobs(state.rows?.[props.sessionId]);
|
|
3350
|
+
const live = rows.filter((job) => ['running', 'stopping'].includes(job.status));
|
|
3351
|
+
const settled = rows.filter((job) => !['running', 'stopping'].includes(job.status));
|
|
3352
|
+
const [now, setNow] = React.useState(Date.now);
|
|
3353
|
+
React.useEffect(() => {
|
|
3354
|
+
if (!live.length) return;
|
|
3355
|
+
const timer = setInterval(() => setNow(Date.now()), 1000);
|
|
3356
|
+
return () => clearInterval(timer);
|
|
3357
|
+
}, [live.length]);
|
|
3358
|
+
function row(job) {
|
|
3359
|
+
const statusKey = `task_${job.status}`;
|
|
3360
|
+
return h('li', { key: job.id, className: 'cw-task-row', 'data-status': job.status },
|
|
3361
|
+
h('span', { className: 'cw-task-dot', 'aria-hidden': true }, job.status === 'completed' ? '✓' : ''),
|
|
3362
|
+
h('div', { className: 'cw-task-main' },
|
|
3363
|
+
h('span', { title: job.label }, job.label || job.kind),
|
|
3364
|
+
h('small', null, t(Object.hasOwn(en, statusKey) ? statusKey : 'task_unknown'), ' · ', jobElapsed(job, now)),
|
|
3365
|
+
job.progress || job.detail ? h('small', { title: job.progress || job.detail }, job.progress || job.detail) : null));
|
|
3366
|
+
}
|
|
3367
|
+
return h('section', { className: 'cw-context-section cw-long-tasks' },
|
|
3368
|
+
h('h2', { className: 'cw-context-heading' }, t('tasksTitle')),
|
|
3369
|
+
h('details', { className: 'cw-task-plan-details', open: undefined },
|
|
3370
|
+
h('summary', null, t('tasksPlan')),
|
|
3371
|
+
h(WorkbenchBoundary, null, h(TaskPlan, { useProjection: props.useProjection, t }))),
|
|
3372
|
+
h('h3', null, t('tasksBackground'), live.length ? ` · ${live.length}` : ''),
|
|
3373
|
+
!jobs || watchFailed ? h('p', { className: 'cw-context-subtle' }, t('tasksUnavailable'))
|
|
3374
|
+
: !rows.length ? h('p', { className: 'cw-context-subtle' }, t('tasksEmpty')) : null,
|
|
3375
|
+
live.length ? h('ul', { className: 'cw-task-list' }, live.map(row)) : null,
|
|
3376
|
+
settled.length ? h('details', { className: 'cw-task-settled' },
|
|
3377
|
+
h('summary', null, `${t('tasksSettled')} · ${settled.length}`),
|
|
3378
|
+
h('ul', { className: 'cw-task-list' }, settled.map(row))) : null,
|
|
3379
|
+
rows.length && props.renderSlot ? h('div', { className: 'cw-native-jobs' },
|
|
3380
|
+
h('small', null, t('tasksNative')),
|
|
3381
|
+
props.renderSlot('workbench.context.jobs', {})) : null,
|
|
3382
|
+
);
|
|
3383
|
+
}
|
|
3384
|
+
|
|
3385
|
+
// Own only the mirror and its subscription; never dispose a native entry.
|
|
3386
|
+
function disposeSafely(dispose) {
|
|
3387
|
+
try { if (typeof dispose === 'function') dispose(); }
|
|
3388
|
+
catch (error) { reportDiagnostic(`integration cleanup failed: ${error?.message || error}`); }
|
|
3389
|
+
}
|
|
3390
|
+
function mirrorNativeEntry(slots, source, target, match, onError) {
|
|
3391
|
+
let active = true, current, dispose, unsubscribe;
|
|
3392
|
+
function sync() {
|
|
3393
|
+
if (!active) return;
|
|
3394
|
+
try {
|
|
3395
|
+
const entry = slots.entriesOfSlot(source).find(match);
|
|
3396
|
+
if (current === entry) return;
|
|
3397
|
+
disposeSafely(dispose); dispose = undefined; current = undefined;
|
|
3398
|
+
if (entry) {
|
|
3399
|
+
dispose = slots.register({ name: target, locale: entry.locale, inject: entry.inject }, entry.component);
|
|
3400
|
+
current = entry;
|
|
3401
|
+
}
|
|
3402
|
+
} catch (error) {
|
|
3403
|
+
// Native registry notifications must never receive our exception.
|
|
3404
|
+
disposeSafely(dispose); dispose = current = undefined;
|
|
3405
|
+
reportDiagnostic(`optional slot mirror unavailable: ${error?.message || error}`);
|
|
3406
|
+
onError?.();
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3409
|
+
function cleanup() {
|
|
3410
|
+
if (!active) return;
|
|
3411
|
+
active = false;
|
|
3412
|
+
disposeSafely(unsubscribe); disposeSafely(dispose);
|
|
3413
|
+
unsubscribe = dispose = current = undefined;
|
|
3414
|
+
}
|
|
3415
|
+
try {
|
|
3416
|
+
unsubscribe = slots.subscribe(source, sync);
|
|
3417
|
+
sync();
|
|
3418
|
+
} catch (error) {
|
|
3419
|
+
cleanup();
|
|
3420
|
+
reportDiagnostic(`optional slot subscription unavailable: ${error?.message || error}`);
|
|
3421
|
+
onError?.();
|
|
3422
|
+
}
|
|
3423
|
+
return cleanup;
|
|
3424
|
+
}
|
|
3425
|
+
function bridgeNativeJobs(slots) {
|
|
3426
|
+
return mirrorNativeEntry(slots, 'conversation.session.header.actions', 'workbench.context.jobs',
|
|
3427
|
+
(entry) => entry.options?.id === 'job-list');
|
|
3428
|
+
}
|
|
3429
|
+
|
|
3053
3430
|
function ContextPanelBase(props) {
|
|
3054
3431
|
const t = (key) => translate(props, key);
|
|
3055
3432
|
const state = props.useSessions((value) => value) || {};
|
|
@@ -3108,6 +3485,7 @@ window.__ModuleLoader__.load({
|
|
|
3108
3485
|
) : h('div', { className: 'cw-context-subtle' }, t('contextEmpty')),
|
|
3109
3486
|
categories.length ? h('p', { className: 'cw-context-subtle cw-context-note' }, t('contextEstimate')) : null,
|
|
3110
3487
|
),
|
|
3488
|
+
h(WorkbenchBoundary, null, h(LongTasks, { ...props, key: props.sessionId })),
|
|
3111
3489
|
h(ContextOutputs, { key: props.sessionId, sessionId: props.sessionId, cwd, files: outputFiles, t }),
|
|
3112
3490
|
h('section', { className: 'cw-context-section' },
|
|
3113
3491
|
h('details', { className: 'cw-context-details' },
|
|
@@ -3157,6 +3535,8 @@ window.__ModuleLoader__.load({
|
|
|
3157
3535
|
});
|
|
3158
3536
|
const percentFormat = new Intl.NumberFormat(locale, { style: 'percent', maximumFractionDigits: 1 });
|
|
3159
3537
|
const creditFormat = new Intl.NumberFormat(locale, { maximumFractionDigits: 2 });
|
|
3538
|
+
const compareNames = new Intl.Collator('en', { sensitivity: 'base' }).compare;
|
|
3539
|
+
const sortedUsage = [...usage].sort((first, second) => compareNames(first.name, second.name));
|
|
3160
3540
|
const metric = (id, label, value) => h('div', {
|
|
3161
3541
|
key: id, className: 'cw-metric-head cw-provider-balance',
|
|
3162
3542
|
}, h('span', null, label), h('strong', null, value));
|
|
@@ -3170,7 +3550,7 @@ window.__ModuleLoader__.load({
|
|
|
3170
3550
|
`${creditFormat.format(credits.remaining)} / ${creditFormat.format(credits.total)}`)) : null,
|
|
3171
3551
|
);
|
|
3172
3552
|
|
|
3173
|
-
return h('div', { className: 'cw-provider-usage', 'aria-live': 'polite' },
|
|
3553
|
+
return h('div', { className: 'cw-provider-usage', 'aria-live': 'polite' }, sortedUsage.map((provider) => {
|
|
3174
3554
|
const name = provider.id === 'dreamina' ? t('usageDreaminaName') : provider.name;
|
|
3175
3555
|
const duplicate = usage.some((other) => other !== provider && other.name === provider.name);
|
|
3176
3556
|
return h('section', { key: provider.id },
|
|
@@ -3183,6 +3563,10 @@ window.__ModuleLoader__.load({
|
|
|
3183
3563
|
provider.balance ? metric('extra', t('usageExtraBalance'), formatMoney(provider.balance)) : null,
|
|
3184
3564
|
provider.charges?.map((charge) => metric(charge.id,
|
|
3185
3565
|
t(charge.id === 'spent' ? 'usageKeySpent' : 'usageKeyRemaining'), formatMoney(charge))),
|
|
3566
|
+
provider.organizationSpend?.length
|
|
3567
|
+
? metric('organization-spend', t('usageOrganizationSpend'), provider.organizationSpend.map(formatMoney).join(' · ')) : null,
|
|
3568
|
+
Number.isFinite(provider.organizationTokens)
|
|
3569
|
+
? metric('organization-tokens', t('usageOrganizationTokens'), formatTokenCount(provider.organizationTokens)) : null,
|
|
3186
3570
|
provider.windows?.filter(({ id }) => Object.hasOwn(USAGE_WINDOW_LABELS, id)).map(windowView),
|
|
3187
3571
|
Number.isFinite(provider.credits)
|
|
3188
3572
|
? metric('credits', t('usageDreaminaBalance'), creditFormat.format(provider.credits)) : null,
|
|
@@ -3198,7 +3582,7 @@ window.__ModuleLoader__.load({
|
|
|
3198
3582
|
const meter = contextMeter(useProjection('contextPressure'));
|
|
3199
3583
|
const usage = useProjection('tokenUsage');
|
|
3200
3584
|
const [open, setOpen] = React.useState(false);
|
|
3201
|
-
const { triggerRef, menuRef, position } = useAnchoredPopup(open, 340,
|
|
3585
|
+
const { triggerRef, menuRef, position } = useAnchoredPopup(open, 340, 720);
|
|
3202
3586
|
const ready = Boolean(position);
|
|
3203
3587
|
const popupId = React.useId();
|
|
3204
3588
|
const rows = [
|
|
@@ -3532,6 +3916,7 @@ window.__ModuleLoader__.load({
|
|
|
3532
3916
|
const readAttachments = React.useCallback(() => attachmentDrafts.read(sessionId), [sessionId]);
|
|
3533
3917
|
const { images, files, submission: draftSubmission } =
|
|
3534
3918
|
React.useSyncExternalStore(attachmentDrafts.subscribe, readAttachments);
|
|
3919
|
+
const draftStorageStatus = React.useSyncExternalStore(attachmentDrafts.subscribe, attachmentDrafts.status);
|
|
3535
3920
|
const [commandSending, setSending] = React.useState(false);
|
|
3536
3921
|
const sending = commandSending || Boolean(draftSubmission);
|
|
3537
3922
|
const [recognizing, setRecognizing] = React.useState(false);
|
|
@@ -3605,6 +3990,7 @@ window.__ModuleLoader__.load({
|
|
|
3605
3990
|
}, [commandLauncherOpen]);
|
|
3606
3991
|
const running = Boolean(session?.running);
|
|
3607
3992
|
const unavailable = Boolean(props.disabled || props.blocked || !sessionId || !promptAllowed);
|
|
3993
|
+
const draftLoading = draftStorageStatus === 'loading';
|
|
3608
3994
|
const voiceLocked = unavailable || sending;
|
|
3609
3995
|
const previousVoiceDraft = voiceDraft.current;
|
|
3610
3996
|
voiceDraft.current = {
|
|
@@ -3645,7 +4031,7 @@ window.__ModuleLoader__.load({
|
|
|
3645
4031
|
},
|
|
3646
4032
|
}), []);
|
|
3647
4033
|
const t = (key) => translate(props, key);
|
|
3648
|
-
const canSend = Boolean(!unavailable && !sending && (text.trim() || images.length || files.length));
|
|
4034
|
+
const canSend = Boolean(!unavailable && !draftLoading && !sending && (text.trim() || images.length || files.length));
|
|
3649
4035
|
const slashQuery = images.length === 0 ? commandQuery(text) : null;
|
|
3650
4036
|
const commandMenuVisible = commandLauncherOpen || (slashQuery !== null && commandDismissed !== text);
|
|
3651
4037
|
const commandChoices = commandLauncherOpen && text.trim() && slashQuery === null
|
|
@@ -3728,6 +4114,7 @@ window.__ModuleLoader__.load({
|
|
|
3728
4114
|
}, [commandMenuVisible, sessionId]);
|
|
3729
4115
|
|
|
3730
4116
|
function addImageFiles(fileList) {
|
|
4117
|
+
if (draftLoading) { setError(t('draft_loading')); return; }
|
|
3731
4118
|
const available = Math.max(0, MAX_IMAGES - readAttachments().images.length);
|
|
3732
4119
|
const incoming = Array.from(fileList || []).filter((file) => file?.type?.startsWith('image/')).slice(0, available);
|
|
3733
4120
|
if (incoming.length === 0) return;
|
|
@@ -3749,6 +4136,7 @@ window.__ModuleLoader__.load({
|
|
|
3749
4136
|
}
|
|
3750
4137
|
|
|
3751
4138
|
function addDraftFiles(fileList) {
|
|
4139
|
+
if (draftLoading) { setError(t('draft_loading')); return; }
|
|
3752
4140
|
const incoming = Array.from(fileList || []).filter(Boolean);
|
|
3753
4141
|
if (incoming.length === 0) return;
|
|
3754
4142
|
if (readAttachments().files.length + incoming.length > MAX_FILES) {
|
|
@@ -4105,7 +4493,7 @@ window.__ModuleLoader__.load({
|
|
|
4105
4493
|
) : null,
|
|
4106
4494
|
h('div', { className: 'cw-composer-row' },
|
|
4107
4495
|
h(ComposerAddMenu, {
|
|
4108
|
-
disabled: unavailable || sending,
|
|
4496
|
+
disabled: unavailable || draftLoading || sending,
|
|
4109
4497
|
t,
|
|
4110
4498
|
onImages: () => {
|
|
4111
4499
|
if (images.length < MAX_IMAGES) fileInputRef.current?.click();
|
|
@@ -4174,6 +4562,12 @@ window.__ModuleLoader__.load({
|
|
|
4174
4562
|
),
|
|
4175
4563
|
),
|
|
4176
4564
|
props.accessory || null,
|
|
4565
|
+
(images.length || files.length || ['loading', 'error'].includes(draftStorageStatus))
|
|
4566
|
+
? h('div', { className: 'cw-draft-status', role: 'status' },
|
|
4567
|
+
t(`draft_${draftStorageStatus}`),
|
|
4568
|
+
draftStorageStatus === 'error' ? h('button', {
|
|
4569
|
+
type: 'button', onClick: () => attachmentDrafts.retry(),
|
|
4570
|
+
}, t('imageRetry')) : null) : null,
|
|
4177
4571
|
error ? h('div', { className: 'cw-composer-error', role: 'alert' }, error) : null,
|
|
4178
4572
|
props.blocked?.reason ? h('div', { className: 'cw-composer-error' }, props.blocked.reason) : null,
|
|
4179
4573
|
),
|
|
@@ -4191,41 +4585,55 @@ window.__ModuleLoader__.load({
|
|
|
4191
4585
|
|
|
4192
4586
|
const HeroGreeting = withBoundary(HeroGreetingBase);
|
|
4193
4587
|
const ToolCallMessage = withBoundary(ToolCallMessageBase);
|
|
4194
|
-
|
|
4588
|
+
function Composer(props, fallback) {
|
|
4589
|
+
const required = ['useSession', 'useAccess', 'useProjection', 'useSessions', 'renderSlot'];
|
|
4590
|
+
if (required.some((name) => typeof props[name] !== 'function')) {
|
|
4591
|
+
fallback();
|
|
4592
|
+
return null;
|
|
4593
|
+
}
|
|
4594
|
+
return h(WorkbenchBoundary, { onFailure: fallback }, h(ComposerBase, props));
|
|
4595
|
+
}
|
|
4195
4596
|
const ContextPanel = withBoundary(ContextPanelBase);
|
|
4196
4597
|
const HeaderContextButton = withBoundary(ContextButton);
|
|
4197
4598
|
|
|
4198
4599
|
function registerComposer(slots) {
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
name: WORKBENCH_VOICE_SLOT, locale: entry.locale, inject: entry.inject,
|
|
4217
|
-
}, entry.component);
|
|
4218
|
-
} catch (error) {
|
|
4219
|
-
reportDiagnostic(`voice input activation failed: ${error?.message || error}`);
|
|
4220
|
-
}
|
|
4600
|
+
let active = true, fallbackPending = false, disposeComposer, disposeVoice;
|
|
4601
|
+
function cleanup() {
|
|
4602
|
+
if (!active) return;
|
|
4603
|
+
active = false;
|
|
4604
|
+
// Child mirrors/subscriptions go first, before their parent slot vanishes.
|
|
4605
|
+
disposeSafely(disposeVoice); disposeSafely(disposeComposer);
|
|
4606
|
+
disposeVoice = disposeComposer = undefined;
|
|
4607
|
+
}
|
|
4608
|
+
function fallback() {
|
|
4609
|
+
if (!active || fallbackPending) return;
|
|
4610
|
+
fallbackPending = true;
|
|
4611
|
+
// This callback belongs to one registration, never a newer replacement.
|
|
4612
|
+
queueMicrotask(() => {
|
|
4613
|
+
if (!active) return;
|
|
4614
|
+
runtime.composerFallback = true;
|
|
4615
|
+
cleanup(); compatibilityChanged();
|
|
4616
|
+
});
|
|
4221
4617
|
}
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4618
|
+
if (!['register', 'subscribe', 'entriesOfSlot'].every((key) => typeof slots?.[key] === 'function')) {
|
|
4619
|
+
runtime.composerFallback = true; compatibilityChanged(); return cleanup;
|
|
4620
|
+
}
|
|
4621
|
+
try {
|
|
4622
|
+
disposeComposer = slots.register({
|
|
4623
|
+
name: 'conversation.composer.bar', priority: -1,
|
|
4624
|
+
children: { [WORKBENCH_VOICE_SLOT]: { kind: 'single', scope: 'session' } },
|
|
4625
|
+
}, (props) => Composer(props, fallback));
|
|
4626
|
+
disposeVoice = mirrorNativeEntry(slots, NATIVE_INPUT_ACTIVITY, WORKBENCH_VOICE_SLOT,
|
|
4627
|
+
(entry) => entry.locale === 'voice-input', fallback);
|
|
4628
|
+
if (fallbackPending) {
|
|
4629
|
+
runtime.composerFallback = true; cleanup();
|
|
4630
|
+
} else runtime.composerFallback = false;
|
|
4631
|
+
} catch (error) {
|
|
4632
|
+
runtime.composerFallback = true; cleanup();
|
|
4633
|
+
reportDiagnostic(`composer integration unavailable: ${error?.message || error}`);
|
|
4634
|
+
}
|
|
4635
|
+
compatibilityChanged();
|
|
4636
|
+
return cleanup;
|
|
4229
4637
|
}
|
|
4230
4638
|
|
|
4231
4639
|
return {
|
|
@@ -4233,7 +4641,7 @@ window.__ModuleLoader__.load({
|
|
|
4233
4641
|
'slots', 'locale', 'shortcuts', 'configForms', 'uiWorkspace', 'workspaces', 'sessions', 'remote',
|
|
4234
4642
|
],
|
|
4235
4643
|
...(typeof window !== 'undefined' && window.__CLAUDE_WORKBENCH_TEST__
|
|
4236
|
-
? { testHooks: { commandQuery, filterCommandRows, commandRowsFromDescriptors, commandInsertion, executeHostCommand, summarizeToolCall, summarizeWorkGroup, isDangerousPermission, rootModelGroup, formatTokenCount, contextPercent, mentionQuery, referenceRows, submitMode, formatBytes, voiceDraftInsertion, createSessionHistory, createAttachmentDrafts } }
|
|
4644
|
+
? { testHooks: { commandQuery, filterCommandRows, commandRowsFromDescriptors, commandInsertion, executeHostCommand, summarizeToolCall, summarizeWorkGroup, isDangerousPermission, rootModelGroup, formatTokenCount, contextPercent, mentionQuery, referenceRows, submitMode, formatBytes, voiceDraftInsertion, createSessionHistory, createAttachmentDrafts, createAttachmentStorage, compatibilityChecks, orderJobs, jobElapsed, registerComposer, bridgeNativeJobs } }
|
|
4237
4645
|
: {}),
|
|
4238
4646
|
apply(ctx) {
|
|
4239
4647
|
runtime.ctx = ctx;
|
|
@@ -4241,6 +4649,16 @@ window.__ModuleLoader__.load({
|
|
|
4241
4649
|
runtime.workspaces = ctx.get('workspaces');
|
|
4242
4650
|
runtime.sessions = ctx.get('sessions');
|
|
4243
4651
|
runtime.composerSettings = ctx.configForms.get('ui-conversation');
|
|
4652
|
+
try {
|
|
4653
|
+
ctx.inject(['jobs'], (scope) => {
|
|
4654
|
+
scope.effect(() => {
|
|
4655
|
+
const jobs = scope.get('jobs'); runtime.jobs = jobs; compatibilityChanged();
|
|
4656
|
+
return () => { if (runtime.jobs === jobs) { runtime.jobs = undefined; compatibilityChanged(); } };
|
|
4657
|
+
}, 'fps: native background jobs');
|
|
4658
|
+
});
|
|
4659
|
+
} catch (error) {
|
|
4660
|
+
reportDiagnostic(`background jobs unavailable: ${error?.message || error}`);
|
|
4661
|
+
}
|
|
4244
4662
|
for (const [property, namespace] of [
|
|
4245
4663
|
['commandsRemote', 'commands'], ['fileReferences', 'fileReferences'],
|
|
4246
4664
|
['sessionReferences', 'sessionReferenceResolver'], ['permissionPresets', 'permissionPresets'],
|
|
@@ -4249,9 +4667,9 @@ window.__ModuleLoader__.load({
|
|
|
4249
4667
|
ctx.inject([`remote.${namespace}`], (scope) => {
|
|
4250
4668
|
scope.effect(() => {
|
|
4251
4669
|
const service = scope.remote?.[namespace];
|
|
4252
|
-
runtime[property] = service;
|
|
4670
|
+
runtime[property] = service; compatibilityChanged();
|
|
4253
4671
|
return () => {
|
|
4254
|
-
if (runtime[property] === service) runtime[property] = undefined;
|
|
4672
|
+
if (runtime[property] === service) { runtime[property] = undefined; compatibilityChanged(); }
|
|
4255
4673
|
};
|
|
4256
4674
|
}, `claude-workbench: ${namespace}`);
|
|
4257
4675
|
});
|
|
@@ -4362,7 +4780,7 @@ window.__ModuleLoader__.load({
|
|
|
4362
4780
|
install('image generation settings', () => ctx.slots.inject(
|
|
4363
4781
|
'plugins.bundle.config',
|
|
4364
4782
|
guardInjection('image generation settings', () => ctx.slots.register({
|
|
4365
|
-
name: 'plugins.bundle.config', key: '
|
|
4783
|
+
name: 'plugins.bundle.config', key: '@local/claude-code-workbench', locale: NS,
|
|
4366
4784
|
}, withBoundary(ImageGenerationSettings))),
|
|
4367
4785
|
)),
|
|
4368
4786
|
install('new session greeting', () => ctx.slots.inject(
|
|
@@ -4401,8 +4819,10 @@ window.__ModuleLoader__.load({
|
|
|
4401
4819
|
'sidebar.right.pane.tab',
|
|
4402
4820
|
guardInjection('context body', () => ctx.slots.register({
|
|
4403
4821
|
name: 'sidebar.right.pane.tab', key: CONTEXT_TAB,
|
|
4822
|
+
children: { 'workbench.context.jobs': { kind: 'single', scope: 'session' } },
|
|
4404
4823
|
}, ContextPanel)),
|
|
4405
4824
|
);
|
|
4825
|
+
const disposeJobs = install('native job details', () => bridgeNativeJobs(ctx.slots));
|
|
4406
4826
|
const disposeHeader = ctx.slots.inject(
|
|
4407
4827
|
'conversation.session.header.utilities',
|
|
4408
4828
|
guardInjection('session header utilities', () => ctx.slots.register({
|
|
@@ -4410,6 +4830,7 @@ window.__ModuleLoader__.load({
|
|
|
4410
4830
|
}, HeaderContextButton)),
|
|
4411
4831
|
);
|
|
4412
4832
|
return () => {
|
|
4833
|
+
disposeJobs();
|
|
4413
4834
|
disposeHeader();
|
|
4414
4835
|
disposeBody();
|
|
4415
4836
|
disposeType();
|