@steerable/agent-shell-web 0.6.60 → 0.6.62
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steerable/agent-shell-web",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.62",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Steerable framework — product-neutral renderer SPA source for @steerable/agent-shell (React + Vite). Consumed by product web entry points via the `@/` alias (anchored at this package's src/) and the createProductViteConfig factory. Published as a source package (src/), plus the compiled vite.base config factory (vite.base.js + .d.ts) for Node-side config loading.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"react-router-dom": "^6.30.0",
|
|
46
46
|
"rehype-raw": "^7.0.0",
|
|
47
47
|
"remark-gfm": "^4.0.1",
|
|
48
|
-
"@steerable/agent-protocol": "0.6.
|
|
49
|
-
"@steerable/agent-ui": "0.6.
|
|
48
|
+
"@steerable/agent-protocol": "0.6.62",
|
|
49
|
+
"@steerable/agent-ui": "0.6.62"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@tailwindcss/vite": "^4.3.0",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"typescript": "^5.8.3",
|
|
60
60
|
"vite": "^5.4.21",
|
|
61
61
|
"vitest": "^3.2.4",
|
|
62
|
-
"@steerable/pack-sdk": "0.6.
|
|
62
|
+
"@steerable/pack-sdk": "0.6.62"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
65
65
|
"gen": "echo agent-shell-web",
|
|
@@ -126,18 +126,20 @@ export function SidebarAccount() {
|
|
|
126
126
|
</div>
|
|
127
127
|
{account.status === 'ready' && account.total ? (
|
|
128
128
|
<>
|
|
129
|
-
<div className="mt-1.5 flex items-center justify-between gap-
|
|
130
|
-
<span className="shrink-0 text-agent-foreground"
|
|
129
|
+
<div className="mt-1.5 flex items-center justify-between gap-1.5 text-xs">
|
|
130
|
+
<span className="shrink-0 text-agent-foreground" title={t('Account balance')}>
|
|
131
|
+
{t('Balance')}
|
|
132
|
+
</span>
|
|
131
133
|
<div className="flex min-w-0 items-center justify-end gap-1">
|
|
132
134
|
<span
|
|
133
135
|
data-testid="sidebar-account-total"
|
|
134
|
-
className="
|
|
136
|
+
className="shrink-0 font-medium tabular-nums text-agent-foreground"
|
|
135
137
|
>
|
|
136
138
|
{formatMoney(account.total, account.currency)}
|
|
137
139
|
</span>
|
|
138
140
|
{availableLabel ? (
|
|
139
141
|
<span
|
|
140
|
-
className={`
|
|
142
|
+
className={`min-w-0 truncate text-[10px] ${account.available ? 'text-emerald-600' : 'text-amber-600'}`}
|
|
141
143
|
>
|
|
142
144
|
{availableLabel}
|
|
143
145
|
</span>
|
|
@@ -146,7 +148,7 @@ export function SidebarAccount() {
|
|
|
146
148
|
</div>
|
|
147
149
|
{detail ? (
|
|
148
150
|
<p
|
|
149
|
-
className="mt-1 truncate
|
|
151
|
+
className="mt-1 truncate text-[9px] leading-none text-agent-muted-foreground"
|
|
150
152
|
title={detail}
|
|
151
153
|
>
|
|
152
154
|
{detail}
|
|
@@ -46,12 +46,12 @@ export function todoProgressCopy(todos: SessionTodo[]): string {
|
|
|
46
46
|
const summary = summarizeTodos(todos);
|
|
47
47
|
if (summary.total === 0) return '';
|
|
48
48
|
if (summary.completed === summary.total) {
|
|
49
|
-
return
|
|
49
|
+
return `${summary.completed}/${summary.total}`;
|
|
50
50
|
}
|
|
51
51
|
const currentIndex = todos.findIndex((todo) => todo.status === 'in_progress');
|
|
52
52
|
const step =
|
|
53
53
|
currentIndex >= 0 ? currentIndex + 1 : Math.min(summary.completed + 1, summary.total);
|
|
54
|
-
return
|
|
54
|
+
return `${step}/${summary.total}`;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
export function TodoItems({
|
|
@@ -211,17 +211,6 @@ export function SessionTodoList({ todos }: { todos: SessionTodo[] }) {
|
|
|
211
211
|
<span>{t('Task list')}</span>
|
|
212
212
|
</div>
|
|
213
213
|
<div className="flex items-center gap-1.5">
|
|
214
|
-
{progressLabel && (
|
|
215
|
-
<span
|
|
216
|
-
className={`rounded-full px-1.5 py-0.2 text-[10px] font-medium ${
|
|
217
|
-
isAllCompleted
|
|
218
|
-
? 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400'
|
|
219
|
-
: 'bg-blue-500/10 text-blue-600 dark:text-blue-400'
|
|
220
|
-
}`}
|
|
221
|
-
>
|
|
222
|
-
{progressLabel}
|
|
223
|
-
</span>
|
|
224
|
-
)}
|
|
225
214
|
<button
|
|
226
215
|
type="button"
|
|
227
216
|
onClick={() => setUserToggled(false)}
|
|
@@ -17,8 +17,12 @@ export interface TurnFile {
|
|
|
17
17
|
additions?: number;
|
|
18
18
|
/** 删除行数(展示用,如 -0)。 */
|
|
19
19
|
deletions?: number;
|
|
20
|
-
/** 类别:deliverable =
|
|
20
|
+
/** 类别:deliverable = 本轮交付卡片;intermediate = 其余本轮写过的文件。 */
|
|
21
21
|
category?: 'deliverable' | 'intermediate';
|
|
22
|
+
/** 后端已经按分层证据完成选择;前端必须原样采用 category。 */
|
|
23
|
+
selection?: 'resolved';
|
|
24
|
+
/** 交付选择的来源,供调试和后续展示使用。 */
|
|
25
|
+
deliverySource?: 'generation' | 'final-reference' | 'present-files' | 'inference';
|
|
22
26
|
/** present_files 给出的一行说明(交付卡片副标题)。 */
|
|
23
27
|
description?: string;
|
|
24
28
|
}
|
|
@@ -41,6 +45,13 @@ export function parseTurnFiles(raw: unknown): TurnFile[] | null {
|
|
|
41
45
|
...(rec.category === 'deliverable' || rec.category === 'intermediate'
|
|
42
46
|
? { category: rec.category }
|
|
43
47
|
: {}),
|
|
48
|
+
...(rec.selection === 'resolved' ? { selection: rec.selection } : {}),
|
|
49
|
+
...(rec.deliverySource === 'generation' ||
|
|
50
|
+
rec.deliverySource === 'final-reference' ||
|
|
51
|
+
rec.deliverySource === 'present-files' ||
|
|
52
|
+
rec.deliverySource === 'inference'
|
|
53
|
+
? { deliverySource: rec.deliverySource }
|
|
54
|
+
: {}),
|
|
44
55
|
...(typeof rec.description === 'string' && rec.description
|
|
45
56
|
? { description: rec.description }
|
|
46
57
|
: {}),
|
|
@@ -103,8 +114,8 @@ export const DELIVERABLE_EXTENSIONS = new Set([
|
|
|
103
114
|
]);
|
|
104
115
|
|
|
105
116
|
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
117
|
+
* 单文件扩展名猜测。回合里有多份文件时,`groupTurnFiles` 会再拿掉同名预览。
|
|
118
|
+
* 没有 category 的是声明机制上线前落库的消息。
|
|
108
119
|
*/
|
|
109
120
|
export function getTurnFileCategory(file: TurnFile): 'deliverable' | 'intermediate' {
|
|
110
121
|
if (file.category) return file.category;
|
|
@@ -247,6 +258,54 @@ function parseDiffStats(diffText: string): { additions: number; deletions: numbe
|
|
|
247
258
|
return { additions, deletions };
|
|
248
259
|
}
|
|
249
260
|
|
|
261
|
+
const SOURCE_EXTENSIONS = new Set([
|
|
262
|
+
'.pptx', '.ppt', '.key', // shell-neutral:allow — Office 幻灯片扩展名,不是产品品牌
|
|
263
|
+
'.docx', '.doc', '.pages',
|
|
264
|
+
'.xlsx', '.xls', '.xlsm', '.numbers',
|
|
265
|
+
]);
|
|
266
|
+
|
|
267
|
+
const PREVIEW_NAME = /预览|截图|图像版|preview|screenshot|thumbnail/i;
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* 旧消息没有 selection=resolved,只纠正最明确的一种历史误判:同目录的
|
|
271
|
+
* 幻灯片与预览 PDF / 截图都被标成交付物。新消息完全采用后端结论。
|
|
272
|
+
*/
|
|
273
|
+
function selectLegacyDeliverablePaths(files: readonly TurnFile[]): Set<string> {
|
|
274
|
+
const chosen = new Set(
|
|
275
|
+
files.filter((file) => getTurnFileCategory(file) === 'deliverable').map((file) => file.path),
|
|
276
|
+
);
|
|
277
|
+
const sources = files
|
|
278
|
+
.filter((file) => SOURCE_EXTENSIONS.has(getFileExtension(file.path)))
|
|
279
|
+
.map((file) => file.path);
|
|
280
|
+
for (const candidate of [...chosen]) {
|
|
281
|
+
const source = sources.find((item) => isLegacyPreviewOf(candidate, item));
|
|
282
|
+
if (!source) continue;
|
|
283
|
+
chosen.delete(candidate);
|
|
284
|
+
chosen.add(source);
|
|
285
|
+
}
|
|
286
|
+
return new Set(chosen);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function isLegacyPreviewOf(filePath: string, sourcePath: string): boolean {
|
|
290
|
+
if (filePath === sourcePath || getFileExtension(sourcePath) !== '.pptx') return false; // shell-neutral:allow — Office extension
|
|
291
|
+
const ext = getFileExtension(filePath);
|
|
292
|
+
if (ext !== '.pdf' && !['.png', '.jpg', '.jpeg', '.webp'].includes(ext)) return false;
|
|
293
|
+
const file = splitTurnFilePath(filePath);
|
|
294
|
+
const source = splitTurnFilePath(sourcePath);
|
|
295
|
+
const normalized = (name: string) =>
|
|
296
|
+
name
|
|
297
|
+
.replace(/\.[^.]+$/, '')
|
|
298
|
+
.replace(PREVIEW_NAME, '')
|
|
299
|
+
.replace(/[\s._\-—–()()【】[\]]+/g, '')
|
|
300
|
+
.toLowerCase()
|
|
301
|
+
.normalize('NFC');
|
|
302
|
+
if (normalized(file.name) === normalized(source.name)) return true;
|
|
303
|
+
return (
|
|
304
|
+
PREVIEW_NAME.test(file.name) &&
|
|
305
|
+
file.dir.normalize('NFC') === source.dir.normalize('NFC')
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
250
309
|
/** 把本轮文件按最终交付物(最终文件)与中间修改文件分类,并合并行数统计。 */
|
|
251
310
|
export function groupTurnFiles(
|
|
252
311
|
files: TurnFile[],
|
|
@@ -287,8 +346,16 @@ export function groupTurnFiles(
|
|
|
287
346
|
let totalAdditions = 0;
|
|
288
347
|
let totalDeletions = 0;
|
|
289
348
|
|
|
349
|
+
const backendResolved =
|
|
350
|
+
validFiles.length > 0 && validFiles.every((file) => file.selection === 'resolved');
|
|
351
|
+
const deliverablePaths = backendResolved
|
|
352
|
+
? new Set(
|
|
353
|
+
validFiles.filter((file) => file.category === 'deliverable').map((file) => file.path),
|
|
354
|
+
)
|
|
355
|
+
: selectLegacyDeliverablePaths(validFiles);
|
|
356
|
+
|
|
290
357
|
for (const file of validFiles) {
|
|
291
|
-
const cat =
|
|
358
|
+
const cat = deliverablePaths.has(file.path) ? 'deliverable' : 'intermediate';
|
|
292
359
|
// 匹配统计:尝试直接匹配或 basename 匹配
|
|
293
360
|
let stats = statsByPath.get(file.path);
|
|
294
361
|
if (!stats) {
|