pi-web-ui 0.63.1 → 0.63.3

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.
Files changed (63) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +504 -504
  3. package/README.zh-CN.md +427 -427
  4. package/bin/pi-web-ui.mjs +1809 -1809
  5. package/deploy/com.xingshuyin.pi-web-ui.plist +48 -48
  6. package/deploy/nginx-subpath.conf +88 -88
  7. package/deploy/pi-web-ui-task.xml +54 -54
  8. package/deploy/pi-web-ui.service +31 -31
  9. package/dist/server/agent-service.js +228 -11
  10. package/dist/server/attachments.js +3 -3
  11. package/dist/server/client-state.js +18 -0
  12. package/dist/server/dsh/dsh-agent-service.js +21 -0
  13. package/dist/server/dsh/runtime/cordis.yml +1 -1
  14. package/dist/server/dsh/runtime/goal-rpc.mjs +645 -645
  15. package/dist/server/dsh/runtime/launcher.mjs +164 -164
  16. package/dist/server/dsh/runtime/override.patch.yml +71 -71
  17. package/dist/server/dsh/runtime/runtime-root.mjs +86 -86
  18. package/dist/server/index.js +11 -0
  19. package/dist/server/marker-service.js +270 -0
  20. package/dist/server/markers/builtins/notify.js +20 -0
  21. package/dist/server/markers/builtins/rename.js +102 -0
  22. package/dist/server/markers/builtins/services.js +119 -0
  23. package/dist/server/markers/builtins/todo.js +130 -0
  24. package/dist/server/markers/index.js +24 -0
  25. package/dist/server/markers/marker.js +53 -0
  26. package/dist/server/markers/registry.js +28 -0
  27. package/dist/server/markers/store.js +47 -0
  28. package/dist/server/settings-service.js +3 -0
  29. package/dist/server/slash-commands.js +34 -0
  30. package/dist/server/terminals.js +15 -4
  31. package/dist/server/themes.js +24 -5
  32. package/dist/server/vision-bridge.js +9 -9
  33. package/extensions/webui.ts +190 -190
  34. package/package.json +1 -1
  35. package/themes/cyberpunk.css +81 -80
  36. package/themes/dazzle.css +81 -80
  37. package/themes/md-preview.css +98 -97
  38. package/themes/white.css +160 -159
  39. package/web/dist/assets/TerminalPanel-BFrV6B8W.js +2 -0
  40. package/web/dist/assets/index-BFoSybNe.js +324 -0
  41. package/web/dist/assets/index-D9G_7fPE.css +10 -0
  42. package/web/dist/favicon.svg +8 -8
  43. package/web/dist/icons/icon-1024.png +0 -0
  44. package/web/dist/icons/icon-192.png +0 -0
  45. package/web/dist/icons/icon-512.png +0 -0
  46. package/web/dist/icons/maskable-1024.png +0 -0
  47. package/web/dist/icons/maskable-192.png +0 -0
  48. package/web/dist/icons/maskable-512.png +0 -0
  49. package/web/dist/index.html +23 -17
  50. package/web/dist/manifest.webmanifest +50 -0
  51. package/web/dist/sw.js +126 -0
  52. package/web/public/favicon.svg +8 -8
  53. package/web/public/icons/icon-1024.png +0 -0
  54. package/web/public/icons/icon-192.png +0 -0
  55. package/web/public/icons/icon-512.png +0 -0
  56. package/web/public/icons/maskable-1024.png +0 -0
  57. package/web/public/icons/maskable-192.png +0 -0
  58. package/web/public/icons/maskable-512.png +0 -0
  59. package/web/public/manifest.webmanifest +50 -0
  60. package/web/public/sw.js +126 -0
  61. package/web/dist/assets/TerminalPanel-DwutHqgi.js +0 -2
  62. package/web/dist/assets/index-CJoS1bmZ.js +0 -323
  63. package/web/dist/assets/index-CrDJOsa5.css +0 -10
@@ -23,6 +23,7 @@ import { hasActiveSubagentRun, hasPendingWaitSubscription, shouldRetainActive }
23
23
  import { syncPluginToolsIntoSession } from "./plugins.js";
24
24
  import { SettingsService } from "./settings-service.js";
25
25
  import { GoalService } from "./goal-service.js";
26
+ import { MarkerService } from "./marker-service.js";
26
27
  import { SlashCommandsService, parseSlash } from "./slash-commands.js";
27
28
  import { ModelAdminService } from "./model-admin.js";
28
29
  import { FilesService, workspacePath } from "./files-service.js";
@@ -83,14 +84,14 @@ export class QuiesceRejectedError extends Error {
83
84
  * programs wait for input that never comes. Legacy Chinese files are often
84
85
  * GBK/GB2312 — read them with the right encoding, never paste mojibake into
85
86
  * reasoning/answers. */
86
- const WINDOWS_PERSONA = `You are a coding agent running on Windows. The bash tool runs Git Bash (bash.exe), not PowerShell. Follow these rules to avoid hanging the session:
87
-
88
-
89
-
90
- - ALWAYS pass a timeout parameter to the bash tool (in seconds). There is NO default timeout — a command that never finishes (servers, watchers, infinite loops, slow downloads/installs) will hang the entire conversation indefinitely. Pick a generous timeout for long-running work, but never omit it.
91
- - NEVER run interactive or foreground long-running commands through the bash tool (vi, less, top, python -, node -, npm run dev, sleep 10000). For servers/daemons use background execution with output redirected to a log file, then poll the log; stop them when done.
92
- - In the interactive terminal (TTY) — which is Git Bash too, not PowerShell — NEVER use heredocs (<<'EOF' ... EOF) or here-strings, and NEVER start interactive programs (vi, less, python -, node -, npm init): they wait for keyboard input that never arrives and hang the terminal forever. Prefer writing a temp script file (e.g. .pi-tmp.sh) and running it non-interactively. ALWAYS pass a timeout to long-running commands (e.g. \`timeout 120 npm run dev\`).
93
-
87
+ const WINDOWS_PERSONA = `You are a coding agent running on Windows. The bash tool runs Git Bash (bash.exe), not PowerShell. Follow these rules to avoid hanging the session:
88
+
89
+
90
+
91
+ - ALWAYS pass a timeout parameter to the bash tool (in seconds). There is NO default timeout — a command that never finishes (servers, watchers, infinite loops, slow downloads/installs) will hang the entire conversation indefinitely. Pick a generous timeout for long-running work, but never omit it.
92
+ - NEVER run interactive or foreground long-running commands through the bash tool (vi, less, top, python -, node -, npm run dev, sleep 10000). For servers/daemons use background execution with output redirected to a log file, then poll the log; stop them when done.
93
+ - In the interactive terminal (TTY) — which is Git Bash too, not PowerShell — NEVER use heredocs (<<'EOF' ... EOF) or here-strings, and NEVER start interactive programs (vi, less, python -, node -, npm init): they wait for keyboard input that never arrives and hang the terminal forever. Prefer writing a temp script file (e.g. .pi-tmp.sh) and running it non-interactively. ALWAYS pass a timeout to long-running commands (e.g. \`timeout 120 npm run dev\`).
94
+
94
95
  Many legacy Chinese text files (.html/.txt/.md/.log, exported documents) are GBK/GB2312 encoded: the read tool decodes UTF-8 only and will show mojibake (乱码) for them. If a file's content looks garbled, read it through the terminal instead: in Git Bash use \`cat file | iconv -f GBK -t UTF-8\` (or \`iconv -f GBK -t UTF-8 file\`); in cmd use \`chcp 65001 && type file\`; in PowerShell use \`Get-Content -Encoding Default file\`. Never paste mojibake into your reasoning or answer — describe the decoded content instead.`;
95
96
  /** bash 工具输出限制/过滤管道引导:模型习惯套 `| tail/-n`、`| head`、`| grep`、`| less`
96
97
  * 等限输出。这些管道在持久终端里会①缓冲(可见终端全程哑火、看不到实时进度)②把退出码
@@ -176,6 +177,34 @@ export function makeAdaptiveBashTool(killable, terminalBacked, useTerminal) {
176
177
  execute: (id, params, signal, onUpdate, ctx) => (useTerminal() ? terminalBacked : killable).execute(id, params, signal, onUpdate, ctx),
177
178
  };
178
179
  }
180
+ /**
181
+ * 内置标记只读查询工具(markers_list)— 读操作仍走真工具。
182
+ */
183
+ function makeMarkersListTool(getActiveId, markerSvc) {
184
+ return {
185
+ name: "markers_list",
186
+ label: "List marker state",
187
+ description: "只读查询内联标记状态。状态【写】操作请一律用内联标记([[todo:new:...]] / [[svc:add:...]] 等)写在回答正文里,不要调用本工具做写操作。",
188
+ parameters: Type.Object({
189
+ action: Type.Unsafe({ enum: ["list"] }),
190
+ tool: Type.Optional(Type.Union([Type.Literal("todo"), Type.Literal("svc")], { description: "查询哪个命名空间(默认 todo)" })),
191
+ includeDeleted: Type.Optional(Type.Boolean({ description: "是否包含已删除任务(tombstone,仅 todo)" })),
192
+ }),
193
+ execute: async (_id, params) => {
194
+ const p = params;
195
+ const which = p.tool ?? "todo";
196
+ const convId = getActiveId();
197
+ if (which === "svc") {
198
+ const text = markerSvc.describe(convId, "svc", false);
199
+ return { content: [{ type: "text", text }], details: { action: "list", tool: "svc" } };
200
+ }
201
+ const text = markerSvc.describe(convId, "todo", !!p.includeDeleted);
202
+ const state = markerSvc.getRawState(convId, "todo");
203
+ const visible = (state?.tasks ?? []).filter((t) => p.includeDeleted || t.status !== "deleted");
204
+ return { content: [{ type: "text", text }], details: { action: "list", todos: visible, nextId: state?.nextId } };
205
+ },
206
+ };
207
+ }
179
208
  /**
180
209
  * 插件结构化工具 → SDK ToolDefinition。
181
210
  * execute 返回值宽容处理:{content,details} 原样收编;字符串/对象包成文本块。
@@ -246,6 +275,16 @@ function extractPartialText(partial) {
246
275
  }
247
276
  return null;
248
277
  }
278
+ function extractAssistantTextFromContent(content) {
279
+ if (typeof content === "string")
280
+ return content;
281
+ if (!Array.isArray(content))
282
+ return "";
283
+ return content
284
+ .filter((c) => c.type === "text" && typeof c.text === "string")
285
+ .map((c) => c.text)
286
+ .join("\n");
287
+ }
249
288
  export { workspacePath };
250
289
  /** Hard cap on how long ONE tool call may run before the watchdog aborts the
251
290
  * session. The SDK bash tool has NO default timeout, so a command that never
@@ -262,6 +301,14 @@ const MAX_OPEN_CONVERSATIONS = 8;
262
301
  const DEFAULT_CONV_TITLE = "新对话";
263
302
  /** First user text in a session, truncated for the conversation list. */
264
303
  function conversationTitle(session) {
304
+ try {
305
+ const named = session.sessionManager.getSessionName();
306
+ if (named && named.trim())
307
+ return named.trim();
308
+ }
309
+ catch {
310
+ // best-effort — fall through to first-message title
311
+ }
265
312
  try {
266
313
  for (const m of session.agent.state.messages) {
267
314
  if (m.role !== "user")
@@ -755,12 +802,29 @@ export class ClientSession {
755
802
  watchTimer = null;
756
803
  /** 子代理模板库(全局共享,<dataDir>/subagent-templates.json)。 */
757
804
  subagentTemplates;
805
+ /** 内置标记服务(todo/notify/svc/rename 等,可全局/分组开关)。 */
806
+ markerSvc;
758
807
  constructor(clientId, cwd, agentDir, stateStore) {
759
808
  this.clientId = clientId;
760
809
  this.cwd = cwd;
761
810
  this.agentDir = agentDir;
762
811
  this.stateStore = stateStore;
763
812
  this.subagentTemplates = new SubagentTemplatesStore(join(stateStore.dataDir, "subagent-templates.json"));
813
+ this.markerSvc = new MarkerService({
814
+ clientId,
815
+ stateStore,
816
+ emit: (msg) => this.emit(msg),
817
+ isDisposed: () => this.disposed,
818
+ getActiveConversationId: () => this.activeId,
819
+ getSessionManager: (id) => {
820
+ const c = this.convs.get(id);
821
+ return c ? c.session.sessionManager : undefined;
822
+ },
823
+ renameConversation: (convId, title) => {
824
+ // 复用现有重命名路径(内存标题 + 磁盘 session_info)
825
+ void this.renameConversation(convId, title);
826
+ },
827
+ });
764
828
  this.settingsSvc = new SettingsService({
765
829
  clientId,
766
830
  stateStore,
@@ -779,6 +843,11 @@ export class ClientSession {
779
843
  },
780
844
  effectiveDefaultSystemPrompt: () => this.effectiveDefaultSystemPrompt(),
781
845
  effectiveSystemPrompt: () => this.effectiveSystemPrompt(),
846
+ getMarkerState: () => ({
847
+ markersEnabled: this.markerSvc.current.markersEnabled,
848
+ disabledMarkers: [...this.markerSvc.current.disabledMarkers],
849
+ markers: this.markerSvc.listForUi(),
850
+ }),
782
851
  }, this.subagentTemplates);
783
852
  this.goalSvc = new GoalService({
784
853
  clientId,
@@ -909,6 +978,10 @@ export class ClientSession {
909
978
  // 管道末尾命令(tail 恒 0、grep 无命中 1)、长驻/出错命令挂到超时。
910
979
  // 让模型改用 bash 的 tail 参数,长驻/交互改走持久终端。
911
980
  out.push(PIPELESS_BASH_GUIDANCE);
981
+ // 内置标记工具引导(按总开关/分组开关过滤)
982
+ const markerGuidance = this.markerSvc.buildGuidance();
983
+ if (markerGuidance)
984
+ out.push(markerGuidance);
912
985
  return out;
913
986
  },
914
987
  // 技能:模板非空白名单时只启用白名单里的;否则按主会话禁用集过滤。
@@ -961,6 +1034,8 @@ export class ClientSession {
961
1034
  // 第一方子代理工具(spawn/get_result/steer/list/stop)。子代理会话
962
1035
  // 也注册了它们,因此可自然嵌套派发。
963
1036
  ...makeSubagentTools(this.subagentHost),
1037
+ // 内置标记只读查询工具(todo/svc 状态查询,写操作走内联标记)。
1038
+ makeMarkersListTool(() => this.activeId, this.markerSvc),
964
1039
  ],
965
1040
  });
966
1041
  // 终端工具开关从创建起就生效(工具始终注册进注册表,只调活跃集)。
@@ -1328,6 +1403,23 @@ export class ClientSession {
1328
1403
  case "agent_end": {
1329
1404
  this.scheduleSessionsRefresh();
1330
1405
  this.refreshConversationTitle(conv);
1406
+ // agent_end 兜底:若 entry_appended 未触发(部分 SDK 路径),扫描最后一条 assistant 消息补处理
1407
+ try {
1408
+ const msgs = event.messages;
1409
+ if (Array.isArray(msgs)) {
1410
+ for (let i = msgs.length - 1; i >= 0; i--) {
1411
+ const m = msgs[i];
1412
+ if (m?.role === "assistant") {
1413
+ const text = extractAssistantTextFromContent(m.content);
1414
+ if (text && text.includes("[[")) {
1415
+ void this.markerSvc.handleAssistantText(conv.id, text);
1416
+ }
1417
+ break;
1418
+ }
1419
+ }
1420
+ }
1421
+ }
1422
+ catch { }
1331
1423
  // Manual interrupt (Stop button / abort): the last assistant message
1332
1424
  // carries stopReason "aborted". A half-finished run should NOT be
1333
1425
  // reviewed (it would fail and inject a revision, only to be stopped
@@ -1355,10 +1447,18 @@ export class ClientSession {
1355
1447
  }
1356
1448
  break;
1357
1449
  }
1358
- case "entry_appended":
1450
+ case "entry_appended": {
1359
1451
  this.scheduleSessionsRefresh();
1360
1452
  this.refreshConversationTitle(conv);
1453
+ // 内置标记:assistant 终稿落库时解析执行(todo/rename 等)
1454
+ const entry = event.entry;
1455
+ if (entry?.type === "message" && entry.message?.role === "assistant") {
1456
+ const text = extractAssistantTextFromContent(entry.message.content);
1457
+ if (text)
1458
+ void this.markerSvc.handleAssistantText(conv.id, text);
1459
+ }
1361
1460
  break;
1461
+ }
1362
1462
  case "message_update": {
1363
1463
  // Live assistant-message increment, deliberately OUTSIDE the snapshot
1364
1464
  // channel: send() drops snapshots under backpressure (big sessions),
@@ -1887,6 +1987,20 @@ export class ClientSession {
1887
1987
  setModel: (id) => this.setModel(id),
1888
1988
  setCwd: (path) => this.setCwd(path),
1889
1989
  setThinking: (level) => this.setThinking(level),
1990
+ renameSession: async (name) => {
1991
+ this.session.setSessionName(name);
1992
+ this.conv.title = name;
1993
+ this.invalidateSessionInfos();
1994
+ this.emitConversations();
1995
+ await this.pushSessions();
1996
+ this.emit({
1997
+ type: "notice",
1998
+ level: "info",
1999
+ text: `已重命名当前会话为「${name}」`,
2000
+ textEn: `Renamed current session to "${name}"`,
2001
+ });
2002
+ this.flushSnapshot();
2003
+ },
1890
2004
  refreshSessions: () => this.refreshSessions(),
1891
2005
  afterReload: () => this.applyTerminalToolGating(this.session),
1892
2006
  pluginCommands: () => this.pluginCommandsProvider?.() ?? [],
@@ -2081,7 +2195,31 @@ export class ClientSession {
2081
2195
  }
2082
2196
  /** Persist + apply a partial settings update (prompt text/mode, toggles). */
2083
2197
  async setSettings(partial) {
2084
- await this.settingsSvc.set(partial);
2198
+ const { markersEnabled, disabledMarkers, ...rest } = partial;
2199
+ let markerChanged = false;
2200
+ if (markersEnabled !== undefined || disabledMarkers !== undefined) {
2201
+ this.markerSvc.setAll({
2202
+ ...(markersEnabled !== undefined ? { markersEnabled } : {}),
2203
+ ...(disabledMarkers !== undefined ? { disabledMarkers } : {}),
2204
+ });
2205
+ markerChanged = true;
2206
+ }
2207
+ await this.settingsSvc.set(rest);
2208
+ if (markerChanged) {
2209
+ // 标记开关影响 system prompt 引导,需重载生效(流式中则延迟)
2210
+ this.pushSettings();
2211
+ this.flushSnapshot();
2212
+ // 尝试立即重载,若流式中会由 SettingsService 延迟到 agent_end
2213
+ if (!this.session.isStreaming) {
2214
+ try {
2215
+ await this.session.reload();
2216
+ this.applyTerminalToolGating(this.session);
2217
+ await this.pushSlashCommands();
2218
+ this.pushSettings();
2219
+ }
2220
+ catch { }
2221
+ }
2222
+ }
2085
2223
  }
2086
2224
  /** Save the CURRENT settings as a named preset (overwrites if exists). */
2087
2225
  async savePreset(name) {
@@ -2225,7 +2363,7 @@ export class ClientSession {
2225
2363
  // rename entirely. A failed send still leaves the name, which matches
2226
2364
  // what the user typed intent-wise; the entry_appended fallback below
2227
2365
  // re-derives it from the persisted transcript when needed.
2228
- if (conv.title === DEFAULT_CONV_TITLE && text.trim()) {
2366
+ if (conv.title === DEFAULT_CONV_TITLE && text.trim() && !conv.session.sessionName?.trim()) {
2229
2367
  const trimmed = text.trim().replace(/\s+/g, " ");
2230
2368
  conv.title = trimmed.length > 30 ? `${trimmed.slice(0, 30)}…` : trimmed;
2231
2369
  this.emitConversations();
@@ -2918,6 +3056,85 @@ export class ClientSession {
2918
3056
  });
2919
3057
  }
2920
3058
  }
3059
+ /** Rename a persisted session by appending a session_info entry — the same
3060
+ * mechanism pi's /name uses (SessionManager.appendSessionInfo). Works on
3061
+ * any transcript under the sessions root, live or not; no session switch. */
3062
+ async renameSession(path, name) {
3063
+ try {
3064
+ const trimmed = (name ?? "").trim();
3065
+ if (!trimmed)
3066
+ return;
3067
+ const abs = resolve(path);
3068
+ const sessionsRoot = resolve(this.agentDir, "sessions");
3069
+ if (!abs.startsWith(sessionsRoot + sep)) {
3070
+ this.emit({
3071
+ type: "notice",
3072
+ level: "error",
3073
+ text: "只能重命名会话目录中的对话记录",
3074
+ textEn: "Only transcripts inside the session directory can be renamed",
3075
+ });
3076
+ return;
3077
+ }
3078
+ const mgr = SessionManager.open(abs);
3079
+ mgr.appendSessionInfo(trimmed);
3080
+ this.setConversationTitleForFile(abs, trimmed);
3081
+ this.invalidateSessionInfos();
3082
+ await this.refreshSessions();
3083
+ }
3084
+ catch (err) {
3085
+ this.emit({
3086
+ type: "notice",
3087
+ level: "error",
3088
+ text: `重命名会话失败:${err.message}`,
3089
+ textEn: `Failed to rename session: ${err.message}`,
3090
+ });
3091
+ }
3092
+ }
3093
+ /** Rename a live conversation by id: retitle in memory AND persist a
3094
+ * session_info entry to its transcript so History matches immediately. */
3095
+ async renameConversation(id, name) {
3096
+ try {
3097
+ const trimmed = (name ?? "").trim();
3098
+ if (!trimmed)
3099
+ return;
3100
+ const conv = this.convs.get(id);
3101
+ if (!conv)
3102
+ return;
3103
+ conv.title = trimmed;
3104
+ try {
3105
+ const file = conv.session.sessionFile;
3106
+ if (file !== undefined)
3107
+ SessionManager.open(resolve(file)).appendSessionInfo(trimmed);
3108
+ }
3109
+ catch {
3110
+ // in-memory title still updated; transcript write is best-effort
3111
+ }
3112
+ this.emitConversations();
3113
+ this.invalidateSessionInfos();
3114
+ await this.refreshSessions();
3115
+ }
3116
+ catch (err) {
3117
+ this.emit({
3118
+ type: "notice",
3119
+ level: "error",
3120
+ text: `重命名对话失败:${err.message}`,
3121
+ textEn: `Failed to rename conversation: ${err.message}`,
3122
+ });
3123
+ }
3124
+ }
3125
+ /** Point every live conversation holding this transcript file at a new title. */
3126
+ setConversationTitleForFile(abs, title) {
3127
+ let changed = false;
3128
+ for (const conv of this.convs.values()) {
3129
+ const file = conv.session.sessionFile;
3130
+ if (file !== undefined && resolve(file) === abs) {
3131
+ conv.title = title;
3132
+ changed = true;
3133
+ }
3134
+ }
3135
+ if (changed)
3136
+ this.emitConversations();
3137
+ }
2921
3138
  /** Dismiss a running conversation from the left-panel list without deleting its
2922
3139
  * transcript file. Only idle (non-streaming) conversations that are not
2923
3140
  * retained by terminal/wake/review state can be dismissed. The session stays
@@ -449,9 +449,9 @@ export async function buildAttachmentMessages(ctx, attachments) {
449
449
  content: [
450
450
  {
451
451
  type: "text",
452
- text: `
453
- <vision-bridge>
454
- ${transcript}
452
+ text: `
453
+ <vision-bridge>
454
+ ${transcript}
455
455
  </vision-bridge>`,
456
456
  },
457
457
  ...(pathImg
@@ -291,4 +291,22 @@ export class ClientStateStore {
291
291
  delete all[clientId].projectModels;
292
292
  this.save();
293
293
  }
294
+ /** 内置标记工具开关(全局 + 按 marker 禁用)。 */
295
+ getMarkerSettings(clientId) {
296
+ const s = this.load()[clientId]?.markers;
297
+ return {
298
+ markersEnabled: s?.markersEnabled ?? true,
299
+ disabledMarkers: s?.disabledMarkers ?? [],
300
+ };
301
+ }
302
+ saveMarkerSettings(clientId, settings) {
303
+ const all = this.load();
304
+ const state = (all[clientId] ??= { projects: [] });
305
+ const cur = state.markers ?? { markersEnabled: true, disabledMarkers: [] };
306
+ state.markers = {
307
+ markersEnabled: settings.markersEnabled ?? cur.markersEnabled ?? true,
308
+ disabledMarkers: settings.disabledMarkers ?? cur.disabledMarkers ?? [],
309
+ };
310
+ this.save();
311
+ }
294
312
  }
@@ -1575,6 +1575,24 @@ export class DshClientSession {
1575
1575
  }
1576
1576
  this.emit({ type: "sessions", sessions: summaries });
1577
1577
  }
1578
+ async renameSession(_path, _name) {
1579
+ this.emit({
1580
+ type: "notice",
1581
+ level: "warning",
1582
+ text: "DSH 引擎暂不支持重命名会话",
1583
+ textEn: "The DSH engine does not support renaming sessions yet",
1584
+ });
1585
+ }
1586
+ async renameConversation(id, name) {
1587
+ const trimmed = (name ?? "").trim();
1588
+ if (!trimmed)
1589
+ return;
1590
+ const conv = this.convs.get(id);
1591
+ if (!conv)
1592
+ return;
1593
+ conv.title = trimmed;
1594
+ this.emitConversations();
1595
+ }
1578
1596
  async deleteSession(path) {
1579
1597
  try {
1580
1598
  const { rmSync } = await import("node:fs");
@@ -2022,6 +2040,9 @@ export class DshClientSession {
2022
2040
  reviewSkills: [],
2023
2041
  extensions: [],
2024
2042
  presets: this.stateStore.getPresets(this.clientId),
2043
+ markersEnabled: true,
2044
+ disabledMarkers: [],
2045
+ markers: [],
2025
2046
  subagentTemplates: [],
2026
2047
  subagentDefaultTemplates: [],
2027
2048
  };
@@ -1 +1 @@
1
- []
1
+ []