@xiaoso/dsh-tool-plus 0.1.8-beta.2 → 0.1.8-beta.4
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/lib/client.js +524 -65
- package/lib/index.mjs +2 -2
- package/lib/{scrapers-DU4u4-Co.mjs → scrapers-BWgeG8qC.mjs} +1 -1
- package/lib/types/web/client/ToolCardShell.d.ts +15 -5
- package/lib/types/web/client/labels.d.ts +1 -1
- package/lib/types/web/client/row-utils.d.ts +28 -8
- package/lib/types/web/client/rows/card-leading.d.ts +37 -0
- package/lib/types/web/client/rows/command-card-facts.d.ts +45 -0
- package/lib/types/web/client/rows/fallback-card-facts.d.ts +52 -0
- package/lib/types/web/client/rows/read-card-facts.d.ts +33 -0
- package/lib/types/web/client/rows/search-card-facts.d.ts +37 -0
- package/lib/{utils-C-Lo_JC6.mjs → utils-CdKv-8uE.mjs} +1 -1
- package/package.json +1 -1
- package/presets/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -1720,13 +1720,13 @@ window.__ModuleLoader__.load({
|
|
|
1720
1720
|
return (c === "x" ? r : r & 3 | 8).toString(16);
|
|
1721
1721
|
});
|
|
1722
1722
|
}
|
|
1723
|
-
function isRecord$
|
|
1723
|
+
function isRecord$4(value) {
|
|
1724
1724
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1725
1725
|
}
|
|
1726
1726
|
function parseConnectionResponse(value) {
|
|
1727
|
-
if (!isRecord$
|
|
1727
|
+
if (!isRecord$4(value) || value.type !== "server-response" || typeof value.rpcId !== "string") throw new TypeError("connection: invalid server-response envelope");
|
|
1728
1728
|
const result = value.result;
|
|
1729
|
-
if (!isRecord$
|
|
1729
|
+
if (!isRecord$4(result)) throw new TypeError("connection: invalid server-response result");
|
|
1730
1730
|
if (result.ok === true) return {
|
|
1731
1731
|
rpcId: value.rpcId,
|
|
1732
1732
|
result: {
|
|
@@ -1734,9 +1734,9 @@ window.__ModuleLoader__.load({
|
|
|
1734
1734
|
value: result.value
|
|
1735
1735
|
}
|
|
1736
1736
|
};
|
|
1737
|
-
if (result.ok !== false || !isRecord$
|
|
1737
|
+
if (result.ok !== false || !isRecord$4(result.error)) throw new TypeError("connection: invalid server-response result");
|
|
1738
1738
|
const error = result.error;
|
|
1739
|
-
if (typeof error.code !== "string" || typeof error.message !== "string" || !isRecord$
|
|
1739
|
+
if (typeof error.code !== "string" || typeof error.message !== "string" || !isRecord$4(error.details)) throw new TypeError("connection: invalid server-response failure");
|
|
1740
1740
|
return {
|
|
1741
1741
|
rpcId: value.rpcId,
|
|
1742
1742
|
result: {
|
|
@@ -3085,7 +3085,7 @@ window.__ModuleLoader__.load({
|
|
|
3085
3085
|
return json === void 0 ? 0 : utf8ByteLength(json);
|
|
3086
3086
|
}
|
|
3087
3087
|
/** Whether `value` is a plain object (never an array or `null`). */
|
|
3088
|
-
function isRecord$
|
|
3088
|
+
function isRecord$3(value) {
|
|
3089
3089
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3090
3090
|
}
|
|
3091
3091
|
/** Non-blank string, or `null`. */
|
|
@@ -3110,7 +3110,7 @@ window.__ModuleLoader__.load({
|
|
|
3110
3110
|
* @returns the narrowed metadata, or `null` when it is not a usable write card.
|
|
3111
3111
|
*/
|
|
3112
3112
|
function narrowWriteCardMeta(meta) {
|
|
3113
|
-
if (!isRecord$
|
|
3113
|
+
if (!isRecord$3(meta) || meta.kind !== "write") return null;
|
|
3114
3114
|
const path = nonBlankString(meta.path);
|
|
3115
3115
|
if (path === null) return null;
|
|
3116
3116
|
return {
|
|
@@ -3130,13 +3130,13 @@ window.__ModuleLoader__.load({
|
|
|
3130
3130
|
* @returns the narrowed metadata, or `null` when it is not a usable diff card.
|
|
3131
3131
|
*/
|
|
3132
3132
|
function narrowEditCardMeta(meta) {
|
|
3133
|
-
if (!isRecord$
|
|
3133
|
+
if (!isRecord$3(meta) || meta.kind !== "edit") return null;
|
|
3134
3134
|
if (jsonByteLength(meta) > 131072) return null;
|
|
3135
3135
|
const raw = meta.diffs;
|
|
3136
3136
|
if (!Array.isArray(raw) || raw.length === 0) return null;
|
|
3137
3137
|
const diffs = [];
|
|
3138
3138
|
for (const hunk of raw) {
|
|
3139
|
-
if (!isRecord$
|
|
3139
|
+
if (!isRecord$3(hunk)) return null;
|
|
3140
3140
|
const path = nonBlankString(hunk.path);
|
|
3141
3141
|
if (path === null) return null;
|
|
3142
3142
|
const { oldText, newText } = hunk;
|
|
@@ -3159,7 +3159,7 @@ window.__ModuleLoader__.load({
|
|
|
3159
3159
|
* @returns the narrowed metadata, or `null` when it is not a usable preview.
|
|
3160
3160
|
*/
|
|
3161
3161
|
function narrowAstEditCardMeta(meta) {
|
|
3162
|
-
if (!isRecord$
|
|
3162
|
+
if (!isRecord$3(meta) || meta.kind !== "ast_edit") return null;
|
|
3163
3163
|
if (jsonByteLength(meta) > 65536) return null;
|
|
3164
3164
|
if (typeof meta.preview !== "string") return null;
|
|
3165
3165
|
const replacements = nonNegativeInteger(meta.replacements);
|
|
@@ -3167,7 +3167,7 @@ window.__ModuleLoader__.load({
|
|
|
3167
3167
|
if (!Array.isArray(meta.files)) return null;
|
|
3168
3168
|
const files = [];
|
|
3169
3169
|
for (const file of meta.files) {
|
|
3170
|
-
if (!isRecord$
|
|
3170
|
+
if (!isRecord$3(file)) return null;
|
|
3171
3171
|
const path = nonBlankString(file.path);
|
|
3172
3172
|
const count = nonNegativeInteger(file.count);
|
|
3173
3173
|
if (path === null || count === null) return null;
|
|
@@ -3195,7 +3195,7 @@ window.__ModuleLoader__.load({
|
|
|
3195
3195
|
* @returns the narrowed metadata, or `null` when it is not a usable read card.
|
|
3196
3196
|
*/
|
|
3197
3197
|
function narrowReadCardMeta(meta) {
|
|
3198
|
-
if (!isRecord$
|
|
3198
|
+
if (!isRecord$3(meta) || meta.kind !== "read") return null;
|
|
3199
3199
|
if (jsonByteLength(meta) > 262144) return null;
|
|
3200
3200
|
const path = nonBlankString(meta.path);
|
|
3201
3201
|
const offset = positiveInteger(meta.offset);
|
|
@@ -3205,7 +3205,7 @@ window.__ModuleLoader__.load({
|
|
|
3205
3205
|
const lines = [];
|
|
3206
3206
|
let previous = offset - 1;
|
|
3207
3207
|
for (const line of meta.lines) {
|
|
3208
|
-
if (!isRecord$
|
|
3208
|
+
if (!isRecord$3(line)) return null;
|
|
3209
3209
|
const number = positiveInteger(line.number);
|
|
3210
3210
|
if (number === null || number <= previous || number > totalLines) return null;
|
|
3211
3211
|
if (typeof line.text !== "string") return null;
|
|
@@ -3226,12 +3226,12 @@ window.__ModuleLoader__.load({
|
|
|
3226
3226
|
}
|
|
3227
3227
|
/** Narrow one grouped-matches file entry, or `null` when malformed. */
|
|
3228
3228
|
function narrowSearchFile(file) {
|
|
3229
|
-
if (!isRecord$
|
|
3229
|
+
if (!isRecord$3(file)) return null;
|
|
3230
3230
|
const path = nonBlankString(file.path);
|
|
3231
3231
|
if (path === null || !Array.isArray(file.matches)) return null;
|
|
3232
3232
|
const matches = [];
|
|
3233
3233
|
for (const match of file.matches) {
|
|
3234
|
-
if (!isRecord$
|
|
3234
|
+
if (!isRecord$3(match)) return null;
|
|
3235
3235
|
const lineNumber = positiveInteger(match.lineNumber);
|
|
3236
3236
|
if (lineNumber === null || typeof match.line !== "string") return null;
|
|
3237
3237
|
matches.push({
|
|
@@ -3250,7 +3250,7 @@ window.__ModuleLoader__.load({
|
|
|
3250
3250
|
* @returns the narrowed metadata, or `null` when it is not a usable search card.
|
|
3251
3251
|
*/
|
|
3252
3252
|
function narrowSearchCardMeta(meta) {
|
|
3253
|
-
if (!isRecord$
|
|
3253
|
+
if (!isRecord$3(meta) || meta.kind !== "search") return null;
|
|
3254
3254
|
if (jsonByteLength(meta) > 65536) return null;
|
|
3255
3255
|
if (typeof meta.truncated !== "boolean") return null;
|
|
3256
3256
|
const total = nonNegativeInteger(meta.total);
|
|
@@ -3291,7 +3291,7 @@ window.__ModuleLoader__.load({
|
|
|
3291
3291
|
* @returns the narrowed metadata, or `null` when it is not a usable terminal card.
|
|
3292
3292
|
*/
|
|
3293
3293
|
function narrowTerminalCardMeta(meta) {
|
|
3294
|
-
if (!isRecord$
|
|
3294
|
+
if (!isRecord$3(meta) || meta.kind !== "terminal") return null;
|
|
3295
3295
|
if (meta.mode === "background") {
|
|
3296
3296
|
const jobId = nonBlankString(meta.jobId);
|
|
3297
3297
|
return jobId === null ? null : {
|
|
@@ -3349,6 +3349,7 @@ window.__ModuleLoader__.load({
|
|
|
3349
3349
|
running: "运行中",
|
|
3350
3350
|
done: "完成",
|
|
3351
3351
|
failed: "失败",
|
|
3352
|
+
stopped: "已停止",
|
|
3352
3353
|
cancelled: "已取消",
|
|
3353
3354
|
timedOut: "已超时",
|
|
3354
3355
|
noOutput: "无输出",
|
|
@@ -3365,6 +3366,8 @@ window.__ModuleLoader__.load({
|
|
|
3365
3366
|
"astEdit.replacements": "{count} 处替换",
|
|
3366
3367
|
"astEdit.files": "{count} 个文件",
|
|
3367
3368
|
"astEdit.parseErrors": "解析错误",
|
|
3369
|
+
"astEdit.rules": "规则",
|
|
3370
|
+
"astEdit.rulesRest": "… 其余 {count} 条",
|
|
3368
3371
|
"read.window": "显示 {shown} / {total} 行",
|
|
3369
3372
|
"read.collapseAria": "收起内容",
|
|
3370
3373
|
"read.expandAria": "展开其余 {count} 行",
|
|
@@ -3374,6 +3377,7 @@ window.__ModuleLoader__.load({
|
|
|
3374
3377
|
"search.matches": "{shown} 处匹配 · {files} 个文件",
|
|
3375
3378
|
"search.matches.truncated": "显示 {shown} / 共 {total} 处匹配 · {files} 个文件",
|
|
3376
3379
|
"search.noResults": "无结果",
|
|
3380
|
+
"search.scope": "范围 {path}",
|
|
3377
3381
|
"search.collapseAria": "收起结果",
|
|
3378
3382
|
"search.expandAria": "展开其余 {count} 行结果",
|
|
3379
3383
|
"search.expandRest": "… 其余 {count} 行",
|
|
@@ -3409,6 +3413,7 @@ window.__ModuleLoader__.load({
|
|
|
3409
3413
|
running: "Running",
|
|
3410
3414
|
done: "Done",
|
|
3411
3415
|
failed: "Failed",
|
|
3416
|
+
stopped: "Stopped",
|
|
3412
3417
|
cancelled: "Cancelled",
|
|
3413
3418
|
timedOut: "Timed out",
|
|
3414
3419
|
noOutput: "No output",
|
|
@@ -3425,6 +3430,8 @@ window.__ModuleLoader__.load({
|
|
|
3425
3430
|
"astEdit.replacements": "{count} replacements",
|
|
3426
3431
|
"astEdit.files": "{count} files",
|
|
3427
3432
|
"astEdit.parseErrors": "Parse errors",
|
|
3433
|
+
"astEdit.rules": "Rules",
|
|
3434
|
+
"astEdit.rulesRest": "… {count} more",
|
|
3428
3435
|
"read.window": "Showing {shown} of {total} lines",
|
|
3429
3436
|
"read.collapseAria": "Collapse content",
|
|
3430
3437
|
"read.expandAria": "Expand {count} more lines",
|
|
@@ -3434,6 +3441,7 @@ window.__ModuleLoader__.load({
|
|
|
3434
3441
|
"search.matches": "{shown} matches · {files} files",
|
|
3435
3442
|
"search.matches.truncated": "Showing {shown} of {total} matches · {files} files",
|
|
3436
3443
|
"search.noResults": "No results",
|
|
3444
|
+
"search.scope": "in {path}",
|
|
3437
3445
|
"search.collapseAria": "Collapse results",
|
|
3438
3446
|
"search.expandAria": "Expand {count} more result lines",
|
|
3439
3447
|
"search.expandRest": "… {count} more lines",
|
|
@@ -3464,7 +3472,7 @@ window.__ModuleLoader__.load({
|
|
|
3464
3472
|
running: "running",
|
|
3465
3473
|
ok: null,
|
|
3466
3474
|
error: "failed",
|
|
3467
|
-
warning: "
|
|
3475
|
+
warning: "stopped"
|
|
3468
3476
|
};
|
|
3469
3477
|
/** Parsed-argument cache: the node is immutable, so one parse serves every render. */
|
|
3470
3478
|
const parsedArgs = /* @__PURE__ */ new WeakMap();
|
|
@@ -3483,10 +3491,25 @@ window.__ModuleLoader__.load({
|
|
|
3483
3491
|
return typeof call === "object" && call !== null ? call : null;
|
|
3484
3492
|
}
|
|
3485
3493
|
/** Whether `value` is a plain object, the only argument shape the cards accept. */
|
|
3486
|
-
function isRecord$
|
|
3494
|
+
function isRecord$2(value) {
|
|
3487
3495
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3488
3496
|
}
|
|
3489
3497
|
/**
|
|
3498
|
+
* The call's original argument text.
|
|
3499
|
+
*
|
|
3500
|
+
* A running call carries it directly; a settled result carries the head
|
|
3501
|
+
* backfilled from the in-window `tool/call`, which is absent when window
|
|
3502
|
+
* truncation left that event outside. The text outlives the parse: a call whose
|
|
3503
|
+
* arguments are still streaming is not valid JSON yet, and the shell states its
|
|
3504
|
+
* first line rather than pretending the call said nothing.
|
|
3505
|
+
* @param block - the frozen running-or-settled call node.
|
|
3506
|
+
* @returns the argument text, or `''` when no head survives.
|
|
3507
|
+
*/
|
|
3508
|
+
function cardArgsRaw(block) {
|
|
3509
|
+
const raw = callHead(block)?.argsRaw;
|
|
3510
|
+
return typeof raw === "string" ? raw : "";
|
|
3511
|
+
}
|
|
3512
|
+
/**
|
|
3490
3513
|
* Parse the call's raw argument JSON.
|
|
3491
3514
|
*
|
|
3492
3515
|
* The parsed object is memoized per node (the node is immutable), so repeated
|
|
@@ -3505,7 +3528,7 @@ window.__ModuleLoader__.load({
|
|
|
3505
3528
|
let parsed = null;
|
|
3506
3529
|
if (typeof raw === "string") try {
|
|
3507
3530
|
const value = JSON.parse(raw);
|
|
3508
|
-
if (isRecord$
|
|
3531
|
+
if (isRecord$2(value)) parsed = value;
|
|
3509
3532
|
} catch {
|
|
3510
3533
|
parsed = null;
|
|
3511
3534
|
}
|
|
@@ -3515,30 +3538,26 @@ window.__ModuleLoader__.load({
|
|
|
3515
3538
|
/**
|
|
3516
3539
|
* Run state of one call.
|
|
3517
3540
|
*
|
|
3518
|
-
* A
|
|
3519
|
-
*
|
|
3520
|
-
*
|
|
3521
|
-
*
|
|
3522
|
-
*
|
|
3541
|
+
* A turn stopped mid-call is projected as a settled `isError` block carrying
|
|
3542
|
+
* `error.code === 'interrupted'`; the shipped row model reads that before
|
|
3543
|
+
* `isError`, so a stopped call is amber and named "stopped" rather than failed.
|
|
3544
|
+
* Any other settled `isError` result is an error. A foreground command that
|
|
3545
|
+
* timed out or was cancelled is amber rather than green (the shipped terminal
|
|
3546
|
+
* model reads only the trailing exit marker, which is exactly what the card
|
|
3547
|
+
* owner was told not to trust). A non-zero exit stays `ok`: bash reports a
|
|
3548
|
+
* failing command as result data, and the card's own status pill carries the
|
|
3549
|
+
* red.
|
|
3523
3550
|
* @param block - the frozen running-or-settled call node.
|
|
3524
3551
|
* @returns the card run state.
|
|
3525
3552
|
*/
|
|
3526
3553
|
function cardState(block) {
|
|
3527
3554
|
if (!isSettled(block)) return "running";
|
|
3555
|
+
if (isRecord$2(block.error) && block.error.code === "interrupted") return "warning";
|
|
3528
3556
|
if (block.isError === true) return "error";
|
|
3529
3557
|
const terminal = narrowTerminalCardMeta(cardMeta(block));
|
|
3530
3558
|
if (terminal !== null && terminal.mode === "foreground" && (terminal.timedOut || terminal.aborted)) return "warning";
|
|
3531
3559
|
return "ok";
|
|
3532
3560
|
}
|
|
3533
|
-
/** Map a card run state onto the primitive's dot semantics. */
|
|
3534
|
-
function dotState(state) {
|
|
3535
|
-
switch (state) {
|
|
3536
|
-
case "running": return "ongoing";
|
|
3537
|
-
case "error": return "error";
|
|
3538
|
-
case "warning": return "warning";
|
|
3539
|
-
default: return "done";
|
|
3540
|
-
}
|
|
3541
|
-
}
|
|
3542
3561
|
/**
|
|
3543
3562
|
* The assistive-technology status word for a run state, or `null` when the
|
|
3544
3563
|
* state is already carried by the row's content.
|
|
@@ -3556,7 +3575,7 @@ window.__ModuleLoader__.load({
|
|
|
3556
3575
|
if (!isSettled(block) || !Array.isArray(block.content)) return "";
|
|
3557
3576
|
const parts = [];
|
|
3558
3577
|
for (const part of block.content) {
|
|
3559
|
-
if (!isRecord$
|
|
3578
|
+
if (!isRecord$2(part)) continue;
|
|
3560
3579
|
if (part.type === "text" && typeof part.text === "string") parts.push(part.text);
|
|
3561
3580
|
}
|
|
3562
3581
|
return parts.join("\n");
|
|
@@ -3782,15 +3801,339 @@ window.__ModuleLoader__.load({
|
|
|
3782
3801
|
};
|
|
3783
3802
|
}
|
|
3784
3803
|
//#endregion
|
|
3804
|
+
//#region src/web/client/rows/card-leading.ts
|
|
3805
|
+
/**
|
|
3806
|
+
* Wire tool name to glyph family: the shipped name table for the six keys this
|
|
3807
|
+
* plugin shadows, plus its own two tools.
|
|
3808
|
+
*/
|
|
3809
|
+
const GLYPH_BY_TOOL = {
|
|
3810
|
+
bash: "bash",
|
|
3811
|
+
read: "read",
|
|
3812
|
+
write: "edit",
|
|
3813
|
+
edit: "edit",
|
|
3814
|
+
grep: "search",
|
|
3815
|
+
glob: "search",
|
|
3816
|
+
ast_grep: "search",
|
|
3817
|
+
ast_edit: "edit"
|
|
3818
|
+
};
|
|
3819
|
+
/**
|
|
3820
|
+
* Glyph family a row leads with.
|
|
3821
|
+
* @param toolName - the call's wire tool name, when the node carries one.
|
|
3822
|
+
* @returns the family, or `others` (the shipped generic glyph) for an unknown name.
|
|
3823
|
+
*/
|
|
3824
|
+
function leadingGlyph(toolName) {
|
|
3825
|
+
if (typeof toolName !== "string") return "others";
|
|
3826
|
+
return GLYPH_BY_TOOL[toolName] ?? "others";
|
|
3827
|
+
}
|
|
3828
|
+
/**
|
|
3829
|
+
* Whether a run state keeps the row's glyph or replaces it with a state dot.
|
|
3830
|
+
* @param state - the card's run state.
|
|
3831
|
+
* @returns the leading kind; `error` and `warning` are the two dot states.
|
|
3832
|
+
*/
|
|
3833
|
+
function leadingKind(state) {
|
|
3834
|
+
switch (state) {
|
|
3835
|
+
case "error": return "error";
|
|
3836
|
+
case "warning": return "warning";
|
|
3837
|
+
default: return "glyph";
|
|
3838
|
+
}
|
|
3839
|
+
}
|
|
3840
|
+
//#endregion
|
|
3841
|
+
//#region src/web/client/rows/command-card-facts.ts
|
|
3842
|
+
/**
|
|
3843
|
+
* Collapsed-fact builders for the two command-shaped cards: the command a
|
|
3844
|
+
* `bash` description hides, and the rewrite rule an `ast_edit` preview only
|
|
3845
|
+
* shows once expanded.
|
|
3846
|
+
*
|
|
3847
|
+
* Both cards collapse to one head line. `bash`'s head used to be the model's
|
|
3848
|
+
* own `description` — a sentence about intent, never the command that ran — so
|
|
3849
|
+
* the only way to learn what executed was to expand the card. `ast_edit`'s head
|
|
3850
|
+
* names the file and the replacement count, and its body previews the *result*:
|
|
3851
|
+
* the rule that produced that result lives only in the call's `ops` argument,
|
|
3852
|
+
* which no card showed at all. Each builder recovers one fact from the call's
|
|
3853
|
+
* own arguments, so nothing is inferred from output, and a call head that fell
|
|
3854
|
+
* outside the session window degrades to the facts the card already holds.
|
|
3855
|
+
* @module @xiaoso/dsh-tool-plus/web/client/rows/command-card-facts
|
|
3856
|
+
*/
|
|
3857
|
+
/** Whether `value` is a plain object — the only op shape the engine sends. */
|
|
3858
|
+
function isRecord$1(value) {
|
|
3859
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3860
|
+
}
|
|
3861
|
+
/**
|
|
3862
|
+
* The `bash` card's collapsed summary: what ran, placed after the sentence
|
|
3863
|
+
* saying why.
|
|
3864
|
+
*
|
|
3865
|
+
* The command is the fact the head must carry, so the description can never be
|
|
3866
|
+
* the whole summary — it is a prefix at most. Only its first line is used: a
|
|
3867
|
+
* summary is one row high, and the rest of a multi-line command belongs to the
|
|
3868
|
+
* expanded body, which draws the whole thing.
|
|
3869
|
+
* @param description - the model's one-line intent, or `null` when absent.
|
|
3870
|
+
* @param command - the command actually executed (never empty).
|
|
3871
|
+
* @param outcome - the interruption word (`timedOut` / `cancelled`), or `null`.
|
|
3872
|
+
* @returns the outcome alone when the run was interrupted; otherwise the
|
|
3873
|
+
* description joined to the command's first line, or that line alone.
|
|
3874
|
+
*/
|
|
3875
|
+
function bashCardSummary(description, command, outcome) {
|
|
3876
|
+
if (outcome !== null && outcome !== "") return outcome;
|
|
3877
|
+
const intent = description === null ? "" : description.trim();
|
|
3878
|
+
const line = firstLine(command);
|
|
3879
|
+
return intent === "" ? line : `${intent} · ${line}`;
|
|
3880
|
+
}
|
|
3881
|
+
/**
|
|
3882
|
+
* The `ast_edit` card's rule line: the first usable `ops` entry, written the
|
|
3883
|
+
* way the tool's own `ops` argument writes it (`pat → out`).
|
|
3884
|
+
*
|
|
3885
|
+
* Only the first rule is stated, with the count of the entries behind it: the
|
|
3886
|
+
* card exists to preview one change, and a call may carry many rules whose
|
|
3887
|
+
* patterns are whole AST fragments — listing them would bury the preview. The
|
|
3888
|
+
* remaining count is the array tail, not the number of *usable* entries, so it
|
|
3889
|
+
* always describes the call as it was made.
|
|
3890
|
+
* @param args - the call's parsed arguments, or `null` when its head is gone.
|
|
3891
|
+
* @param t - this plugin's card translate seat.
|
|
3892
|
+
* @returns the rule line, or `null` when the call carries no usable rule.
|
|
3893
|
+
*/
|
|
3894
|
+
function astEditRuleLine(args, t) {
|
|
3895
|
+
const ops = args?.ops;
|
|
3896
|
+
if (!Array.isArray(ops)) return null;
|
|
3897
|
+
for (let index = 0; index < ops.length; index += 1) {
|
|
3898
|
+
const op = ops[index];
|
|
3899
|
+
if (!isRecord$1(op)) continue;
|
|
3900
|
+
const pat = argText(op, "pat");
|
|
3901
|
+
if (pat === null) continue;
|
|
3902
|
+
const replacement = op.out;
|
|
3903
|
+
if (typeof replacement !== "string") continue;
|
|
3904
|
+
const rest = ops.length - 1;
|
|
3905
|
+
const rule = `${t("astEdit.rules")}: ${pat} →${replacement === "" ? "" : ` ${replacement}`}`;
|
|
3906
|
+
return rest === 0 ? rule : `${rule} · ${t("astEdit.rulesRest", { count: rest })}`;
|
|
3907
|
+
}
|
|
3908
|
+
return null;
|
|
3909
|
+
}
|
|
3910
|
+
//#endregion
|
|
3911
|
+
//#region src/web/client/rows/read-card-facts.ts
|
|
3912
|
+
/**
|
|
3913
|
+
* Collapsed-summary facts for the `read` card.
|
|
3914
|
+
*
|
|
3915
|
+
* The Host projection strips the inline selector off the path
|
|
3916
|
+
* (`stripReadSelector` in `web/host/read.ts`) because the card's *link* must
|
|
3917
|
+
* point at a real file, and it keeps only the file's own window. The collapsed
|
|
3918
|
+
* head is then the one place where the question the model actually asked is
|
|
3919
|
+
* still readable — and the projected path cannot answer it: a windowed read of
|
|
3920
|
+
* `package.json:22-25,31-34` and a whole-file read of `package.json` project to
|
|
3921
|
+
* the very same string, so the card hides the difference.
|
|
3922
|
+
*
|
|
3923
|
+
* The summary is therefore built from the call's own argument, selector and
|
|
3924
|
+
* all, with the projected path kept as the fallback for a result whose call
|
|
3925
|
+
* head fell outside the session window (a truncated window carries no
|
|
3926
|
+
* `tool/call` event, so no argument survives). That fallback states a fact the
|
|
3927
|
+
* card already holds; nothing is invented.
|
|
3928
|
+
* @module @xiaoso/dsh-tool-plus/web/client/rows/read-card-facts
|
|
3929
|
+
*/
|
|
3930
|
+
/**
|
|
3931
|
+
* The `read` card's collapsed summary: the path the model asked for, keeping
|
|
3932
|
+
* whatever inline selector it carried (`:22-25`, `:raw`, `:conflicts`, `:28-`,
|
|
3933
|
+
* a sqlite row, an archive member) and shortened against the workspace root.
|
|
3934
|
+
*
|
|
3935
|
+
* The selector is deliberately not validated against the read grammar: the head
|
|
3936
|
+
* reports the call verbatim, and a selector this plugin does not recognize is
|
|
3937
|
+
* still what the model typed — substituting the projection's stripped path
|
|
3938
|
+
* would erase the evidence that two calls differ.
|
|
3939
|
+
* @param args - the call's parsed arguments, or `null` when its head is gone.
|
|
3940
|
+
* @param metaPath - the Host-projected path (already selector-free).
|
|
3941
|
+
* @param cwd - the session workspace root, when known.
|
|
3942
|
+
* @returns the workspace-shortened argument path, or the projected path.
|
|
3943
|
+
*/
|
|
3944
|
+
function readCardSummary(args, metaPath, cwd) {
|
|
3945
|
+
return displayPath(argText(args, "path") ?? metaPath, cwd);
|
|
3946
|
+
}
|
|
3947
|
+
//#endregion
|
|
3948
|
+
//#region src/web/client/rows/fallback-card-facts.ts
|
|
3949
|
+
/**
|
|
3950
|
+
* Collapsed-summary facts for the generic card — every row's degradation path.
|
|
3951
|
+
*
|
|
3952
|
+
* A call lands on the generic shell when its own card cannot be built: it is
|
|
3953
|
+
* still running, its result carried no text, its metadata fell outside the
|
|
3954
|
+
* session window, it is a Code Dispatch child, or the Host projected an
|
|
3955
|
+
* interruption the card has no material for. In every one of those cases the
|
|
3956
|
+
* call's ARGUMENTS are the only material left, and the shipped generic row
|
|
3957
|
+
* answers them with the call's own fact: its variant's summary keys first, then
|
|
3958
|
+
* ANY string the payload carries, then the first line of the raw argument text
|
|
3959
|
+
* (`deriveSummary` in ui-tool's `tool-call-model.ts`). It never answers with an
|
|
3960
|
+
* argument count.
|
|
3961
|
+
*
|
|
3962
|
+
* That is the rule here. The count it replaces told the reader nothing: a
|
|
3963
|
+
* directory read, a `:raw` window, a running `grep` and a child call all
|
|
3964
|
+
* collapsed to "1 个参数" — the one thing a degraded row must not do, since the
|
|
3965
|
+
* head is then the only place the fact exists.
|
|
3966
|
+
*
|
|
3967
|
+
* The key preference follows THIS plugin's own tool schemas rather than the
|
|
3968
|
+
* shipped `SUMMARY_KEYS`: our tools replaced the built-in ones with the OMP
|
|
3969
|
+
* native parameter names (`read`/`write` take `path`, `ast_grep` takes `pat`,
|
|
3970
|
+
* `ast_edit` takes `paths`), and the shipped table's `file_path` is what the
|
|
3971
|
+
* built-in tools used. Both spellings are accepted, ours first, so a transcript
|
|
3972
|
+
* recorded before the takeover still states its path.
|
|
3973
|
+
* @module @xiaoso/dsh-tool-plus/web/client/rows/fallback-card-facts
|
|
3974
|
+
*/
|
|
3975
|
+
/**
|
|
3976
|
+
* First entry of a path-list argument (`ast_edit.paths`), tolerating the
|
|
3977
|
+
* single-string form some calls use.
|
|
3978
|
+
* @param args - the call's parsed arguments, or `null`.
|
|
3979
|
+
* @param name - the argument's name.
|
|
3980
|
+
* @returns the first non-empty path, or `null`.
|
|
3981
|
+
*/
|
|
3982
|
+
function firstPath(args, name) {
|
|
3983
|
+
const value = args?.[name];
|
|
3984
|
+
if (typeof value === "string" && value !== "") return value;
|
|
3985
|
+
if (!Array.isArray(value)) return null;
|
|
3986
|
+
for (const entry of value) if (typeof entry === "string" && entry !== "") return entry;
|
|
3987
|
+
return null;
|
|
3988
|
+
}
|
|
3989
|
+
/**
|
|
3990
|
+
* The first non-empty string the payload carries under one of `keys` — the
|
|
3991
|
+
* shipped row's own key-preference rule, first line only (a payload's own
|
|
3992
|
+
* newlines never reach a one-line head).
|
|
3993
|
+
* @param args - the call's parsed arguments, or `null`.
|
|
3994
|
+
* @param keys - argument names, most significant first.
|
|
3995
|
+
* @returns the value, or `null` when none of them is a non-empty string.
|
|
3996
|
+
*/
|
|
3997
|
+
function pickKeys(args, keys) {
|
|
3998
|
+
for (const key of keys) {
|
|
3999
|
+
const value = argText(args, key);
|
|
4000
|
+
if (value !== null) return firstLine(value);
|
|
4001
|
+
}
|
|
4002
|
+
return null;
|
|
4003
|
+
}
|
|
4004
|
+
/**
|
|
4005
|
+
* The `queries` a search call may carry, joined one query per line — the one
|
|
4006
|
+
* special case the shipped row makes before its key table.
|
|
4007
|
+
* @param args - the call's parsed arguments, or `null`.
|
|
4008
|
+
* @returns the joined subject, or `null` when there is no usable query.
|
|
4009
|
+
*/
|
|
4010
|
+
function queriesSubject(args) {
|
|
4011
|
+
const queries = args?.["queries"];
|
|
4012
|
+
if (!Array.isArray(queries)) return null;
|
|
4013
|
+
const parts = queries.filter((query) => typeof query === "string" && query !== "").map(firstLine);
|
|
4014
|
+
return parts.length === 0 ? null : parts.join(", ");
|
|
4015
|
+
}
|
|
4016
|
+
/**
|
|
4017
|
+
* The fact the call states for a key this plugin renders.
|
|
4018
|
+
* @param toolName - the call's wire tool name.
|
|
4019
|
+
* @param args - the call's parsed arguments, or `null`.
|
|
4020
|
+
* @param cwd - the session workspace root, for shortening an absolute path.
|
|
4021
|
+
* @returns the stated fact, or `null` when this key states nothing usable.
|
|
4022
|
+
*/
|
|
4023
|
+
function statedFact(toolName, args, cwd) {
|
|
4024
|
+
switch (toolName) {
|
|
4025
|
+
case "read": {
|
|
4026
|
+
const asked = pickKeys(args, [
|
|
4027
|
+
"path",
|
|
4028
|
+
"file_path",
|
|
4029
|
+
"url"
|
|
4030
|
+
]);
|
|
4031
|
+
return asked === null ? null : readCardSummary(args, asked, cwd);
|
|
4032
|
+
}
|
|
4033
|
+
case "bash": {
|
|
4034
|
+
const command = argText(args, "command");
|
|
4035
|
+
const description = argText(args, "description");
|
|
4036
|
+
if (command === null) return description;
|
|
4037
|
+
return bashCardSummary(description, command, null);
|
|
4038
|
+
}
|
|
4039
|
+
case "write": {
|
|
4040
|
+
const path = pickKeys(args, ["path", "file_path"]);
|
|
4041
|
+
return path === null ? null : displayPath(path, cwd);
|
|
4042
|
+
}
|
|
4043
|
+
case "edit": {
|
|
4044
|
+
const path = pickKeys(args, ["file_path", "path"]);
|
|
4045
|
+
return path === null ? null : displayPath(path, cwd);
|
|
4046
|
+
}
|
|
4047
|
+
case "ast_edit": {
|
|
4048
|
+
const path = firstPath(args, "paths");
|
|
4049
|
+
return path === null ? null : displayPath(path, cwd);
|
|
4050
|
+
}
|
|
4051
|
+
case "grep":
|
|
4052
|
+
case "glob":
|
|
4053
|
+
case "ast_grep": {
|
|
4054
|
+
const subject = queriesSubject(args) ?? pickKeys(args, [
|
|
4055
|
+
"query",
|
|
4056
|
+
"pattern",
|
|
4057
|
+
"pat",
|
|
4058
|
+
"url",
|
|
4059
|
+
"path"
|
|
4060
|
+
]);
|
|
4061
|
+
return subject === null ? null : displayPath(subject, cwd);
|
|
4062
|
+
}
|
|
4063
|
+
default: return null;
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
/**
|
|
4067
|
+
* The generic card's collapsed summary: the fact the call itself states.
|
|
4068
|
+
* @param toolName - the call's wire tool name (empty when truncated).
|
|
4069
|
+
* @param args - the call's parsed arguments, or `null` when its head is gone.
|
|
4070
|
+
* @param cwd - the session workspace root, for shortening an absolute path.
|
|
4071
|
+
* @param t - the card translate seat.
|
|
4072
|
+
* @param raw - the call's original argument text, for a payload that is not
|
|
4073
|
+
* valid JSON yet (a call still streaming its arguments) or whose only values
|
|
4074
|
+
* are non-strings.
|
|
4075
|
+
* @returns the collapsed summary text.
|
|
4076
|
+
*/
|
|
4077
|
+
function fallbackCardSummary(toolName, args, cwd, t, raw = null) {
|
|
4078
|
+
const stated = statedFact(toolName, args, cwd);
|
|
4079
|
+
if (stated !== null) return stated;
|
|
4080
|
+
if (args !== null) {
|
|
4081
|
+
for (const value of Object.values(args)) if (typeof value === "string" && value !== "") return firstLine(value);
|
|
4082
|
+
}
|
|
4083
|
+
const text = raw === null || raw === "" ? null : firstLine(raw);
|
|
4084
|
+
if (text !== null && text !== "") return text;
|
|
4085
|
+
return argsSummary(args, t);
|
|
4086
|
+
}
|
|
4087
|
+
/**
|
|
4088
|
+
* Everything from the first colon is a read selector, not part of a filename.
|
|
4089
|
+
* The colon after a Windows drive letter is not one, and a URL is not a session
|
|
4090
|
+
* file at all.
|
|
4091
|
+
* @param value - the path argument, verbatim.
|
|
4092
|
+
* @returns the file the path names, or `null` when it names no openable file.
|
|
4093
|
+
*/
|
|
4094
|
+
function stripSelector(value) {
|
|
4095
|
+
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(value)) return null;
|
|
4096
|
+
const start = /^[a-zA-Z]:[\\/]/.test(value) ? 2 : 0;
|
|
4097
|
+
const colon = value.indexOf(":", start);
|
|
4098
|
+
const base = colon === -1 ? value : value.slice(0, colon);
|
|
4099
|
+
return base === "" ? null : base;
|
|
4100
|
+
}
|
|
4101
|
+
/**
|
|
4102
|
+
* The real file a degraded file-backed row may offer to open — `null` when the
|
|
4103
|
+
* call names no single file.
|
|
4104
|
+
*
|
|
4105
|
+
* The head states the call verbatim (selector included) while the link has to
|
|
4106
|
+
* name a real file: the same split the read card itself makes between its
|
|
4107
|
+
* summary and its link. Whether that file then previews is the side bar's
|
|
4108
|
+
* decision, never this module's.
|
|
4109
|
+
* @param toolName - the call's wire tool name.
|
|
4110
|
+
* @param args - the call's parsed arguments, or `null`.
|
|
4111
|
+
* @returns the file to open, or `null` when there is nothing to open.
|
|
4112
|
+
*/
|
|
4113
|
+
function fallbackCardLink(toolName, args) {
|
|
4114
|
+
switch (toolName) {
|
|
4115
|
+
case "read": {
|
|
4116
|
+
const asked = pickKeys(args, ["path", "file_path"]);
|
|
4117
|
+
return asked === null ? null : stripSelector(asked);
|
|
4118
|
+
}
|
|
4119
|
+
case "write": return pickKeys(args, ["path", "file_path"]);
|
|
4120
|
+
case "edit": return pickKeys(args, ["file_path", "path"]);
|
|
4121
|
+
case "ast_edit": return firstPath(args, "paths");
|
|
4122
|
+
default: return null;
|
|
4123
|
+
}
|
|
4124
|
+
}
|
|
4125
|
+
//#endregion
|
|
3785
4126
|
//#region src/web/client/ToolCardShell.tsx
|
|
3786
4127
|
/**
|
|
3787
4128
|
* The shared card shell every row renders through.
|
|
3788
4129
|
*
|
|
3789
4130
|
* The shipped `ToolRow` is not reusable from a plugin (ui-tool is not part of
|
|
3790
4131
|
* the client platform baseline), so this is our own: one 24px header line
|
|
3791
|
-
* (
|
|
4132
|
+
* (tool glyph + title + separator + ellipsized summary + optional `+N -M`
|
|
3792
4133
|
* suffix + clickable path), a keyboard-reachable disclosure, and a body that
|
|
3793
|
-
* is either the card a row built or the flattened result text.
|
|
4134
|
+
* is either the card a row built or the flattened result text. The leading
|
|
4135
|
+
* glyph gives way to a state dot on a failed or interrupted call, and the row
|
|
4136
|
+
* sweeps while the call runs, as the shipped row does.
|
|
3794
4137
|
*
|
|
3795
4138
|
* Two properties matter more than the visuals:
|
|
3796
4139
|
* - **Never throw.** Every value is coerced defensively, and {@link CardBoundary}
|
|
@@ -3802,17 +4145,50 @@ window.__ModuleLoader__.load({
|
|
|
3802
4145
|
* both come out right.
|
|
3803
4146
|
* @module @xiaoso/dsh-tool-plus/web/client/ToolCardShell
|
|
3804
4147
|
*/
|
|
4148
|
+
/** Leading glyphs (the shipped table): every glyph renders at 14 inside the 16px leading box. */
|
|
4149
|
+
const LEADING_GLYPHS = {
|
|
4150
|
+
bash: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconApiOutline14, { size: 14 }),
|
|
4151
|
+
read: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBrowseOutline16, { size: 14 }),
|
|
4152
|
+
edit: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 14 }),
|
|
4153
|
+
search: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: 14 }),
|
|
4154
|
+
others: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSparkle16, { size: 14 })
|
|
4155
|
+
};
|
|
4156
|
+
/**
|
|
4157
|
+
* The leading mark of one row: the tool's glyph while the call is clean or
|
|
4158
|
+
* running, and the state dot a failure or an interrupt swaps in — the shipped
|
|
4159
|
+
* `leadingFor` rule, so a failed row gives up its identity for the state
|
|
4160
|
+
* exactly where the shipped row does.
|
|
4161
|
+
* @param state - the card's run state.
|
|
4162
|
+
* @param toolName - the call's wire tool name, when the node carries one.
|
|
4163
|
+
* @returns the node the leading box draws.
|
|
4164
|
+
*/
|
|
4165
|
+
function leadingFor(state, toolName) {
|
|
4166
|
+
switch (leadingKind(state)) {
|
|
4167
|
+
case "error": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "error" });
|
|
4168
|
+
case "warning": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "warning" });
|
|
4169
|
+
default: return LEADING_GLYPHS[leadingGlyph(toolName)];
|
|
4170
|
+
}
|
|
4171
|
+
}
|
|
3805
4172
|
/** Chrome CSS, keyed by `data-plugin-css` and injected once per page. */
|
|
3806
4173
|
const CSS = `
|
|
3807
4174
|
.twc-root{display:flex;flex-direction:column;min-width:0}
|
|
3808
|
-
|
|
4175
|
+
/* The sweep's own anchor and clip, repeated from the shipped sheet so the band
|
|
4176
|
+
cannot escape the row if the shared row chrome ever drops them. */
|
|
4177
|
+
.twc-row{position:relative;min-width:0;overflow:hidden}
|
|
3809
4178
|
.twc-leading{flex-shrink:0}
|
|
4179
|
+
/* Running sweep — the shipped row's own treatment (ui-tool/ToolRow.module.css),
|
|
4180
|
+
copied band for band: a 300px glare of the theme background glides from
|
|
4181
|
+
off-left to off-right, washing the glyph and the text toward the background
|
|
4182
|
+
as it passes. Same anchor as the shipped sheet puts it on: DisclosureRow's
|
|
4183
|
+
own row already carries the position:relative and the overflow:hidden that
|
|
4184
|
+
clips the band. */
|
|
4185
|
+
.twc-root[data-state='running'] .twc-row::after{content:'';position:absolute;top:0;bottom:0;left:0;width:300px;background:linear-gradient(90deg,transparent 0%,color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%,transparent 100%);animation:twc-row-sweep 2.6s ease-out infinite;pointer-events:none}
|
|
4186
|
+
@keyframes twc-row-sweep{0%{left:-300px}90%,100%{left:100%}}
|
|
3810
4187
|
.twc-chevron{color:var(--dsw-alias-label-secondary)}
|
|
3811
4188
|
.twc-title{font-weight:400}
|
|
3812
4189
|
.twc-sep{flex:none;width:2px;height:2px;border-radius:1px;margin:0 8px;background:var(--dsw-alias-label-caption)}
|
|
3813
|
-
.twc-summary{flex:
|
|
4190
|
+
.twc-summary{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary)}
|
|
3814
4191
|
.twc-summarySuffix{flex:none;margin-left:4px;white-space:nowrap;font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary)}
|
|
3815
|
-
.twc-diffStat{margin-left:10px;font-family:var(--ds-font-family-code);font-size:calc(var(--dsh-content-font-size-secondary,13px) - 2px);color:var(--dsw-alias-label-caption)}
|
|
3816
4192
|
.twc-errorSummary{color:var(--dsw-alias-state-error-primary)}
|
|
3817
4193
|
.twc-fileLink{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin:0;padding:0;border:none;background:none;font:inherit;text-align:left;font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-secondary);text-decoration:underline dotted;text-decoration-color:var(--dsw-alias-label-tertiary);text-decoration-thickness:1px;text-underline-offset:3px;cursor:pointer;border-radius:4px}
|
|
3818
4194
|
.twc-fileLink:hover{color:var(--dsw-alias-label-primary);text-decoration-color:currentColor}
|
|
@@ -3873,8 +4249,11 @@ window.__ModuleLoader__.load({
|
|
|
3873
4249
|
.twc-root:hover .twc-inspect,.twc-inspect:focus-visible{opacity:1}
|
|
3874
4250
|
.twc-inspect:hover{background:var(--dsw-alias-interactive-bg-hover-solid);color:var(--dsw-alias-label-primary)}
|
|
3875
4251
|
.twc-visuallyHidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
|
|
3876
|
-
|
|
3877
|
-
|
|
4252
|
+
/* Motion is feedback, never decoration: the running sweep is the one animation
|
|
4253
|
+
this shell borrows from the shipped row, which runs it with no motion query
|
|
4254
|
+
at all. A reduced-motion reader loses only the glide — the state itself is
|
|
4255
|
+
still announced through the visually-hidden status word. */
|
|
4256
|
+
@media (prefers-reduced-motion: reduce){.twc-inspect{transition:none}.twc-root[data-state='running'] .twc-row::after{display:none}}
|
|
3878
4257
|
`;
|
|
3879
4258
|
/** Inject the card stylesheet once per page; the loader drops plugin style tags on unload. */
|
|
3880
4259
|
let cssInjected = false;
|
|
@@ -3901,7 +4280,7 @@ window.__ModuleLoader__.load({
|
|
|
3901
4280
|
*/
|
|
3902
4281
|
function ToolCardShell(props) {
|
|
3903
4282
|
injectCss();
|
|
3904
|
-
const { t, state, title, children, filePath, openFile, inspect } = props;
|
|
4283
|
+
const { t, state, title, children, filePath, openFile, inspect, tool } = props;
|
|
3905
4284
|
const [expanded, setExpanded] = (0, react.useState)(props.defaultOpen === true);
|
|
3906
4285
|
const bodyCard = hasContent(children) ? children : null;
|
|
3907
4286
|
const bodyText = typeof props.text === "string" && props.text !== "" ? props.text : null;
|
|
@@ -3932,7 +4311,7 @@ window.__ModuleLoader__.load({
|
|
|
3932
4311
|
}),
|
|
3933
4312
|
fileLink ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3934
4313
|
type: "button",
|
|
3935
|
-
className: "twc-fileLink",
|
|
4314
|
+
className: errorTone ? "twc-fileLink twc-errorSummary" : "twc-fileLink",
|
|
3936
4315
|
onClick: openPath,
|
|
3937
4316
|
onKeyDown: openPathKeyDown,
|
|
3938
4317
|
children: summaryText
|
|
@@ -3957,7 +4336,7 @@ window.__ModuleLoader__.load({
|
|
|
3957
4336
|
leadingClassName: "twc-leading",
|
|
3958
4337
|
titleClassName: "twc-title",
|
|
3959
4338
|
chevronClassName: "twc-chevron",
|
|
3960
|
-
icon:
|
|
4339
|
+
icon: leadingFor(state, tool),
|
|
3961
4340
|
title,
|
|
3962
4341
|
open,
|
|
3963
4342
|
expandable,
|
|
@@ -3986,20 +4365,24 @@ window.__ModuleLoader__.load({
|
|
|
3986
4365
|
* A card is only drawn once its metadata passed a `narrow*` guard, so anything
|
|
3987
4366
|
* else — a running call, a window-truncated call, a Code Dispatch child, a
|
|
3988
4367
|
* failure, a payload from another plugin version — lands here with the call's
|
|
3989
|
-
* own title, run state,
|
|
4368
|
+
* own title, run state, the fact its arguments state, and result text.
|
|
3990
4369
|
* @param props - see {@link GenericCardProps}.
|
|
3991
4370
|
* @returns the generic shell.
|
|
3992
4371
|
*/
|
|
3993
|
-
function GenericCard({ t, title, block, args, inspect }) {
|
|
4372
|
+
function GenericCard({ t, title, block, args, toolName, cwd, openFile, inspect }) {
|
|
3994
4373
|
const state = cardState(block);
|
|
3995
4374
|
const output = resultText(block);
|
|
3996
4375
|
const failure = state === "error" ? firstLine(output) : "";
|
|
4376
|
+
const link = failure === "" ? fallbackCardLink(toolName, args) : null;
|
|
3997
4377
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCardShell, {
|
|
3998
4378
|
t,
|
|
3999
4379
|
state,
|
|
4000
4380
|
title,
|
|
4001
|
-
|
|
4381
|
+
tool: toolName,
|
|
4382
|
+
summary: failure === "" ? fallbackCardSummary(toolName, args, cwd, t, cardArgsRaw(block)) : failure,
|
|
4002
4383
|
summaryTone: failure === "" ? "default" : "error",
|
|
4384
|
+
filePath: link,
|
|
4385
|
+
openFile: link === null ? void 0 : openFile,
|
|
4003
4386
|
text: output,
|
|
4004
4387
|
inspect
|
|
4005
4388
|
});
|
|
@@ -4086,7 +4469,7 @@ window.__ModuleLoader__.load({
|
|
|
4086
4469
|
* @returns the preview card.
|
|
4087
4470
|
*/
|
|
4088
4471
|
function AstEditCard(props) {
|
|
4089
|
-
const { t, block, cwd, inspect, openFile, title } = props;
|
|
4472
|
+
const { t, block, cwd, inspect, openFile, title, toolName } = props;
|
|
4090
4473
|
const args = parseCardArgs(block);
|
|
4091
4474
|
const meta = narrowAstEditCardMeta(cardMeta(block));
|
|
4092
4475
|
if (isSubCall(block) || block.isError === true || meta === null || meta.preview === "") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCard, {
|
|
@@ -4094,17 +4477,22 @@ window.__ModuleLoader__.load({
|
|
|
4094
4477
|
title,
|
|
4095
4478
|
block,
|
|
4096
4479
|
args,
|
|
4480
|
+
toolName,
|
|
4481
|
+
cwd,
|
|
4482
|
+
openFile,
|
|
4097
4483
|
inspect
|
|
4098
4484
|
});
|
|
4099
4485
|
const files = meta.files.length;
|
|
4100
4486
|
const single = files === 1 ? meta.files[0]?.path : void 0;
|
|
4101
4487
|
const output = resultText(block);
|
|
4102
4488
|
const parseErrors = PARSE_ERROR.test(output);
|
|
4489
|
+
const rule = astEditRuleLine(args, t);
|
|
4103
4490
|
const summary = single !== void 0 && single !== "" ? displayPath(single, cwd) : t("astEdit.files", { count: files });
|
|
4104
4491
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCardShell, {
|
|
4105
4492
|
t,
|
|
4106
4493
|
state: cardState(block),
|
|
4107
4494
|
title,
|
|
4495
|
+
tool: toolName,
|
|
4108
4496
|
summary,
|
|
4109
4497
|
summarySuffix: `${t("astEdit.replacements", { count: meta.replacements })} · ${t("astEdit.files", { count: files })}`,
|
|
4110
4498
|
filePath: single,
|
|
@@ -4112,14 +4500,21 @@ window.__ModuleLoader__.load({
|
|
|
4112
4500
|
inspect,
|
|
4113
4501
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4114
4502
|
className: "twc-body",
|
|
4115
|
-
children: [
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4503
|
+
children: [
|
|
4504
|
+
rule !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4505
|
+
className: "twc-note",
|
|
4506
|
+
children: rule
|
|
4507
|
+
}),
|
|
4508
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.CodeBlock, {
|
|
4509
|
+
code: meta.preview,
|
|
4510
|
+
copyLabel: t("copy"),
|
|
4511
|
+
copiedLabel: t("copied")
|
|
4512
|
+
}),
|
|
4513
|
+
output !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4514
|
+
className: "twc-note",
|
|
4515
|
+
children: parseErrors ? `${t("astEdit.parseErrors")}: ${output}` : output
|
|
4516
|
+
})
|
|
4517
|
+
]
|
|
4123
4518
|
})
|
|
4124
4519
|
});
|
|
4125
4520
|
}
|
|
@@ -4160,7 +4555,7 @@ window.__ModuleLoader__.load({
|
|
|
4160
4555
|
* @returns the diff card.
|
|
4161
4556
|
*/
|
|
4162
4557
|
function FileMutationCard(props) {
|
|
4163
|
-
const { t, block, cwd, inspect, openFile, title } = props;
|
|
4558
|
+
const { t, block, cwd, inspect, openFile, title, toolName } = props;
|
|
4164
4559
|
const args = parseCardArgs(block);
|
|
4165
4560
|
const meta = narrowEditCardMeta(cardMeta(block));
|
|
4166
4561
|
const replaceForm = replaceHunks(args);
|
|
@@ -4169,6 +4564,9 @@ window.__ModuleLoader__.load({
|
|
|
4169
4564
|
title,
|
|
4170
4565
|
block,
|
|
4171
4566
|
args,
|
|
4567
|
+
toolName,
|
|
4568
|
+
cwd,
|
|
4569
|
+
openFile,
|
|
4172
4570
|
inspect
|
|
4173
4571
|
});
|
|
4174
4572
|
const diffs = meta === null ? replaceForm ?? [] : meta.diffs;
|
|
@@ -4180,6 +4578,7 @@ window.__ModuleLoader__.load({
|
|
|
4180
4578
|
t,
|
|
4181
4579
|
state: cardState(block),
|
|
4182
4580
|
title,
|
|
4581
|
+
tool: toolName,
|
|
4183
4582
|
summary,
|
|
4184
4583
|
summarySuffix: `+${totals.added} -${totals.removed}`,
|
|
4185
4584
|
filePath: single,
|
|
@@ -4371,7 +4770,7 @@ window.__ModuleLoader__.load({
|
|
|
4371
4770
|
* @returns the read card.
|
|
4372
4771
|
*/
|
|
4373
4772
|
function ReadCard(props) {
|
|
4374
|
-
const { t, block, cwd, inspect, openFile, title } = props;
|
|
4773
|
+
const { t, block, cwd, inspect, openFile, title, toolName } = props;
|
|
4375
4774
|
const args = parseCardArgs(block);
|
|
4376
4775
|
const meta = narrowReadCardMeta(cardMeta(block));
|
|
4377
4776
|
const material = (0, react.useMemo)(() => imageCardMaterial(block, cwd), [block, cwd]);
|
|
@@ -4380,6 +4779,9 @@ window.__ModuleLoader__.load({
|
|
|
4380
4779
|
title,
|
|
4381
4780
|
block,
|
|
4382
4781
|
args,
|
|
4782
|
+
toolName,
|
|
4783
|
+
cwd,
|
|
4784
|
+
openFile,
|
|
4383
4785
|
inspect
|
|
4384
4786
|
});
|
|
4385
4787
|
if (meta === null) {
|
|
@@ -4388,6 +4790,9 @@ window.__ModuleLoader__.load({
|
|
|
4388
4790
|
title,
|
|
4389
4791
|
block,
|
|
4390
4792
|
args,
|
|
4793
|
+
toolName,
|
|
4794
|
+
cwd,
|
|
4795
|
+
openFile,
|
|
4391
4796
|
inspect
|
|
4392
4797
|
});
|
|
4393
4798
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImageReadCard, {
|
|
@@ -4395,6 +4800,7 @@ window.__ModuleLoader__.load({
|
|
|
4395
4800
|
block,
|
|
4396
4801
|
material,
|
|
4397
4802
|
title,
|
|
4803
|
+
toolName,
|
|
4398
4804
|
inspect,
|
|
4399
4805
|
openFile,
|
|
4400
4806
|
loadImage: props.loadImage
|
|
@@ -4404,7 +4810,8 @@ window.__ModuleLoader__.load({
|
|
|
4404
4810
|
t,
|
|
4405
4811
|
state: cardState(block),
|
|
4406
4812
|
title,
|
|
4407
|
-
|
|
4813
|
+
tool: toolName,
|
|
4814
|
+
summary: readCardSummary(args, meta.path, cwd),
|
|
4408
4815
|
filePath: meta.path,
|
|
4409
4816
|
filePathLine: meta.offset,
|
|
4410
4817
|
openFile,
|
|
@@ -4448,13 +4855,14 @@ window.__ModuleLoader__.load({
|
|
|
4448
4855
|
* @param props - see {@link ImageReadCardProps}.
|
|
4449
4856
|
* @returns the image card.
|
|
4450
4857
|
*/
|
|
4451
|
-
function ImageReadCard({ t, block, material, title, inspect, openFile, loadImage }) {
|
|
4858
|
+
function ImageReadCard({ t, block, material, title, toolName, inspect, openFile, loadImage }) {
|
|
4452
4859
|
const images = material.images;
|
|
4453
4860
|
const text = resultText(block);
|
|
4454
4861
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ToolCardShell, {
|
|
4455
4862
|
t,
|
|
4456
4863
|
state: cardState(block),
|
|
4457
4864
|
title,
|
|
4865
|
+
tool: toolName,
|
|
4458
4866
|
summary: material.label,
|
|
4459
4867
|
filePath: material.path,
|
|
4460
4868
|
openFile,
|
|
@@ -4655,6 +5063,45 @@ window.__ModuleLoader__.load({
|
|
|
4655
5063
|
}), document.body);
|
|
4656
5064
|
}
|
|
4657
5065
|
//#endregion
|
|
5066
|
+
//#region src/web/client/rows/search-card-facts.ts
|
|
5067
|
+
/** Separator between the suffix's facts; the shell adds the summary's own. */
|
|
5068
|
+
const FACT_SEPARATOR = " · ";
|
|
5069
|
+
/**
|
|
5070
|
+
* A target may be a `;`-delimited list of paths. Shortening the whole list in
|
|
5071
|
+
* one pass would cut the workspace prefix off its first entry only — the rest
|
|
5072
|
+
* stay absolute, and the same scope then reads as two unrelated places — so
|
|
5073
|
+
* each entry is shortened on its own and the list is rejoined.
|
|
5074
|
+
* @param scope - the call's own target argument.
|
|
5075
|
+
* @param cwd - the session workspace root, when known.
|
|
5076
|
+
* @returns the workspace-shortened target.
|
|
5077
|
+
*/
|
|
5078
|
+
function displayScope(scope, cwd) {
|
|
5079
|
+
if (!scope.includes(";")) return displayPath(scope, cwd);
|
|
5080
|
+
return scope.split(";").map((entry) => displayPath(entry.trim(), cwd)).join("; ");
|
|
5081
|
+
}
|
|
5082
|
+
/**
|
|
5083
|
+
* The search card's collapsed trailing fragment: the scope the call searched,
|
|
5084
|
+
* then what the search found.
|
|
5085
|
+
* @param args - the call's parsed arguments, or `null` when its head is gone.
|
|
5086
|
+
* @param meta - the Host-projected search metadata (already narrowed).
|
|
5087
|
+
* @param cwd - the session workspace root, for shortening an absolute scope.
|
|
5088
|
+
* @param t - the card translate seat.
|
|
5089
|
+
* @returns the joined fragment, or `null` when there is no fact to add.
|
|
5090
|
+
*/
|
|
5091
|
+
function searchCardSuffix(args, meta, cwd, t) {
|
|
5092
|
+
const facts = [];
|
|
5093
|
+
if (meta.shape === "matches") {
|
|
5094
|
+
const scope = argText(args, "path");
|
|
5095
|
+
if (scope !== null) facts.push(t("search.scope", { path: displayScope(scope, cwd) }));
|
|
5096
|
+
}
|
|
5097
|
+
if (meta.total === 0) facts.push(t("search.noResults"));
|
|
5098
|
+
else if (meta.shape === "matches") {
|
|
5099
|
+
const shown = meta.files.reduce((count, file) => count + file.matches.length, 0);
|
|
5100
|
+
facts.push(searchBlockLabels(t).matchesSummary(shown, meta.total, meta.files.length, meta.truncated));
|
|
5101
|
+
} else facts.push(searchBlockLabels(t).pathsSummary(meta.paths.length, meta.total, meta.truncated));
|
|
5102
|
+
return facts.length === 0 ? null : `· ${facts.join(FACT_SEPARATOR)}`;
|
|
5103
|
+
}
|
|
5104
|
+
//#endregion
|
|
4658
5105
|
//#region src/web/client/rows/SearchRow.tsx
|
|
4659
5106
|
/**
|
|
4660
5107
|
* `grep` / `glob` / `ast_grep` row: a search result card.
|
|
@@ -4689,7 +5136,7 @@ window.__ModuleLoader__.load({
|
|
|
4689
5136
|
* @returns the search card.
|
|
4690
5137
|
*/
|
|
4691
5138
|
function SearchCard(props) {
|
|
4692
|
-
const { t, block, inspect, title } = props;
|
|
5139
|
+
const { t, block, cwd, inspect, title, toolName } = props;
|
|
4693
5140
|
const args = parseCardArgs(block);
|
|
4694
5141
|
const meta = narrowSearchCardMeta(cardMeta(block));
|
|
4695
5142
|
if (isSubCall(block) || block.isError === true || meta === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCard, {
|
|
@@ -4697,6 +5144,8 @@ window.__ModuleLoader__.load({
|
|
|
4697
5144
|
title,
|
|
4698
5145
|
block,
|
|
4699
5146
|
args,
|
|
5147
|
+
toolName,
|
|
5148
|
+
cwd,
|
|
4700
5149
|
inspect
|
|
4701
5150
|
});
|
|
4702
5151
|
const subject = argText(args, "pattern") ?? argText(args, "pat") ?? argText(args, "path");
|
|
@@ -4705,7 +5154,9 @@ window.__ModuleLoader__.load({
|
|
|
4705
5154
|
t,
|
|
4706
5155
|
state: cardState(block),
|
|
4707
5156
|
title,
|
|
5157
|
+
tool: toolName,
|
|
4708
5158
|
summary: subject ?? argsSummary(args, t),
|
|
5159
|
+
summarySuffix: searchCardSuffix(args, meta, cwd, t),
|
|
4709
5160
|
inspect,
|
|
4710
5161
|
children: meta.shape === "matches" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SearchBlock, {
|
|
4711
5162
|
kind: "matches",
|
|
@@ -4762,7 +5213,7 @@ window.__ModuleLoader__.load({
|
|
|
4762
5213
|
* @returns the terminal card or the generic shell.
|
|
4763
5214
|
*/
|
|
4764
5215
|
function TerminalCard(props) {
|
|
4765
|
-
const { t, block, cwd: sessionCwd, home, inspect, title } = props;
|
|
5216
|
+
const { t, block, cwd: sessionCwd, home, inspect, title, toolName } = props;
|
|
4766
5217
|
const args = parseCardArgs(block);
|
|
4767
5218
|
const meta = narrowTerminalCardMeta(cardMeta(block));
|
|
4768
5219
|
const command = argText(args, "command");
|
|
@@ -4772,12 +5223,15 @@ window.__ModuleLoader__.load({
|
|
|
4772
5223
|
title,
|
|
4773
5224
|
block,
|
|
4774
5225
|
args,
|
|
5226
|
+
toolName,
|
|
5227
|
+
cwd: sessionCwd,
|
|
4775
5228
|
inspect
|
|
4776
5229
|
});
|
|
4777
5230
|
if (meta !== null && meta.mode === "background") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCardShell, {
|
|
4778
5231
|
t,
|
|
4779
5232
|
state,
|
|
4780
5233
|
title,
|
|
5234
|
+
tool: toolName,
|
|
4781
5235
|
summary: t("bash.background", { jobId: meta.jobId }),
|
|
4782
5236
|
text: resultText(block),
|
|
4783
5237
|
inspect
|
|
@@ -4790,11 +5244,12 @@ window.__ModuleLoader__.load({
|
|
|
4790
5244
|
const interrupted = timedOut || aborted;
|
|
4791
5245
|
const exitCode = running || interrupted ? void 0 : typeof foreground?.exitCode === "number" ? foreground.exitCode : typeof status.exitCode === "number" ? status.exitCode : void 0;
|
|
4792
5246
|
const workdir = argText(args, "workdir") ?? foreground?.workingDir ?? sessionCwd;
|
|
4793
|
-
const summary = interrupted ? timedOut ? t("timedOut") : t("cancelled") :
|
|
5247
|
+
const summary = bashCardSummary(argText(args, "description"), command, interrupted ? timedOut ? t("timedOut") : t("cancelled") : null);
|
|
4794
5248
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCardShell, {
|
|
4795
5249
|
t,
|
|
4796
5250
|
state,
|
|
4797
5251
|
title,
|
|
5252
|
+
tool: toolName,
|
|
4798
5253
|
summary,
|
|
4799
5254
|
inspect,
|
|
4800
5255
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.TerminalBlock, {
|
|
@@ -4846,7 +5301,7 @@ window.__ModuleLoader__.load({
|
|
|
4846
5301
|
* @returns the preview card.
|
|
4847
5302
|
*/
|
|
4848
5303
|
function WriteCard(props) {
|
|
4849
|
-
const { t, block, cwd, inspect, openFile, title } = props;
|
|
5304
|
+
const { t, block, cwd, inspect, openFile, title, toolName } = props;
|
|
4850
5305
|
const args = parseCardArgs(block);
|
|
4851
5306
|
const meta = narrowWriteCardMeta(cardMeta(block));
|
|
4852
5307
|
const content = argText(args, "content");
|
|
@@ -4856,6 +5311,9 @@ window.__ModuleLoader__.load({
|
|
|
4856
5311
|
title,
|
|
4857
5312
|
block,
|
|
4858
5313
|
args,
|
|
5314
|
+
toolName,
|
|
5315
|
+
cwd,
|
|
5316
|
+
openFile,
|
|
4859
5317
|
inspect
|
|
4860
5318
|
});
|
|
4861
5319
|
const lines = t("write.lines", { count: lineCount(content) });
|
|
@@ -4865,6 +5323,7 @@ window.__ModuleLoader__.load({
|
|
|
4865
5323
|
t,
|
|
4866
5324
|
state: cardState(block),
|
|
4867
5325
|
title,
|
|
5326
|
+
tool: toolName,
|
|
4868
5327
|
summary: displayPath(path, cwd),
|
|
4869
5328
|
summarySuffix: lines + skip,
|
|
4870
5329
|
filePath: path,
|