pi-web-ui 0.63.4 → 0.64.1
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/dist/server/agent-service.js +47 -16
- package/dist/server/dsh/dsh-agent-service.js +30 -7
- package/dist/server/files-service.js +48 -6
- package/dist/server/goal-service.js +4 -1
- package/dist/server/index.js +5 -2
- package/dist/server/marker-service.js +10 -4
- package/dist/server/markers/builtins/rename.js +4 -6
- package/dist/server/markers/builtins/services.js +6 -2
- package/dist/server/markers/builtins/todo.js +8 -4
- package/dist/server/model-admin.js +12 -6
- package/dist/server/plugins.js +4 -4
- package/dist/server/settings-service.js +5 -1
- package/dist/server/slash-commands.js +3 -0
- package/dist/server/terminals.js +21 -11
- package/dist/server/webui-context.js +2 -2
- package/package.json +109 -109
- package/web/dist/assets/TerminalPanel-C5VIdPmn.js +6 -0
- package/web/dist/assets/index-BmDs6ukf.js +326 -0
- package/web/dist/assets/index-DVnuQrK5.css +10 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/TerminalPanel-BFrV6B8W.js +0 -2
- package/web/dist/assets/index-BFoSybNe.js +0 -324
- package/web/dist/assets/index-D9G_7fPE.css +0 -10
|
@@ -201,7 +201,10 @@ function makeMarkersListTool(getActiveId, markerSvc) {
|
|
|
201
201
|
const text = markerSvc.describe(convId, "todo", !!p.includeDeleted);
|
|
202
202
|
const state = markerSvc.getRawState(convId, "todo");
|
|
203
203
|
const visible = (state?.tasks ?? []).filter((t) => p.includeDeleted || t.status !== "deleted");
|
|
204
|
-
return {
|
|
204
|
+
return {
|
|
205
|
+
content: [{ type: "text", text }],
|
|
206
|
+
details: { action: "list", todos: visible, nextId: state?.nextId },
|
|
207
|
+
};
|
|
205
208
|
},
|
|
206
209
|
};
|
|
207
210
|
}
|
|
@@ -592,7 +595,7 @@ export class ClientSession {
|
|
|
592
595
|
try {
|
|
593
596
|
await conv.session.bindExtensions({
|
|
594
597
|
mode: "rpc",
|
|
595
|
-
onError: (err) => this.emit({ type: "notice", level: "error", text: err.error }),
|
|
598
|
+
onError: (err) => this.emit({ type: "notice", level: "error", text: err.error, textEn: err.error }),
|
|
596
599
|
});
|
|
597
600
|
}
|
|
598
601
|
catch {
|
|
@@ -604,6 +607,7 @@ export class ClientSession {
|
|
|
604
607
|
type: "notice",
|
|
605
608
|
level: "error",
|
|
606
609
|
text: `子代理 ${conversationId} 启动失败: ${err instanceof Error ? err.message : String(err)}`,
|
|
610
|
+
textEn: `Subagent ${conversationId} failed to start: ${err instanceof Error ? err.message : String(err)}`,
|
|
607
611
|
});
|
|
608
612
|
});
|
|
609
613
|
this.emitConversations();
|
|
@@ -818,7 +822,9 @@ export class ClientSession {
|
|
|
818
822
|
getActiveConversationId: () => this.activeId,
|
|
819
823
|
getSessionManager: (id) => {
|
|
820
824
|
const c = this.convs.get(id);
|
|
821
|
-
return c
|
|
825
|
+
return c
|
|
826
|
+
? c.session.sessionManager
|
|
827
|
+
: undefined;
|
|
822
828
|
},
|
|
823
829
|
renameConversation: (convId, title) => {
|
|
824
830
|
// 复用现有重命名路径(内存标题 + 磁盘 session_info)
|
|
@@ -905,6 +911,7 @@ export class ClientSession {
|
|
|
905
911
|
type: "notice",
|
|
906
912
|
level: d.type,
|
|
907
913
|
text: d.message,
|
|
914
|
+
textEn: d.message,
|
|
908
915
|
});
|
|
909
916
|
}
|
|
910
917
|
}
|
|
@@ -1175,7 +1182,7 @@ export class ClientSession {
|
|
|
1175
1182
|
mode: "rpc",
|
|
1176
1183
|
uiContext: this.webUi,
|
|
1177
1184
|
onError: (err) => {
|
|
1178
|
-
this.emit({ type: "notice", level: "error", text: err.error });
|
|
1185
|
+
this.emit({ type: "notice", level: "error", text: err.error, textEn: err.error });
|
|
1179
1186
|
},
|
|
1180
1187
|
});
|
|
1181
1188
|
conv.unsubscribe = conv.session.subscribe((event) => this.onEvent(conv, event));
|
|
@@ -1432,7 +1439,7 @@ export class ClientSession {
|
|
|
1432
1439
|
if (aborted) {
|
|
1433
1440
|
const stopNotice = this.goalSvc.onAgentEnd(conv, true);
|
|
1434
1441
|
if (stopNotice) {
|
|
1435
|
-
this.emit({ type: "notice", level: "warning", text: stopNotice });
|
|
1442
|
+
this.emit({ type: "notice", level: "warning", text: stopNotice.text, textEn: stopNotice.textEn });
|
|
1436
1443
|
}
|
|
1437
1444
|
break;
|
|
1438
1445
|
}
|
|
@@ -2012,7 +2019,7 @@ export class ClientSession {
|
|
|
2012
2019
|
const result = await def.run(args, { clientId: this.clientId });
|
|
2013
2020
|
// 字符串返回值 → 通知条回显给发起人;富展示用 broadcast/sendTo。
|
|
2014
2021
|
if (typeof result === "string" && result.trim()) {
|
|
2015
|
-
this.emit({ type: "notice", level: "info", text: result });
|
|
2022
|
+
this.emit({ type: "notice", level: "info", text: result, textEn: result });
|
|
2016
2023
|
}
|
|
2017
2024
|
}
|
|
2018
2025
|
catch (err) {
|
|
@@ -2513,8 +2520,8 @@ export class ClientSession {
|
|
|
2513
2520
|
this.bg.push();
|
|
2514
2521
|
}
|
|
2515
2522
|
/** 插件设置保存结果等需要从 index.ts 发 notice 时用(emit 是私有的)。 */
|
|
2516
|
-
emitNotice(level, text) {
|
|
2517
|
-
this.emit({ type: "notice", level, text });
|
|
2523
|
+
emitNotice(level, text, textEn) {
|
|
2524
|
+
this.emit({ type: "notice", level, text, textEn });
|
|
2518
2525
|
}
|
|
2519
2526
|
/** Kill ONE background server (by port); returns whether anything was killed. */
|
|
2520
2527
|
/** Kill ONE background server (by port) OR a plugin task (by taskId). */
|
|
@@ -2644,7 +2651,12 @@ export class ClientSession {
|
|
|
2644
2651
|
});
|
|
2645
2652
|
conv.runtime = runtime;
|
|
2646
2653
|
conv.session = runtime.session;
|
|
2647
|
-
this.emit({
|
|
2654
|
+
this.emit({
|
|
2655
|
+
type: "notice",
|
|
2656
|
+
level: "warning",
|
|
2657
|
+
text: reason,
|
|
2658
|
+
textEn: `${reason} (forced reset: run did not terminate)`,
|
|
2659
|
+
});
|
|
2648
2660
|
await this.bindSession();
|
|
2649
2661
|
this.emitConversations();
|
|
2650
2662
|
void this.pushSlashCommands();
|
|
@@ -3036,6 +3048,9 @@ export class ClientSession {
|
|
|
3036
3048
|
text: stillRunning
|
|
3037
3049
|
? "对话仍在后台运行,已停止删除;请等待其结束后再删除"
|
|
3038
3050
|
: "未能切换到其他对话,已取消删除本次操作",
|
|
3051
|
+
textEn: stillRunning
|
|
3052
|
+
? "Conversation is still running in the background; delete aborted — wait for it to finish and retry"
|
|
3053
|
+
: "Could not switch to another conversation; delete cancelled",
|
|
3039
3054
|
});
|
|
3040
3055
|
return;
|
|
3041
3056
|
}
|
|
@@ -3142,11 +3157,21 @@ export class ClientSession {
|
|
|
3142
3157
|
async dismissConversation(id) {
|
|
3143
3158
|
const conv = this.convs.get(id);
|
|
3144
3159
|
if (!conv) {
|
|
3145
|
-
this.emit({
|
|
3160
|
+
this.emit({
|
|
3161
|
+
type: "notice",
|
|
3162
|
+
level: "warning",
|
|
3163
|
+
text: "该对话不存在或已关闭",
|
|
3164
|
+
textEn: "This conversation does not exist or is already closed",
|
|
3165
|
+
});
|
|
3146
3166
|
return;
|
|
3147
3167
|
}
|
|
3148
3168
|
if (id === this.activeId) {
|
|
3149
|
-
this.emit({
|
|
3169
|
+
this.emit({
|
|
3170
|
+
type: "notice",
|
|
3171
|
+
level: "warning",
|
|
3172
|
+
text: "当前对话不能直接移出,请先切换到其他对话",
|
|
3173
|
+
textEn: "The active conversation cannot be removed directly — switch to another conversation first",
|
|
3174
|
+
});
|
|
3150
3175
|
return;
|
|
3151
3176
|
}
|
|
3152
3177
|
if (!conv.listed) {
|
|
@@ -3171,6 +3196,7 @@ export class ClientSession {
|
|
|
3171
3196
|
type: "notice",
|
|
3172
3197
|
level: "warning",
|
|
3173
3198
|
text: `对话「${conv.title}」仍在运行中,请先等待结束或点击停止后再移出`,
|
|
3199
|
+
textEn: `Conversation "${conv.title}" is still running — wait for it to finish or press Stop before removing`,
|
|
3174
3200
|
});
|
|
3175
3201
|
}
|
|
3176
3202
|
else if (conv.terminals.list().length > 0) {
|
|
@@ -3178,6 +3204,7 @@ export class ClientSession {
|
|
|
3178
3204
|
type: "notice",
|
|
3179
3205
|
level: "warning",
|
|
3180
3206
|
text: `对话「${conv.title}」还有未关闭的终端,请先关闭终端后再移出`,
|
|
3207
|
+
textEn: `Conversation "${conv.title}" still has open terminals — close them before removing`,
|
|
3181
3208
|
});
|
|
3182
3209
|
}
|
|
3183
3210
|
else {
|
|
@@ -3185,6 +3212,7 @@ export class ClientSession {
|
|
|
3185
3212
|
type: "notice",
|
|
3186
3213
|
level: "warning",
|
|
3187
3214
|
text: `对话「${conv.title}」暂时无法移出(存在待处理的后台任务/审查)`,
|
|
3215
|
+
textEn: `Conversation "${conv.title}" cannot be removed right now (pending background task/review)`,
|
|
3188
3216
|
});
|
|
3189
3217
|
}
|
|
3190
3218
|
return;
|
|
@@ -3484,6 +3512,9 @@ export class ClientSession {
|
|
|
3484
3512
|
async uploadFile(relDir, name, data) {
|
|
3485
3513
|
return this.files.uploadFile(relDir, name, data);
|
|
3486
3514
|
}
|
|
3515
|
+
async makeDir(relPath) {
|
|
3516
|
+
return this.files.makeDir(relPath);
|
|
3517
|
+
}
|
|
3487
3518
|
async cycleModel() {
|
|
3488
3519
|
try {
|
|
3489
3520
|
const result = await this.session.cycleModel();
|
|
@@ -3565,7 +3596,7 @@ export class ClientSession {
|
|
|
3565
3596
|
this.removeConversation(displaced.id);
|
|
3566
3597
|
for (const d of newRuntime.diagnostics) {
|
|
3567
3598
|
if (d.type !== "info") {
|
|
3568
|
-
this.emit({ type: "notice", level: d.type, text: d.message });
|
|
3599
|
+
this.emit({ type: "notice", level: d.type, text: d.message, textEn: d.message });
|
|
3569
3600
|
}
|
|
3570
3601
|
}
|
|
3571
3602
|
await this.bindSession();
|
|
@@ -3725,17 +3756,17 @@ export class ClientSession {
|
|
|
3725
3756
|
}
|
|
3726
3757
|
/** Push the user command list (.pi/commands.json) to the client. */
|
|
3727
3758
|
async listCommands() {
|
|
3728
|
-
const { commands, path, warning } = await loadCommands(this.cwd);
|
|
3759
|
+
const { commands, path, warning, warningEn } = await loadCommands(this.cwd);
|
|
3729
3760
|
if (warning) {
|
|
3730
|
-
this.emit({ type: "notice", level: "warning", text: warning });
|
|
3761
|
+
this.emit({ type: "notice", level: "warning", text: warning, textEn: warningEn });
|
|
3731
3762
|
}
|
|
3732
3763
|
this.emit({ type: "commands", commands, path });
|
|
3733
3764
|
}
|
|
3734
3765
|
/** Persist the user command list (.pi/commands.json). */
|
|
3735
3766
|
async saveCommands(commands) {
|
|
3736
|
-
const { path, error } = await saveCommandsFile(this.cwd, commands);
|
|
3767
|
+
const { path, error, errorEn } = await saveCommandsFile(this.cwd, commands);
|
|
3737
3768
|
if (error) {
|
|
3738
|
-
this.emit({ type: "notice", level: "error", text: error });
|
|
3769
|
+
this.emit({ type: "notice", level: "error", text: error, textEn: errorEn });
|
|
3739
3770
|
return;
|
|
3740
3771
|
}
|
|
3741
3772
|
this.emit({ type: "commands", commands, path });
|
|
@@ -1029,8 +1029,8 @@ export class DshClientSession {
|
|
|
1029
1029
|
for (const sink of [...this.sinks])
|
|
1030
1030
|
sink(msg);
|
|
1031
1031
|
}
|
|
1032
|
-
emitNotice(level, text) {
|
|
1033
|
-
this.emit({ type: "notice", level, text });
|
|
1032
|
+
emitNotice(level, text, textEn) {
|
|
1033
|
+
this.emit({ type: "notice", level, text, textEn });
|
|
1034
1034
|
}
|
|
1035
1035
|
pushTerminals() {
|
|
1036
1036
|
for (const conv of this.convs.values()) {
|
|
@@ -1308,6 +1308,9 @@ export class DshClientSession {
|
|
|
1308
1308
|
text: hadGoal
|
|
1309
1309
|
? "已新建分支继续对话(DSH 引擎不支持原地续聊旧会话);原目标已随旧会话存档,如需继续请重新设置目标"
|
|
1310
1310
|
: "已新建分支继续对话(DSH 引擎不支持原地续聊旧会话)",
|
|
1311
|
+
textEn: hadGoal
|
|
1312
|
+
? "Started a branch to continue (DSH engine cannot resume an old session in place); the old goal was archived with it — set a new goal to continue"
|
|
1313
|
+
: "Started a branch to continue (DSH engine cannot resume an old session in place)",
|
|
1311
1314
|
});
|
|
1312
1315
|
return fork;
|
|
1313
1316
|
}
|
|
@@ -1622,11 +1625,21 @@ export class DshClientSession {
|
|
|
1622
1625
|
async dismissConversation(id) {
|
|
1623
1626
|
const conv = this.convs.get(id);
|
|
1624
1627
|
if (!conv) {
|
|
1625
|
-
this.emit({
|
|
1628
|
+
this.emit({
|
|
1629
|
+
type: "notice",
|
|
1630
|
+
level: "warning",
|
|
1631
|
+
text: "该对话不存在或已关闭",
|
|
1632
|
+
textEn: "This conversation does not exist or is already closed",
|
|
1633
|
+
});
|
|
1626
1634
|
return;
|
|
1627
1635
|
}
|
|
1628
1636
|
if (id === this.activeId) {
|
|
1629
|
-
this.emit({
|
|
1637
|
+
this.emit({
|
|
1638
|
+
type: "notice",
|
|
1639
|
+
level: "warning",
|
|
1640
|
+
text: "当前对话不能直接移出,请先切换到其他对话",
|
|
1641
|
+
textEn: "The active conversation cannot be removed directly — switch to another conversation first",
|
|
1642
|
+
});
|
|
1630
1643
|
return;
|
|
1631
1644
|
}
|
|
1632
1645
|
if (!conv.listed) {
|
|
@@ -1638,11 +1651,17 @@ export class DshClientSession {
|
|
|
1638
1651
|
type: "notice",
|
|
1639
1652
|
level: "warning",
|
|
1640
1653
|
text: `对话「${conv.title}」仍在运行中,请先等待结束或停止后再移出`,
|
|
1654
|
+
textEn: `Conversation "${conv.title}" is still running — wait for it to finish or press Stop before removing`,
|
|
1641
1655
|
});
|
|
1642
1656
|
return;
|
|
1643
1657
|
}
|
|
1644
1658
|
if (conv.terminals.list().length > 0) {
|
|
1645
|
-
this.emit({
|
|
1659
|
+
this.emit({
|
|
1660
|
+
type: "notice",
|
|
1661
|
+
level: "warning",
|
|
1662
|
+
text: `对话「${conv.title}」还有未关闭的终端,请先关闭终端后再移出`,
|
|
1663
|
+
textEn: `Conversation "${conv.title}" still has open terminals — close them before removing`,
|
|
1664
|
+
});
|
|
1646
1665
|
return;
|
|
1647
1666
|
}
|
|
1648
1667
|
this.removeConversation(id);
|
|
@@ -1895,6 +1914,9 @@ export class DshClientSession {
|
|
|
1895
1914
|
async completePath(input) {
|
|
1896
1915
|
await this.files.completePath(input);
|
|
1897
1916
|
}
|
|
1917
|
+
async makeDir(input) {
|
|
1918
|
+
await this.files.makeDir(input);
|
|
1919
|
+
}
|
|
1898
1920
|
// -----------------------------------------------------------------------
|
|
1899
1921
|
// 模型 / 思考
|
|
1900
1922
|
// -----------------------------------------------------------------------
|
|
@@ -2665,7 +2687,7 @@ export class DshClientSession {
|
|
|
2665
2687
|
try {
|
|
2666
2688
|
const result = await def.run(args, { clientId: this.clientId });
|
|
2667
2689
|
if (typeof result === "string" && result.trim()) {
|
|
2668
|
-
this.emit({ type: "notice", level: "info", text: result });
|
|
2690
|
+
this.emit({ type: "notice", level: "info", text: result, textEn: result });
|
|
2669
2691
|
}
|
|
2670
2692
|
}
|
|
2671
2693
|
catch (err) {
|
|
@@ -2884,7 +2906,8 @@ export class DshClientSession {
|
|
|
2884
2906
|
}
|
|
2885
2907
|
async cloneProvider(_provider, reqId) {
|
|
2886
2908
|
const error = "DSH 引擎不支持自定义 provider";
|
|
2887
|
-
|
|
2909
|
+
const errorEn = "DSH engine does not support custom providers";
|
|
2910
|
+
this.emit({ type: "notice", level: "error", text: error, textEn: errorEn });
|
|
2888
2911
|
this.emit({ type: "clone_provider_result", reqId, ok: false, error });
|
|
2889
2912
|
}
|
|
2890
2913
|
// -----------------------------------------------------------------------
|
|
@@ -657,14 +657,14 @@ export class FilesService {
|
|
|
657
657
|
* for the target dir (the recursive watcher may not cover it on posix).
|
|
658
658
|
*/
|
|
659
659
|
async uploadFile(relDir, name, data) {
|
|
660
|
-
const emitErr = (text) => this.host.emit({ type: "notice", level: "error", text });
|
|
660
|
+
const emitErr = (text, textEn) => this.host.emit({ type: "notice", level: "error", text, textEn });
|
|
661
661
|
try {
|
|
662
662
|
const root = this.host.getCwd();
|
|
663
663
|
let wp;
|
|
664
664
|
if (relDir) {
|
|
665
665
|
wp = workspacePath(resolve(root), relDir);
|
|
666
666
|
if (!wp) {
|
|
667
|
-
emitErr(`路径超出工作区:${relDir}`);
|
|
667
|
+
emitErr(`路径超出工作区:${relDir}`, `Path outside workspace: ${relDir}`);
|
|
668
668
|
return;
|
|
669
669
|
}
|
|
670
670
|
}
|
|
@@ -678,16 +678,16 @@ export class FilesService {
|
|
|
678
678
|
const abs = resolve(wp.abs, safe);
|
|
679
679
|
const rawRel = relative(root, abs);
|
|
680
680
|
if (rawRel.startsWith("..") || rawRel.includes(`${sep}..`)) {
|
|
681
|
-
emitErr(`文件名不合法:${name}`);
|
|
681
|
+
emitErr(`文件名不合法:${name}`, `Invalid file name: ${name}`);
|
|
682
682
|
return;
|
|
683
683
|
}
|
|
684
684
|
const buf = Buffer.from(data, "base64");
|
|
685
685
|
if (buf.length === 0) {
|
|
686
|
-
emitErr(`空文件:${name}`);
|
|
686
|
+
emitErr(`空文件:${name}`, `Empty file: ${name}`);
|
|
687
687
|
return;
|
|
688
688
|
}
|
|
689
689
|
if (buf.length > MAX_UPLOAD_BYTES) {
|
|
690
|
-
emitErr(`文件过大:${name}(上限 ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)}MB
|
|
690
|
+
emitErr(`文件过大:${name}(上限 ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)}MB)`, `File too large: ${name} (max ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)}MB)`);
|
|
691
691
|
return;
|
|
692
692
|
}
|
|
693
693
|
mkdirSync(wp.abs, { recursive: true });
|
|
@@ -707,7 +707,49 @@ export class FilesService {
|
|
|
707
707
|
});
|
|
708
708
|
}
|
|
709
709
|
catch (err) {
|
|
710
|
-
emitErr(`上传文件失败:${err.message}`);
|
|
710
|
+
emitErr(`上传文件失败:${err.message}`, `Upload failed: ${err.message}`);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Create a folder. Accepts absolute, ~-prefixed or session-relative paths
|
|
715
|
+
* (same expansion rules as completePath — the cwd picker may browse outside
|
|
716
|
+
* the session root, and set_cwd itself accepts any directory). Answers
|
|
717
|
+
* with a notice; the picker refreshes its listing on its own.
|
|
718
|
+
*/
|
|
719
|
+
async makeDir(input) {
|
|
720
|
+
try {
|
|
721
|
+
const fs = await import("node:fs/promises");
|
|
722
|
+
const { resolve, sep, isAbsolute } = await import("node:path");
|
|
723
|
+
const { homedir } = await import("node:os");
|
|
724
|
+
const home = homedir();
|
|
725
|
+
let expanded = input.trim();
|
|
726
|
+
if (!expanded)
|
|
727
|
+
throw new Error("路径为空");
|
|
728
|
+
if (expanded === "~" || expanded === "~\\") {
|
|
729
|
+
expanded = home;
|
|
730
|
+
}
|
|
731
|
+
else if (expanded.startsWith("~/") || expanded.startsWith("~\\")) {
|
|
732
|
+
expanded = home + sep + expanded.slice(2);
|
|
733
|
+
}
|
|
734
|
+
else if (!isAbsolute(expanded)) {
|
|
735
|
+
expanded = resolve(this.host.getCwd(), expanded);
|
|
736
|
+
}
|
|
737
|
+
const abs = resolve(expanded);
|
|
738
|
+
await fs.mkdir(abs, { recursive: true });
|
|
739
|
+
this.host.emit({
|
|
740
|
+
type: "notice",
|
|
741
|
+
level: "info",
|
|
742
|
+
text: `已创建文件夹:${abs}`,
|
|
743
|
+
textEn: `Folder created: ${abs}`,
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
catch (err) {
|
|
747
|
+
this.host.emit({
|
|
748
|
+
type: "notice",
|
|
749
|
+
level: "error",
|
|
750
|
+
text: `创建文件夹失败:${err.message}`,
|
|
751
|
+
textEn: `Failed to create folder: ${err.message}`,
|
|
752
|
+
});
|
|
711
753
|
}
|
|
712
754
|
}
|
|
713
755
|
/**
|
|
@@ -611,7 +611,10 @@ export class GoalService {
|
|
|
611
611
|
g.status = "已手动停止,目标审查已中止";
|
|
612
612
|
g.statusEn = "Stopped manually, goal review aborted";
|
|
613
613
|
this.emitGoalStatus();
|
|
614
|
-
return
|
|
614
|
+
return {
|
|
615
|
+
text: "⏹ 已手动停止,目标审查已中止(想继续可重新设定目标)",
|
|
616
|
+
textEn: "⏹ Stopped manually, goal review aborted (set a new goal to continue)",
|
|
617
|
+
};
|
|
615
618
|
}
|
|
616
619
|
return null;
|
|
617
620
|
}
|
package/dist/server/index.js
CHANGED
|
@@ -664,6 +664,9 @@ wss.on("connection", (ws) => {
|
|
|
664
664
|
case "complete_path":
|
|
665
665
|
void cs.completePath(msg.path);
|
|
666
666
|
break;
|
|
667
|
+
case "make_dir":
|
|
668
|
+
void cs.makeDir(msg.path);
|
|
669
|
+
break;
|
|
667
670
|
case "check_update":
|
|
668
671
|
void cs.checkUpdate();
|
|
669
672
|
break;
|
|
@@ -800,10 +803,10 @@ wss.on("connection", (ws) => {
|
|
|
800
803
|
case "plugin_settings": {
|
|
801
804
|
const r = pluginMgr.savePluginSettings(msg.pluginId, msg.values ?? {});
|
|
802
805
|
if (r.error) {
|
|
803
|
-
cs?.emitNotice("error", `插件设置保存失败:${r.error}`);
|
|
806
|
+
cs?.emitNotice("error", `插件设置保存失败:${r.error}`, `Failed to save plugin settings: ${r.error}`);
|
|
804
807
|
}
|
|
805
808
|
else {
|
|
806
|
-
cs?.emitNotice("info", "插件设置已保存");
|
|
809
|
+
cs?.emitNotice("info", "插件设置已保存", "Plugin settings saved");
|
|
807
810
|
}
|
|
808
811
|
break;
|
|
809
812
|
}
|
|
@@ -33,7 +33,8 @@ export class MarkerService {
|
|
|
33
33
|
return false;
|
|
34
34
|
if ((name === "rename" || name === "title") && this.settings.disabledMarkers.includes("conv"))
|
|
35
35
|
return false;
|
|
36
|
-
if (name === "conv" &&
|
|
36
|
+
if (name === "conv" &&
|
|
37
|
+
(this.settings.disabledMarkers.includes("rename") || this.settings.disabledMarkers.includes("title")))
|
|
37
38
|
return false;
|
|
38
39
|
return !this.settings.disabledMarkers.includes(name);
|
|
39
40
|
}
|
|
@@ -154,8 +155,8 @@ export class MarkerService {
|
|
|
154
155
|
const state = getOrInit(token.tool);
|
|
155
156
|
const ctx = {
|
|
156
157
|
conversationId,
|
|
157
|
-
notify: (msg, level) => {
|
|
158
|
-
this.host.emit({ type: "notice", level: level ?? "info", text: msg });
|
|
158
|
+
notify: (msg, level, msgEn) => {
|
|
159
|
+
this.host.emit({ type: "notice", level: level ?? "info", text: msg, textEn: msgEn });
|
|
159
160
|
},
|
|
160
161
|
renameConversation: (title) => {
|
|
161
162
|
this.host.renameConversation(conversationId, title);
|
|
@@ -182,7 +183,12 @@ export class MarkerService {
|
|
|
182
183
|
dirty.add(token.tool);
|
|
183
184
|
}
|
|
184
185
|
else if (result.error) {
|
|
185
|
-
this.host.emit({
|
|
186
|
+
this.host.emit({
|
|
187
|
+
type: "notice",
|
|
188
|
+
level: "warning",
|
|
189
|
+
text: `[${token.tool}] ${result.error}`,
|
|
190
|
+
textEn: `[${token.tool}] ${result.error}`,
|
|
191
|
+
});
|
|
186
192
|
}
|
|
187
193
|
}
|
|
188
194
|
for (const ns of dirty) {
|
|
@@ -43,7 +43,7 @@ export const renameMarker = {
|
|
|
43
43
|
return { applied: false, error: "当前环境不支持重命名" };
|
|
44
44
|
try {
|
|
45
45
|
ctx.renameConversation(title);
|
|
46
|
-
ctx.notify(`已重命名为:${title}`, "info");
|
|
46
|
+
ctx.notify(`已重命名为:${title}`, "info", `Renamed to: ${title}`);
|
|
47
47
|
return { applied: true, feedback: `renamed to "${title}"` };
|
|
48
48
|
}
|
|
49
49
|
catch (e) {
|
|
@@ -56,9 +56,7 @@ export const renameMarker = {
|
|
|
56
56
|
/** 别名:[[rename:set:标题]] 等同 [[conv:rename:标题]],方便模型直觉书写。 */
|
|
57
57
|
export const renameAliasMarker = {
|
|
58
58
|
name: "rename",
|
|
59
|
-
guidance: [
|
|
60
|
-
"- [[rename:set:<新标题>]] 同 [[conv:rename:<新标题>]]:重命名当前对话。",
|
|
61
|
-
],
|
|
59
|
+
guidance: ["- [[rename:set:<新标题>]] 同 [[conv:rename:<新标题>]]:重命名当前对话。"],
|
|
62
60
|
async apply(token, ctx) {
|
|
63
61
|
// 兼容任意 op:只要能取到标题就重命名
|
|
64
62
|
const title = extractTitle(token) || token.op?.trim() || "";
|
|
@@ -71,7 +69,7 @@ export const renameAliasMarker = {
|
|
|
71
69
|
return { applied: false, error: "当前环境不支持重命名" };
|
|
72
70
|
try {
|
|
73
71
|
ctx.renameConversation(trimmed);
|
|
74
|
-
ctx.notify(`已重命名为:${trimmed}`, "info");
|
|
72
|
+
ctx.notify(`已重命名为:${trimmed}`, "info", `Renamed to: ${trimmed}`);
|
|
75
73
|
return { applied: true, feedback: `renamed to "${trimmed}"` };
|
|
76
74
|
}
|
|
77
75
|
catch (e) {
|
|
@@ -94,7 +92,7 @@ export const titleAliasMarker = {
|
|
|
94
92
|
if (!ctx.renameConversation)
|
|
95
93
|
return { applied: false, error: "当前环境不支持重命名" };
|
|
96
94
|
ctx.renameConversation(title.slice(0, 80));
|
|
97
|
-
ctx.notify(`已重命名为:${title.slice(0, 80)}`, "info");
|
|
95
|
+
ctx.notify(`已重命名为:${title.slice(0, 80)}`, "info", `Renamed to: ${title.slice(0, 80)}`);
|
|
98
96
|
return { applied: true, feedback: `renamed` };
|
|
99
97
|
},
|
|
100
98
|
overlay: undefined,
|
|
@@ -21,7 +21,9 @@ function findService(state, id) {
|
|
|
21
21
|
return state.services.find((s) => s.id === id);
|
|
22
22
|
}
|
|
23
23
|
function lineOf(s) {
|
|
24
|
-
const meta = [s.pid !== undefined ? `pid ${s.pid}` : "", s.port !== undefined ? `:${s.port}` : ""]
|
|
24
|
+
const meta = [s.pid !== undefined ? `pid ${s.pid}` : "", s.port !== undefined ? `:${s.port}` : ""]
|
|
25
|
+
.filter(Boolean)
|
|
26
|
+
.join(" ");
|
|
25
27
|
return `${s.stopped ? "[x]" : "[ ]"} #${s.id}: ${s.name}${meta ? ` (${meta})` : ""}${s.command ? ` — ${s.command}` : ""}`;
|
|
26
28
|
}
|
|
27
29
|
export function describeServices(state) {
|
|
@@ -53,7 +55,9 @@ export const servicesMarker = {
|
|
|
53
55
|
stopped: false,
|
|
54
56
|
};
|
|
55
57
|
state.services.push(svc);
|
|
56
|
-
const meta = [svc.pid !== undefined ? `pid ${svc.pid}` : "", svc.port !== undefined ? `:${svc.port}` : ""]
|
|
58
|
+
const meta = [svc.pid !== undefined ? `pid ${svc.pid}` : "", svc.port !== undefined ? `:${svc.port}` : ""]
|
|
59
|
+
.filter(Boolean)
|
|
60
|
+
.join(" ");
|
|
57
61
|
return { applied: true, feedback: `Registered #${svc.id}: ${name}${meta ? ` (${meta})` : ""}` };
|
|
58
62
|
}
|
|
59
63
|
case "stop": {
|
|
@@ -16,10 +16,14 @@ function parseId(raw) {
|
|
|
16
16
|
}
|
|
17
17
|
function formatStatus(s) {
|
|
18
18
|
switch (s) {
|
|
19
|
-
case "pending":
|
|
20
|
-
|
|
21
|
-
case "
|
|
22
|
-
|
|
19
|
+
case "pending":
|
|
20
|
+
return "pending";
|
|
21
|
+
case "in_progress":
|
|
22
|
+
return "in_progress";
|
|
23
|
+
case "completed":
|
|
24
|
+
return "completed";
|
|
25
|
+
default:
|
|
26
|
+
return s;
|
|
23
27
|
}
|
|
24
28
|
}
|
|
25
29
|
export function describeTodos(state, includeDeleted = false) {
|
|
@@ -575,19 +575,19 @@ export class ModelAdminService {
|
|
|
575
575
|
*/
|
|
576
576
|
async cloneProvider(providerId, reqId) {
|
|
577
577
|
const pid = providerId.trim();
|
|
578
|
-
const fail = (error) => {
|
|
579
|
-
this.host.emit({ type: "notice", level: "error", text: error });
|
|
578
|
+
const fail = (error, errorEn) => {
|
|
579
|
+
this.host.emit({ type: "notice", level: "error", text: error, textEn: errorEn });
|
|
580
580
|
this.host.emit({ type: "clone_provider_result", reqId, ok: false, error });
|
|
581
581
|
};
|
|
582
582
|
try {
|
|
583
583
|
if (!pid) {
|
|
584
|
-
fail("请填写服务商 ID");
|
|
584
|
+
fail("请填写服务商 ID", "Enter a provider ID");
|
|
585
585
|
return;
|
|
586
586
|
}
|
|
587
587
|
const mr = this.host.modelRuntime();
|
|
588
588
|
const p = mr.getProvider(pid);
|
|
589
589
|
if (!p) {
|
|
590
|
-
fail(`供应商 ${pid}
|
|
590
|
+
fail(`供应商 ${pid} 不存在`, `Provider ${pid} does not exist`);
|
|
591
591
|
return;
|
|
592
592
|
}
|
|
593
593
|
const noBaseUrl = !p.baseUrl;
|
|
@@ -617,7 +617,7 @@ export class ModelAdminService {
|
|
|
617
617
|
models = readModels();
|
|
618
618
|
}
|
|
619
619
|
if (models.length === 0) {
|
|
620
|
-
fail(`${pid}
|
|
620
|
+
fail(`${pid} 的模型列表为空,无法复制(请稍后重试)`, `Model list for ${pid} is empty, cannot clone (retry later)`);
|
|
621
621
|
return;
|
|
622
622
|
}
|
|
623
623
|
// 供应商级 api 取占比最高,模型保留全量去重(避免 muse-spark 被过滤)
|
|
@@ -652,11 +652,14 @@ export class ModelAdminService {
|
|
|
652
652
|
text: noBaseUrl
|
|
653
653
|
? `📋 已复制 ${pid} → ${newId}(${kept.length} 个模型),该供应商无远程 baseUrl,已生成模板请手动填写 baseUrl 和新的 API 密钥后保存`
|
|
654
654
|
: `📋 已复制 ${pid} → ${newId}(${kept.length} 个模型),请填入新的 API 密钥后保存`,
|
|
655
|
+
textEn: noBaseUrl
|
|
656
|
+
? `📋 Cloned ${pid} → ${newId} (${kept.length} models); this provider has no remote baseUrl — template generated, fill in baseUrl and a new API key, then save`
|
|
657
|
+
: `📋 Cloned ${pid} → ${newId} (${kept.length} models); fill in the new API key, then save`,
|
|
655
658
|
});
|
|
656
659
|
this.host.emit({ type: "clone_provider_result", reqId, ok: true, config, configs: [config] });
|
|
657
660
|
}
|
|
658
661
|
catch (err) {
|
|
659
|
-
fail(`复制服务商失败:${err.message}`);
|
|
662
|
+
fail(`复制服务商失败:${err.message}`, `Failed to clone provider: ${err.message}`);
|
|
660
663
|
}
|
|
661
664
|
this.host.flushSnapshot();
|
|
662
665
|
}
|
|
@@ -1015,6 +1018,9 @@ export class ModelAdminService {
|
|
|
1015
1018
|
text: added > 0
|
|
1016
1019
|
? `🔄 已刷新 ${pid}:新增 ${added} 个模型,共 ${merged.length} 个`
|
|
1017
1020
|
: `🔄 已刷新 ${pid}:无新增模型(共 ${merged.length} 个)`,
|
|
1021
|
+
textEn: added > 0
|
|
1022
|
+
? `🔄 Refreshed ${pid}: ${added} new models, ${merged.length} total`
|
|
1023
|
+
: `🔄 Refreshed ${pid}: no new models (${merged.length} total)`,
|
|
1018
1024
|
});
|
|
1019
1025
|
return done(true, { added, total: merged.length });
|
|
1020
1026
|
}
|
package/dist/server/plugins.js
CHANGED
|
@@ -308,7 +308,7 @@ export class PluginManager {
|
|
|
308
308
|
if (prev === key)
|
|
309
309
|
return; // 同版本能力清单,不再打扰
|
|
310
310
|
const list = perms.length ? perms.join(", ") : "无";
|
|
311
|
-
this.notifyAll(perms.length ? "warning" : "info", `插件「${info.name}」已激活(${prev ? "能力清单变更" : "首次安装"};声明能力:${list}
|
|
311
|
+
this.notifyAll(perms.length ? "warning" : "info", `插件「${info.name}」已激活(${prev ? "能力清单变更" : "首次安装"};声明能力:${list})——请确认来源可信`, `Plugin "${info.name}" activated (${prev ? "capability list changed" : "first install"}; declared: ${list}) — verify the source is trusted`);
|
|
312
312
|
writeFileSync(markerFile, JSON.stringify({ v: 1, key, perms }), "utf8");
|
|
313
313
|
}
|
|
314
314
|
catch (err) {
|
|
@@ -344,8 +344,8 @@ export class PluginManager {
|
|
|
344
344
|
this.deliverAll({ type: "plugin_data", pluginId, payload });
|
|
345
345
|
}
|
|
346
346
|
/** 系统通知:发给所有 socket(复用 notice 消息,前端 toast 展示)。 */
|
|
347
|
-
notifyAll(level, text) {
|
|
348
|
-
this.deliverAll({ type: "notice", level, text });
|
|
347
|
+
notifyAll(level, text, textEn) {
|
|
348
|
+
this.deliverAll({ type: "notice", level, text, textEn });
|
|
349
349
|
}
|
|
350
350
|
/** 给指定客户端定向发一条插件消息;找不到该 socket 时静默忽略。 */
|
|
351
351
|
sendTo(clientId, pluginId, payload) {
|
|
@@ -708,7 +708,7 @@ export class PluginManager {
|
|
|
708
708
|
const self = this; // 对象字面量 getter 里不能用插件宿主的 this (oxlint no-this-alias: 誤報, getter closure 需要 host)
|
|
709
709
|
const host = {
|
|
710
710
|
broadcast: (payload) => this.broadcast(info.id, payload),
|
|
711
|
-
notify: (level, text) => this.notifyAll(level, text),
|
|
711
|
+
notify: (level, text, textEn) => this.notifyAll(level, text, textEn),
|
|
712
712
|
sendTo: (clientId, payload) => this.sendTo(clientId, info.id, payload),
|
|
713
713
|
onMessage: (h) => {
|
|
714
714
|
handlers.add(h);
|
|
@@ -212,7 +212,11 @@ export class SettingsService {
|
|
|
212
212
|
presets: this.presets.map((p) => ({ ...p })),
|
|
213
213
|
...(this.host.getMarkerState
|
|
214
214
|
? this.host.getMarkerState()
|
|
215
|
-
: {
|
|
215
|
+
: {
|
|
216
|
+
markersEnabled: true,
|
|
217
|
+
disabledMarkers: [],
|
|
218
|
+
markers: [],
|
|
219
|
+
}),
|
|
216
220
|
subagentTemplates: this.templates.list(),
|
|
217
221
|
subagentDefaultTemplates: DEFAULT_TEMPLATES.map((t) => t.name),
|
|
218
222
|
},
|
|
@@ -174,6 +174,9 @@ export class SlashCommandsService {
|
|
|
174
174
|
text: current
|
|
175
175
|
? `当前模型:${current.name}(${current.provider}/${current.id})。用法:/model <名称>`
|
|
176
176
|
: `用法:/model <名称>`,
|
|
177
|
+
textEn: current
|
|
178
|
+
? `Current model: ${current.name} (${current.provider}/${current.id}). Usage: /model <name>`
|
|
179
|
+
: `Usage: /model <name>`,
|
|
177
180
|
});
|
|
178
181
|
return true;
|
|
179
182
|
}
|