pi-web-ui 0.62.0 → 0.63.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +504 -497
- package/README.zh-CN.md +427 -422
- package/bin/pi-web-ui.mjs +1809 -1881
- package/deploy/com.xingshuyin.pi-web-ui.plist +48 -48
- package/deploy/nginx-subpath.conf +88 -88
- package/deploy/pi-web-ui-task.xml +54 -54
- package/deploy/pi-web-ui.service +31 -31
- package/dist/server/agent-service.js +318 -88
- package/dist/server/attachments.js +37 -28
- package/dist/server/bg-servers.js +6 -1
- package/dist/server/client-state.js +15 -7
- package/dist/server/control-socket.js +1 -5
- package/dist/server/dsh/dsh-agent-service.js +454 -118
- package/dist/server/dsh/dsh-client.js +1 -3
- package/dist/server/dsh/dsh-sessions.js +4 -14
- package/dist/server/dsh/runtime/cordis.yml +1 -1
- package/dist/server/dsh/runtime/goal-rpc.mjs +645 -662
- package/dist/server/dsh/runtime/launcher.mjs +8 -18
- package/dist/server/dsh/runtime/override.patch.yml +9 -9
- package/dist/server/dsh/runtime/runtime-root.mjs +7 -11
- package/dist/server/ensure-bash.js +2 -5
- package/dist/server/files-service.js +42 -36
- package/dist/server/goal-service.js +59 -30
- package/dist/server/index.js +17 -10
- package/dist/server/mcp-bridge.js +11 -5
- package/dist/server/model-admin.js +78 -72
- package/dist/server/plugin-facilities.js +2 -4
- package/dist/server/plugin-updater.js +1 -1
- package/dist/server/plugins.js +26 -8
- package/dist/server/process-utils.js +4 -4
- package/dist/server/protocol-version.js +1 -1
- package/dist/server/scm.js +27 -21
- package/dist/server/serialize.js +1 -3
- package/dist/server/settings-service.js +63 -21
- package/dist/server/slash-commands.js +29 -3
- package/dist/server/subagent-templates.js +249 -0
- package/dist/server/subagents.js +150 -0
- package/dist/server/terminals.js +101 -40
- package/dist/server/text-sniff.js +4 -24
- package/dist/server/update-check.js +2 -4
- package/dist/server/uploads.js +1 -3
- package/dist/server/vision-bridge.js +2 -7
- package/dist/server/wait-subscription-scan.js +102 -21
- package/dist/server/webui-context.js +1 -1
- package/extensions/webui.ts +190 -192
- package/package.json +109 -103
- package/web/dist/assets/TerminalPanel-B9vcI7-_.js +2 -0
- package/web/dist/assets/index-B8Wv29p5.css +10 -0
- package/web/dist/assets/index-CWUWD0rq.js +323 -0
- package/web/dist/favicon.svg +8 -8
- package/web/dist/index.html +15 -15
- package/web/public/favicon.svg +8 -8
- package/web/dist/assets/TerminalPanel-BkCWDsIG.js +0 -2
- package/web/dist/assets/index-BRVKed7W.js +0 -321
- package/web/dist/assets/index-ORTpR6ZQ.css +0 -10
|
@@ -32,15 +32,14 @@ import { BgServerTracker } from "../bg-servers.js";
|
|
|
32
32
|
import { ClientStateStore } from "../client-state.js";
|
|
33
33
|
import { FilesService, workspacePath } from "../files-service.js";
|
|
34
34
|
import { QuiesceRejectedError } from "../agent-service.js";
|
|
35
|
-
import { killPidTree } from "../process-utils.js";
|
|
36
35
|
import { NATIVE_COMMANDS, parseSlash } from "../slash-commands.js";
|
|
37
36
|
import { TerminalManager, loadCommands, saveCommandsFile } from "../terminals.js";
|
|
38
|
-
import { saveUpload
|
|
39
|
-
import { checkAll as checkAllUpdates, collectTargets
|
|
37
|
+
import { saveUpload } from "../uploads.js";
|
|
38
|
+
import { checkAll as checkAllUpdates, collectTargets } from "../update-check.js";
|
|
40
39
|
import { previewKind } from "../text-sniff.js";
|
|
41
|
-
import { DshRuntime,
|
|
40
|
+
import { DshRuntime, loadDeepSeekKey } from "./dsh-client.js";
|
|
42
41
|
import { DshStreamAccumulator, assistantMessageEventToUiMessage, toolResultEventToUiMessage, userMessageEventToUiMessage, } from "./dsh-serialize.js";
|
|
43
|
-
import { firstUserText, findSessionFilesForCwd,
|
|
42
|
+
import { firstUserText, findSessionFilesForCwd, readSessionLog, replayEventsToMessages } from "./dsh-sessions.js";
|
|
44
43
|
const SNAPSHOT_INTERVAL_MS = 60;
|
|
45
44
|
const MAX_OPEN_CONVERSATIONS = 8;
|
|
46
45
|
const DEFAULT_CONV_TITLE = "新对话";
|
|
@@ -67,7 +66,7 @@ export function dshSessionRoot(dataDir) {
|
|
|
67
66
|
return join(dataDir, "dsh-sessions");
|
|
68
67
|
}
|
|
69
68
|
function estimateCost(t) {
|
|
70
|
-
if (
|
|
69
|
+
if (t.input + t.output === 0)
|
|
71
70
|
return 0;
|
|
72
71
|
return (((t.input + (t.cacheWrite ?? 0)) * DSH_PRICE_INPUT +
|
|
73
72
|
(t.cacheRead ?? 0) * DSH_PRICE_CACHE_READ +
|
|
@@ -250,6 +249,7 @@ export class DshClientSession {
|
|
|
250
249
|
type: "notice",
|
|
251
250
|
level: "error",
|
|
252
251
|
text: `DSH 运行时启动失败:${err.message}。请检查 DeepSeek API key(~/.pi/agent/auth.json)与 dsh 依赖安装。`,
|
|
252
|
+
textEn: `DSH runtime failed to start: ${err.message}. Check the DeepSeek API key (~/.pi/agent/auth.json) and dsh dependencies.`,
|
|
253
253
|
});
|
|
254
254
|
});
|
|
255
255
|
// P0-3 convs 内存回收定时器(unref:不阻止进程退出)。
|
|
@@ -303,13 +303,24 @@ export class DshClientSession {
|
|
|
303
303
|
type: "notice",
|
|
304
304
|
level: "error",
|
|
305
305
|
text: "DSH 运行时反复崩溃,已停止自动重启。请检查 DeepSeek API key 与 dsh 依赖安装。",
|
|
306
|
+
textEn: "DSH runtime keeps crashing; auto-restart stopped. Check the DeepSeek API key and dsh dependencies.",
|
|
306
307
|
});
|
|
307
308
|
this.flushSnapshot();
|
|
308
309
|
return;
|
|
309
310
|
}
|
|
310
|
-
this.emit({
|
|
311
|
+
this.emit({
|
|
312
|
+
type: "notice",
|
|
313
|
+
level: "warning",
|
|
314
|
+
text: "DSH 运行时意外退出,正在自动重启…",
|
|
315
|
+
textEn: "DSH runtime exited unexpectedly; restarting…",
|
|
316
|
+
});
|
|
311
317
|
void this.startWithRetry().catch((err) => {
|
|
312
|
-
this.emit({
|
|
318
|
+
this.emit({
|
|
319
|
+
type: "notice",
|
|
320
|
+
level: "error",
|
|
321
|
+
text: `自动重启失败:${err.message}`,
|
|
322
|
+
textEn: `Auto-restart failed: ${err.message}`,
|
|
323
|
+
});
|
|
313
324
|
});
|
|
314
325
|
this.flushSnapshot();
|
|
315
326
|
}
|
|
@@ -327,9 +338,7 @@ export class DshClientSession {
|
|
|
327
338
|
if (conv.terminals.list().length > 0)
|
|
328
339
|
continue;
|
|
329
340
|
const idle = now - conv.lastEventAt;
|
|
330
|
-
const limit = conv.listed
|
|
331
|
-
? DshClientSession.CONV_RECLAIM_LISTED_IDLE_MS
|
|
332
|
-
: DshClientSession.CONV_RECLAIM_IDLE_MS;
|
|
341
|
+
const limit = conv.listed ? DshClientSession.CONV_RECLAIM_LISTED_IDLE_MS : DshClientSession.CONV_RECLAIM_IDLE_MS;
|
|
333
342
|
if (idle > limit) {
|
|
334
343
|
this.removeConversation(id);
|
|
335
344
|
changed = true;
|
|
@@ -435,10 +444,21 @@ export class DshClientSession {
|
|
|
435
444
|
questions: (params0.questions ?? []).map((q) => ({
|
|
436
445
|
id: String(q.id ?? ""),
|
|
437
446
|
question: String(q.question ?? ""),
|
|
438
|
-
...(typeof q.detail === "string"
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
...q.
|
|
447
|
+
...(typeof q.detail === "string"
|
|
448
|
+
? { detail: q.detail }
|
|
449
|
+
: {}),
|
|
450
|
+
...(typeof q.header === "string"
|
|
451
|
+
? { header: q.header }
|
|
452
|
+
: {}),
|
|
453
|
+
...(Array.isArray(q.options)
|
|
454
|
+
? {
|
|
455
|
+
options: q.options.map((o) => ({
|
|
456
|
+
label: String(o.label ?? ""),
|
|
457
|
+
...(typeof o.description === "string" ? { description: o.description } : {}),
|
|
458
|
+
})),
|
|
459
|
+
}
|
|
460
|
+
: {}),
|
|
461
|
+
...(q.multiSelect ? { multiSelect: true } : {}),
|
|
442
462
|
})),
|
|
443
463
|
});
|
|
444
464
|
}
|
|
@@ -458,7 +478,12 @@ export class DshClientSession {
|
|
|
458
478
|
await this.runtime.answerQuestion(id, answers, cancelled);
|
|
459
479
|
}
|
|
460
480
|
catch (err) {
|
|
461
|
-
this.emit({
|
|
481
|
+
this.emit({
|
|
482
|
+
type: "notice",
|
|
483
|
+
level: "error",
|
|
484
|
+
text: `回答失败:${err.message}`,
|
|
485
|
+
textEn: `Answer failed: ${err.message}`,
|
|
486
|
+
});
|
|
462
487
|
}
|
|
463
488
|
}
|
|
464
489
|
// -----------------------------------------------------------------------
|
|
@@ -468,12 +493,17 @@ export class DshClientSession {
|
|
|
468
493
|
/** 桥接的插件工具最小形状(对齐 plugins.ts 的 PluginAgentTool,仅取桥接所需字段)。 */
|
|
469
494
|
bridgedTool(t) {
|
|
470
495
|
const tool = t;
|
|
471
|
-
if (!tool ||
|
|
496
|
+
if (!tool ||
|
|
497
|
+
typeof tool.name !== "string" ||
|
|
498
|
+
typeof tool.description !== "string" ||
|
|
499
|
+
typeof tool.execute !== "function")
|
|
472
500
|
return undefined;
|
|
473
501
|
return {
|
|
474
502
|
name: tool.name,
|
|
475
503
|
description: tool.description,
|
|
476
|
-
...(tool.parameters && typeof tool.parameters === "object"
|
|
504
|
+
...(tool.parameters && typeof tool.parameters === "object"
|
|
505
|
+
? { parameters: tool.parameters }
|
|
506
|
+
: {}),
|
|
477
507
|
execute: tool.execute,
|
|
478
508
|
};
|
|
479
509
|
}
|
|
@@ -654,9 +684,11 @@ export class DshClientSession {
|
|
|
654
684
|
conv.goal.reviewing = true;
|
|
655
685
|
const max = conv.goal.maxRounds || conv.dsGoal?.maxGoalRounds || 0;
|
|
656
686
|
conv.goal.status =
|
|
687
|
+
max > 0 && round >= max ? `已达轮数上限(${round}/${max}),目标未完成` : `目标进行中(第 ${round} 轮)…`;
|
|
688
|
+
conv.goal.statusEn =
|
|
657
689
|
max > 0 && round >= max
|
|
658
|
-
?
|
|
659
|
-
:
|
|
690
|
+
? `Round cap reached (${round}/${max}), goal incomplete`
|
|
691
|
+
: `Goal in progress (round ${round})…`;
|
|
660
692
|
this.emitGoalStatus();
|
|
661
693
|
}
|
|
662
694
|
break;
|
|
@@ -667,8 +699,7 @@ export class DshClientSession {
|
|
|
667
699
|
// 重复文本(DSH 有时重放同一用户消息)→ 去重。
|
|
668
700
|
const text = msg.content.map((c) => ("text" in c ? c.text : "")).join("");
|
|
669
701
|
if (text &&
|
|
670
|
-
conv.messages.some((m) => m.role === "user" &&
|
|
671
|
-
m.content.map((c) => ("text" in c ? c.text : "")).join("") === text)) {
|
|
702
|
+
conv.messages.some((m) => m.role === "user" && m.content.map((c) => ("text" in c ? c.text : "")).join("") === text)) {
|
|
672
703
|
break;
|
|
673
704
|
}
|
|
674
705
|
this.appendMessage(conv, msg);
|
|
@@ -682,8 +713,9 @@ export class DshClientSession {
|
|
|
682
713
|
void this.hydrateImageBlocks(conv, msg, imgRefs);
|
|
683
714
|
}
|
|
684
715
|
if (conv.title === DEFAULT_CONV_TITLE) {
|
|
685
|
-
const t = conv.messages
|
|
686
|
-
|
|
716
|
+
const t = conv.messages
|
|
717
|
+
.find((m) => m.role === "user")
|
|
718
|
+
?.content?.map((c) => ("text" in c ? c.text : ""))
|
|
687
719
|
.join(" ")
|
|
688
720
|
.trim();
|
|
689
721
|
if (t)
|
|
@@ -748,8 +780,7 @@ export class DshClientSession {
|
|
|
748
780
|
// message_delta 实时通道:本机快速完成时 60ms 延迟快照总被
|
|
749
781
|
// assistant/message 抢跑(streaming 从未被捕捉)——delta 直接
|
|
750
782
|
// 走独立通道,保证逐 token 渲染(前端 patch streamingMessage)。
|
|
751
|
-
if (conv.id === this.conv.id &&
|
|
752
|
-
(chunk.type === "text-delta" || chunk.type === "reasoning-delta")) {
|
|
783
|
+
if (conv.id === this.conv.id && (chunk.type === "text-delta" || chunk.type === "reasoning-delta")) {
|
|
753
784
|
this.emit({
|
|
754
785
|
type: "message_delta",
|
|
755
786
|
conversationId: conv.id,
|
|
@@ -810,9 +841,7 @@ export class DshClientSession {
|
|
|
810
841
|
case "agent/inbox/spliced": {
|
|
811
842
|
// 用户 prompt 注入 → 清理 followUp 队列中已消费的文本。
|
|
812
843
|
const inserted = ev.data?.inserted ?? [];
|
|
813
|
-
const texts = inserted
|
|
814
|
-
.map((m) => m.content?.find((c) => c.type === "text")?.text ?? "")
|
|
815
|
-
.filter(Boolean);
|
|
844
|
+
const texts = inserted.map((m) => m.content?.find((c) => c.type === "text")?.text ?? "").filter(Boolean);
|
|
816
845
|
if (texts.length > 0) {
|
|
817
846
|
for (const t of texts) {
|
|
818
847
|
const i = conv.queue.followUp.indexOf(t);
|
|
@@ -900,10 +929,7 @@ export class DshClientSession {
|
|
|
900
929
|
const prev = this.emittedMessages;
|
|
901
930
|
const rev = ++this.snapRev;
|
|
902
931
|
// 增量:同一 conversation 且纯 append 时发 snapshot_delta。
|
|
903
|
-
let incremental = !forceFull &&
|
|
904
|
-
prev !== null &&
|
|
905
|
-
this.emittedRev > 0 &&
|
|
906
|
-
prev.length <= cur.length;
|
|
932
|
+
let incremental = !forceFull && prev !== null && this.emittedRev > 0 && prev.length <= cur.length;
|
|
907
933
|
if (incremental && prev) {
|
|
908
934
|
for (let i = 0; i < prev.length; i++) {
|
|
909
935
|
if (prev[i] !== cur[i]) {
|
|
@@ -958,9 +984,7 @@ export class DshClientSession {
|
|
|
958
984
|
sessionId: conv.sessionId,
|
|
959
985
|
conversationId: this.activeId,
|
|
960
986
|
rev,
|
|
961
|
-
streamingMessage: conv.streaming
|
|
962
|
-
? conv.streaming.toUiMessage(conv.lastEventAt, this.model, "deepseek")
|
|
963
|
-
: null,
|
|
987
|
+
streamingMessage: conv.streaming ? conv.streaming.toUiMessage(conv.lastEventAt, this.model, "deepseek") : null,
|
|
964
988
|
isStreaming: conv.isStreaming,
|
|
965
989
|
model: {
|
|
966
990
|
id: this.model,
|
|
@@ -1001,6 +1025,7 @@ export class DshClientSession {
|
|
|
1001
1025
|
emit(msg) {
|
|
1002
1026
|
if (this.disposed)
|
|
1003
1027
|
return;
|
|
1028
|
+
// eslint-disable-next-line unicorn/no-useless-spread -- snapshot: handlers may unsubscribe mid-emit
|
|
1004
1029
|
for (const sink of [...this.sinks])
|
|
1005
1030
|
sink(msg);
|
|
1006
1031
|
}
|
|
@@ -1046,6 +1071,7 @@ export class DshClientSession {
|
|
|
1046
1071
|
cwd: conv.cwd,
|
|
1047
1072
|
messageCount: conv.messages.length,
|
|
1048
1073
|
isStreaming: conv.isStreaming,
|
|
1074
|
+
isSubagent: false,
|
|
1049
1075
|
});
|
|
1050
1076
|
}
|
|
1051
1077
|
this.emit({ type: "conversations", conversations: list, activeId: this.activeId });
|
|
@@ -1073,6 +1099,7 @@ export class DshClientSession {
|
|
|
1073
1099
|
type: "notice",
|
|
1074
1100
|
level: "warning",
|
|
1075
1101
|
text: `当前项目运行的对话已达上限(${MAX_OPEN_CONVERSATIONS} 个)`,
|
|
1102
|
+
textEn: `This project already has the max open conversations (${MAX_OPEN_CONVERSATIONS}).`,
|
|
1076
1103
|
});
|
|
1077
1104
|
return;
|
|
1078
1105
|
}
|
|
@@ -1109,6 +1136,7 @@ export class DshClientSession {
|
|
|
1109
1136
|
type: "notice",
|
|
1110
1137
|
level: "error",
|
|
1111
1138
|
text: `切换工作区后重启运行时失败:${err.message}`,
|
|
1139
|
+
textEn: `Failed to restart the runtime after switching workspace: ${err.message}`,
|
|
1112
1140
|
});
|
|
1113
1141
|
});
|
|
1114
1142
|
}
|
|
@@ -1180,6 +1208,7 @@ export class DshClientSession {
|
|
|
1180
1208
|
type: "notice",
|
|
1181
1209
|
level: "error",
|
|
1182
1210
|
text: "服务器正在排空存量工作(quiesce),已拒绝新的对话/消息/编辑。存量运行会继续跑完;用 pi-web-ui server unquiesce 可恢复。",
|
|
1211
|
+
textEn: "Server is draining (quiesce); new chats/messages/edits rejected. Existing runs continue; resume with pi-web-ui server unquiesce.",
|
|
1183
1212
|
});
|
|
1184
1213
|
this.flushSnapshot();
|
|
1185
1214
|
return true;
|
|
@@ -1213,7 +1242,7 @@ export class DshClientSession {
|
|
|
1213
1242
|
/* 补图失败保持占位 */
|
|
1214
1243
|
}
|
|
1215
1244
|
}
|
|
1216
|
-
async promptConv(conv, text, attachments,
|
|
1245
|
+
async promptConv(conv, text, attachments, _queue = false) {
|
|
1217
1246
|
try {
|
|
1218
1247
|
if (this.quiesceBlocked())
|
|
1219
1248
|
return;
|
|
@@ -1227,9 +1256,7 @@ export class DshClientSession {
|
|
|
1227
1256
|
content: [
|
|
1228
1257
|
{ type: "text", text },
|
|
1229
1258
|
...(Array.isArray(attachments)
|
|
1230
|
-
? attachments
|
|
1231
|
-
.filter((a) => a.imageData)
|
|
1232
|
-
.map((a) => ({ type: "image", dataUrl: a.imageData }))
|
|
1259
|
+
? attachments.filter((a) => a.imageData).map((a) => ({ type: "image", dataUrl: a.imageData }))
|
|
1233
1260
|
: []),
|
|
1234
1261
|
],
|
|
1235
1262
|
timestamp: Date.now(),
|
|
@@ -1252,6 +1279,7 @@ export class DshClientSession {
|
|
|
1252
1279
|
type: "notice",
|
|
1253
1280
|
level: "error",
|
|
1254
1281
|
text: `提示发送失败:${err.message}`,
|
|
1282
|
+
textEn: `Failed to send prompt: ${err.message}`,
|
|
1255
1283
|
});
|
|
1256
1284
|
}
|
|
1257
1285
|
this.flushSnapshot();
|
|
@@ -1292,19 +1320,27 @@ export class DshClientSession {
|
|
|
1292
1320
|
for (let i = conv.messages.length - 1; i >= 0; i--) {
|
|
1293
1321
|
const m = conv.messages[i];
|
|
1294
1322
|
if (m.role === "user") {
|
|
1295
|
-
lastUser = m.content
|
|
1323
|
+
lastUser = m.content
|
|
1324
|
+
.map((c) => ("text" in c ? c.text : ""))
|
|
1325
|
+
.join("")
|
|
1326
|
+
.trim();
|
|
1296
1327
|
if (lastUser)
|
|
1297
1328
|
break;
|
|
1298
1329
|
}
|
|
1299
1330
|
}
|
|
1300
1331
|
const hist = this.histToContext(conv);
|
|
1301
|
-
|
|
1332
|
+
this.forkConversation(conv);
|
|
1302
1333
|
const text = lastUser
|
|
1303
1334
|
? hist.trim()
|
|
1304
1335
|
? `${lastUser}\n\n(以下为原对话上下文,仅作参考):\n${hist}`
|
|
1305
1336
|
: lastUser
|
|
1306
1337
|
: "请继续";
|
|
1307
|
-
this.emit({
|
|
1338
|
+
this.emit({
|
|
1339
|
+
type: "notice",
|
|
1340
|
+
level: "info",
|
|
1341
|
+
text: "已自动重发(原会话不可续聊)",
|
|
1342
|
+
textEn: "Auto-resent (the original session cannot be resumed)",
|
|
1343
|
+
});
|
|
1308
1344
|
void this.prompt(text);
|
|
1309
1345
|
}
|
|
1310
1346
|
/** 附件 → DSH contentBlocks(v1 简化:文本内联 / 路径引用 / 图片占位)。 */
|
|
@@ -1329,7 +1365,10 @@ export class DshClientSession {
|
|
|
1329
1365
|
blocks.push({ type: "image", attachment: saved.ref });
|
|
1330
1366
|
}
|
|
1331
1367
|
catch (err) {
|
|
1332
|
-
blocks.push({
|
|
1368
|
+
blocks.push({
|
|
1369
|
+
type: "text",
|
|
1370
|
+
text: `\n[图片附件: ${a.name ?? "image"}(保存失败 ${err.message})]`,
|
|
1371
|
+
});
|
|
1333
1372
|
}
|
|
1334
1373
|
}
|
|
1335
1374
|
else if (a.fileData) {
|
|
@@ -1339,7 +1378,10 @@ export class DshClientSession {
|
|
|
1339
1378
|
blocks.push({ type: "text", text: `\n[上传文件: ${saved.abs}]` });
|
|
1340
1379
|
}
|
|
1341
1380
|
catch (err) {
|
|
1342
|
-
blocks.push({
|
|
1381
|
+
blocks.push({
|
|
1382
|
+
type: "text",
|
|
1383
|
+
text: `\n[上传文件: ${a.name ?? "upload"}(落盘失败 ${err.message})]`,
|
|
1384
|
+
});
|
|
1343
1385
|
}
|
|
1344
1386
|
}
|
|
1345
1387
|
else if (resolved) {
|
|
@@ -1431,20 +1473,36 @@ export class DshClientSession {
|
|
|
1431
1473
|
conv.goal.verdict = "pending";
|
|
1432
1474
|
conv.goal.feedback = undefined;
|
|
1433
1475
|
conv.goal.status = "已手动停止,目标已中止";
|
|
1476
|
+
conv.goal.statusEn = "Stopped manually, goal aborted";
|
|
1434
1477
|
this.emitGoalStatus();
|
|
1435
1478
|
}
|
|
1436
|
-
this.emit({
|
|
1479
|
+
this.emit({
|
|
1480
|
+
type: "notice",
|
|
1481
|
+
level: "info",
|
|
1482
|
+
text: "已停止(DSH 中止 = 重启运行时,进行中的其他对话也会停止)",
|
|
1483
|
+
textEn: "Stopped (DSH abort restarts the runtime; other running conversations stop too)",
|
|
1484
|
+
});
|
|
1437
1485
|
try {
|
|
1438
1486
|
await this.runtime.restart(this.model);
|
|
1439
1487
|
}
|
|
1440
1488
|
catch (err) {
|
|
1441
|
-
this.emit({
|
|
1489
|
+
this.emit({
|
|
1490
|
+
type: "notice",
|
|
1491
|
+
level: "error",
|
|
1492
|
+
text: `中止后重启失败:${err.message}`,
|
|
1493
|
+
textEn: `Failed to restart after abort: ${err.message}`,
|
|
1494
|
+
});
|
|
1442
1495
|
}
|
|
1443
1496
|
this.flushSnapshot(true);
|
|
1444
1497
|
}
|
|
1445
1498
|
async abortBash() {
|
|
1446
1499
|
// DSH 无 per-tool 取消;bash 工具由运行时管理,超时策略在运行时侧。
|
|
1447
|
-
this.emit({
|
|
1500
|
+
this.emit({
|
|
1501
|
+
type: "notice",
|
|
1502
|
+
level: "info",
|
|
1503
|
+
text: "DSH 引擎暂不支持单独中止 bash(可整体停止对话)",
|
|
1504
|
+
textEn: "The DSH engine cannot stop bash alone (stop the whole conversation instead)",
|
|
1505
|
+
});
|
|
1448
1506
|
}
|
|
1449
1507
|
// -----------------------------------------------------------------------
|
|
1450
1508
|
// 后台任务
|
|
@@ -1522,16 +1580,56 @@ export class DshClientSession {
|
|
|
1522
1580
|
const { rmSync } = await import("node:fs");
|
|
1523
1581
|
const abs = resolve(path);
|
|
1524
1582
|
if (!abs.startsWith(this.sessionRoot + sep)) {
|
|
1525
|
-
this.emit({
|
|
1583
|
+
this.emit({
|
|
1584
|
+
type: "notice",
|
|
1585
|
+
level: "error",
|
|
1586
|
+
text: "拒绝删除会话目录之外的路径",
|
|
1587
|
+
textEn: "Refusing to delete a path outside the session directory",
|
|
1588
|
+
});
|
|
1526
1589
|
return;
|
|
1527
1590
|
}
|
|
1528
1591
|
rmSync(abs, { recursive: true, force: true });
|
|
1529
1592
|
await this.pushSessions();
|
|
1530
|
-
this.emit({ type: "notice", level: "info", text: "会话已删除" });
|
|
1593
|
+
this.emit({ type: "notice", level: "info", text: "会话已删除", textEn: "Session deleted" });
|
|
1531
1594
|
}
|
|
1532
1595
|
catch (err) {
|
|
1533
|
-
this.emit({
|
|
1596
|
+
this.emit({
|
|
1597
|
+
type: "notice",
|
|
1598
|
+
level: "error",
|
|
1599
|
+
text: `删除失败:${err.message}`,
|
|
1600
|
+
textEn: `Delete failed: ${err.message}`,
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
async dismissConversation(id) {
|
|
1605
|
+
const conv = this.convs.get(id);
|
|
1606
|
+
if (!conv) {
|
|
1607
|
+
this.emit({ type: "notice", level: "warning", text: "该对话不存在或已关闭" });
|
|
1608
|
+
return;
|
|
1609
|
+
}
|
|
1610
|
+
if (id === this.activeId) {
|
|
1611
|
+
this.emit({ type: "notice", level: "warning", text: "当前对话不能直接移出,请先切换到其他对话" });
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
if (!conv.listed) {
|
|
1615
|
+
this.emitConversations();
|
|
1616
|
+
return;
|
|
1617
|
+
}
|
|
1618
|
+
if (conv.isStreaming) {
|
|
1619
|
+
this.emit({
|
|
1620
|
+
type: "notice",
|
|
1621
|
+
level: "warning",
|
|
1622
|
+
text: `对话「${conv.title}」仍在运行中,请先等待结束或停止后再移出`,
|
|
1623
|
+
});
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
if (conv.terminals.list().length > 0) {
|
|
1627
|
+
this.emit({ type: "notice", level: "warning", text: `对话「${conv.title}」还有未关闭的终端` });
|
|
1628
|
+
return;
|
|
1534
1629
|
}
|
|
1630
|
+
this.removeConversation(id);
|
|
1631
|
+
this.emitConversations();
|
|
1632
|
+
this.flushSnapshot();
|
|
1535
1633
|
}
|
|
1536
1634
|
/** 切换会话:读 JSONL 回放 → 新建 conversation(同一 sessionId 续聊)。 */
|
|
1537
1635
|
async switchSession(path) {
|
|
@@ -1555,7 +1653,12 @@ export class DshClientSession {
|
|
|
1555
1653
|
this.flushSnapshot(true);
|
|
1556
1654
|
}
|
|
1557
1655
|
catch (err) {
|
|
1558
|
-
this.emit({
|
|
1656
|
+
this.emit({
|
|
1657
|
+
type: "notice",
|
|
1658
|
+
level: "error",
|
|
1659
|
+
text: `切换会话失败:${err.message}`,
|
|
1660
|
+
textEn: `Failed to switch session: ${err.message}`,
|
|
1661
|
+
});
|
|
1559
1662
|
}
|
|
1560
1663
|
}
|
|
1561
1664
|
// -----------------------------------------------------------------------
|
|
@@ -1572,7 +1675,7 @@ export class DshClientSession {
|
|
|
1572
1675
|
try {
|
|
1573
1676
|
const { readdirSync } = await import("node:fs");
|
|
1574
1677
|
for (const name of readdirSync(dir)) {
|
|
1575
|
-
if (
|
|
1678
|
+
if (!name.startsWith(".") && /\.ya?ml$/iu.test(name)) {
|
|
1576
1679
|
try {
|
|
1577
1680
|
const st = statSync(join(dir, name));
|
|
1578
1681
|
if (st.isFile()) {
|
|
@@ -1597,10 +1700,20 @@ export class DshClientSession {
|
|
|
1597
1700
|
if (this.runtime.alive) {
|
|
1598
1701
|
await this.runtime.restart(this.model);
|
|
1599
1702
|
}
|
|
1600
|
-
this.emit({
|
|
1703
|
+
this.emit({
|
|
1704
|
+
type: "notice",
|
|
1705
|
+
level: "info",
|
|
1706
|
+
text: "已重扫用户 patch 并重启运行时",
|
|
1707
|
+
textEn: "Rescanned user patches and restarted the runtime",
|
|
1708
|
+
});
|
|
1601
1709
|
}
|
|
1602
1710
|
catch (err) {
|
|
1603
|
-
this.emit({
|
|
1711
|
+
this.emit({
|
|
1712
|
+
type: "notice",
|
|
1713
|
+
level: "error",
|
|
1714
|
+
text: `重扫用户 patch 失败:${err.message}`,
|
|
1715
|
+
textEn: `Failed to rescan user patches: ${err.message}`,
|
|
1716
|
+
});
|
|
1604
1717
|
}
|
|
1605
1718
|
await this.listDshPatches();
|
|
1606
1719
|
}
|
|
@@ -1683,7 +1796,6 @@ export class DshClientSession {
|
|
|
1683
1796
|
return;
|
|
1684
1797
|
}
|
|
1685
1798
|
try {
|
|
1686
|
-
const { readFileSync: readSessionFile } = await import("node:fs");
|
|
1687
1799
|
const files = findSessionFilesForCwd(this.sessionRoot, this.cwd);
|
|
1688
1800
|
const results = [];
|
|
1689
1801
|
for (const file of files) {
|
|
@@ -1697,15 +1809,13 @@ export class DshClientSession {
|
|
|
1697
1809
|
const blocks = ev.data?.message?.content ??
|
|
1698
1810
|
ev.data?.content ??
|
|
1699
1811
|
[];
|
|
1700
|
-
let text = blocks
|
|
1701
|
-
.map((b) => (b?.type === "text" ? b.text ?? "" : ""))
|
|
1702
|
-
.join("\n");
|
|
1812
|
+
let text = blocks.map((b) => (b?.type === "text" ? (b.text ?? "") : "")).join("\n");
|
|
1703
1813
|
if (ev.type === "tool/result") {
|
|
1704
1814
|
// 工具输出在嵌套 content[](tool-result 块内),一并纳入索引。
|
|
1705
1815
|
const nested = blocks
|
|
1706
1816
|
.filter((b) => b?.type === "tool-result")
|
|
1707
1817
|
.flatMap((b) => b.content ?? [])
|
|
1708
|
-
.map((b) => (b?.type === "text" ? b.text ?? "" : ""))
|
|
1818
|
+
.map((b) => (b?.type === "text" ? (b.text ?? "") : ""))
|
|
1709
1819
|
.join("\n");
|
|
1710
1820
|
if (nested)
|
|
1711
1821
|
text = `${text}\n${nested}`;
|
|
@@ -1792,8 +1902,7 @@ export class DshClientSession {
|
|
|
1792
1902
|
continue;
|
|
1793
1903
|
seen.add(m.id);
|
|
1794
1904
|
const local = known.get(m.id);
|
|
1795
|
-
const vision = local?.vision ??
|
|
1796
|
-
(m.inputModalities?.includes("image") ?? false);
|
|
1905
|
+
const vision = local?.vision ?? m.inputModalities?.includes("image") ?? false;
|
|
1797
1906
|
if (!local)
|
|
1798
1907
|
this.dynamicModels.add(m.id);
|
|
1799
1908
|
models.push({
|
|
@@ -1823,7 +1932,12 @@ export class DshClientSession {
|
|
|
1823
1932
|
// 校验:本地表 + 运行时动态目录(P2-17)。
|
|
1824
1933
|
const known = DSH_MODELS.some((m) => m.id === modelId) || this.dynamicModels.has(modelId);
|
|
1825
1934
|
if (!known) {
|
|
1826
|
-
this.emit({
|
|
1935
|
+
this.emit({
|
|
1936
|
+
type: "notice",
|
|
1937
|
+
level: "warning",
|
|
1938
|
+
text: `未知模型:${modelId}`,
|
|
1939
|
+
textEn: `Unknown model: ${modelId}`,
|
|
1940
|
+
});
|
|
1827
1941
|
return;
|
|
1828
1942
|
}
|
|
1829
1943
|
if (modelId === this.model)
|
|
@@ -1834,15 +1948,21 @@ export class DshClientSession {
|
|
|
1834
1948
|
type: "notice",
|
|
1835
1949
|
level: "warning",
|
|
1836
1950
|
text: "有对话正在运行,切换模型将中止当前所有运行(DSH 换模型 = 重启运行时)",
|
|
1951
|
+
textEn: "Conversations are running; switching models aborts all of them (DSH model switch restarts the runtime)",
|
|
1837
1952
|
});
|
|
1838
1953
|
}
|
|
1839
1954
|
this.model = modelId;
|
|
1840
1955
|
try {
|
|
1841
1956
|
await this.runtime.restart(modelId);
|
|
1842
|
-
this.emit({ type: "notice", level: "info", text: `已切换到 ${modelId}` });
|
|
1957
|
+
this.emit({ type: "notice", level: "info", text: `已切换到 ${modelId}`, textEn: `Switched to ${modelId}` });
|
|
1843
1958
|
}
|
|
1844
1959
|
catch (err) {
|
|
1845
|
-
this.emit({
|
|
1960
|
+
this.emit({
|
|
1961
|
+
type: "notice",
|
|
1962
|
+
level: "error",
|
|
1963
|
+
text: `切换模型失败:${err.message}`,
|
|
1964
|
+
textEn: `Failed to switch model: ${err.message}`,
|
|
1965
|
+
});
|
|
1846
1966
|
}
|
|
1847
1967
|
this.flushSnapshot(true);
|
|
1848
1968
|
}
|
|
@@ -1853,7 +1973,12 @@ export class DshClientSession {
|
|
|
1853
1973
|
}
|
|
1854
1974
|
setThinking(level) {
|
|
1855
1975
|
if (level !== "high") {
|
|
1856
|
-
this.emit({
|
|
1976
|
+
this.emit({
|
|
1977
|
+
type: "notice",
|
|
1978
|
+
level: "info",
|
|
1979
|
+
text: "DeepSeek V4 仅支持高思考强度",
|
|
1980
|
+
textEn: "DeepSeek V4 only supports high thinking intensity",
|
|
1981
|
+
});
|
|
1857
1982
|
return;
|
|
1858
1983
|
}
|
|
1859
1984
|
this.thinkingLevel = level;
|
|
@@ -1861,7 +1986,12 @@ export class DshClientSession {
|
|
|
1861
1986
|
}
|
|
1862
1987
|
cycleThinking() {
|
|
1863
1988
|
// DSH 固定 high。
|
|
1864
|
-
this.emit({
|
|
1989
|
+
this.emit({
|
|
1990
|
+
type: "notice",
|
|
1991
|
+
level: "info",
|
|
1992
|
+
text: "DeepSeek V4 仅支持高思考强度",
|
|
1993
|
+
textEn: "DeepSeek V4 only supports high thinking intensity",
|
|
1994
|
+
});
|
|
1865
1995
|
}
|
|
1866
1996
|
// -----------------------------------------------------------------------
|
|
1867
1997
|
// 设置 / 系统提示词
|
|
@@ -1892,6 +2022,8 @@ export class DshClientSession {
|
|
|
1892
2022
|
reviewSkills: [],
|
|
1893
2023
|
extensions: [],
|
|
1894
2024
|
presets: this.stateStore.getPresets(this.clientId),
|
|
2025
|
+
subagentTemplates: [],
|
|
2026
|
+
subagentDefaultTemplates: [],
|
|
1895
2027
|
};
|
|
1896
2028
|
this.emit({ type: "settings_state", settings });
|
|
1897
2029
|
}
|
|
@@ -1962,7 +2094,12 @@ export class DshClientSession {
|
|
|
1962
2094
|
}
|
|
1963
2095
|
async reloadExtensions() {
|
|
1964
2096
|
// DSH 引擎无 pi 扩展体系。
|
|
1965
|
-
this.emit({
|
|
2097
|
+
this.emit({
|
|
2098
|
+
type: "notice",
|
|
2099
|
+
level: "info",
|
|
2100
|
+
text: "DSH 引擎不支持 pi 扩展热重载",
|
|
2101
|
+
textEn: "The DSH engine does not support pi extension hot-reload",
|
|
2102
|
+
});
|
|
1966
2103
|
}
|
|
1967
2104
|
async savePreset(name) {
|
|
1968
2105
|
const presets = this.stateStore.getPresets(this.clientId);
|
|
@@ -1982,13 +2119,23 @@ export class DshClientSession {
|
|
|
1982
2119
|
reviewDisabledSkills: [],
|
|
1983
2120
|
};
|
|
1984
2121
|
this.stateStore.savePresets(this.clientId, existing ? presets.map((p) => (p.name === name ? preset : p)) : [...presets, preset]);
|
|
1985
|
-
this.emit({
|
|
2122
|
+
this.emit({
|
|
2123
|
+
type: "notice",
|
|
2124
|
+
level: "info",
|
|
2125
|
+
text: `预设「${name}」已保存`,
|
|
2126
|
+
textEn: `Preset "${name}" saved`,
|
|
2127
|
+
});
|
|
1986
2128
|
this.pushSettings();
|
|
1987
2129
|
}
|
|
1988
2130
|
async applyPreset(name) {
|
|
1989
2131
|
const preset = this.stateStore.getPresets(this.clientId).find((p) => p.name === name);
|
|
1990
2132
|
if (!preset) {
|
|
1991
|
-
this.emit({
|
|
2133
|
+
this.emit({
|
|
2134
|
+
type: "notice",
|
|
2135
|
+
level: "warning",
|
|
2136
|
+
text: `预设「${name}」不存在`,
|
|
2137
|
+
textEn: `Preset "${name}" does not exist`,
|
|
2138
|
+
});
|
|
1992
2139
|
return;
|
|
1993
2140
|
}
|
|
1994
2141
|
this.settings.promptMode = preset.promptMode;
|
|
@@ -2022,6 +2169,25 @@ export class DshClientSession {
|
|
|
2022
2169
|
this.pushSettings();
|
|
2023
2170
|
}
|
|
2024
2171
|
// -----------------------------------------------------------------------
|
|
2172
|
+
// 子代理模板(DSH 引擎无子代理概念 —— 设置面板分区由前端 isDsh 隐藏)
|
|
2173
|
+
// -----------------------------------------------------------------------
|
|
2174
|
+
async saveSubagentTemplate() {
|
|
2175
|
+
this.emit({
|
|
2176
|
+
type: "notice",
|
|
2177
|
+
level: "info",
|
|
2178
|
+
text: "DSH 引擎不支持子代理模板(请在 pi 引擎中使用)",
|
|
2179
|
+
textEn: "Subagent templates are not supported by the DSH engine (use the pi engine instead)",
|
|
2180
|
+
});
|
|
2181
|
+
}
|
|
2182
|
+
async deleteSubagentTemplate() {
|
|
2183
|
+
this.emit({
|
|
2184
|
+
type: "notice",
|
|
2185
|
+
level: "info",
|
|
2186
|
+
text: "DSH 引擎不支持子代理模板(请在 pi 引擎中使用)",
|
|
2187
|
+
textEn: "Subagent templates are not supported by the DSH engine (use the pi engine instead)",
|
|
2188
|
+
});
|
|
2189
|
+
}
|
|
2190
|
+
// -----------------------------------------------------------------------
|
|
2025
2191
|
// 目标(DSH 原生 goal 域:goal/change 事件驱动,round-driver 自动轮次)
|
|
2026
2192
|
// -----------------------------------------------------------------------
|
|
2027
2193
|
emitGoalStatus() {
|
|
@@ -2041,6 +2207,7 @@ export class DshClientSession {
|
|
|
2041
2207
|
g.verdict = "pending";
|
|
2042
2208
|
g.feedback = undefined;
|
|
2043
2209
|
g.status = "";
|
|
2210
|
+
g.statusEn = "";
|
|
2044
2211
|
}
|
|
2045
2212
|
else if (data.goal?.objective) {
|
|
2046
2213
|
conv.dsGoal = {
|
|
@@ -2070,22 +2237,29 @@ export class DshClientSession {
|
|
|
2070
2237
|
max > 0 && rounds >= max
|
|
2071
2238
|
? `已达轮数上限(${rounds}/${max}),目标未完成`
|
|
2072
2239
|
: `目标进行中(第 ${rounds + 1} 轮)…`;
|
|
2240
|
+
g.statusEn =
|
|
2241
|
+
max > 0 && rounds >= max
|
|
2242
|
+
? `Round cap reached (${rounds}/${max}), goal incomplete`
|
|
2243
|
+
: `Goal in progress (round ${rounds + 1})…`;
|
|
2073
2244
|
}
|
|
2074
2245
|
else if (phase === "complete") {
|
|
2075
2246
|
g.reviewing = false;
|
|
2076
2247
|
g.verdict = "pass";
|
|
2077
2248
|
g.status = "✅ 目标已达成";
|
|
2249
|
+
g.statusEn = "✅ Goal achieved";
|
|
2078
2250
|
}
|
|
2079
2251
|
else if (phase === "blocked") {
|
|
2080
2252
|
g.reviewing = false;
|
|
2081
2253
|
g.verdict = "fail";
|
|
2082
2254
|
g.feedback = data.goal.blockedReason ?? "(模型报告受阻)";
|
|
2083
2255
|
g.status = "目标受阻";
|
|
2256
|
+
g.statusEn = "Goal blocked";
|
|
2084
2257
|
}
|
|
2085
2258
|
else if (phase === "paused") {
|
|
2086
2259
|
g.reviewing = false;
|
|
2087
2260
|
g.verdict = "pending";
|
|
2088
2261
|
g.status = "目标已暂停";
|
|
2262
|
+
g.statusEn = "Goal paused";
|
|
2089
2263
|
}
|
|
2090
2264
|
else {
|
|
2091
2265
|
g.reviewing = false;
|
|
@@ -2120,6 +2294,7 @@ export class DshClientSession {
|
|
|
2120
2294
|
g.verdict = "pending";
|
|
2121
2295
|
g.feedback = undefined;
|
|
2122
2296
|
g.status = "目标已设,等待生成…";
|
|
2297
|
+
g.statusEn = "Goal set, waiting to generate…";
|
|
2123
2298
|
this.goalPrefs = { reviewModel: g.reviewModel, maxRounds: g.maxRounds, locked: g.locked };
|
|
2124
2299
|
this.stateStore.saveGoalPrefs(this.clientId, {
|
|
2125
2300
|
reviewModel: g.reviewModel,
|
|
@@ -2127,7 +2302,12 @@ export class DshClientSession {
|
|
|
2127
2302
|
locked: g.locked,
|
|
2128
2303
|
});
|
|
2129
2304
|
this.emitGoalStatus();
|
|
2130
|
-
this.emit({
|
|
2305
|
+
this.emit({
|
|
2306
|
+
type: "notice",
|
|
2307
|
+
level: "info",
|
|
2308
|
+
text: `🎯 已设目标:${text.slice(0, 80)}${text.length > 80 ? "…" : ""}`,
|
|
2309
|
+
textEn: `🎯 Goal set: ${text.slice(0, 80)}${text.length > 80 ? "…" : ""}`,
|
|
2310
|
+
});
|
|
2131
2311
|
try {
|
|
2132
2312
|
// goal/set 创建 + arm;round-driver 在 agent idle 时自动续轮,无需额外 prompt。
|
|
2133
2313
|
const res = (await this.runtime.goalSet(conv.sessionId, text, effectiveMax > 0 ? effectiveMax : undefined));
|
|
@@ -2137,7 +2317,13 @@ export class DshClientSession {
|
|
|
2137
2317
|
catch (err) {
|
|
2138
2318
|
g.reviewing = false;
|
|
2139
2319
|
g.status = `目标设置失败:${err.message}`;
|
|
2140
|
-
|
|
2320
|
+
g.statusEn = `Goal setup failed: ${err.message}`;
|
|
2321
|
+
this.emit({
|
|
2322
|
+
type: "notice",
|
|
2323
|
+
level: "error",
|
|
2324
|
+
text: `目标设置失败:${err.message}`,
|
|
2325
|
+
textEn: `Goal setup failed: ${err.message}`,
|
|
2326
|
+
});
|
|
2141
2327
|
}
|
|
2142
2328
|
this.emitGoalStatus();
|
|
2143
2329
|
this.flushSnapshot();
|
|
@@ -2166,6 +2352,7 @@ export class DshClientSession {
|
|
|
2166
2352
|
g.verdict = "pending";
|
|
2167
2353
|
g.feedback = undefined;
|
|
2168
2354
|
g.status = "";
|
|
2355
|
+
g.statusEn = "";
|
|
2169
2356
|
this.emitGoalStatus();
|
|
2170
2357
|
this.flushSnapshot();
|
|
2171
2358
|
}
|
|
@@ -2180,7 +2367,12 @@ export class DshClientSession {
|
|
|
2180
2367
|
return;
|
|
2181
2368
|
const g = conv.goal;
|
|
2182
2369
|
if (g.goal || g.reviewing || g.wizard.active) {
|
|
2183
|
-
this.emit({
|
|
2370
|
+
this.emit({
|
|
2371
|
+
type: "notice",
|
|
2372
|
+
level: "warning",
|
|
2373
|
+
text: "已有目标或调研进行中,请先完成或清除",
|
|
2374
|
+
textEn: "A goal or survey is already active — finish or clear it first",
|
|
2375
|
+
});
|
|
2184
2376
|
return;
|
|
2185
2377
|
}
|
|
2186
2378
|
g.wizard.active = true;
|
|
@@ -2189,12 +2381,15 @@ export class DshClientSession {
|
|
|
2189
2381
|
g.wizard.step = 0;
|
|
2190
2382
|
g.wizard.maxSteps = 6;
|
|
2191
2383
|
g.wizard.status = "调研中…";
|
|
2384
|
+
g.wizard.statusEn = "Scoping…";
|
|
2192
2385
|
g.status = "目标调研中…";
|
|
2386
|
+
g.statusEn = "Scoping the goal…";
|
|
2193
2387
|
this.emitGoalStatus();
|
|
2194
2388
|
this.emit({
|
|
2195
2389
|
type: "notice",
|
|
2196
2390
|
level: "info",
|
|
2197
2391
|
text: `🔍 正在围绕需求展开调研:${draft.slice(0, 60)}${draft.length > 60 ? "…" : ""}`,
|
|
2392
|
+
textEn: `🔍 Surveying the requirement: ${draft.slice(0, 60)}${draft.length > 60 ? "…" : ""}`,
|
|
2198
2393
|
});
|
|
2199
2394
|
try {
|
|
2200
2395
|
const waiter = new Promise((resolve, reject) => {
|
|
@@ -2215,26 +2410,42 @@ export class DshClientSession {
|
|
|
2215
2410
|
await waiter;
|
|
2216
2411
|
}
|
|
2217
2412
|
catch (err) {
|
|
2218
|
-
this.emit({
|
|
2413
|
+
this.emit({
|
|
2414
|
+
type: "notice",
|
|
2415
|
+
level: "warning",
|
|
2416
|
+
text: `目标调研中断:${err.message}`,
|
|
2417
|
+
textEn: `Goal survey interrupted: ${err.message}`,
|
|
2418
|
+
});
|
|
2219
2419
|
}
|
|
2220
2420
|
conv.turnWaiter = undefined;
|
|
2221
2421
|
g.wizard.active = false;
|
|
2222
2422
|
g.wizard.step = 0;
|
|
2223
2423
|
g.wizard.status = "";
|
|
2424
|
+
g.wizard.statusEn = "";
|
|
2224
2425
|
// 解析模型最终输出中的 GOAL: 行。
|
|
2225
2426
|
const finalText = this.lastAssistantText(conv);
|
|
2226
2427
|
const goalMatch = finalText.match(/GOAL\s*[::]\s*([\s\S]*)/i);
|
|
2227
2428
|
let refined = goalMatch ? goalMatch[1].trim() : "";
|
|
2228
2429
|
if (!refined) {
|
|
2229
2430
|
// 未按格式:取最后一段非空文本(截断防污染)。
|
|
2230
|
-
refined =
|
|
2431
|
+
refined =
|
|
2432
|
+
finalText
|
|
2433
|
+
.split("\n")
|
|
2434
|
+
.filter((l) => l.trim())
|
|
2435
|
+
.pop()
|
|
2436
|
+
?.trim() ?? "";
|
|
2231
2437
|
if (refined.length > 300)
|
|
2232
2438
|
refined = refined.slice(0, 300);
|
|
2233
2439
|
}
|
|
2234
2440
|
if (!refined && g.goal) {
|
|
2235
2441
|
// 模型可能直接用了 create_goal —— 目标已在运行时,镜像它即可。
|
|
2236
2442
|
this.emitGoalStatus();
|
|
2237
|
-
this.emit({
|
|
2443
|
+
this.emit({
|
|
2444
|
+
type: "notice",
|
|
2445
|
+
level: "info",
|
|
2446
|
+
text: "🎯 调研完成,目标已由模型创建",
|
|
2447
|
+
textEn: "🎯 Survey done; the model created the goal",
|
|
2448
|
+
});
|
|
2238
2449
|
return;
|
|
2239
2450
|
}
|
|
2240
2451
|
this.emitGoalStatus();
|
|
@@ -2248,10 +2459,16 @@ export class DshClientSession {
|
|
|
2248
2459
|
type: "notice",
|
|
2249
2460
|
level: "info",
|
|
2250
2461
|
text: `🎯 调研完成,目标已设为:${refined.slice(0, 80)}${refined.length > 80 ? "…" : ""}`,
|
|
2462
|
+
textEn: `🎯 Survey done, goal set: ${refined.slice(0, 80)}${refined.length > 80 ? "…" : ""}`,
|
|
2251
2463
|
});
|
|
2252
2464
|
}
|
|
2253
2465
|
else {
|
|
2254
|
-
this.emit({
|
|
2466
|
+
this.emit({
|
|
2467
|
+
type: "notice",
|
|
2468
|
+
level: "warning",
|
|
2469
|
+
text: "调研未产出有效目标,请重试",
|
|
2470
|
+
textEn: "The survey produced no usable goal — retry",
|
|
2471
|
+
});
|
|
2255
2472
|
}
|
|
2256
2473
|
}
|
|
2257
2474
|
/** 调研向导指令(主会话 prompt):先提问收敛,最后只输出 GOAL: 行。 */
|
|
@@ -2304,10 +2521,15 @@ export class DshClientSession {
|
|
|
2304
2521
|
async saveCommands(commands) {
|
|
2305
2522
|
const { path, error } = await saveCommandsFile(this.cwd, commands);
|
|
2306
2523
|
if (error) {
|
|
2307
|
-
this.emit({
|
|
2524
|
+
this.emit({
|
|
2525
|
+
type: "notice",
|
|
2526
|
+
level: "error",
|
|
2527
|
+
text: `保存命令失败:${error}`,
|
|
2528
|
+
textEn: `Failed to save command: ${error}`,
|
|
2529
|
+
});
|
|
2308
2530
|
}
|
|
2309
2531
|
else {
|
|
2310
|
-
this.emit({ type: "notice", level: "info", text: `命令已保存(${path}
|
|
2532
|
+
this.emit({ type: "notice", level: "info", text: `命令已保存(${path})`, textEn: `Command saved (${path})` });
|
|
2311
2533
|
}
|
|
2312
2534
|
}
|
|
2313
2535
|
// -----------------------------------------------------------------------
|
|
@@ -2343,7 +2565,12 @@ export class DshClientSession {
|
|
|
2343
2565
|
return true;
|
|
2344
2566
|
case "model": {
|
|
2345
2567
|
if (!args.trim()) {
|
|
2346
|
-
this.emit({
|
|
2568
|
+
this.emit({
|
|
2569
|
+
type: "notice",
|
|
2570
|
+
level: "info",
|
|
2571
|
+
text: `当前模型:${this.model}。用法:/model <名称>`,
|
|
2572
|
+
textEn: `Current model: ${this.model}. Usage: /model <name>`,
|
|
2573
|
+
});
|
|
2347
2574
|
return true;
|
|
2348
2575
|
}
|
|
2349
2576
|
const q = args.trim().toLowerCase();
|
|
@@ -2357,13 +2584,19 @@ export class DshClientSession {
|
|
|
2357
2584
|
type: "notice",
|
|
2358
2585
|
level: "error",
|
|
2359
2586
|
text: `没有匹配到模型:${args.trim()}(可用模型见顶栏模型列表)`,
|
|
2587
|
+
textEn: `No matching model: ${args.trim()} (see the model list in the top bar)`,
|
|
2360
2588
|
});
|
|
2361
2589
|
}
|
|
2362
2590
|
return true;
|
|
2363
2591
|
}
|
|
2364
2592
|
case "cwd": {
|
|
2365
2593
|
if (!args.trim()) {
|
|
2366
|
-
this.emit({
|
|
2594
|
+
this.emit({
|
|
2595
|
+
type: "notice",
|
|
2596
|
+
level: "info",
|
|
2597
|
+
text: `当前工作目录:${this.cwd}`,
|
|
2598
|
+
textEn: `Current directory: ${this.cwd}`,
|
|
2599
|
+
});
|
|
2367
2600
|
return true;
|
|
2368
2601
|
}
|
|
2369
2602
|
await this.setCwd(args.trim());
|
|
@@ -2377,14 +2610,29 @@ export class DshClientSession {
|
|
|
2377
2610
|
case "copy":
|
|
2378
2611
|
return true; // 前端本地实现
|
|
2379
2612
|
case "reload":
|
|
2380
|
-
this.emit({
|
|
2613
|
+
this.emit({
|
|
2614
|
+
type: "notice",
|
|
2615
|
+
level: "info",
|
|
2616
|
+
text: "已重新加载(DSH 引擎无扩展/技能热重载,运行时能力内置)",
|
|
2617
|
+
textEn: "Reloaded (the DSH engine has no extension/skill hot-reload; runtime capabilities are built in)",
|
|
2618
|
+
});
|
|
2381
2619
|
await this.pushSlashCommands();
|
|
2382
2620
|
return true;
|
|
2383
2621
|
case "compact":
|
|
2384
|
-
this.emit({
|
|
2622
|
+
this.emit({
|
|
2623
|
+
type: "notice",
|
|
2624
|
+
level: "info",
|
|
2625
|
+
text: "DSH 引擎不支持上下文压缩(运行时自动管理)",
|
|
2626
|
+
textEn: "The DSH engine does not support context compaction (the runtime manages it)",
|
|
2627
|
+
});
|
|
2385
2628
|
return true;
|
|
2386
2629
|
case "thinking":
|
|
2387
|
-
this.emit({
|
|
2630
|
+
this.emit({
|
|
2631
|
+
type: "notice",
|
|
2632
|
+
level: "info",
|
|
2633
|
+
text: "DeepSeek V4 仅支持高思考强度",
|
|
2634
|
+
textEn: "DeepSeek V4 only supports high thinking intensity",
|
|
2635
|
+
});
|
|
2388
2636
|
return true;
|
|
2389
2637
|
case "pi-web-ui:quit":
|
|
2390
2638
|
this.onQuit?.();
|
|
@@ -2404,6 +2652,7 @@ export class DshClientSession {
|
|
|
2404
2652
|
type: "notice",
|
|
2405
2653
|
level: "error",
|
|
2406
2654
|
text: `插件命令 /${name} 执行失败:${err.message}`,
|
|
2655
|
+
textEn: `Plugin command /${name} failed: ${err.message}`,
|
|
2407
2656
|
});
|
|
2408
2657
|
}
|
|
2409
2658
|
return true;
|
|
@@ -2424,7 +2673,9 @@ export class DshClientSession {
|
|
|
2424
2673
|
}
|
|
2425
2674
|
async checkUpdate() {
|
|
2426
2675
|
try {
|
|
2427
|
-
const latest = await checkAllUpdates([
|
|
2676
|
+
const latest = await checkAllUpdates([
|
|
2677
|
+
{ name: "pi-web-ui", version: DshClientSession.currentAppVersion(), kind: "webui" },
|
|
2678
|
+
]);
|
|
2428
2679
|
const item = latest[0];
|
|
2429
2680
|
this.emit({
|
|
2430
2681
|
type: "update_status",
|
|
@@ -2436,7 +2687,14 @@ export class DshClientSession {
|
|
|
2436
2687
|
});
|
|
2437
2688
|
}
|
|
2438
2689
|
catch (err) {
|
|
2439
|
-
this.emit({
|
|
2690
|
+
this.emit({
|
|
2691
|
+
type: "update_status",
|
|
2692
|
+
current: DshClientSession.currentAppVersion(),
|
|
2693
|
+
latest: null,
|
|
2694
|
+
latestPublishedAt: null,
|
|
2695
|
+
upToDate: true,
|
|
2696
|
+
error: err.message,
|
|
2697
|
+
});
|
|
2440
2698
|
}
|
|
2441
2699
|
}
|
|
2442
2700
|
async checkUpdatesAll(force = false) {
|
|
@@ -2460,7 +2718,7 @@ export class DshClientSession {
|
|
|
2460
2718
|
})),
|
|
2461
2719
|
});
|
|
2462
2720
|
}
|
|
2463
|
-
catch
|
|
2721
|
+
catch {
|
|
2464
2722
|
this.emit({ type: "update_status_all", items: [] });
|
|
2465
2723
|
}
|
|
2466
2724
|
}
|
|
@@ -2473,7 +2731,7 @@ export class DshClientSession {
|
|
|
2473
2731
|
async setProviderApiKey(provider, apiKey) {
|
|
2474
2732
|
const key = apiKey.trim();
|
|
2475
2733
|
if (!key) {
|
|
2476
|
-
this.emit({ type: "notice", level: "error", text: "请填写 API 密钥" });
|
|
2734
|
+
this.emit({ type: "notice", level: "error", text: "请填写 API 密钥", textEn: "Enter an API key" });
|
|
2477
2735
|
return;
|
|
2478
2736
|
}
|
|
2479
2737
|
try {
|
|
@@ -2489,13 +2747,23 @@ export class DshClientSession {
|
|
|
2489
2747
|
auth[provider.trim()] = { type: "api_key", key };
|
|
2490
2748
|
mkdirSync(dirname(authPath), { recursive: true });
|
|
2491
2749
|
writeFileSync(authPath, JSON.stringify(auth, null, 2) + "\n");
|
|
2492
|
-
this.emit({
|
|
2750
|
+
this.emit({
|
|
2751
|
+
type: "notice",
|
|
2752
|
+
level: "info",
|
|
2753
|
+
text: `✅ 已保存 ${provider.trim()} 的 API 密钥`,
|
|
2754
|
+
textEn: `Saved API key`,
|
|
2755
|
+
});
|
|
2493
2756
|
if (this.runtime.alive)
|
|
2494
2757
|
await this.runtime.restart(this.model);
|
|
2495
2758
|
this.flushSnapshot();
|
|
2496
2759
|
}
|
|
2497
2760
|
catch (err) {
|
|
2498
|
-
this.emit({
|
|
2761
|
+
this.emit({
|
|
2762
|
+
type: "notice",
|
|
2763
|
+
level: "error",
|
|
2764
|
+
text: `保存 key 失败:${err.message}`,
|
|
2765
|
+
textEn: `Failed to save key: ${err.message}`,
|
|
2766
|
+
});
|
|
2499
2767
|
}
|
|
2500
2768
|
}
|
|
2501
2769
|
async clearProviderApiKey(provider) {
|
|
@@ -2504,28 +2772,48 @@ export class DshClientSession {
|
|
|
2504
2772
|
const authPath = join(this.agentDir, "auth.json");
|
|
2505
2773
|
const auth = JSON.parse(readFileSync(authPath, "utf8"));
|
|
2506
2774
|
if (!(pid in auth)) {
|
|
2507
|
-
this.emit({ type: "notice", level: "info", text: `${pid}
|
|
2775
|
+
this.emit({ type: "notice", level: "info", text: `${pid} 没有已保存的密钥`, textEn: `No saved key` });
|
|
2508
2776
|
return;
|
|
2509
2777
|
}
|
|
2510
2778
|
delete auth[pid];
|
|
2511
2779
|
writeFileSync(authPath, JSON.stringify(auth, null, 2) + "\n");
|
|
2512
|
-
this.emit({
|
|
2780
|
+
this.emit({
|
|
2781
|
+
type: "notice",
|
|
2782
|
+
level: "info",
|
|
2783
|
+
text: `🗑 已清除 ${pid} 的密钥,该服务商回到未配置状态`,
|
|
2784
|
+
textEn: "Cleared",
|
|
2785
|
+
});
|
|
2513
2786
|
if (this.runtime.alive && pid === "deepseek")
|
|
2514
2787
|
await this.runtime.restart(this.model);
|
|
2515
2788
|
this.flushSnapshot();
|
|
2516
2789
|
}
|
|
2517
2790
|
catch (err) {
|
|
2518
|
-
this.emit({
|
|
2791
|
+
this.emit({
|
|
2792
|
+
type: "notice",
|
|
2793
|
+
level: "error",
|
|
2794
|
+
text: `清除失败:${err.message}`,
|
|
2795
|
+
textEn: `Clear failed: ${err.message}`,
|
|
2796
|
+
});
|
|
2519
2797
|
}
|
|
2520
2798
|
}
|
|
2521
2799
|
async listModelsConfig() {
|
|
2522
2800
|
this.emit({ type: "models_config", providers: [] });
|
|
2523
2801
|
}
|
|
2524
|
-
async saveModelConfig(
|
|
2525
|
-
this.emit({
|
|
2802
|
+
async saveModelConfig(_providerId, _config) {
|
|
2803
|
+
this.emit({
|
|
2804
|
+
type: "notice",
|
|
2805
|
+
level: "warning",
|
|
2806
|
+
text: "DSH 引擎使用内置 DeepSeek 模型,不支持自定义模型配置",
|
|
2807
|
+
textEn: "The DSH engine uses the built-in DeepSeek model",
|
|
2808
|
+
});
|
|
2526
2809
|
}
|
|
2527
|
-
async deleteModelConfig(
|
|
2528
|
-
this.emit({
|
|
2810
|
+
async deleteModelConfig(_providerId) {
|
|
2811
|
+
this.emit({
|
|
2812
|
+
type: "notice",
|
|
2813
|
+
level: "warning",
|
|
2814
|
+
text: "DSH 引擎不支持自定义模型配置",
|
|
2815
|
+
textEn: "The DSH engine does not support custom model configs",
|
|
2816
|
+
});
|
|
2529
2817
|
}
|
|
2530
2818
|
async listProviders() {
|
|
2531
2819
|
this.emit({
|
|
@@ -2543,22 +2831,37 @@ export class DshClientSession {
|
|
|
2543
2831
|
listProviderKeys() {
|
|
2544
2832
|
this.emit({ type: "provider_keys", keys: {} });
|
|
2545
2833
|
}
|
|
2546
|
-
async addProviderKey(
|
|
2547
|
-
this.emit({
|
|
2834
|
+
async addProviderKey(_provider, _apiKey, _name) {
|
|
2835
|
+
this.emit({
|
|
2836
|
+
type: "notice",
|
|
2837
|
+
level: "warning",
|
|
2838
|
+
text: "DSH 引擎不支持多密钥",
|
|
2839
|
+
textEn: "The DSH engine does not support multiple keys",
|
|
2840
|
+
});
|
|
2548
2841
|
}
|
|
2549
|
-
async activateProviderKey(
|
|
2550
|
-
this.emit({
|
|
2842
|
+
async activateProviderKey(_provider, _keyName) {
|
|
2843
|
+
this.emit({
|
|
2844
|
+
type: "notice",
|
|
2845
|
+
level: "warning",
|
|
2846
|
+
text: "DSH 引擎不支持多密钥",
|
|
2847
|
+
textEn: "The DSH engine does not support multiple keys",
|
|
2848
|
+
});
|
|
2551
2849
|
}
|
|
2552
|
-
async removeProviderKey(
|
|
2553
|
-
this.emit({
|
|
2850
|
+
async removeProviderKey(_provider, _keyName) {
|
|
2851
|
+
this.emit({
|
|
2852
|
+
type: "notice",
|
|
2853
|
+
level: "warning",
|
|
2854
|
+
text: "DSH 引擎不支持多密钥",
|
|
2855
|
+
textEn: "The DSH engine does not support multiple keys",
|
|
2856
|
+
});
|
|
2554
2857
|
}
|
|
2555
|
-
async fetchModelsList(reqId,
|
|
2858
|
+
async fetchModelsList(reqId, _baseUrl, _apiKey, _authHeader, _api) {
|
|
2556
2859
|
this.emit({ type: "fetch_models_result", reqId, ok: false, error: "DSH 引擎不支持自定义 provider 探测" });
|
|
2557
2860
|
}
|
|
2558
|
-
async refreshProviderModels(
|
|
2861
|
+
async refreshProviderModels(_providerId, reqId) {
|
|
2559
2862
|
this.emit({ type: "refresh_provider_result", reqId, ok: false, error: "DSH 引擎不支持自定义 provider" });
|
|
2560
2863
|
}
|
|
2561
|
-
async cloneProvider(
|
|
2864
|
+
async cloneProvider(_provider, reqId) {
|
|
2562
2865
|
const error = "DSH 引擎不支持自定义 provider";
|
|
2563
2866
|
this.emit({ type: "notice", level: "error", text: error });
|
|
2564
2867
|
this.emit({ type: "clone_provider_result", reqId, ok: false, error });
|
|
@@ -2566,7 +2869,7 @@ export class DshClientSession {
|
|
|
2566
2869
|
// -----------------------------------------------------------------------
|
|
2567
2870
|
// 其他
|
|
2568
2871
|
// -----------------------------------------------------------------------
|
|
2569
|
-
resolveDialog(
|
|
2872
|
+
resolveDialog(_id, _value) {
|
|
2570
2873
|
// DSH 引擎无扩展 UI 桥(dialog 由插件宿主走,v1 忽略)。
|
|
2571
2874
|
}
|
|
2572
2875
|
async editMessage(messageId, text, attachments) {
|
|
@@ -2575,7 +2878,12 @@ export class DshClientSession {
|
|
|
2575
2878
|
const conv = this.conv;
|
|
2576
2879
|
const idx = conv.messages.findIndex((m) => m.id === messageId);
|
|
2577
2880
|
if (idx < 0) {
|
|
2578
|
-
this.emit({
|
|
2881
|
+
this.emit({
|
|
2882
|
+
type: "notice",
|
|
2883
|
+
level: "warning",
|
|
2884
|
+
text: "找不到要编辑的消息",
|
|
2885
|
+
textEn: "Message to edit not found",
|
|
2886
|
+
});
|
|
2579
2887
|
return;
|
|
2580
2888
|
}
|
|
2581
2889
|
// 截断到编辑点之前的所有消息 + 用编辑后的文本 prompt。
|
|
@@ -2603,14 +2911,24 @@ export class DshClientSession {
|
|
|
2603
2911
|
this.flushSnapshot(true);
|
|
2604
2912
|
}
|
|
2605
2913
|
catch (err) {
|
|
2606
|
-
this.emit({
|
|
2914
|
+
this.emit({
|
|
2915
|
+
type: "notice",
|
|
2916
|
+
level: "error",
|
|
2917
|
+
text: `编辑重问失败:${err.message}`,
|
|
2918
|
+
textEn: `Edit-and-reask failed: ${err.message}`,
|
|
2919
|
+
});
|
|
2607
2920
|
}
|
|
2608
2921
|
}
|
|
2609
2922
|
async setCwd(newCwd) {
|
|
2610
2923
|
try {
|
|
2611
2924
|
const abs = resolve(newCwd);
|
|
2612
2925
|
if (!existsSync(abs) || !statSync(abs).isDirectory()) {
|
|
2613
|
-
this.emit({
|
|
2926
|
+
this.emit({
|
|
2927
|
+
type: "notice",
|
|
2928
|
+
level: "error",
|
|
2929
|
+
text: `切换工作目录失败:目录不存在:${newCwd}`,
|
|
2930
|
+
textEn: `Failed to switch directory; does not exist: ${newCwd}`,
|
|
2931
|
+
});
|
|
2614
2932
|
return;
|
|
2615
2933
|
}
|
|
2616
2934
|
if (abs === this.cwd)
|
|
@@ -2622,20 +2940,23 @@ export class DshClientSession {
|
|
|
2622
2940
|
await this.runtime.restart(this.model);
|
|
2623
2941
|
}
|
|
2624
2942
|
catch (err) {
|
|
2625
|
-
this.emit({
|
|
2943
|
+
this.emit({
|
|
2944
|
+
type: "notice",
|
|
2945
|
+
level: "error",
|
|
2946
|
+
text: `切换工作区后重启运行时失败:${err.message}`,
|
|
2947
|
+
textEn: `Failed to restart the runtime after switching workspace: ${err.message}`,
|
|
2948
|
+
});
|
|
2626
2949
|
}
|
|
2627
2950
|
// 旧 active 会话:活跃的(流式/有终端/跑过)标 listed → 后台运行可见
|
|
2628
2951
|
// (与 pi 的 displaceActive 语义一致);空白的直接弃(不列)。
|
|
2629
2952
|
const prev = this.conv;
|
|
2630
2953
|
prev.listed =
|
|
2631
|
-
prev.isStreaming ||
|
|
2632
|
-
prev.terminals.list().length > 0 ||
|
|
2633
|
-
prev.promptedSinceActive ||
|
|
2634
|
-
prev.messages.length > 0;
|
|
2954
|
+
prev.isStreaming || prev.terminals.list().length > 0 || prev.promptedSinceActive || prev.messages.length > 0;
|
|
2635
2955
|
// 新项目 → 新会话。
|
|
2636
2956
|
this.activeId = this.addConversation(`web-${randomUUID().slice(0, 12)}`, abs, false).id;
|
|
2637
2957
|
// 旧项目非活跃 conversation 回收(pi 的 displaceActive 语义:切走后
|
|
2638
2958
|
// 非 streaming / 无终端 / 未列出的旧会话从内存移除,磁盘 JSONL 可回放恢复)。
|
|
2959
|
+
// eslint-disable-next-line unicorn/no-useless-spread -- snapshot: handlers may unsubscribe mid-emit
|
|
2639
2960
|
for (const [id, c] of [...this.convs]) {
|
|
2640
2961
|
if (id === this.activeId)
|
|
2641
2962
|
continue;
|
|
@@ -2650,7 +2971,12 @@ export class DshClientSession {
|
|
|
2650
2971
|
this.removeConversation(id);
|
|
2651
2972
|
}
|
|
2652
2973
|
this.onCwdChanged?.(abs);
|
|
2653
|
-
this.emit({
|
|
2974
|
+
this.emit({
|
|
2975
|
+
type: "notice",
|
|
2976
|
+
level: "info",
|
|
2977
|
+
text: `已切换到工作目录:${abs}`,
|
|
2978
|
+
textEn: `Switched to directory: ${abs}`,
|
|
2979
|
+
});
|
|
2654
2980
|
this.emitConversations();
|
|
2655
2981
|
void this.pushSessions();
|
|
2656
2982
|
void this.pushProjects();
|
|
@@ -2660,7 +2986,12 @@ export class DshClientSession {
|
|
|
2660
2986
|
this.flushSnapshot(true);
|
|
2661
2987
|
}
|
|
2662
2988
|
catch (err) {
|
|
2663
|
-
this.emit({
|
|
2989
|
+
this.emit({
|
|
2990
|
+
type: "notice",
|
|
2991
|
+
level: "error",
|
|
2992
|
+
text: `切换目录失败:${err.message}`,
|
|
2993
|
+
textEn: `Failed to switch directory: ${err.message}`,
|
|
2994
|
+
});
|
|
2664
2995
|
}
|
|
2665
2996
|
}
|
|
2666
2997
|
async dispose() {
|
|
@@ -2786,7 +3117,12 @@ export class DshAgentService {
|
|
|
2786
3117
|
this.clients.set(clientId, cs);
|
|
2787
3118
|
this.stateStore.remember(clientId, cwd);
|
|
2788
3119
|
if (cwd !== this.cwd) {
|
|
2789
|
-
send({
|
|
3120
|
+
send({
|
|
3121
|
+
type: "notice",
|
|
3122
|
+
level: "info",
|
|
3123
|
+
text: `已恢复上次的工作目录:${cwd}`,
|
|
3124
|
+
textEn: `Restored the last working directory: ${cwd}`,
|
|
3125
|
+
});
|
|
2790
3126
|
}
|
|
2791
3127
|
}
|
|
2792
3128
|
cs.attachSink(send);
|