dsh-super-code 0.3.6 → 0.3.8

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 (64) hide show
  1. package/README.md +95 -63
  2. package/client/index.js +1049 -47
  3. package/cordis.patch.yml +1 -1
  4. package/docs/Changelog.md +19 -0
  5. package/docs/Compatibility.md +44 -0
  6. package/docs/EvaluationResults.md +134 -58
  7. package/docs/UsageGuide.md +112 -0
  8. package/lib/connection/directories.d.ts +30 -0
  9. package/lib/connection/directories.d.ts.map +1 -0
  10. package/lib/connection/directories.js +82 -0
  11. package/lib/connection/directories.js.map +1 -0
  12. package/lib/connection/git.d.ts +5 -0
  13. package/lib/connection/git.d.ts.map +1 -0
  14. package/lib/connection/git.js +86 -0
  15. package/lib/connection/git.js.map +1 -0
  16. package/lib/connection/http.d.ts +38 -0
  17. package/lib/connection/http.d.ts.map +1 -0
  18. package/lib/connection/http.js +275 -0
  19. package/lib/connection/http.js.map +1 -0
  20. package/lib/connection/service.d.ts +57 -0
  21. package/lib/connection/service.d.ts.map +1 -0
  22. package/lib/connection/service.js +168 -0
  23. package/lib/connection/service.js.map +1 -0
  24. package/lib/connection/store.d.ts +21 -0
  25. package/lib/connection/store.d.ts.map +1 -0
  26. package/lib/connection/store.js +139 -0
  27. package/lib/connection/store.js.map +1 -0
  28. package/lib/connection/tasks.d.ts +84 -0
  29. package/lib/connection/tasks.d.ts.map +1 -0
  30. package/lib/connection/tasks.js +270 -0
  31. package/lib/connection/tasks.js.map +1 -0
  32. package/lib/connection/types.d.ts +90 -0
  33. package/lib/connection/types.d.ts.map +1 -0
  34. package/lib/connection/types.js +21 -0
  35. package/lib/connection/types.js.map +1 -0
  36. package/lib/core/deletion-command.d.ts +3 -0
  37. package/lib/core/deletion-command.d.ts.map +1 -0
  38. package/lib/core/deletion-command.js +169 -0
  39. package/lib/core/deletion-command.js.map +1 -0
  40. package/lib/dsh/budgets.d.ts +5 -0
  41. package/lib/dsh/budgets.d.ts.map +1 -1
  42. package/lib/dsh/budgets.js +11 -0
  43. package/lib/dsh/budgets.js.map +1 -1
  44. package/lib/dsh/connection-host.d.ts +81 -0
  45. package/lib/dsh/connection-host.d.ts.map +1 -0
  46. package/lib/dsh/connection-host.js +130 -0
  47. package/lib/dsh/connection-host.js.map +1 -0
  48. package/lib/dsh/connection.d.ts +72 -0
  49. package/lib/dsh/connection.d.ts.map +1 -0
  50. package/lib/dsh/connection.js +183 -0
  51. package/lib/dsh/connection.js.map +1 -0
  52. package/lib/dsh/deletion-protection.d.ts +13 -0
  53. package/lib/dsh/deletion-protection.d.ts.map +1 -0
  54. package/lib/dsh/deletion-protection.js +68 -0
  55. package/lib/dsh/deletion-protection.js.map +1 -0
  56. package/lib/dsh/index.d.ts.map +1 -1
  57. package/lib/dsh/index.js +6 -0
  58. package/lib/dsh/index.js.map +1 -1
  59. package/lib/task-feedback-projection.d.ts +68 -0
  60. package/lib/task-feedback-projection.d.ts.map +1 -0
  61. package/lib/task-feedback-projection.js +176 -0
  62. package/lib/task-feedback-projection.js.map +1 -0
  63. package/package.json +7 -6
  64. package/presets/super-code/agent.cordis.yml +1 -1
package/client/index.js CHANGED
@@ -256,12 +256,93 @@ const ActionButton = React.forwardRef(function ActionButton({ children, title, .
256
256
 
257
257
  const LOCALE_NS = 'dshSuperCode'
258
258
  const zh = {
259
- 'settings.presetTab': '预设状态', 'settings.contextTab': '上下文配置', 'settings.tabs': 'Super Code 配置',
259
+ 'feedback.title': '任务状态',
260
+ 'feedback.refresh': '刷新任务状态',
261
+ 'feedback.noGoal': '尚无目标记录',
262
+ 'feedback.source.record': '任务记录',
263
+ 'feedback.source.request': '用户原文',
264
+ 'feedback.latest': '本轮要求',
265
+ 'feedback.progress': '步骤 {done} / {total}',
266
+ 'feedback.progressPartial': '已记录步骤 {done} / {total}',
267
+ 'feedback.steps': '步骤进度',
268
+ 'feedback.todo.pending': '待处理',
269
+ 'feedback.todo.in_progress': '进行中',
270
+ 'feedback.todo.completed': '已完成',
271
+ 'feedback.activity.running': '执行中',
272
+ 'feedback.activity.succeeded': '执行成功',
273
+ 'feedback.activity.failed': '执行失败',
274
+ 'feedback.activity.interrupted': '已中断',
275
+ 'feedback.waiting': '等待确认',
276
+ 'feedback.error': '执行出错',
277
+ 'feedback.interrupted': '已中断',
278
+ 'feedback.noSteps': '未记录步骤或工具动作',
279
+ 'feedback.loading': '正在读取记录…',
280
+ 'feedback.recentOnly': '仅展示最近动作,完整记录见详情',
281
+ 'feedback.open': '查看执行记录',
282
+ 'feedback.openFileRecord': '查看文件操作记录',
283
+ 'feedback.files': '文件变更记录',
284
+ 'feedback.filesHint': '仅含已记录的成功操作,终端修改可能缺失',
285
+ 'feedback.filesEmpty': '暂无已确认的文件操作',
286
+ 'feedback.filesUnknown': '文件记录尚不完整',
287
+ 'feedback.file.write': '写入',
288
+ 'feedback.file.edit': '编辑',
289
+ 'feedback.file.create': '新增',
290
+ 'feedback.file.patch': '补丁',
291
+ 'feedback.partial': '部分 Agent 或历史记录尚未加载,当前显示已知信息。',
292
+ 'feedback.agentsPartial': '任务条目超过显示上限,可在动作执行中查看其他 Agent。',
293
+ 'status.blocked': '受阻',
294
+
295
+ 'settings.homeTab': '首页', 'settings.presetTab': '预设配置', 'settings.protectionTab': '删除保护',
296
+ "connection.requestTitle": "请求访问工作目录",
297
+ "connection.requestHint": "外部 MCP 客户端申请访问以下目录及其子目录。同意后会加入允许列表,后续委派仍使用 DSH 的权限设置。",
298
+ "connection.requestClient": "请求客户端",
299
+ "connection.requestPath": "目录",
300
+ "connection.requestReason": "申请理由",
301
+ "connection.requestError": "操作失败,目录可能已变化或设置无法保存。请重试或拒绝。",
302
+ "connection.approveDirectory": "允许访问",
303
+ "connection.rejectDirectory": "拒绝",
304
+ "connection.error.directory-limit": "最多允许选择 32 个目录。",
305
+ "connection.error.picker-unavailable": "无法打开目录选择器,请重新读取后重试。",
306
+ 'home.protection': '保护', 'home.extensions': '扩展', 'home.configure': '配置', 'home.managePreset': '配置预设',
307
+ 'home.connectedSessions': '正在连接的对话', 'home.historySessions': '历史对话',
308
+ 'home.connections': '已连接客户端', 'home.noConnections': '暂无客户端连接', 'home.sessions': '关联会话', 'home.noSessions': '暂无关联会话',
309
+ 'home.disconnect': '断开', 'home.openSession': '打开会话', 'home.connected': '已连接', 'home.enabled': '已开启', 'home.disabled': '已关闭',
310
+ 'home.mcp': 'MCP 服务', 'home.connectionCount': '{count} / {max} 个连接', 'home.setupConnection': '配置并启用',
311
+ 'home.lastSeen': '最近活动', 'home.refresh': '刷新概览', 'home.error': '状态读取或操作失败,请刷新后重试。',
312
+ 'connection.maxConnections': '最大连接数', 'connection.connectionsHint': '按 MCP 客户端会话计数。断开或空闲 10 分钟后释放连接;已接收任务继续执行。',
313
+ 'connection.addDirectory': '添加目录', 'connection.noDirectories': '暂无目录,请通过文件管理器添加。',
314
+ 'connection.workspaceDirectory': 'DSH 工作区', 'connection.customDirectory': '添加的目录', 'connection.directoryUnavailable': '目录不可用',
315
+ 'connection.removeDirectory': '移除目录', 'connection.directoryRefresh': '刷新目录',
316
+ 'connection.directoryBrowser': '选择目录', 'connection.selectDirectory': '选择此目录', 'connection.parentDirectory': '上一级',
317
+ 'connection.emptyDirectory': '没有子目录', 'connection.browserError': '无法读取此目录,请重试或返回上一级。',
318
+ 'connection.directoryLimit': '最多允许选择 32 个目录。',
319
+ 'taskStatus.starting': '正在启动', 'taskStatus.running': '执行中', 'taskStatus.waiting_confirmation': '等待确认',
320
+ 'taskStatus.cancelling': '正在取消', 'taskStatus.finished': '已结束', 'taskStatus.failed': '失败',
321
+ 'taskStatus.cancelled': '已取消', 'taskStatus.interrupted': '已中断', 'taskStatus.unavailable': '状态不可用',
322
+ 'settings.connectionTab': '连接', 'connection.title': '连接', 'connection.enabled': '启用 MCP 连接',
323
+ 'connection.state.disabled': '连接已关闭', 'connection.state.listening': '正在监听', 'connection.state.error': '连接不可用', 'connection.state.loading': '正在读取…',
324
+ 'connection.scope': '允许外部客户端委派任务到当前 DSH。仅监听本机,使用当前模型、Super Code 预设和宿主权限。关闭连接不会停止已接收的任务。',
325
+ 'connection.autosave': '修改后自动保存', 'connection.saving': '正在保存…',
326
+ 'connection.port': '本机端口', 'connection.directories': '允许的工作目录',
327
+ 'connection.directoriesHint': '勾选允许的目录,包含其子目录。每次打开设置会同步 DSH 工作区,保留已保存的选择。',
328
+ 'connection.save': '保存连接', 'connection.cancel': '取消修改', 'connection.reload': '重新读取', 'connection.saved': '已保存连接设置。',
329
+ 'connection.client': '连接 Prompt', 'connection.copyCommand': '复制连接 Prompt', 'connection.showToken': '显示凭证', 'connection.hideToken': '隐藏凭证', 'connection.copyToken': '复制凭证', 'connection.token': '连接凭证', 'connection.rotate': '更换凭证',
330
+ 'connection.prompt': '请配置并连接 DSH Super Code MCP 服务:\n地址:{url}\n传输:Streamable HTTP\n认证:Authorization: Bearer <连接凭证>,凭证由我单独提供,请妥善保存在客户端。\n连接后先使用 dsh_sessions 按工作目录查看最近对话、状态和连接额度,由调用方选择传 taskId 复用历史,或传 newConversation: true 新建;不要默认复用最新对话。然后使用 dsh_run 委派任务、dsh_wait 查询状态或找回任务、dsh_send 补充要求、dsh_cancel 取消任务。使用稳定的 requestId 避免重复提交,只使用允许的绝对工作目录;新增目录请用 dsh_request_directory 提交申请,并等待用户在 DSH 中同意。权限确认需在 DSH 中处理;finished 仅表示执行结束,仍需验证结果。',
331
+ 'connection.tokenHint': '请将凭证安全保存在客户端。更换凭证后,旧凭证立即失效。',
332
+ 'connection.rotated': '已更换凭证,请更新客户端。', 'connection.copied': '已复制。',
333
+ 'connection.error.unavailable': '连接服务尚未就绪或当前配置目录不可写,请重新读取。', 'connection.error.invalid': '请检查端口、最大连接数和目录。启用连接至少需要一个有效目录。',
334
+ 'connection.error.conflict': '连接设置已在别处修改。草稿已保留,请重新读取。', 'connection.error.save-failed': '保存失败,草稿已保留。', 'connection.error.port-unavailable': '端口被占用或无法监听。请更换端口并保存。', 'connection.error.storage-unavailable': '任务状态保存失败,请检查配置目录。', 'connection.error.clipboard': '复制失败,请手动复制。',
335
+ 'settings.contextTab': '上下文配置', 'settings.tabs': 'Super Code 配置',
260
336
  'budget.common': '通用预算', 'budget.superCode': 'Super Code 专属预算',
261
337
  'budget.applyToOfficialPresets': '同时应用到官方预设', 'budget.officialHint': '开启后,通用预算也用于标准、PTC、极简和创造模式;不包含下方的 Super Code 专属预算。',
262
338
  'budget.superCodeHint': '仅用于 Super Code 的任务记录和任务记忆,始终不影响官方预设。',
263
339
  'budget.defaultValue': '(默认:{value})', 'budget.off': '关闭', 'budget.managed': '宿主管理',
264
- 'budget.title': '上下文配置', 'budget.scope': '保存后对新建 Agent 生效,运行中的任务保留原配置。',
340
+ 'delete.title': '删除保护', 'delete.mode': '保护模式', 'delete.off': '关闭', 'delete.relaxed': '宽松', 'delete.strict': '严格', 'delete.official': '同时应用到官方预设',
341
+ 'delete.hint.off': '不添加删除保护提示,不额外拦截命令。宿主原有权限检查仍然有效。',
342
+ 'delete.hint.relaxed': '默认使用 trash,脚本中也建议使用;用户明确要求时可以使用 rm,无需插件额外确认。',
343
+ 'delete.hint.strict': '默认使用 trash,脚本也禁止直接使用 rm。识别到永久删除命令时,仅清理临时文件或构建产物可经用户逐次二次确认放行。',
344
+ 'delete.officialHint': '默认仅保护 Super Code。此开关独立于预算的官方预设开关;保存后立即作用于后续命令。',
345
+ 'budget.title': '上下文配置', 'budget.scope': '预算保存后对新建 Agent 生效,运行中的任务保留原配置。',
265
346
  'budget.defaults': '留空沿用默认值,括号内显示默认值;通用预算随宿主版本变化。恢复默认会清除这里的覆盖,回到插件加载配置;未配置时跟随宿主。',
266
347
  'budget.context': '上下文压缩', 'budget.execution': '批量工具执行', 'budget.advanced': '更多预算',
267
348
  'budget.pressureBasis': '压缩预算基准', 'budget.inherit': '跟随默认', 'budget.host': '宿主策略', 'budget.window': '完整上下文窗口',
@@ -272,8 +353,8 @@ const zh = {
272
353
  'budget.contextHint': '保留比例需小于触发比例。较大的预算会占用更多上下文。',
273
354
  'budget.ptcHint': '包含工具等待时间;默认时限不能超过上限,也不能超过宿主允许值。',
274
355
  'budget.ptcUnavailable': '此宿主的 PTC 计时由宿主管理;其他预算仍可配置。', 'budget.ptcCeiling': '宿主允许的最长时限:{seconds} 秒。',
275
- 'budget.save': '保存预算', 'budget.reset': '恢复默认', 'budget.reload': '重新读取', 'budget.saving': '正在保存…',
276
- 'budget.saved': '已保存,对新建 Agent 生效。', 'budget.resetDone': '已恢复默认,对新建 Agent 生效。',
356
+ 'budget.save': '保存设置', 'budget.reset': '恢复默认', 'budget.reload': '重新读取', 'budget.saving': '正在保存…',
357
+ 'budget.saved': '已保存。删除保护立即生效,预算对新建 Agent 生效。', 'budget.resetDone': '已恢复默认。删除保护立即生效,预算对新建 Agent 生效。',
277
358
  'budget.error.connection': '暂时无法读取预算,请重试。', 'budget.error.invalid': '请检查数值范围、保留比例和执行时限。',
278
359
  'budget.error.retain': '保留比例必须小于压缩触发比例;未填写时分别按 16% 和 80% 校验。', 'budget.error.ptc-order': '默认执行时限不能超过执行时限上限。',
279
360
  'budget.error.conflict': '预算已在别处修改。草稿已保留,请重新读取后再修改。', 'budget.error.read-only': '当前宿主的设置只读,无法保存。',
@@ -285,7 +366,7 @@ const zh = {
285
366
  'memory.deleteTitle': '删除记忆', 'memory.deleteHint': '确定删除“{summary}”吗?删除后无法恢复。',
286
367
  'memory.writeError': '操作失败,内容已保留,请重试。', 'memory.conflict': '这条记忆已在别处更改,请取消后刷新再编辑。',
287
368
  'memory.missingEdit': '这条记忆已被删除,输入内容已保留。', 'memory.full': '该主题或记忆库已满,请删除不再需要的条目。',
288
- 'workspace.title': 'Super Code', 'workspace.description': 'Agent 执行树与记忆库', 'workspace.resize': '调整执行树与记忆库高度',
369
+ 'workspace.title': 'Super Code', 'workspace.description': '任务状态、Agent 执行树与记忆库', 'workspace.resize': '调整执行树与记忆库高度',
289
370
  'tree.collapsedLabel': '已收起', 'tree.organizeLabel': '自动整理',
290
371
  'detail.toolCall': '工具调用', 'detail.toolResult': '工具结果', 'detail.toolError': '工具失败',
291
372
  'detail.cache': '缓存', 'detail.info': '详情', 'tree.countCompact': '{shown}/{total} · 运行 {running}',
@@ -349,12 +430,93 @@ const zh = {
349
430
  'detail.path': 'Agent 路径', 'detail.title': 'Agent 详情', 'detail.refresh': '刷新对话详情', 'detail.task': '任务', 'detail.agent': 'Agent', 'detail.interrupted': 'Agent · 已中断', 'detail.error': '执行错误', 'detail.incomplete': '执行未完成,请稍后重试。', 'detail.input': '输入', 'detail.output': '输出', 'detail.cacheRead': '缓存读取', 'detail.cacheWrite': '缓存写入', 'detail.total': 'Token 总量', 'detail.hitRate': '缓存命中率', 'detail.moreUsage': '用量详情', 'detail.unavailable': '未提供', 'detail.reading': '正在读取对话…', 'detail.read': '读取中…', 'detail.older': '加载更早记录', 'detail.latest': '回到最新', 'detail.empty': '暂无执行内容', 'detail.new': '有新内容 · 查看最新', 'detail.loadError': '对话详情读取失败', 'detail.connection': '连接中断,正在重连', 'detail.historyError': '历史记录读取失败', 'detail.open': '展开全文', 'detail.noAccess': '无法打开详情,请重试', 'tree.loadError': '部分执行记录读取失败,请刷新重试', 'detail.unknown': '用量未知', 'main.agent': '主 Agent', 'tree.open': '点击查看详情', 'tree.expand': '展开', 'tree.collapse': '收起', 'image': '[图片]'
350
431
  }
351
432
  const en = {
352
- 'settings.presetTab': 'Preset status', 'settings.contextTab': 'Context settings', 'settings.tabs': 'Super Code settings',
433
+ 'feedback.title': 'Task status',
434
+ 'feedback.refresh': 'Refresh task status',
435
+ 'feedback.noGoal': 'No recorded objective',
436
+ 'feedback.source.record': 'Recorded objective',
437
+ 'feedback.source.request': 'User request',
438
+ 'feedback.latest': 'Latest request',
439
+ 'feedback.progress': 'Steps {done} / {total}',
440
+ 'feedback.progressPartial': 'Recorded steps {done} / {total}',
441
+ 'feedback.steps': 'Step progress',
442
+ 'feedback.todo.pending': 'Pending',
443
+ 'feedback.todo.in_progress': 'In progress',
444
+ 'feedback.todo.completed': 'Completed',
445
+ 'feedback.activity.running': 'Running',
446
+ 'feedback.activity.succeeded': 'Succeeded',
447
+ 'feedback.activity.failed': 'Failed',
448
+ 'feedback.activity.interrupted': 'Interrupted',
449
+ 'feedback.waiting': 'Awaiting approval',
450
+ 'feedback.error': 'Execution error',
451
+ 'feedback.interrupted': 'Interrupted',
452
+ 'feedback.noSteps': 'No recorded steps or tool activity',
453
+ 'feedback.loading': 'Loading records…',
454
+ 'feedback.recentOnly': 'Recent activity only; open details for the full record.',
455
+ 'feedback.open': 'View execution log',
456
+ 'feedback.openFileRecord': 'View file operation log',
457
+ 'feedback.files': 'File operations',
458
+ 'feedback.filesHint': 'Recorded successful operations; terminal changes may be absent.',
459
+ 'feedback.filesEmpty': 'No confirmed file operations yet',
460
+ 'feedback.filesUnknown': 'File records are incomplete',
461
+ 'feedback.file.write': 'Written',
462
+ 'feedback.file.edit': 'Edited',
463
+ 'feedback.file.create': 'Created',
464
+ 'feedback.file.patch': 'Patched',
465
+ 'feedback.partial': 'Some Agent or history records are not loaded; showing known facts.',
466
+ 'feedback.agentsPartial': 'The task list reached its limit; view other Agents in the execution tree.',
467
+ 'status.blocked': 'Blocked',
468
+
469
+ 'settings.homeTab': 'Home', 'settings.presetTab': 'Preset configuration', 'settings.protectionTab': 'Deletion protection',
470
+ "connection.requestTitle": "Working directory access request",
471
+ "connection.requestHint": "An external MCP client requests access to this directory and its descendants. Approval adds it to the allowlist. DSH permissions still apply to delegated tasks.",
472
+ "connection.requestClient": "Requesting client",
473
+ "connection.requestPath": "Directory",
474
+ "connection.requestReason": "Reason",
475
+ "connection.requestError": "Could not save this decision. The directory may have changed or settings may be unwritable. Retry or reject.",
476
+ "connection.approveDirectory": "Allow access",
477
+ "connection.rejectDirectory": "Reject",
478
+ "connection.error.directory-limit": "Select up to 32 directories.",
479
+ "connection.error.picker-unavailable": "Could not open the directory picker. Reload and try again.",
480
+ 'home.protection': 'Protection', 'home.extensions': 'Extensions', 'home.configure': 'Configure', 'home.managePreset': 'Configure preset',
481
+ 'home.connectedSessions': 'Connected conversations', 'home.historySessions': 'Past conversations',
482
+ 'home.connections': 'Connected clients', 'home.noConnections': 'No clients connected', 'home.sessions': 'Linked sessions', 'home.noSessions': 'No linked sessions',
483
+ 'home.disconnect': 'Disconnect', 'home.openSession': 'Open session', 'home.connected': 'Connected', 'home.enabled': 'On', 'home.disabled': 'Off',
484
+ 'home.mcp': 'MCP service', 'home.connectionCount': '{count} / {max} connections', 'home.setupConnection': 'Set up connection',
485
+ 'home.lastSeen': 'Last activity', 'home.refresh': 'Refresh overview', 'home.error': 'Could not read or update the status. Refresh and try again.',
486
+ 'connection.maxConnections': 'Maximum connections', 'connection.connectionsHint': 'Counts MCP client sessions. Disconnect or 10 minutes of inactivity releases a connection; accepted tasks continue.',
487
+ 'connection.addDirectory': 'Add directory', 'connection.noDirectories': 'No directories. Add one with the directory picker.',
488
+ 'connection.workspaceDirectory': 'DSH workspace', 'connection.customDirectory': 'Added directory', 'connection.directoryUnavailable': 'Directory unavailable',
489
+ 'connection.removeDirectory': 'Remove directory', 'connection.directoryRefresh': 'Refresh directories',
490
+ 'connection.directoryBrowser': 'Choose directory', 'connection.selectDirectory': 'Select this directory', 'connection.parentDirectory': 'Parent directory',
491
+ 'connection.emptyDirectory': 'No subdirectories', 'connection.browserError': 'Could not read this directory. Retry or return to its parent.',
492
+ 'connection.directoryLimit': 'Select up to 32 directories.',
493
+ 'taskStatus.starting': 'Starting', 'taskStatus.running': 'Running', 'taskStatus.waiting_confirmation': 'Waiting for confirmation',
494
+ 'taskStatus.cancelling': 'Cancelling', 'taskStatus.finished': 'Finished', 'taskStatus.failed': 'Failed',
495
+ 'taskStatus.cancelled': 'Cancelled', 'taskStatus.interrupted': 'Interrupted', 'taskStatus.unavailable': 'Unavailable',
496
+ 'settings.connectionTab': 'Connection', 'connection.title': 'Connection', 'connection.enabled': 'Enable MCP connection',
497
+ 'connection.state.disabled': 'Connection off', 'connection.state.listening': 'Listening', 'connection.state.error': 'Connection unavailable', 'connection.state.loading': 'Loading…',
498
+ 'connection.scope': 'Let external clients delegate tasks to this DSH host using its model, Super Code preset and permissions. Listens on localhost only. Disabling the connection does not stop accepted tasks.',
499
+ 'connection.autosave': 'Changes save automatically', 'connection.saving': 'Saving…',
500
+ 'connection.port': 'Local port', 'connection.directories': 'Allowed working directories',
501
+ 'connection.directoriesHint': 'Select allowed directories, including their subdirectories. Reopening settings refreshes DSH workspaces and retains saved selections.',
502
+ 'connection.save': 'Save connection', 'connection.cancel': 'Cancel changes', 'connection.reload': 'Reload', 'connection.saved': 'Connection settings saved.',
503
+ 'connection.client': 'Connection Prompt', 'connection.copyCommand': 'Copy connection prompt', 'connection.showToken': 'Show token', 'connection.hideToken': 'Hide token', 'connection.copyToken': 'Copy token', 'connection.token': 'Connection token', 'connection.rotate': 'Rotate token',
504
+ 'connection.prompt': 'Configure and connect to the DSH Super Code MCP service:\nURL: {url}\nTransport: Streamable HTTP\nAuthentication: Authorization: Bearer <connection token>. I will supply the token separately; store it securely in the client.\nAfter connecting, call dsh_sessions for the working directory to show recent conversations, statuses and connection capacity. Let the caller choose whether to pass taskId to reuse history or newConversation: true to create a new root; never default to the latest conversation. Then use dsh_run to delegate, dsh_wait to observe or recover, dsh_send for follow-ups, and dsh_cancel to cancel. Use stable requestId values to avoid duplicate submissions and only allowed absolute working directories. Request additional access with dsh_request_directory and wait for user approval in DSH. Resolve permissions in DSH. A finished task still requires result verification.',
505
+ 'connection.tokenHint': 'Store the token securely in the client. Rotation invalidates the previous token immediately.',
506
+ 'connection.rotated': 'Token rotated. Update your client.', 'connection.copied': 'Copied.',
507
+ 'connection.error.unavailable': 'The service is not ready or the profile directory is not writable. Reload to retry.', 'connection.error.invalid': 'Check the port, maximum connections and directories. Enabling requires at least one valid directory.',
508
+ 'connection.error.conflict': 'Connection settings changed elsewhere. Your draft is retained; reload to continue.', 'connection.error.save-failed': 'Save failed. Your draft is retained.', 'connection.error.port-unavailable': 'The port is occupied or cannot be opened. Save with another port.', 'connection.error.storage-unavailable': 'Task state could not be saved. Check the profile directory.', 'connection.error.clipboard': 'Copy failed. Copy the text manually.',
509
+ 'settings.contextTab': 'Context settings', 'settings.tabs': 'Super Code settings',
353
510
  'budget.common': 'Common budgets', 'budget.superCode': 'Super Code budgets',
354
511
  'budget.applyToOfficialPresets': 'Also apply to official presets', 'budget.officialHint': 'Apply common budgets to Standard, PTC, Minimal and Create. Super Code task and memory limits below remain exclusive to Super Code.',
355
512
  'budget.superCodeHint': 'Task records and task memory for Super Code. These limits never affect official presets.',
356
513
  'budget.defaultValue': '(default: {value})', 'budget.off': 'off', 'budget.managed': 'host-managed',
357
- 'budget.title': 'Context settings', 'budget.scope': 'Changes apply to new Agents. Running tasks keep their current settings.',
514
+ 'delete.title': 'Deletion protection', 'delete.mode': 'Protection mode', 'delete.off': 'Off', 'delete.relaxed': 'Relaxed', 'delete.strict': 'Strict', 'delete.official': 'Also apply to official presets',
515
+ 'delete.hint.off': 'Adds no deletion instructions or command interception. Host permission checks still apply.',
516
+ 'delete.hint.relaxed': 'Prefer trash, including in scripts. Explicit user requests may use rm without extra plugin confirmation.',
517
+ 'delete.hint.strict': 'Prefer trash; scripts must not directly use rm. Recognized permanent deletion requires separate approval for each temporary-file or build-artifact cleanup.',
518
+ 'delete.officialHint': 'Protects Super Code by default. This switch is independent of official-preset budgets; saved changes apply to subsequent commands immediately.',
519
+ 'budget.title': 'Context settings', 'budget.scope': 'Budget changes apply to new Agents. Running tasks keep their current settings.',
358
520
  'budget.defaults': 'Leave fields blank to inherit defaults. Parentheses show defaults; common budgets follow the host version. Reset clears overrides and restores the plugin configuration, or host defaults when unconfigured.',
359
521
  'budget.context': 'Context compression', 'budget.execution': 'Batched tool execution', 'budget.advanced': 'More budgets',
360
522
  'budget.pressureBasis': 'Compression budget basis', 'budget.inherit': 'Inherit default', 'budget.host': 'Host policy', 'budget.window': 'Full context window',
@@ -365,8 +527,8 @@ const en = {
365
527
  'budget.contextHint': 'Retention must be below the compression threshold. Larger budgets use more context.',
366
528
  'budget.ptcHint': 'Includes tool waits. The default deadline must fit within both the configured maximum and the host ceiling.',
367
529
  'budget.ptcUnavailable': 'This host manages PTC timing. Other budgets remain configurable.', 'budget.ptcCeiling': 'Host deadline ceiling: {seconds} seconds.',
368
- 'budget.save': 'Save budgets', 'budget.reset': 'Reset to defaults', 'budget.reload': 'Reload settings', 'budget.saving': 'Saving…',
369
- 'budget.saved': 'Saved. New Agents will use these settings.', 'budget.resetDone': 'Defaults restored for new Agents.',
530
+ 'budget.save': 'Save settings', 'budget.reset': 'Reset to defaults', 'budget.reload': 'Reload settings', 'budget.saving': 'Saving…',
531
+ 'budget.saved': 'Saved. Deletion protection applies immediately; budgets apply to new Agents.', 'budget.resetDone': 'Defaults restored. Deletion protection applies immediately; budgets apply to new Agents.',
370
532
  'budget.error.connection': 'Budgets could not be loaded. Try again.', 'budget.error.invalid': 'Check the ranges, retention ratio, and deadlines.',
371
533
  'budget.error.retain': 'Retention must be below the compression threshold. Blank values are checked as 16% and 80%, respectively.', 'budget.error.ptc-order': 'The default deadline must not exceed the maximum deadline.',
372
534
  'budget.error.conflict': 'Budgets changed elsewhere. Your draft is retained; reload before editing again.', 'budget.error.read-only': 'This host has read-only settings.',
@@ -378,7 +540,7 @@ const en = {
378
540
  'memory.deleteTitle': 'Delete memory', 'memory.deleteHint': 'Delete “{summary}”? This cannot be undone.',
379
541
  'memory.writeError': 'The operation failed. Your input is retained; try again.', 'memory.conflict': 'This memory changed elsewhere. Cancel and refresh before editing again.',
380
542
  'memory.missingEdit': 'This memory was deleted. Your input is retained.', 'memory.full': 'This topic or library is full. Remove entries you no longer need.',
381
- 'workspace.title': 'Super Code', 'workspace.description': 'Agent tree and memory library', 'workspace.resize': 'Resize agent tree and memory library',
543
+ 'workspace.title': 'Super Code', 'workspace.description': 'Task status, Agent tree and memory library', 'workspace.resize': 'Resize agent tree and memory library',
382
544
  'tree.collapsedLabel': 'Collapsed', 'tree.organizeLabel': 'Organize',
383
545
  'detail.toolCall': 'Tool call', 'detail.toolResult': 'Tool result', 'detail.toolError': 'Tool failed',
384
546
  'detail.cache': 'Cache', 'detail.info': 'Details', 'tree.countCompact': '{shown}/{total} · Running {running}',
@@ -763,12 +925,299 @@ function AgentTree({ useSessions, useSessionStatus, sessionId, useTabInfo, treeS
763
925
  return React.createElement(AgentTreeBody, { key: view.root, state, view, saved, openDetail, refresh, watchCatalog, onToggle, t })
764
926
  }
765
927
 
928
+ /** Join only the selected conversation's known descendants; never infer a plan. */
929
+ function taskFeedbackRows(view) {
930
+ const { rows, more } = visibleAgentRows(view, () => true, view.nodes.size)
931
+ const tasks = rows.map(row => {
932
+ const node = view.nodes.get(row.id), projections = node.projectionValues || {}, facts = projections.superCodeFeedback
933
+ const current = projections.superCodeTasks?.current
934
+ const recorded = current?.kind === 'task' ? current : undefined
935
+ const goal = recorded?.goal || (facts?.goal?.kind === 'recorded' ? facts.goal.text : '')
936
+ const title = recorded?.title || goal || facts?.request || ''
937
+ const todos = facts?.todos || projections.todos || []
938
+ return { ...row, node, facts, title, goal, source: recorded || facts?.goal?.kind === 'recorded' ? 'record' : facts?.request ? 'request' : 'none',
939
+ owner: node.title || node.displayTitle || '', todos, done: todos.filter(item => item.status === 'completed').length,
940
+ total: todos.length, complete: !facts?.omittedTodos, taskStatus: recorded?.status || (facts?.goal?.kind === 'recorded' ? facts.goal.status : '') }
941
+ })
942
+ const files = new Map()
943
+ // File identity remains independent of the virtual viewport.
944
+ for (const node of view.nodes.values()) for (const file of node.projectionValues?.superCodeFeedback?.files || []) {
945
+ const row = files.get(file.path) || { path: file.path, operations: new Set(), sources: [] }
946
+ row.operations.add(file.operation); row.sources.push({ node, seq: file.seq, tool: file.tool })
947
+ files.set(file.path, row)
948
+ }
949
+ return { tasks, more, files: [...files.values()].sort((a, b) => a.path.localeCompare(b.path)).map(file => ({ ...file, ...feedbackFileLabel(file) })),
950
+ partial: [...view.nodes.values()].some(node => !node.projectionValues?.superCodeFeedback || node.projectionValues.superCodeFeedback.omittedFiles) }
951
+ }
952
+ /** Presentation only: retain the full path while shortening workspace-owned labels. */
953
+ function feedbackFileLabel(file) {
954
+ const path = file.path.replace(/\\/g, '/')
955
+ const cwd = file.sources.map(source => source.node.cwd || source.node.projectionValues?.workingDirectory || '')
956
+ .find(value => value && path.startsWith(value.replace(/\\/g, '/').replace(/\/$/, '') + '/'))
957
+ const relative = cwd ? path.slice(cwd.replace(/\\/g, '/').replace(/\/$/, '').length + 1) : path
958
+ const slash = relative.lastIndexOf('/')
959
+ return { name: relative.slice(slash + 1) || relative, directory: slash < 0 ? '' : relative.slice(0, slash) }
960
+ }
961
+ function FeedbackMark({ tone, label }) {
962
+ return React.createElement('span', { className: 'dsh-super-code-feedback-mark ' + tone, role: 'img', 'aria-label': label },
963
+ tone === 'success' && React.createElement('svg', { viewBox: '0 0 16 16', fill: 'none', stroke: 'currentColor', strokeWidth: 1.6, 'aria-hidden': true },
964
+ React.createElement('path', { d: 'm3.5 8 3 3 6-6', strokeLinecap: 'round', strokeLinejoin: 'round' })),
965
+ tone === 'error' && React.createElement('span', { 'aria-hidden': true }, '!'))
966
+ }
967
+ function FeedbackActivity({ activity, t }) {
968
+ const tone = { running: 'active', failed: 'error', succeeded: 'success', interrupted: 'idle' }[activity.status] || 'idle'
969
+ return React.createElement('div', { className: 'dsh-super-code-feedback-activity', 'data-activity-state': activity.status },
970
+ React.createElement(FeedbackMark, { tone, label: text(t, 'feedback.activity.' + activity.status) }),
971
+ React.createElement('span', { className: 'dsh-super-code-feedback-activity-text', title: activity.description || activity.tool }, activity.description || activity.tool),
972
+ activity.description && React.createElement('small', { className: 'dsh-super-code-feedback-tool', title: activity.tool }, activity.tool),
973
+ ['failed', 'interrupted'].includes(activity.status) && React.createElement('small', null, text(t, 'feedback.activity.' + activity.status)))
974
+ }
975
+ /** Flatten presentation rows only; no task, step, or loaded activity is folded. */
976
+ function feedbackListItems(model, root, partial) {
977
+ const items = []
978
+ const add = (kind, key, data = {}, row) => items.push({ kind, key: JSON.stringify(key), row, ...data,
979
+ signature: JSON.stringify([kind, data.content, data.todo, data.activity, data.label, data.count,
980
+ data.file && [data.file.name, data.file.directory, [...data.file.operations]], kind === 'task' &&
981
+ [row.title, row.owner, row.done, row.total, row.complete, row.taskStatus, row.facts?.phase, row.node.running]]) })
982
+ for (const row of model.tasks) {
983
+ const facts = row.facts
984
+ add('task', [row.id, 'task'], {}, row)
985
+ if (row.goal && row.goal !== row.title) add('text', [row.id, 'goal'], { content: row.goal }, row)
986
+ if (facts?.latestRequest && facts.latestRequest !== facts.request) add('request', [row.id, 'request'], { content: facts.latestRequest }, row)
987
+ row.todos.forEach((todo, index) => add('step', [row.id, 'step', index], { todo }, row))
988
+ for (const activity of facts?.activities || []) add('activity', [row.id, 'activity', activity.callId, activity.seq], { activity }, row)
989
+ if (!row.total && !facts?.activities?.length) add('note', [row.id, 'empty'], { label: facts ? 'feedback.noSteps' : 'feedback.loading' }, row)
990
+ if (facts?.omittedActivities) add('note', [row.id, 'recent'], { label: 'feedback.recentOnly' }, row)
991
+ add('open', [row.id, 'open'], { focusable: !row.node.unavailable }, row)
992
+ }
993
+ if (root) {
994
+ add('files', ['files'], { count: model.files.length })
995
+ add('note', ['filesHint'], { label: 'feedback.filesHint' })
996
+ if (!model.files.length) add('note', ['filesEmpty'], { label: partial ? 'feedback.filesUnknown' : 'feedback.filesEmpty' })
997
+ for (const file of model.files) add('file', ['file', file.path], { file, focusable: !file.sources[0].node.unavailable })
998
+ }
999
+ if (partial) add('note', ['partial'], { label: 'feedback.partial' })
1000
+ return items
1001
+ }
1002
+
1003
+ /** Prefix heights allow binary-search windows without mounting offscreen rows. */
1004
+ function feedbackListLayout(items, heights = new Map()) {
1005
+ const estimates = { task: 64, text: 48, request: 70, step: 28, activity: 30, open: 34, files: 40, file: 30, note: 26 }
1006
+ const offsets = [0], byKey = new Map()
1007
+ items.forEach((item, index) => {
1008
+ const measured = heights.get(item.key)
1009
+ const height = measured?.signature === item.signature ? measured.height : estimates[item.kind]
1010
+ offsets.push(offsets[index] + Math.max(1, height)); byKey.set(item.key, index)
1011
+ })
1012
+ return { offsets, byKey, height: offsets.at(-1) }
1013
+ }
1014
+ function feedbackListIndex(layout, top) {
1015
+ let low = 0, high = layout.offsets.length - 1
1016
+ while (low < high) {
1017
+ const middle = Math.floor((low + high) / 2)
1018
+ if (layout.offsets[middle + 1] <= top) low = middle + 1; else high = middle
1019
+ }
1020
+ return Math.min(low, Math.max(0, layout.offsets.length - 2))
1021
+ }
1022
+ function feedbackListWindow(layout, scrollTop, height) {
1023
+ const top = Math.max(0, Math.min(scrollTop, Math.max(0, layout.height - height)))
1024
+ return { top, start: feedbackListIndex(layout, Math.max(0, top - 240)),
1025
+ end: layout.height ? Math.min(layout.offsets.length - 1, feedbackListIndex(layout, top + height + 240) + 1) : 0 }
1026
+ }
1027
+ /** Keep the same visible fact in place when earlier rows resize or arrive. */
1028
+ function feedbackListAnchor(previous, next, scrollTop, height) {
1029
+ if (!previous || !previous.layout.height) return Math.max(0, Math.min(scrollTop, Math.max(0, next.layout.height - height)))
1030
+ const index = feedbackListIndex(previous.layout, scrollTop), key = previous.items[index]?.key
1031
+ const nextIndex = next.layout.byKey.get(key)
1032
+ const top = nextIndex === undefined ? scrollTop : next.layout.offsets[nextIndex] + Math.min(
1033
+ Math.max(0, scrollTop - previous.layout.offsets[index]), next.layout.offsets[nextIndex + 1] - next.layout.offsets[nextIndex] - 1)
1034
+ return Math.max(0, Math.min(top, Math.max(0, next.layout.height - height)))
1035
+ }
1036
+ function FeedbackRowContent({ item, root, openDetail, t }) {
1037
+ const row = item.row
1038
+ if (item.kind === 'task') {
1039
+ const status = feedbackStatus({ ...row, root }, t)
1040
+ const progressLabel = text(t, row.complete ? 'feedback.progress' : 'feedback.progressPartial', { done: row.done, total: row.total })
1041
+ return React.createElement('div', { className: 'dsh-super-code-feedback-task-heading' },
1042
+ React.createElement('div', { className: 'dsh-super-code-feedback-heading' },
1043
+ React.createElement('h3', { title: row.source === 'none' ? '' : text(t, 'feedback.source.' + row.source) }, row.title || text(t, 'feedback.noGoal')),
1044
+ React.createElement('div', { className: 'dsh-super-code-feedback-meta' },
1045
+ React.createElement('small', null, row.owner || (row.id === root ? text(t, 'main.agent') : row.id)),
1046
+ React.createElement('span', { className: 'dsh-super-code-feedback-status', 'data-tone': status.tone }, status.label))),
1047
+ row.total > 0 && React.createElement('span', { className: 'dsh-super-code-feedback-count', 'aria-label': progressLabel, title: progressLabel }, `${row.done}/${row.total}${row.complete ? '' : '+'}`),
1048
+ row.total > 0 && row.complete && React.createElement('progress', { max: row.total, value: row.done, 'aria-label': text(t, 'feedback.steps') }))
1049
+ }
1050
+ if (item.kind === 'step') return React.createElement('div', { className: 'dsh-super-code-feedback-step', 'data-step-state': item.todo.status },
1051
+ React.createElement(FeedbackMark, { tone: item.todo.status === 'completed' ? 'success' : item.todo.status === 'in_progress' ? 'active' : 'pending', label: text(t, 'feedback.todo.' + item.todo.status) }),
1052
+ React.createElement('span', null, item.todo.content))
1053
+ if (item.kind === 'activity') return React.createElement(FeedbackActivity, { activity: item.activity, t })
1054
+ if (item.kind === 'open') return React.createElement(ActionButton, { type: 'button', className: 'dsh-super-code-feedback-open', disabled: !item.focusable,
1055
+ title: text(t, 'feedback.open'), onClick: () => openDetail(row.node) }, text(t, 'feedback.open'), React.createElement(Glyph, { type: 'arrow' }))
1056
+ if (item.kind === 'files') return React.createElement('h3', { className: 'dsh-super-code-feedback-files-heading' }, text(t, 'feedback.files'), React.createElement('span', null, item.count))
1057
+ if (item.kind === 'file') {
1058
+ const file = item.file
1059
+ return React.createElement(ActionButton, { type: 'button', className: 'dsh-super-code-feedback-file', title: file.path,
1060
+ 'aria-label': text(t, 'feedback.openFileRecord') + ' ' + file.path, disabled: !item.focusable, onClick: () => openDetail(file.sources[0].node) },
1061
+ React.createElement(Glyph, { type: 'code' }), React.createElement('strong', null, file.name),
1062
+ file.directory && React.createElement('small', null, file.directory),
1063
+ React.createElement('span', { className: 'dsh-super-code-feedback-file-operation' }, [...file.operations].map(operation => text(t, 'feedback.file.' + operation)).join(' · ')))
1064
+ }
1065
+ if (item.kind === 'note') return React.createElement('p', { className: 'dsh-super-code-feedback-muted', role: item.label === 'tree.loadError' ? 'alert' : undefined }, text(t, item.label))
1066
+ return React.createElement('p', { className: 'dsh-super-code-feedback-text' },
1067
+ item.kind === 'request' && React.createElement('small', null, text(t, 'feedback.latest')), item.content)
1068
+ }
1069
+
1070
+ /** One measured, variable-height viewport for tasks, steps, activity and files. */
1071
+ function FeedbackVirtualList({ items, root, saved, openDetail, t, watchCatalog }) {
1072
+ const viewport = useRef(null), list = useRef(null), previous = useRef(null), pendingFocus = useRef('')
1073
+ const language = localeCode(t)
1074
+ const [scroll, setScroll] = useState({ top: saved.feedbackTop || 0, height: 640, width: saved.feedbackWidth || 0 })
1075
+ const measurements = useRef(saved.feedbackHeights || new Map())
1076
+ const [heightRevision, setHeightRevision] = useState(0)
1077
+ const [focused, setFocused] = useState('')
1078
+ const layout = useMemo(() => feedbackListLayout(items, measurements.current), [items, heightRevision])
1079
+ const range = feedbackListWindow(layout, scroll.top, scroll.height)
1080
+ const indices = Array.from({ length: range.end - range.start }, (_, index) => range.start + index)
1081
+ const focusIndex = layout.byKey.get(focused)
1082
+ if (focusIndex !== undefined && !indices.includes(focusIndex)) indices.push(focusIndex)
1083
+ indices.sort((a, b) => a - b)
1084
+ const renderKey = indices.join(',')
1085
+ const watched = new Set(root ? [root] : [])
1086
+ for (const index of indices) if (items[index].row) watched.add(items[index].row.id)
1087
+ useLayoutEffect(() => {
1088
+ const element = viewport.current
1089
+ const measure = () => {
1090
+ const width = element.clientWidth
1091
+ if (width !== saved.feedbackWidth || language !== saved.feedbackLanguage) {
1092
+ saved.feedbackWidth = width; saved.feedbackLanguage = language
1093
+ saved.feedbackHeights = new Map(); measurements.current = saved.feedbackHeights; setHeightRevision(value => value + 1)
1094
+ }
1095
+ setScroll(value => value.top === element.scrollTop && value.height === element.clientHeight && value.width === width
1096
+ ? value : { top: element.scrollTop, height: element.clientHeight, width })
1097
+ }
1098
+ element.scrollTop = saved.feedbackTop || 0; measure()
1099
+ const observer = new ResizeObserver(measure); observer.observe(element)
1100
+ return () => observer.disconnect()
1101
+ }, [saved, language])
1102
+ useLayoutEffect(() => {
1103
+ const element = viewport.current
1104
+ const top = feedbackListAnchor(previous.current, { items, layout }, previous.current?.top ?? saved.feedbackTop ?? 0, element.clientHeight)
1105
+ element.scrollTop = top; saved.feedbackTop = element.scrollTop
1106
+ previous.current = { items, layout, top: element.scrollTop }
1107
+ setScroll(value => value.top === element.scrollTop ? value : { ...value, top: element.scrollTop })
1108
+ // Retain only current identities; long-running sessions must not leak measurements.
1109
+ for (const key of measurements.current.keys()) if (!layout.byKey.has(key)) measurements.current.delete(key)
1110
+ saved.feedbackHeights = measurements.current
1111
+ }, [items, layout, saved])
1112
+ useLayoutEffect(() => {
1113
+ const rows = [...list.current.children]
1114
+ let active = true
1115
+ const measure = () => {
1116
+ if (!active) return
1117
+ const current = measurements.current
1118
+ let next = current
1119
+ for (const element of rows) {
1120
+ const key = element.dataset.feedbackKey, item = items[layout.byKey.get(key)], height = element.getBoundingClientRect().height
1121
+ if (!item || height <= 0 || current.get(key)?.signature === item.signature && Math.abs(current.get(key).height - height) < .5) continue
1122
+ if (next === current) next = new Map(current)
1123
+ next.set(key, { signature: item.signature, height })
1124
+ }
1125
+ if (next === current) return
1126
+ measurements.current = next; saved.feedbackHeights = next
1127
+ setHeightRevision(value => value + 1)
1128
+ }
1129
+ measure()
1130
+ const observer = new ResizeObserver(measure); rows.forEach(element => observer.observe(element))
1131
+ return () => { active = false; observer.disconnect() }
1132
+ }, [items, renderKey, scroll.width, language, saved])
1133
+ useLayoutEffect(() => {
1134
+ if (!pendingFocus.current) return
1135
+ const element = [...list.current.children].find(row => row.dataset.feedbackKey === pendingFocus.current)
1136
+ const button = element?.querySelector('button')
1137
+ if (button) { button.focus({ preventScroll: true }); pendingFocus.current = '' }
1138
+ }, [renderKey, items, layout, focused])
1139
+ const tab = event => {
1140
+ if (event.key !== 'Tab') return
1141
+ const current = layout.byKey.get(focused)
1142
+ if (current === undefined) return
1143
+ const direction = event.shiftKey ? -1 : 1
1144
+ for (let index = current + direction; index >= 0 && index < items.length; index += direction) {
1145
+ if (!items[index].focusable) continue
1146
+ event.preventDefault(); pendingFocus.current = items[index].key; setFocused(items[index].key)
1147
+ const top = Math.max(0, Math.min(layout.offsets[index], Math.max(0, layout.height - viewport.current.clientHeight)))
1148
+ viewport.current.scrollTop = top; saved.feedbackTop = top
1149
+ if (previous.current) previous.current.top = top
1150
+ setScroll(value => ({ ...value, top })); return
1151
+ }
1152
+ }
1153
+ return React.createElement('div', { ref: viewport, className: 'dsh-super-code-feedback-scroll', tabIndex: 0, 'aria-label': text(t, 'feedback.title'), onKeyDown: tab,
1154
+ onScroll: event => {
1155
+ const top = event.currentTarget.scrollTop; saved.feedbackTop = top
1156
+ if (previous.current) previous.current.top = top
1157
+ setScroll(value => value.top === top ? value : { ...value, top })
1158
+ }, onFocusCapture: event => setFocused(event.target.closest('[data-feedback-key]')?.dataset.feedbackKey || ''),
1159
+ onBlurCapture: event => { if (!event.currentTarget.contains(event.relatedTarget)) setFocused('') } },
1160
+ [...watched].map(id => React.createElement(AgentCatalogWatch, { key: id, id, watchCatalog })),
1161
+ React.createElement('ol', { ref: list, className: 'dsh-super-code-feedback-list', 'aria-label': text(t, 'feedback.title'), style: { height: layout.height }, 'data-feedback-total': items.length },
1162
+ indices.map(index => {
1163
+ const item = items[index], row = item.row, header = item.kind === 'task'
1164
+ const indent = row ? Math.min(Math.max(0, row.level - 1), 4) * 16 + (header ? 0 : 14) : 0
1165
+ return React.createElement('li', { key: item.key, 'data-feedback-key': item.key, 'data-feedback-kind': item.kind,
1166
+ 'data-feedback-agent': row?.id, 'data-feedback-depth': row?.level, 'data-feedback-live': row ? row.node.running === true || row.facts?.phase === 'running' : undefined,
1167
+ 'aria-posinset': index + 1, 'aria-setsize': items.length,
1168
+ style: { transform: `translateY(${layout.offsets[index]}px)`, paddingLeft: indent, '--sc-feedback-indent': indent + 'px' } }, React.createElement(FeedbackRowContent, { item, root, openDetail, t }))
1169
+ })))
1170
+ }
1171
+ function feedbackStatus(row, t = key => zh[key] || key) {
1172
+ if (row.node.unavailable) return { tone: 'unavailable', label: text(t, 'tree.legend.unavailable') }
1173
+ const phase = row.facts?.phase
1174
+ if (row.node.running === true) return { tone: 'active', label: text(t, phase === 'waiting' ? 'feedback.waiting' : 'tree.legend.active') }
1175
+ if (phase === 'error' || phase === 'interrupted') return { tone: 'error', label: text(t, `feedback.${phase}`) }
1176
+ if (['completed', 'cancelled', 'paused', 'blocked'].includes(row.taskStatus)) return { tone: row.taskStatus === 'completed' ? 'success' : 'idle', label: text(t, `status.${row.taskStatus}`) }
1177
+ // Host inactivity or a completed turn does not establish task completion.
1178
+ return agentExecutionStatus(row.node, row.id === row.root, t)
1179
+ }
1180
+ function TaskFeedback({ useSessions, useSessionStatus, sessionId, useTabInfo, treeStates, openDetail, refresh, watchCatalog, onToggle, t }) {
1181
+ const catalog = useSessions(s => s), statuses = useSessionStatus ? useSessionStatus(s => s) : undefined
1182
+ const state = useMemo(() => agentSessionState(catalog, sessionId, statuses), [catalog, sessionId, statuses])
1183
+ const view = useMemo(() => buildAgentView(state), [state.byId, state.subagentsByParent, state.current, state.currentAddress])
1184
+ const { tab } = useTabInfo()
1185
+ const saved = useMemo(() => {
1186
+ let value = treeStates.get(tab.signal)
1187
+ if (!value || value.root !== view.root) { value = { root: view.root, limit: 200, focused: state.current, expanded: new Set(), collapsed: new Set() }; treeStates.set(tab.signal, value) }
1188
+ return value
1189
+ }, [treeStates, tab.signal, view.root])
1190
+ const [busy, setBusy] = useState(false), [error, setError] = useState(false)
1191
+ const model = useMemo(() => taskFeedbackRows(view), [view])
1192
+ const partial = model.partial || Object.values(state.subagentsByParent || {}).some(value => value.state === 'error')
1193
+ const items = useMemo(() => {
1194
+ const rows = feedbackListItems(model, view.root, partial)
1195
+ if (!view.root) rows.unshift({ kind: 'note', key: 'empty', label: 'tree.empty', signature: 'empty' })
1196
+ if (error) rows.unshift({ kind: 'note', key: 'error', label: 'tree.loadError', signature: 'error' })
1197
+ return rows
1198
+ }, [model, view.root, partial, error])
1199
+ const reload = async () => {
1200
+ setBusy(true); setError(false)
1201
+ try {
1202
+ const ids = [...view.nodes.keys()]
1203
+ for (let start = 0; start < ids.length; start += 20) await Promise.all(ids.slice(start, start + 20).map(id => refresh(id)))
1204
+ }
1205
+ catch { setError(true) }
1206
+ finally { setBusy(false) }
1207
+ }
1208
+ return React.createElement('section', { className: 'dsh-super-code-feedback', 'data-task-feedback': true },
1209
+ React.createElement('header', null,
1210
+ React.createElement(WorkspaceSectionToggle, { section: 'tasks', expanded: true, onToggle, t }),
1211
+ React.createElement(ActionButton, { type: 'button', className: 'dsh-super-code-icon-button', title: text(t, 'feedback.refresh'), 'aria-label': text(t, 'feedback.refresh'), disabled: busy || !view.root, onClick: () => void reload() }, React.createElement(IconRefreshOutline16))),
1212
+ React.createElement(FeedbackVirtualList, { key: view.root, items, root: view.root, saved, openDetail, t, watchCatalog }))
1213
+ }
1214
+
766
1215
  function WorkspaceSectionToggle({ section, expanded, onToggle, children, t }) {
767
- const label = text(t, section === 'tree' ? 'tree.title' : 'memory.title')
1216
+ const label = text(t, section === 'tasks' ? 'feedback.title' : section === 'tree' ? 'tree.title' : 'memory.title')
768
1217
  return React.createElement(ActionButton, { type: 'button', className: 'dsh-super-code-section-toggle',
769
1218
  'data-workspace-section': section, 'aria-label': label, 'aria-expanded': expanded, onClick: onToggle },
770
1219
  React.createElement(Glyph, { type: 'chevron' }),
771
- React.createElement(Glyph, { type: section === 'tree' ? 'branch' : 'memory' }),
1220
+ React.createElement(Glyph, { type: section === 'tasks' ? 'context' : section === 'tree' ? 'branch' : 'memory' }),
772
1221
  React.createElement('strong', null, label), children)
773
1222
  }
774
1223
 
@@ -779,7 +1228,7 @@ function SuperCodeWorkspace(props) {
779
1228
  const navigationKey = `${address}/${tab.navigation?.revision || 0}`
780
1229
  const memoryTarget = memoryLibraryTarget(address, '')
781
1230
  const saved = useMemo(() => {
782
- const value = props.workspaceStates.get(tab.signal) || { active: 'tree' }
1231
+ const value = props.workspaceStates.get(tab.signal) || { active: 'tasks' }
783
1232
  if (value.navigationKey !== navigationKey) {
784
1233
  value.navigationKey = navigationKey
785
1234
  if (memoryTarget.id) value.active = 'memory'
@@ -787,10 +1236,10 @@ function SuperCodeWorkspace(props) {
787
1236
  props.workspaceStates.set(tab.signal, value)
788
1237
  return value
789
1238
  }, [props.workspaceStates, tab.signal])
790
- const [active, setActive] = useState(saved.active === 'memory' ? 'memory' : 'tree')
1239
+ const [active, setActive] = useState(['tasks', 'tree', 'memory'].includes(saved.active) ? saved.active : 'tasks')
791
1240
  const layout = useRef(null), focusSection = useRef('')
792
- const toggle = () => {
793
- const next = active === 'tree' ? 'memory' : 'tree'
1241
+ const toggle = section => {
1242
+ const next = active === section ? section === 'tasks' ? 'tree' : 'tasks' : section
794
1243
  saved.active = next; focusSection.current = next; setActive(next)
795
1244
  }
796
1245
  useLayoutEffect(() => {
@@ -805,14 +1254,13 @@ function SuperCodeWorkspace(props) {
805
1254
  }, [active])
806
1255
  return React.createElement('section', { className: 'dsh-super-code-workspace', 'aria-label': text(props.t, 'workspace.title') },
807
1256
  React.createElement('div', { ref: layout, className: 'dsh-super-code-workspace-layout', 'data-active-section': active, style: {
808
- gridTemplateRows: active === 'tree' ? 'minmax(280px, 1fr) 44px' : '44px minmax(280px, 1fr)',
809
- } },
810
- active === 'tree' ? React.createElement(AgentTree, { ...props, key: 'tree', onToggle: toggle })
811
- : React.createElement('header', { key: 'tree-collapsed', className: 'dsh-super-code-workspace-collapsed' },
812
- React.createElement(WorkspaceSectionToggle, { section: 'tree', expanded: false, onToggle: toggle, t: props.t })),
813
- active === 'memory' ? React.createElement(MemoryLibrary, { ...props, key: 'memory', onToggle: toggle })
814
- : React.createElement('header', { key: 'memory-collapsed', className: 'dsh-super-code-workspace-collapsed' },
815
- React.createElement(WorkspaceSectionToggle, { section: 'memory', expanded: false, onToggle: toggle, t: props.t }))))
1257
+ gridTemplateRows: ['tasks', 'tree', 'memory'].map(section => active === section ? 'minmax(280px, 1fr)' : '44px').join(' '),
1258
+ } }, ['tasks', 'tree', 'memory'].map(section => {
1259
+ const onToggle = () => toggle(section)
1260
+ return active === section ? React.createElement(section === 'tasks' ? TaskFeedback : section === 'tree' ? AgentTree : MemoryLibrary, { ...props, key: section, onToggle })
1261
+ : React.createElement('header', { key: section + '-collapsed', className: 'dsh-super-code-workspace-collapsed' },
1262
+ React.createElement(WorkspaceSectionToggle, { section, expanded: false, onToggle, t: props.t }))
1263
+ })))
816
1264
  }
817
1265
 
818
1266
  function AgentCatalogWatch({ id, watchCatalog }) {
@@ -1588,6 +2036,14 @@ const budgetRemoteContribution = {
1588
2036
  })),
1589
2037
  }
1590
2038
 
2039
+ const connectionRemoteContribution = {
2040
+ package: 'dsh-super-code', descriptors: ['readConfig', 'saveConfig', 'revealToken', 'rotateToken', 'overview', 'disconnectClient', 'directories', 'pickDirectory', 'browseDirectory', 'pendingDirectories', 'resolveDirectory'].map(method => ({
2041
+ id: `dsh-super-code:superCodeConnection/${method}`, service: 'superCodeConnection', namespace: 'superCodeConnection', method, invocation: { kind: 'direct' },
2042
+ parameters: (method === 'saveConfig' ? ['content', 'expected'] : method === 'rotateToken' ? ['expected'] : method === 'resolveDirectory' ? ['id', 'decision'] : method === 'disconnectClient' ? ['id'] : method === 'browseDirectory' ? ['path'] : [])
2043
+ .map(name => ({ name, wire: name, source: 'json', codec: remoteStringCodec })), result: { mode: 'src-json' },
2044
+ })),
2045
+ }
2046
+
1591
2047
  const budgetFields = [
1592
2048
  { key: 'thresholdRatio', group: 'context', factor: 0.01, min: 1, max: 100, step: 'any' },
1593
2049
  { key: 'retainRatio', group: 'context', factor: 0.01, min: 0, max: 100, step: 'any' },
@@ -1600,7 +2056,8 @@ const budgetFields = [
1600
2056
  { key: 'maxContextBytes', group: 'superCode', root: true, factor: 1, min: 1024, max: 262144, step: 1 },
1601
2057
  ]
1602
2058
  function budgetDraft(configuration) {
1603
- return Object.fromEntries([['applyToOfficialPresets', configuration.applyToOfficialPresets ?? false], ['pressureBasis', configuration.budgets?.pressureBasis ?? ''],
2059
+ const deletion = configuration.deletionProtection
2060
+ return Object.fromEntries([['deletionProtection', deletion === true ? 'strict' : deletion === false ? 'off' : deletion ?? 'relaxed'], ['deletionProtectionForOfficialPresets', configuration.deletionProtectionForOfficialPresets ?? false], ['applyToOfficialPresets', configuration.applyToOfficialPresets ?? false], ['pressureBasis', configuration.budgets?.pressureBasis ?? ''],
1604
2061
  ...budgetFields.map(field => {
1605
2062
  const value = field.root ? configuration[field.key] : configuration.budgets?.[field.key]
1606
2063
  return [field.key, value === undefined ? '' : String(value / field.factor)]
@@ -1608,6 +2065,14 @@ function budgetDraft(configuration) {
1608
2065
  }
1609
2066
  function budgetConfiguration(draft) {
1610
2067
  const config = {}, budgets = {}
2068
+ if (draft.deletionProtection !== undefined) {
2069
+ if (!['off', 'relaxed', 'strict'].includes(draft.deletionProtection)) return { ok: false, error: 'invalid' }
2070
+ config.deletionProtection = draft.deletionProtection
2071
+ }
2072
+ if (draft.deletionProtectionForOfficialPresets !== undefined) {
2073
+ if (typeof draft.deletionProtectionForOfficialPresets !== 'boolean') return { ok: false, error: 'invalid' }
2074
+ config.deletionProtectionForOfficialPresets = draft.deletionProtectionForOfficialPresets
2075
+ }
1611
2076
  if (draft.applyToOfficialPresets !== undefined) {
1612
2077
  if (typeof draft.applyToOfficialPresets !== 'boolean') return { ok: false, error: 'invalid' }
1613
2078
  config.applyToOfficialPresets = draft.applyToOfficialPresets
@@ -1632,7 +2097,7 @@ function budgetConfiguration(draft) {
1632
2097
  return { ok: true, configuration: config }
1633
2098
  }
1634
2099
 
1635
- function BudgetSettings({ api, t, active }) {
2100
+ function BudgetSettings({ api, t, active, section = 'context', onSaved = () => {} }) {
1636
2101
  const [snapshot, setSnapshot] = useState(null)
1637
2102
  const [draft, setDraft] = useState(() => budgetDraft({})), [busy, setBusy] = useState(false)
1638
2103
  const [error, setError] = useState(''), [notice, setNotice] = useState('')
@@ -1649,24 +2114,33 @@ function BudgetSettings({ api, t, active }) {
1649
2114
  finally { pending.current = false; if (alive.current) setBusy(false) }
1650
2115
  }
1651
2116
  useEffect(() => { alive.current = true; return () => { alive.current = false } }, [api])
1652
- useEffect(() => { if (active && !snapshot && !error) read() }, [api, active])
2117
+ useEffect(() => { if (active && (!snapshot || !dirty) && !error) read() }, [api, active])
1653
2118
  const save = async reset => {
1654
2119
  if (pending.current || !snapshot?.writable) return
1655
- const parsed = reset ? { ok: true } : budgetConfiguration(draft)
2120
+ const protection = section === 'protection'
2121
+ const parsed = budgetConfiguration(draft)
2122
+ if (parsed.ok) {
2123
+ const selected = protection
2124
+ ? { ...snapshot.configuration, deletionProtection: reset ? 'relaxed' : draft.deletionProtection, deletionProtectionForOfficialPresets: reset ? false : draft.deletionProtectionForOfficialPresets }
2125
+ : { ...(reset ? {} : parsed.configuration), deletionProtection: snapshot.configuration.deletionProtection, deletionProtectionForOfficialPresets: snapshot.configuration.deletionProtectionForOfficialPresets }
2126
+ parsed.configuration = selected
2127
+ }
1656
2128
  if (!parsed.ok) { setError(parsed.error); setNotice(''); return }
1657
2129
  pending.current = true; setBusy(true); setError(''); setNotice('')
1658
2130
  try {
1659
- const response = reset ? await api.resetConfig(String(snapshot.revision))
1660
- : await api.saveConfig(JSON.stringify(parsed.configuration), String(snapshot.revision))
2131
+ const response = await api.saveConfig(JSON.stringify(parsed.configuration), String(snapshot.revision))
1661
2132
  if (!response.ok) throw new Error('Remote unavailable')
1662
2133
  if (!alive.current) return
1663
- if (response.value.ok) { receive(response.value.snapshot); setNotice(reset ? 'resetDone' : 'saved') }
2134
+ if (response.value.ok) { receive(response.value.snapshot); onSaved(); setNotice(reset ? 'resetDone' : 'saved') }
1664
2135
  else setError(response.value.error)
1665
2136
  } catch { if (alive.current) setError('save-failed') }
1666
2137
  finally { pending.current = false; if (alive.current) setBusy(false) }
1667
2138
  }
1668
2139
  const disabled = busy || !snapshot?.writable
1669
- const dirty = snapshot && JSON.stringify(draft) !== JSON.stringify(budgetDraft(snapshot.configuration))
2140
+ const sectionDraft = value => section === 'protection'
2141
+ ? [value.deletionProtection, value.deletionProtectionForOfficialPresets]
2142
+ : Object.fromEntries(Object.entries(value).filter(([key]) => !key.startsWith('deletionProtection')))
2143
+ const dirty = snapshot && JSON.stringify(sectionDraft(draft)) !== JSON.stringify(sectionDraft(budgetDraft(snapshot.configuration)))
1670
2144
  const edit = (key, value) => { setDraft(current => ({ ...current, [key]: value })); setError(''); setNotice('') }
1671
2145
  const defaultLabel = (key, field) => {
1672
2146
  const value = field?.root ? snapshot?.defaults?.[key] : snapshot?.defaults?.budgets?.[key]
@@ -1683,7 +2157,21 @@ function BudgetSettings({ api, t, active }) {
1683
2157
  onChange: event => edit(field.key, event.target.value) }))
1684
2158
  })
1685
2159
  return React.createElement('section', { className: 'dsh-super-code-budgets' },
1686
- React.createElement('form', { 'aria-label': t('budget.title'), 'aria-busy': busy, onSubmit: event => { event.preventDefault(); save(false) } },
2160
+ React.createElement('form', { 'aria-label': t(section === 'protection' ? 'delete.title' : 'budget.title'), 'aria-busy': busy, onSubmit: event => { event.preventDefault(); save(false) } },
2161
+ section === 'protection' && React.createElement('section', { className: 'dsh-super-code-budget-group', 'aria-labelledby': 'super-code-deletion-title' },
2162
+ React.createElement('h3', { id: 'super-code-deletion-title' }, t('delete.title')),
2163
+ React.createElement('div', { className: 'dsh-super-code-settings-field' },
2164
+ React.createElement('label', { id: 'super-code-deletion-mode-label' }, t('delete.mode')),
2165
+ React.createElement(SelectControl, { id: 'super-code-deletion-protection', label: t('delete.mode'), value: draft.deletionProtection,
2166
+ choices: ['off', 'relaxed', 'strict'].map(mode => [mode, t(`delete.${mode}`)]), disabled,
2167
+ onChange: value => edit('deletionProtection', value) })),
2168
+ React.createElement('p', { id: 'super-code-deletion-hint', className: 'dsh-super-code-budget-hint' }, t(`delete.hint.${draft.deletionProtection}`)),
2169
+ React.createElement('label', { className: 'dsh-super-code-budget-switch' },
2170
+ React.createElement('input', { id: 'super-code-deletion-official', type: 'checkbox', role: 'switch', checked: draft.deletionProtectionForOfficialPresets, disabled: disabled || draft.deletionProtection === 'off',
2171
+ 'aria-describedby': 'super-code-deletion-official-hint', onChange: event => edit('deletionProtectionForOfficialPresets', event.target.checked) }),
2172
+ React.createElement('span', null, t('delete.official'))),
2173
+ React.createElement('p', { id: 'super-code-deletion-official-hint', className: 'dsh-super-code-budget-hint' }, t('delete.officialHint'))),
2174
+ section !== 'protection' && React.createElement(React.Fragment, null,
1687
2175
  React.createElement('p', { className: 'dsh-super-code-budget-hint' }, t('budget.scope')),
1688
2176
  React.createElement('section', { className: 'dsh-super-code-budget-group', 'aria-labelledby': 'super-code-common-title' },
1689
2177
  React.createElement('h3', { id: 'super-code-common-title' }, t('budget.common')),
@@ -1711,7 +2199,7 @@ function BudgetSettings({ api, t, active }) {
1711
2199
  React.createElement('section', { className: 'dsh-super-code-budget-group dsh-super-code-budget-exclusive', 'aria-labelledby': 'super-code-exclusive-title' },
1712
2200
  React.createElement('h3', { id: 'super-code-exclusive-title' }, t('budget.superCode')),
1713
2201
  React.createElement('p', { className: 'dsh-super-code-budget-hint' }, t('budget.superCodeHint')),
1714
- React.createElement('div', { className: 'dsh-super-code-budget-grid' }, fields('superCode'))),
2202
+ React.createElement('div', { className: 'dsh-super-code-budget-grid' }, fields('superCode')))),
1715
2203
  !busy && snapshot && !snapshot.writable && React.createElement('small', null, t('budget.error.read-only')),
1716
2204
  error && React.createElement('p', { role: 'alert' }, t(`budget.error.${error}`)),
1717
2205
  notice && React.createElement('p', { role: 'status', className: 'dsh-super-code-budget-hint' }, t(`budget.${notice}`)),
@@ -1761,8 +2249,364 @@ function synchronizePresetDisplay(api, locale, reload, onError, preferenceScope)
1761
2249
  return () => { disposed = true; unsubscribe(); unsubscribePreference() }
1762
2250
  }
1763
2251
 
1764
- function PresetSettings({ api, budgetApi, t }) {
1765
- const [tab, setTab] = useState('preset')
2252
+ function connectionDraft(config) {
2253
+ return { enabled: config.enabled, port: String(config.port), allowedDirectories: [...config.allowedDirectories], knownDirectories: [...config.knownDirectories ?? []],
2254
+ maxConcurrent: config.maxConnections ?? 3, maxConnections: String(config.maxConnections ?? 3), directoriesConfigured: config.directoriesConfigured ?? false }
2255
+ }
2256
+ function connectionConfiguration(draft) {
2257
+ const port = Number(draft.port), maxConnections = Number(draft.maxConnections)
2258
+ const allowedDirectories = [...new Set(draft.allowedDirectories)]
2259
+ if (typeof draft.enabled !== 'boolean' || !Number.isInteger(port) || port < 1024 || port > 65535 || !Number.isInteger(maxConnections) || maxConnections < 1 || maxConnections > 32 || allowedDirectories.length > 32 || allowedDirectories.some(path => typeof path !== 'string' || !path) || (draft.enabled && !allowedDirectories.length)) return { ok: false }
2260
+ return { ok: true, configuration: { enabled: draft.enabled, port, allowedDirectories, maxConcurrent: maxConnections, maxConnections, directoriesConfigured: true, knownDirectories: draft.knownDirectories } }
2261
+ }
2262
+ /** Serialize writes and retain newer edits while an earlier revision is being saved. */
2263
+ function createConnectionAutosave(api) {
2264
+ let state = { snapshot: false, draft: connectionDraft({ enabled: false, port: 23457, allowedDirectories: [] }), saving: false, error: '', notice: '', saveCount: 0 }
2265
+ let version = 0, acknowledged = 0, blocked = -1, timer = false, running = false
2266
+ const listeners = new Set()
2267
+ const update = patch => { state = { ...state, ...patch }; for (const listener of listeners) listener() }
2268
+ const flush = () => {
2269
+ if (timer) { clearTimeout(timer); timer = false }
2270
+ if (running) return running
2271
+ if (version === acknowledged || version === blocked || !state.snapshot) return Promise.resolve()
2272
+ running = (async () => {
2273
+ update({ saving: true, error: '', notice: '' })
2274
+ try {
2275
+ while (acknowledged < version) {
2276
+ const sent = version, parsed = connectionConfiguration(state.draft)
2277
+ if (!parsed.ok) { blocked = version; update({ error: 'invalid' }); break }
2278
+ const result = await api.saveConfig(JSON.stringify(parsed.configuration), String(state.snapshot.revision))
2279
+ if (!result.ok) throw new Error('Remote unavailable')
2280
+ if (!result.value.ok) { blocked = version; update({ error: result.value.error }); break }
2281
+ acknowledged = sent
2282
+ update({ snapshot: result.value.snapshot, saveCount: state.saveCount + 1,
2283
+ ...(sent === version ? { draft: connectionDraft(result.value.snapshot.configuration), notice: 'saved' } : {}) })
2284
+ }
2285
+ } catch { blocked = version; update({ error: 'save-failed' }) }
2286
+ finally { update({ saving: false }) }
2287
+ })().finally(() => { running = false })
2288
+ return running
2289
+ }
2290
+ return {
2291
+ getSnapshot: () => state,
2292
+ subscribe: listener => { listeners.add(listener); return () => listeners.delete(listener) },
2293
+ hasChanges: () => version !== acknowledged || state.saving,
2294
+ receive: (snapshot, draft = connectionDraft(snapshot.configuration)) => {
2295
+ acknowledged = ++version
2296
+ update({ snapshot, draft, error: '', notice: '' })
2297
+ },
2298
+ revision: snapshot => update({ snapshot }),
2299
+ change: (patch, delay = 0) => {
2300
+ version++
2301
+ update({ draft: { ...state.draft, ...patch }, error: '', notice: '' })
2302
+ if (timer) { clearTimeout(timer); timer = false }
2303
+ if (delay) timer = setTimeout(() => { timer = false; void flush() }, delay)
2304
+ else void flush()
2305
+ },
2306
+ flush,
2307
+ }
2308
+ }
2309
+ function ConnectionSwitch({ checked, disabled, onChange, children }) {
2310
+ return React.createElement('label', { className: 'dsh-super-code-connection-switch' },
2311
+ React.createElement('input', { type: 'checkbox', role: 'switch', checked, disabled, onChange: event => onChange(event.target.checked) }),
2312
+ React.createElement('span', { className: 'dsh-super-code-connection-track', 'aria-hidden': true }, React.createElement('i')), children)
2313
+ }
2314
+ function DirectoryBrowser({ api, t, onSelect, onClose }) {
2315
+ const [listing, setListing] = useState(null), [busy, setBusy] = useState(false), [error, setError] = useState(false)
2316
+ const dialog = useRef(null), generation = useRef(0), lastPath = useRef('')
2317
+ const browse = async path => {
2318
+ const version = ++generation.current; lastPath.current = path; setBusy(true); setError(false)
2319
+ try { const result = await api.browseDirectory(path); if (!result.ok) throw new Error(); if (version === generation.current) setListing(result.value) }
2320
+ catch { if (version === generation.current) setError(true) }
2321
+ finally { if (version === generation.current) setBusy(false) }
2322
+ }
2323
+ useEffect(() => {
2324
+ const element = dialog.current; element.showModal(); browse('')
2325
+ return () => { generation.current++; if (element.open) element.close() }
2326
+ }, [api])
2327
+ return React.createElement('dialog', { ref: dialog, className: 'dsh-super-code-confirm', 'aria-labelledby': 'super-code-directory-title',
2328
+ onCancel: event => { event.preventDefault(); event.stopPropagation(); onClose() }, onKeyDown: event => event.stopPropagation() },
2329
+ React.createElement('h3', { id: 'super-code-directory-title' }, t('connection.directoryBrowser')),
2330
+ React.createElement('nav', { className: 'dsh-super-code-directory-crumbs', 'aria-label': t('connection.directoryBrowser') },
2331
+ listing?.crumbs.map((row, index) => React.createElement(ActionButton, { key: `${row.path}-${index}`, type: 'button', disabled: busy, onClick: () => browse(row.path) }, row.name || row.path))),
2332
+ listing && React.createElement('code', { className: 'dsh-super-code-directory-path' }, listing.path),
2333
+ React.createElement('div', { className: 'dsh-super-code-directory-browser', 'aria-busy': busy },
2334
+ listing && listing.crumbs.length > 1 && React.createElement(ActionButton, { type: 'button', disabled: busy, onClick: () => browse(listing.crumbs[listing.crumbs.length - 2].path) }, '↑ ', t('connection.parentDirectory')),
2335
+ listing?.entries.map(row => React.createElement(ActionButton, { key: row.path, type: 'button', disabled: busy, onClick: () => browse(row.path) }, React.createElement(Glyph, { type: 'folder' }), row.name)),
2336
+ listing && !listing.entries.length && React.createElement('p', null, t('connection.emptyDirectory'))),
2337
+ error && React.createElement('p', { role: 'alert' }, t('connection.browserError')),
2338
+ React.createElement('div', { className: 'dsh-super-code-confirm-actions' },
2339
+ error && React.createElement(ActionButton, { type: 'button', disabled: busy, onClick: () => browse(lastPath.current) }, t('connection.reload')),
2340
+ React.createElement(ActionButton, { type: 'button', onClick: onClose }, t('preset.cancel')),
2341
+ React.createElement(ActionButton, { type: 'button', className: 'dsh-super-code-primary-button', disabled: busy || error || !listing,
2342
+ onClick: () => onSelect(listing.path) }, t('connection.selectDirectory'))))
2343
+ }
2344
+ function connectionSessions(overview) {
2345
+ const online = new Set(overview.connections.map(row => row.id)), connected = [], history = []
2346
+ for (const row of overview.sessions) (row.connectionId && online.has(row.connectionId) ? connected : history).push(row)
2347
+ return { connected, history }
2348
+ }
2349
+ function ConnectionActivity({ api, t, active, openSession }) {
2350
+ const [overview, setOverview] = useState(null), [busy, setBusy] = useState(false), [error, setError] = useState(false)
2351
+ const alive = useRef(false), pending = useRef(false)
2352
+ const load = async () => {
2353
+ if (pending.current) return
2354
+ pending.current = true
2355
+ try { const result = await api.overview(); if (!result.ok) throw new Error(); if (alive.current) { setOverview(result.value); setError(false) } }
2356
+ catch { if (alive.current) setError(true) }
2357
+ finally { pending.current = false }
2358
+ }
2359
+ useEffect(() => { alive.current = true; return () => { alive.current = false } }, [api])
2360
+ useEffect(() => { if (!active) return; load(); const timer = setInterval(load, 2000); return () => clearInterval(timer) }, [active, api])
2361
+ const disconnect = async id => {
2362
+ if (pending.current) return
2363
+ pending.current = true; setBusy(true)
2364
+ try { const result = await api.disconnectClient(id); if (!result.ok) throw new Error() }
2365
+ catch { if (alive.current) setError(true); return }
2366
+ finally { pending.current = false; if (alive.current) setBusy(false) }
2367
+ if (alive.current) load()
2368
+ }
2369
+ const groups = overview ? connectionSessions(overview) : { connected: [], history: [] }
2370
+ return React.createElement('section', { className: 'dsh-super-code-connection-activity', 'aria-label': t('home.sessions') },
2371
+ React.createElement('h3', null, t('home.connections')),
2372
+ !overview ? React.createElement('p', { role: 'status' }, t('connection.state.loading'))
2373
+ : overview.connections.length ? React.createElement('ul', { className: 'dsh-super-code-connection-list' }, overview.connections.map(row => React.createElement('li', { key: row.id },
2374
+ React.createElement('span', null, React.createElement('strong', null, row.name), React.createElement('small', null, row.version, ' · ', t('home.lastSeen'), ' ', new Date(row.lastSeen).toLocaleTimeString())),
2375
+ React.createElement(ActionButton, { type: 'button', disabled: busy, onClick: () => disconnect(row.id) }, t('home.disconnect'))))) : React.createElement('p', null, t('home.noConnections')),
2376
+ React.createElement('h3', null, t('home.sessions')),
2377
+ ...['connected', 'history'].map(kind => React.createElement('div', { key: kind, 'data-session-group': kind },
2378
+ React.createElement('h4', null, t(kind === 'connected' ? 'home.connectedSessions' : 'home.historySessions'), ' · ', overview ? groups[kind].length : '—'),
2379
+ groups[kind].length ? React.createElement('ul', { className: 'dsh-super-code-connection-list' }, groups[kind].map(row => React.createElement('li', { key: row.taskId },
2380
+ React.createElement('span', null, React.createElement('strong', null, row.client || row.sessionId), React.createElement('small', null, t(`taskStatus.${row.status}`), ' · ', row.cwd), React.createElement('code', null, row.sessionId)),
2381
+ React.createElement(ActionButton, { type: 'button', onClick: () => openSession(row.sessionId) }, t('home.openSession'))))) : overview && React.createElement('p', null, t('home.noSessions')))),
2382
+ error && React.createElement('p', { role: 'alert' }, t('home.error')))
2383
+ }
2384
+
2385
+ function ConnectionSettings({ api, t, active, openSession, onSaved = () => {} }) {
2386
+ const editor = useMemo(() => createConnectionAutosave(api), [api])
2387
+ const editing = useSyncExternalStore(editor.subscribe, editor.getSnapshot, editor.getSnapshot)
2388
+ const { snapshot, draft, saving } = editing
2389
+ const [catalog, setCatalog] = useState({ items: [], picker: 'unavailable' }), [custom, setCustom] = useState([]), [browser, setBrowser] = useState(false)
2390
+ const [busy, setBusy] = useState(false), [error, setError] = useState(''), [notice, setNotice] = useState(''), [token, setToken] = useState('')
2391
+ const alive = useRef(false), pending = useRef(false), visible = useRef(active)
2392
+ visible.current = active
2393
+ const dirty = editor.hasChanges()
2394
+ const receive = (value, directories = catalog) => {
2395
+ const next = connectionDraft(value.configuration)
2396
+ if (!next.directoriesConfigured && !next.allowedDirectories.length) next.allowedDirectories = directories.items.filter(row => row.workspace && row.available).slice(0, 32).map(row => row.path)
2397
+ editor.receive(value, next)
2398
+ }
2399
+ const load = async (discard = false) => {
2400
+ if (pending.current || (discard && editor.getSnapshot().saving)) return
2401
+ pending.current = true; setBusy(true); setError('')
2402
+ try {
2403
+ const [config, directories, credential] = await Promise.all([api.readConfig(), api.directories(), api.revealToken()])
2404
+ if (!config.ok || !directories.ok || !credential.ok) throw new Error()
2405
+ if (!alive.current) return
2406
+ setCatalog(directories.value)
2407
+ if (discard || !dirty) receive(config.value, directories.value)
2408
+ if (visible.current) setToken(credential.value.token)
2409
+ } catch { if (alive.current) setError('unavailable') }
2410
+ finally { pending.current = false; if (alive.current) setBusy(false) }
2411
+ }
2412
+ const action = async kind => {
2413
+ if (pending.current) return
2414
+ pending.current = true; setBusy(true); setError(''); setNotice('')
2415
+ try {
2416
+ const response = kind === 'rotate' ? await api.rotateToken(String(snapshot.revision)) : await api.revealToken()
2417
+ if (!response.ok) throw new Error()
2418
+ if (!alive.current) return
2419
+ if (kind === 'token') { if (visible.current) setToken(response.value.token) }
2420
+ else if (response.value.ok) {
2421
+ if (kind === 'rotate') {
2422
+ editor.revision(response.value.snapshot)
2423
+ const credential = await api.revealToken()
2424
+ if (credential.ok && alive.current && visible.current) setToken(credential.value.token)
2425
+ } else receive(response.value.snapshot)
2426
+ onSaved(); setNotice(kind === 'rotate' ? 'rotated' : 'saved')
2427
+ } else setError(response.value.error)
2428
+ } catch { if (alive.current) setError('unavailable') }
2429
+ finally { pending.current = false; if (alive.current) setBusy(false) }
2430
+ }
2431
+ useEffect(() => { alive.current = true; return () => { alive.current = false; void editor.flush() } }, [editor])
2432
+ useEffect(() => { if (editing.saveCount) onSaved() }, [editing.saveCount])
2433
+ useEffect(() => { if (active) load(); else { setToken(''); setBrowser(false) } }, [api, active])
2434
+ useEffect(() => {
2435
+ if (!active) return
2436
+ const changed = () => { if (dirty) setError('conflict'); else load(true) }
2437
+ window.addEventListener('super-code-directories-changed', changed)
2438
+ return () => window.removeEventListener('super-code-directories-changed', changed)
2439
+ }, [active, dirty, api])
2440
+ const disabled = busy || !snapshot?.writable
2441
+ const change = (patch, delay = 0) => {
2442
+ // Revoking the last directory also stops admission instead of retaining its old grant.
2443
+ editor.change(patch.allowedDirectories?.length === 0 ? { ...patch, enabled: false } : patch, delay)
2444
+ setNotice(''); setError('')
2445
+ }
2446
+ const edit = (key, value) => change({ [key]: value }, ['port', 'maxConnections'].includes(key) ? 350 : 0)
2447
+ const choose = path => {
2448
+ if (!draft.allowedDirectories.includes(path) && draft.allowedDirectories.length >= 32) { setError('directory-limit'); return }
2449
+ setCustom(rows => [...new Set([...rows, path])]); change({ allowedDirectories: [...new Set([...draft.allowedDirectories, path])], knownDirectories: [...new Set([...draft.knownDirectories, path])].slice(-128) }); setNotice(''); setError(''); setBrowser(false)
2450
+ }
2451
+ const addDirectory = async () => {
2452
+ if (pending.current) return
2453
+ if (catalog.picker !== 'native' && !globalThis.__DSH_DIRECTORY_PICKER__?.pick) { setBrowser(true); return }
2454
+ pending.current = true; setBusy(true)
2455
+ try {
2456
+ const result = globalThis.__DSH_DIRECTORY_PICKER__?.pick ? await globalThis.__DSH_DIRECTORY_PICKER__.pick()
2457
+ : await api.pickDirectory().then(result => { if (!result.ok) throw new Error(); return result.value.kind === 'selected' ? result.value.path : null })
2458
+ if (alive.current && result) choose(result)
2459
+ } catch { if (alive.current) setError('picker-unavailable') }
2460
+ finally { pending.current = false; if (alive.current) setBusy(false) }
2461
+ }
2462
+ const rows = [...catalog.items]
2463
+ for (const path of [...custom, ...draft.knownDirectories, ...draft.allowedDirectories]) if (!rows.some(row => row.path === path)) rows.push({ path, title: '', available: true, workspace: false })
2464
+ const copy = async value => {
2465
+ try { await navigator.clipboard.writeText(value); if (alive.current) { setNotice('copied'); setError('') } }
2466
+ catch { if (alive.current) setError('clipboard') }
2467
+ }
2468
+ const command = snapshot?.url ? t('connection.prompt', { url: snapshot.url }) : ''
2469
+ return React.createElement('section', { className: 'dsh-super-code-connection' },
2470
+ React.createElement('div', { className: 'dsh-super-code-settings-status-row' },
2471
+ React.createElement('p', { className: 'dsh-super-code-preset-status', role: 'status', 'data-available': snapshot?.state === 'listening' },
2472
+ React.createElement('i', { 'aria-hidden': true }), t(`connection.state.${snapshot?.state || 'loading'}`)),
2473
+ React.createElement('div', { className: 'dsh-super-code-settings-actions' }, React.createElement(ActionButton, { type: 'button', disabled: busy, onClick: () => load(true) }, t('connection.reload')))),
2474
+ snapshot?.error && React.createElement('p', { role: 'alert' }, t(`connection.error.${snapshot.error}`)),
2475
+ React.createElement(ConnectionActivity, { api, t, active, openSession }),
2476
+ React.createElement('form', { 'aria-label': t('connection.title'), 'aria-busy': saving, onSubmit: event => { event.preventDefault(); void editor.flush() } },
2477
+ React.createElement(ConnectionSwitch, { checked: draft.enabled, disabled, onChange: value => edit('enabled', value) }, t('connection.enabled')),
2478
+ React.createElement('p', { className: 'dsh-super-code-connection-hint' }, t('connection.scope')),
2479
+ React.createElement('div', { className: 'dsh-super-code-connection-grid' },
2480
+ ...[['port', 1024, 65535], ['maxConnections', 1, 32]].map(([key, min, max]) => React.createElement('div', { key, className: 'dsh-super-code-settings-field' },
2481
+ React.createElement('label', { htmlFor: `super-code-connection-${key}` }, t(`connection.${key}`)),
2482
+ React.createElement('input', { id: `super-code-connection-${key}`, type: 'number', min, max, step: 1, required: true, value: draft[key], disabled, onChange: event => edit(key, event.target.value), onBlur: () => { void editor.flush() } })))),
2483
+ React.createElement('small', null, t('connection.connectionsHint')),
2484
+ React.createElement('fieldset', { className: 'dsh-super-code-directory-fieldset' },
2485
+ React.createElement('legend', null, t('connection.directories')),
2486
+ React.createElement('div', { className: 'dsh-super-code-directory-list' },
2487
+ ...rows.map(row => React.createElement('div', { key: row.path, className: 'dsh-super-code-directory-row' },
2488
+ React.createElement('label', null,
2489
+ React.createElement('input', { type: 'checkbox', checked: draft.allowedDirectories.includes(row.path), disabled: disabled || (!row.available && !draft.allowedDirectories.includes(row.path)),
2490
+ onChange: event => {
2491
+ if (event.target.checked && draft.allowedDirectories.length >= 32) { setError('directory-limit'); return }
2492
+ edit('allowedDirectories', event.target.checked ? [...draft.allowedDirectories, row.path] : draft.allowedDirectories.filter(path => path !== row.path))
2493
+ } }),
2494
+ React.createElement('span', null, row.title && React.createElement('strong', null, row.title), React.createElement('code', null, row.path),
2495
+ React.createElement('small', { 'data-unavailable': !row.available }, t(!row.available ? 'connection.directoryUnavailable' : row.workspace ? 'connection.workspaceDirectory' : 'connection.customDirectory')))),
2496
+ !row.workspace && React.createElement(ActionButton, { type: 'button', disabled, 'aria-label': `${t('connection.removeDirectory')}: ${row.path}`,
2497
+ onClick: () => { setCustom(values => values.filter(path => path !== row.path)); setCatalog(value => ({ ...value, items: value.items.filter(item => item.path !== row.path) })); change({ knownDirectories: draft.knownDirectories.filter(path => path !== row.path), allowedDirectories: draft.allowedDirectories.filter(path => path !== row.path) }) } }, t('preset.remove')))),
2498
+ !rows.length && React.createElement('p', null, t('connection.noDirectories'))),
2499
+ React.createElement('div', { className: 'dsh-super-code-settings-actions' },
2500
+ React.createElement(ActionButton, { type: 'button', disabled: disabled || catalog.picker === 'unavailable', onClick: addDirectory }, t('connection.addDirectory'))),
2501
+ React.createElement('small', null, t('connection.directoriesHint'))),
2502
+ React.createElement('p', { role: 'status', className: 'dsh-super-code-connection-hint', 'data-connection-save': saving ? 'saving' : editing.error ? 'error' : editing.notice === 'saved' ? 'saved' : 'idle' },
2503
+ t(saving ? 'connection.saving' : editing.notice === 'saved' ? 'connection.saved' : 'connection.autosave'))),
2504
+ snapshot?.url && React.createElement('div', { className: 'dsh-super-code-connection-client' },
2505
+ React.createElement('h3', null, t('connection.client')), React.createElement('code', null, snapshot.url), React.createElement('pre', null, command),
2506
+ React.createElement('div', { className: 'dsh-super-code-settings-actions' },
2507
+ React.createElement(ActionButton, { type: 'button', disabled: busy, onClick: () => copy(command) }, t('connection.copyCommand')),
2508
+ React.createElement(ActionButton, { type: 'button', disabled, onClick: () => token ? setToken('') : action('token') }, t(token ? 'connection.hideToken' : 'connection.showToken')),
2509
+ active && token && React.createElement(ActionButton, { type: 'button', onClick: () => copy(token) }, t('connection.copyToken')),
2510
+ React.createElement(ActionButton, { type: 'button', disabled: disabled || dirty, onClick: () => action('rotate') }, t('connection.rotate'))),
2511
+ active && token && React.createElement('input', { className: 'dsh-super-code-connection-token', 'aria-label': t('connection.token'), value: token, readOnly: true, autoComplete: 'off', spellCheck: false }),
2512
+ React.createElement('small', null, t('connection.tokenHint'))),
2513
+ notice && React.createElement('p', { role: 'status' }, t(`connection.${notice}`)),
2514
+ (error || editing.error) && React.createElement('p', { role: 'alert' }, t(`connection.error.${error || editing.error}`)),
2515
+ browser && React.createElement(DirectoryBrowser, { api, t, onSelect: choose, onClose: () => setBrowser(false) }))
2516
+ }
2517
+
2518
+ function SettingsHome({ budgetApi, connectionApi, t, active, revision, navigate, children }) {
2519
+ const [budget, setBudget] = useState(null), [connection, setConnection] = useState(null), [overview, setOverview] = useState({ connections: [], sessions: [] })
2520
+ const [busy, setBusy] = useState(false), [error, setError] = useState(false), alive = useRef(false), pending = useRef(false)
2521
+ const load = async () => {
2522
+ if (pending.current) return
2523
+ pending.current = true
2524
+ try {
2525
+ const [b, c, o] = await Promise.all([budgetApi.readConfig(), connectionApi.readConfig(), connectionApi.overview()])
2526
+ if (!b.ok || !c.ok || !o.ok) throw new Error()
2527
+ if (alive.current) { setBudget(b.value); setConnection(c.value); setOverview(o.value); setError(false) }
2528
+ } catch { if (alive.current) setError(true) }
2529
+ finally { pending.current = false }
2530
+ }
2531
+ useEffect(() => { alive.current = true; return () => { alive.current = false } }, [budgetApi, connectionApi])
2532
+ useEffect(() => { if (!active) return; load(); const timer = setInterval(load, 2000); return () => clearInterval(timer) }, [active, revision, budgetApi, connectionApi])
2533
+ const change = async (kind, value) => {
2534
+ if (busy || pending.current) return
2535
+ pending.current = true; setBusy(true)
2536
+ try {
2537
+ let response
2538
+ if (kind === 'protection') response = await budgetApi.saveConfig(JSON.stringify({ ...budget.configuration, deletionProtection: value }), String(budget.revision))
2539
+ else response = await connectionApi.saveConfig(JSON.stringify({ ...connection.configuration, enabled: value }), String(connection.revision))
2540
+ if (!response.ok || !response.value.ok) throw new Error()
2541
+ } catch { if (alive.current) setError(true); return }
2542
+ finally { pending.current = false; if (alive.current) setBusy(false) }
2543
+ if (alive.current) load()
2544
+ }
2545
+ const mode = budget ? budgetDraft(budget.configuration).deletionProtection : ''
2546
+ return React.createElement('section', { className: 'dsh-super-code-home' }, children,
2547
+ React.createElement('div', { className: 'dsh-super-code-settings-actions' }, React.createElement(ActionButton, { type: 'button', onClick: () => navigate('preset') }, t('home.managePreset'))),
2548
+ React.createElement('h3', { className: 'dsh-super-code-home-category' }, t('home.protection')),
2549
+ React.createElement('div', { className: 'dsh-super-code-home-card' },
2550
+ React.createElement('div', { className: 'dsh-super-code-home-heading' }, React.createElement('strong', null, t('delete.title')),
2551
+ React.createElement(ActionButton, { type: 'button', onClick: () => navigate('protection') }, t('home.configure'))),
2552
+ budget && React.createElement(SelectControl, { label: t('delete.mode'), value: mode, choices: ['off', 'relaxed', 'strict'].map(value => [value, t(`delete.${value}`)]), disabled: busy || !budget.writable, onChange: value => change('protection', value) })),
2553
+ React.createElement('h3', { className: 'dsh-super-code-home-category' }, t('home.extensions')),
2554
+ React.createElement('div', { className: 'dsh-super-code-home-card' },
2555
+ React.createElement('div', { className: 'dsh-super-code-home-heading' }, React.createElement('strong', null, t('home.mcp')),
2556
+ React.createElement(ActionButton, { type: 'button', onClick: () => navigate('connection') }, t('home.configure'))),
2557
+ connection && React.createElement(ConnectionSwitch, { checked: connection.configuration.enabled, disabled: busy || !connection.writable,
2558
+ onChange: value => value && !connection.configuration.allowedDirectories.length ? navigate('connection') : change('connection', value) }, t(`connection.state.${connection.state}`)),
2559
+ connection && React.createElement('small', null, t('home.connectionCount', { count: overview.connections.length, max: connection.configuration.maxConnections })),
2560
+ React.createElement('dl', { className: 'dsh-super-code-session-counts' }, ...['connected', 'history'].map(kind => React.createElement('div', { key: kind, 'data-session-count': kind },
2561
+ React.createElement('dt', null, t(kind === 'connected' ? 'home.connectedSessions' : 'home.historySessions')),
2562
+ React.createElement('dd', null, connection ? connectionSessions(overview)[kind].length : '—'))))),
2563
+ error && React.createElement('p', { role: 'alert' }, t('home.error')))
2564
+ }
2565
+ function ConnectionConsent({ api, t }) {
2566
+ const [requests, setRequests] = useState([]), [busy, setBusy] = useState(false), [error, setError] = useState(false)
2567
+ const dialog = useRef(null), alive = useRef(false), pending = useRef(false), resolving = useRef(false), settled = useRef(new Set())
2568
+ const current = requests[0]
2569
+ const load = async () => {
2570
+ if (pending.current) return
2571
+ pending.current = true
2572
+ try { const result = await api.pendingDirectories(); if (alive.current && result.ok) setRequests(result.value.filter(row => !settled.current.has(row.id))) }
2573
+ catch { /* Transport loss never grants consent. */ }
2574
+ finally { pending.current = false }
2575
+ }
2576
+ useEffect(() => { alive.current = true; load(); const timer = setInterval(load, 1000); return () => { alive.current = false; clearInterval(timer) } }, [api])
2577
+ const resolve = async decision => {
2578
+ if (resolving.current || !current) return
2579
+ resolving.current = true; setBusy(true); setError(false)
2580
+ try {
2581
+ const result = await api.resolveDirectory(current.id, decision)
2582
+ if (!result.ok) throw new Error()
2583
+ if (alive.current) { settled.current.add(current.id); setRequests(rows => rows.filter(row => row.id !== current.id)); window.dispatchEvent(new Event('super-code-directories-changed')) }
2584
+ } catch { if (alive.current) setError(true) }
2585
+ finally { resolving.current = false; if (alive.current) setBusy(false) }
2586
+ }
2587
+ useEffect(() => {
2588
+ if (!current || !dialog.current) return
2589
+ setError(false)
2590
+ const element = dialog.current; element.showModal(); element.querySelector('[data-consent-reject]')?.focus()
2591
+ const escape = event => { if (event.key === 'Escape') { event.preventDefault(); event.stopImmediatePropagation(); if (!resolving.current) resolve('rejected') } }
2592
+ window.addEventListener('keydown', escape, true)
2593
+ return () => { window.removeEventListener('keydown', escape, true); if (element.open) element.close() }
2594
+ }, [current?.id])
2595
+ if (!current) return null
2596
+ return React.createElement('dialog', { ref: dialog, className: 'dsh-super-code-confirm', 'aria-labelledby': 'super-code-consent-title',
2597
+ onCancel: event => { event.preventDefault(); event.stopPropagation(); resolve('rejected') } },
2598
+ React.createElement('h3', { id: 'super-code-consent-title' }, t('connection.requestTitle')),
2599
+ React.createElement('p', null, t('connection.requestHint')),
2600
+ React.createElement('dl', { className: 'dsh-super-code-directory-request' },
2601
+ ...[['requestClient', current.client], ['requestPath', current.path], ['requestReason', current.reason]].map(([key, value]) => React.createElement('div', { key }, React.createElement('dt', null, t(`connection.${key}`)), React.createElement('dd', null, value)))),
2602
+ error && React.createElement('p', { role: 'alert' }, t('connection.requestError')),
2603
+ React.createElement('div', { className: 'dsh-super-code-confirm-actions' },
2604
+ React.createElement(ActionButton, { type: 'button', disabled: busy, 'data-consent-reject': true, onClick: () => resolve('rejected') }, t('connection.rejectDirectory')),
2605
+ React.createElement(ActionButton, { type: 'button', disabled: busy, className: 'dsh-super-code-primary-button', onClick: () => resolve('approved') }, t('connection.approveDirectory'))))
2606
+ }
2607
+
2608
+ function PresetSettings({ api, budgetApi, connectionApi, openSession = () => {}, t }) {
2609
+ const [tab, setTab] = useState('home')
1766
2610
  const [status, setStatus] = useState(null), [customId, setCustomId] = useState(null)
1767
2611
  const reinstall = status?.reinstallable ?? status?.state === 'installed'
1768
2612
  const name = customId ?? (reinstall ? status.id : 'super-code')
@@ -1827,18 +2671,22 @@ function PresetSettings({ api, budgetApi, t }) {
1827
2671
  }
1828
2672
  const available = status && ['available', 'installed'].includes(status.state)
1829
2673
  const versionState = status?.presetVersion ? status.presetVersion === status.pluginVersion ? 'match' : 'mismatch' : 'unknown'
2674
+ const [overviewRevision, setOverviewRevision] = useState(0)
2675
+ const onSaved = () => setOverviewRevision(value => value + 1)
2676
+ const tabs = ['home', 'preset', 'context', 'protection', 'connection']
1830
2677
  return React.createElement('section', { className: 'dsh-super-code-settings' },
1831
2678
  React.createElement('div', { className: 'dsh-super-code-settings-tabs', role: 'tablist', 'aria-label': t('settings.tabs'), onKeyDown: event => {
1832
2679
  if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return
1833
2680
  event.preventDefault()
1834
- const selected = event.key === 'Home' ? 'preset' : event.key === 'End' ? 'context' : tab === 'preset' ? 'context' : 'preset'
2681
+ const selected = event.key === 'Home' ? tabs[0] : event.key === 'End' ? tabs[tabs.length - 1] : tabs[(tabs.indexOf(tab) + (event.key === 'ArrowRight' ? 1 : tabs.length - 1)) % tabs.length]
1835
2682
  setTab(selected)
1836
2683
  event.currentTarget.querySelector(`#super-code-tab-${selected}`)?.focus()
1837
- } }, ...['preset', 'context'].map(value => React.createElement('button', { key: value, type: 'button', role: 'tab',
2684
+ } }, ...tabs.map(value => React.createElement('button', { key: value, type: 'button', role: 'tab',
1838
2685
  id: `super-code-tab-${value}`, 'aria-selected': tab === value, 'aria-controls': `super-code-panel-${value}`,
1839
2686
  tabIndex: tab === value ? 0 : -1, onClick: () => setTab(value) }, t(`settings.${value}Tab`)))),
1840
2687
  React.createElement('div', { className: 'dsh-super-code-settings-body' },
1841
- React.createElement('div', { role: 'tabpanel', id: 'super-code-panel-preset', 'aria-labelledby': 'super-code-tab-preset', hidden: tab !== 'preset' },
2688
+ React.createElement('div', { role: 'tabpanel', id: 'super-code-panel-home', 'aria-labelledby': 'super-code-tab-home', hidden: tab !== 'home' },
2689
+ React.createElement(SettingsHome, { budgetApi, connectionApi, t, active: tab === 'home', revision: overviewRevision, navigate: setTab },
1842
2690
  React.createElement('header', { className: 'dsh-super-code-settings-header' },
1843
2691
  React.createElement('span', { className: 'dsh-super-code-settings-mark', 'aria-hidden': true }, React.createElement(Glyph, { type: 'code' })),
1844
2692
  React.createElement('div', { className: 'dsh-super-code-settings-head-text' },
@@ -1859,9 +2707,9 @@ function PresetSettings({ api, budgetApi, t }) {
1859
2707
  React.createElement('div', null, React.createElement('dt', null, t('preset.presetVersion')), React.createElement('dd', null, status.state === 'missing' ? t('preset.versionMissing') : status.presetVersion || t('preset.versionUnknown')))),
1860
2708
  status && status.state !== 'missing' && React.createElement('p', { className: 'dsh-super-code-version-hint', role: 'status', 'data-version-state': versionState },
1861
2709
  t(versionState === 'match' ? 'preset.versionMatches' : versionState === 'mismatch' ? 'preset.versionMismatch' : 'preset.versionUnknownHint'),
1862
- versionState === 'mismatch' && ` ${t(reinstall && status.authorable ? 'preset.versionReinstallHint' : 'preset.versionReloadHint')}`)),
2710
+ versionState === 'mismatch' && ` ${t(reinstall && status.authorable ? 'preset.versionReinstallHint' : 'preset.versionReloadHint')}`)))),
2711
+ React.createElement('div', { role: 'tabpanel', id: 'super-code-panel-preset', 'aria-labelledby': 'super-code-tab-preset', hidden: tab !== 'preset' },
1863
2712
  status?.authorable && React.createElement('form', { onSubmit: event => { event.preventDefault(); submit() } },
1864
- React.createElement('h3', null, t('preset.configuration')),
1865
2713
  React.createElement('div', { className: 'dsh-super-code-settings-field' },
1866
2714
  React.createElement('label', { htmlFor: 'super-code-display-name' }, t('preset.displayName')),
1867
2715
  React.createElement('input', { id: 'super-code-display-name', value: displayName, maxLength: 120, required: true, disabled: busy, 'aria-describedby': 'super-code-display-name-hint', onChange: event => setCustomDisplayName(event.target.value) }),
@@ -1881,7 +2729,11 @@ function PresetSettings({ api, budgetApi, t }) {
1881
2729
  status?.delivery === 'directory' && !status.authorable && React.createElement('p', { role: 'note' }, t('preset.readonly'))),
1882
2730
  error && React.createElement('p', { role: 'alert' }, t(error))),
1883
2731
  React.createElement('div', { role: 'tabpanel', id: 'super-code-panel-context', 'aria-labelledby': 'super-code-tab-context', hidden: tab !== 'context' },
1884
- React.createElement(BudgetSettings, { api: budgetApi, t, active: tab === 'context' }))),
2732
+ React.createElement(BudgetSettings, { api: budgetApi, t, active: tab === 'context', onSaved })),
2733
+ React.createElement('div', { role: 'tabpanel', id: 'super-code-panel-protection', 'aria-labelledby': 'super-code-tab-protection', hidden: tab !== 'protection' },
2734
+ React.createElement(BudgetSettings, { api: budgetApi, t, section: 'protection', active: tab === 'protection', onSaved })),
2735
+ React.createElement('div', { role: 'tabpanel', id: 'super-code-panel-connection', 'aria-labelledby': 'super-code-tab-connection', hidden: tab !== 'connection' },
2736
+ React.createElement(ConnectionSettings, { api: connectionApi, t, active: tab === 'connection', openSession, onSaved }))),
1885
2737
  confirmation && React.createElement('dialog', { ref: dialog, className: 'dsh-super-code-confirm',
1886
2738
  'aria-labelledby': 'super-code-confirm-title', 'aria-describedby': 'super-code-confirm-hint',
1887
2739
  onKeyDown: event => { if (event.key === 'Escape') event.stopPropagation() },
@@ -1919,20 +2771,34 @@ function apply(ctx) {
1919
2771
  // $mount creates a lifecycle effect on this same fiber; awaiting it from
1920
2772
  // another effect would stall the namespace and the settings contribution.
1921
2773
  void ctx.remote.$mount({ package: 'dsh-super-code', descriptors: [
1922
- ...presetRemoteContribution.descriptors, ...memoryRemoteContribution.descriptors, ...budgetRemoteContribution.descriptors,
2774
+ ...presetRemoteContribution.descriptors, ...memoryRemoteContribution.descriptors, ...budgetRemoteContribution.descriptors, ...connectionRemoteContribution.descriptors,
1923
2775
  ] }).catch(error => console.warn('Super Code Remote:', error))
1924
2776
  const memoryStates = new WeakMap(), workspaceStates = new WeakMap()
2777
+ const navigation = { openSession: () => {} }
2778
+ ctx.inject(['uiWorkspace'], ready => {
2779
+ navigation.openSession = id => {
2780
+ if (typeof ready.uiWorkspace.openSession === 'function') ready.uiWorkspace.openSession(id)
2781
+ else ready.sessions.open(id)
2782
+ }
2783
+ ready.effect(() => () => { navigation.openSession = () => {} })
2784
+ })
2785
+ ctx.inject(['remote.superCodeConnection'], ready => {
2786
+ ready.slots.inject('shell.overlay', () => ready.slots.register({
2787
+ name: 'shell.overlay', id: 'super-code-directory-consent', order: 180, locale: LOCALE_NS,
2788
+ inject: () => ({ api: ready.remote.superCodeConnection }),
2789
+ }, ConnectionConsent))
2790
+ })
1925
2791
  // Locale already requires its host's preference service. Declare that same
1926
2792
  // service here: Cordis rejects even optional reads without an injection.
1927
2793
  const preferenceService = 'configForms' in ctx ? 'configForms' : 'settingsScope'
1928
- ctx.inject(['remote.superCodePresets', 'remote.superCodeBudgets', preferenceService], ready => {
2794
+ ctx.inject(['remote.superCodePresets', 'remote.superCodeBudgets', 'remote.superCodeConnection', preferenceService], ready => {
1929
2795
  const preferenceScope = preferenceService === 'configForms'
1930
2796
  ? ready.configForms.get('locale') : ready.settingsScope.bind({ namespace: 'locale' })
1931
2797
  ready.effect(() => synchronizePresetDisplay(ready.remote.superCodePresets, locale,
1932
2798
  () => window.location.reload(), error => console.warn('Super Code preset display:', error), preferenceScope), 'super-code: preset display')
1933
2799
  ready.slots.inject('settings.section', () => ready.slots.register({
1934
2800
  name: 'settings.section', id: 'super-code', order: 120, label: () => t('preset.title'), locale: LOCALE_NS,
1935
- inject: () => ({ api: ready.remote.superCodePresets, budgetApi: ready.remote.superCodeBudgets }),
2801
+ inject: () => ({ api: ready.remote.superCodePresets, budgetApi: ready.remote.superCodeBudgets, connectionApi: ready.remote.superCodeConnection, openSession: id => navigation.openSession(id) }),
1936
2802
  }, PresetSettings))
1937
2803
  })
1938
2804
  ctx.effect(() => {
@@ -2023,6 +2889,28 @@ function apply(ctx) {
2023
2889
  .dsh-super-code-budget-group{display:flex;flex-direction:column;gap:16px;min-width:0}
2024
2890
  .dsh-super-code-budget-group h3{margin:0;font-size:14px;font-weight:600}
2025
2891
  .dsh-super-code-budget-exclusive{border-top:1px solid var(--sc-line);padding-top:24px}
2892
+
2893
+ .dsh-super-code-connection{display:grid;gap:18px}
2894
+ .dsh-super-code-connection form{display:grid;gap:16px}
2895
+ .dsh-super-code-connection-hint{margin:0;color:var(--sc-muted);font-size:12px;line-height:1.6}
2896
+ .dsh-super-code-connection-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
2897
+ .dsh-super-code-connection textarea,.dsh-super-code-connection-token{width:100%;box-sizing:border-box;border:1px solid var(--sc-line);border-radius:8px;background:var(--sc-bg);color:var(--sc-text);padding:9px 11px;font:12px/1.6 ui-monospace,monospace;resize:vertical}
2898
+ .dsh-super-code-connection-client{display:grid;gap:12px;border-top:1px solid var(--sc-line);padding-top:18px;min-width:0}
2899
+ .dsh-super-code-connection-client h3{margin:0;font-size:13px}
2900
+ .dsh-super-code-connection-client pre{white-space:pre-wrap;overflow-wrap:anywhere;font:12px/1.7 ui-monospace,monospace;background:var(--sc-surface);padding:12px;border-radius:8px;margin:0}
2901
+ .dsh-super-code-connection small{color:var(--sc-muted);font-size:12px;line-height:1.6}
2902
+ .dsh-super-code-connection .dsh-super-code-settings-actions{flex-wrap:wrap;gap:8px}
2903
+ .dsh-super-code-connection-switch{position:relative;display:inline-flex;align-items:center;gap:10px;cursor:pointer;font-size:13px;font-weight:500;width:fit-content}
2904
+ .dsh-super-code-connection-switch input[role=switch]{position:absolute;opacity:0;width:34px;height:20px;margin:0;padding:0;left:0;top:50%;transform:translateY(-50%);z-index:1;cursor:pointer}
2905
+ .dsh-super-code-connection-track{display:inline-flex;align-items:center;flex:none;width:34px;height:20px;border:1px solid var(--sc-line);border-radius:20px;padding:2px;background:var(--sc-surface);transition:background .15s}
2906
+ .dsh-super-code-connection-track i{display:block;width:14px;height:14px;border-radius:50%;background:var(--sc-muted);transition:transform .15s}
2907
+ .dsh-super-code-connection-switch input:checked+.dsh-super-code-connection-track{background:var(--sc-accent);border-color:var(--sc-accent)}
2908
+ .dsh-super-code-connection-switch input:checked+.dsh-super-code-connection-track i{transform:translateX(14px);background:#fff}
2909
+ .dsh-super-code-connection-switch input:focus-visible+.dsh-super-code-connection-track{outline:2px solid var(--sc-accent);outline-offset:3px}
2910
+ .dsh-super-code-connection-switch input:disabled+.dsh-super-code-connection-track{opacity:.5}
2911
+ .dsh-super-code-connection input[type=number]{appearance:textfield}
2912
+ .dsh-super-code-connection input::-webkit-inner-spin-button{appearance:none}
2913
+
2026
2914
  .dsh-super-code-budget-switch{display:flex;align-items:center;gap:10px;font-size:13px;cursor:pointer}
2027
2915
  .dsh-super-code-budget-switch input{width:30px;height:17px;flex:none;accent-color:var(--sc-active);cursor:pointer}
2028
2916
  .dsh-super-code-budget-default{color:var(--sc-muted);font-weight:400;font-size:12px}
@@ -2037,6 +2925,60 @@ function apply(ctx) {
2037
2925
  .dsh-super-code-settings [role=alert],.dsh-super-code-detail [role=alert],.dsh-super-code-tree-error{font-size:13px;color:var(--sc-error);overflow-wrap:anywhere}
2038
2926
  @container(max-width:420px){.dsh-super-code-settings-header{padding-bottom:16px;margin-bottom:16px}.dsh-super-code-settings-body{padding:16px}.dsh-super-code-settings-summary{padding:12px}.dsh-super-code-settings-status-row{gap:8px}.dsh-super-code-preset-status{flex-basis:100%}.dsh-super-code-settings-form-actions>small{min-width:0;flex-basis:100%}}
2039
2927
 
2928
+ .dsh-super-code-session-counts{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px;margin:4px 0 0;padding-top:12px;border-top:1px solid var(--sc-line)}
2929
+ .dsh-super-code-session-counts dt{font-size:12px;color:var(--sc-muted)}
2930
+ .dsh-super-code-session-counts dd{font-size:22px;font-variant-numeric:tabular-nums;font-weight:600;margin:3px 0 0}
2931
+ .dsh-super-code-connection-activity{display:grid;gap:10px;border:1px solid var(--sc-line);border-radius:10px;padding:14px}
2932
+ .dsh-super-code-connection-activity h3{margin:0;font-size:13px;font-weight:600}
2933
+ .dsh-super-code-connection-activity h3:not(:first-child){margin-top:6px}
2934
+ .dsh-super-code-connection-activity h4{margin:0 0 6px;font-size:12px;color:var(--sc-muted);font-weight:500}
2935
+ .dsh-super-code-connection-activity p{margin:0;font-size:12px;color:var(--sc-muted)}
2936
+ .dsh-super-code-connection-activity button{border:1px solid var(--sc-line);border-radius:7px;background:var(--sc-bg);padding:4px 8px;font-size:12px}
2937
+ .dsh-super-code-connection-activity button:hover{background:var(--sc-hover)}
2938
+ /* Settings overview and directory consent use the same host theme aliases. */
2939
+ .dsh-super-code-settings-tabs{gap:20px;overflow-x:auto;white-space:nowrap}
2940
+ .dsh-super-code-home{display:grid;gap:12px}
2941
+ .dsh-super-code-home-category{font-size:11px;letter-spacing:.06em;color:var(--sc-muted);margin:12px 0 0}
2942
+ .dsh-super-code-home-card{display:grid;gap:12px;border:1px solid var(--sc-line);border-radius:10px;padding:14px}
2943
+ .dsh-super-code-home-heading{display:flex;justify-content:space-between;align-items:center;gap:12px}
2944
+ .dsh-super-code-home-heading strong{font-size:13px;font-weight:600}
2945
+ .dsh-super-code-home-card :is(p,small){margin:0;font-size:12px;color:var(--sc-muted)}
2946
+ .dsh-super-code-home-card h4{font-size:12px;font-weight:500;margin:6px 0 0}
2947
+ .dsh-super-code-home-card button,.dsh-super-code-home>.dsh-super-code-settings-actions button{border:1px solid var(--sc-line);border-radius:7px;background:var(--sc-bg);padding:4px 8px;font-size:12px}
2948
+ .dsh-super-code-home-card button:hover{background:var(--sc-hover)}
2949
+ .dsh-super-code-connection-list{list-style:none;padding:0;margin:0;max-height:280px;overflow:auto}
2950
+ .dsh-super-code-connection-list li{display:flex;align-items:center;gap:10px;padding:9px 0;border-bottom:1px solid var(--sc-line)}
2951
+ .dsh-super-code-connection-list li:last-child{border:0}
2952
+ .dsh-super-code-connection-list li>span{display:grid;gap:2px;flex:1;min-width:0;overflow-wrap:anywhere}
2953
+ .dsh-super-code-connection-list strong{font-size:12px;font-weight:500}
2954
+ .dsh-super-code-connection-list code{font:10px/1.5 var(--sc-mono);color:var(--sc-muted)}
2955
+ .dsh-super-code-connection-list button{flex:none}
2956
+ .dsh-super-code-home .dsh-super-code-settings-header{padding-bottom:12px;margin-bottom:0}
2957
+ .dsh-super-code-directory-fieldset{border:0;padding:0;margin:0;min-width:0;display:grid;gap:10px}
2958
+ .dsh-super-code-directory-fieldset legend{font-size:13px;font-weight:500;margin-bottom:10px}
2959
+ .dsh-super-code-directory-list{border:1px solid var(--sc-line);border-radius:9px;max-height:330px;overflow:auto}
2960
+ .dsh-super-code-directory-list>p{margin:0;padding:18px;color:var(--sc-muted);font-size:12px}
2961
+ .dsh-super-code-directory-row{display:flex;align-items:center;gap:8px;padding:10px 12px}
2962
+ .dsh-super-code-directory-row+.dsh-super-code-directory-row{border-top:1px solid var(--sc-line)}
2963
+ .dsh-super-code-directory-row label{display:flex;align-items:center;flex:1;min-width:0;gap:10px;cursor:pointer}
2964
+ .dsh-super-code-directory-row input[type=checkbox]{appearance:none;width:16px;height:16px;flex:none;border:1px solid var(--sc-line);padding:0;border-radius:4px;background:var(--sc-bg);cursor:pointer}
2965
+ .dsh-super-code-directory-row input[type=checkbox]:checked{background:var(--sc-accent);border-color:var(--sc-accent);box-shadow:inset 0 0 0 3px var(--sc-bg)}
2966
+ .dsh-super-code-directory-row input:disabled{opacity:.5;cursor:default}
2967
+ .dsh-super-code-directory-row label>span{display:grid;min-width:0;gap:1px}
2968
+ .dsh-super-code-directory-row strong{font-size:12px;font-weight:500}
2969
+ .dsh-super-code-directory-row code{font:11px/1.6 var(--sc-mono);overflow-wrap:anywhere}
2970
+ .dsh-super-code-directory-row small[data-unavailable=true]{color:var(--sc-warning)}
2971
+ .dsh-super-code-directory-row>button{font-size:11px;border:0;background:none;padding:5px;color:var(--sc-muted)}
2972
+ .dsh-super-code-directory-crumbs{display:flex;flex-wrap:wrap;gap:4px;margin:12px 0}
2973
+ .dsh-super-code-directory-crumbs button{font-size:11px}
2974
+ .dsh-super-code-directory-path{display:block;overflow-wrap:anywhere;font-size:12px;margin-bottom:10px}
2975
+ .dsh-super-code-directory-browser{display:grid;max-height:320px;overflow:auto;gap:2px}
2976
+ .dsh-super-code-directory-browser button{display:flex;align-items:center;justify-content:flex-start;gap:8px;border:0;text-align:left;overflow-wrap:anywhere}
2977
+ .dsh-super-code-directory-request{display:grid;gap:12px;padding:14px;border:1px solid var(--sc-line);border-radius:9px;background:var(--sc-surface)}
2978
+ .dsh-super-code-directory-request dt{font-size:11px;color:var(--sc-muted);margin-bottom:3px}
2979
+ .dsh-super-code-directory-request dd{font-size:13px;line-height:1.6;overflow-wrap:anywhere;white-space:pre-wrap;margin:0}
2980
+ .dsh-super-code-directory-request>div:nth-child(2) dd{font-family:var(--sc-mono);font-size:12px}
2981
+
2040
2982
  /* Confirmation. */
2041
2983
  .dsh-super-code-confirm{width:min(520px,calc(100vw - 32px));max-height:calc(100dvh - 48px);overflow:auto;border:1px solid var(--sc-line);border-radius:16px;padding:24px;background:var(--sc-bg);box-shadow:0 12px 48px #0003}
2042
2984
  .dsh-super-code-confirm::backdrop{background:#10182066}
@@ -2054,7 +2996,7 @@ function apply(ctx) {
2054
2996
 
2055
2997
  /* Exactly one section fills the pane; the other keeps its clickable heading. */
2056
2998
  .dsh-super-code-workspace{height:100%;min-height:0;overflow:auto;background:var(--sc-bg)}
2057
- .dsh-super-code-workspace-layout{display:grid;height:100%;min-height:324px;min-width:0}
2999
+ .dsh-super-code-workspace-layout{display:grid;height:100%;min-height:368px;min-width:0}
2058
3000
  .dsh-super-code-workspace-collapsed{display:flex;align-items:center;border-block:1px solid var(--sc-line);background:var(--sc-bg);padding:0 12px}
2059
3001
  .dsh-super-code-section-toggle{display:flex;align-items:center;gap:6px;flex:1;min-width:0;width:100%;min-height:36px;border:0;border-radius:6px;background:transparent;padding:6px 8px;text-align:left}
2060
3002
  .dsh-super-code-section-toggle>.dsh-super-code-tree-count{margin-left:auto}
@@ -2065,6 +3007,66 @@ function apply(ctx) {
2065
3007
  .dsh-super-code-section-toggle:hover{background:var(--sc-hover)}
2066
3008
  .dsh-super-code-workspace-collapsed>.dsh-super-code-section-toggle{width:100%;height:100%}
2067
3009
 
3010
+
3011
+ /* Read-only journal feedback, independent of the graph camera and model context. */
3012
+ .dsh-super-code-feedback{height:100%;min-height:0;min-width:0;display:flex;flex-direction:column}
3013
+ .dsh-super-code-feedback>header{display:flex;align-items:center;gap:8px;padding:4px 12px;border-bottom:1px solid var(--sc-line)}
3014
+ .dsh-super-code-feedback-scroll{flex:1;min-height:0;overflow:auto;padding:8px 12px 12px;overscroll-behavior:contain;overflow-anchor:none}
3015
+ .dsh-super-code-feedback-list{position:relative;list-style:none;padding:0;margin:0}
3016
+ .dsh-super-code-feedback-list>li{position:absolute;top:0;left:0;width:100%;display:flow-root;min-width:0;font-size:12px;line-height:20px;overflow-wrap:anywhere}
3017
+ .dsh-super-code-feedback-list>li[data-feedback-agent]:not([data-feedback-kind=task]):before{content:"";position:absolute;left:calc(var(--sc-feedback-indent) - 10px);top:0;bottom:0;border-left:1px solid var(--sc-line)}
3018
+ .dsh-super-code-feedback-task-heading{display:flex;flex-wrap:wrap;align-items:flex-start;gap:2px 6px;padding:8px 4px 6px}
3019
+ .dsh-super-code-feedback-heading{flex:1;min-width:0}
3020
+ .dsh-super-code-feedback-heading h3{overflow-wrap:anywhere;font-size:13px;line-height:20px;font-weight:600;margin:0}
3021
+ .dsh-super-code-feedback small{color:var(--sc-muted);font-size:11px;line-height:18px}
3022
+ .dsh-super-code-feedback-meta{display:flex;align-items:center;gap:7px;min-width:0;margin-top:1px}
3023
+ .dsh-super-code-feedback-meta>small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
3024
+ .dsh-super-code-feedback-status{display:flex;align-items:center;gap:5px;flex:none;font-size:11px;line-height:18px;color:var(--sc-muted)}
3025
+ .dsh-super-code-feedback-status:before{content:"";width:3px;height:3px;border-radius:50%;background:var(--sc-faint)}
3026
+ .dsh-super-code-feedback-status[data-tone=active]{color:var(--sc-accent)}
3027
+ .dsh-super-code-feedback-status[data-tone=error]{color:var(--sc-error)}
3028
+ .dsh-super-code-feedback-count{flex:none;font-size:11px;font-variant-numeric:tabular-nums;line-height:20px;color:var(--sc-muted);padding-inline:4px}
3029
+ .dsh-super-code-feedback-muted{font-size:11px;color:var(--sc-muted);line-height:18px;margin:3px 0;white-space:pre-wrap}
3030
+ .dsh-super-code-feedback-text{margin:3px 0;white-space:pre-wrap}
3031
+ .dsh-super-code-feedback-text>small{display:block}
3032
+ .dsh-super-code-feedback progress{display:block;flex-basis:100%;width:100%;height:2px;accent-color:var(--sc-accent);border:0;border-radius:8px;overflow:hidden;background:var(--sc-line);margin:4px 0 0;opacity:.8}
3033
+ .dsh-super-code-feedback progress::-webkit-progress-bar{background:var(--sc-line)}
3034
+ .dsh-super-code-feedback progress::-webkit-progress-value{background:var(--sc-accent);transition:width .3s ease}
3035
+ .dsh-super-code-feedback progress::-moz-progress-bar{background:var(--sc-accent)}
3036
+ .dsh-super-code-feedback-step{display:flex;align-items:flex-start;gap:7px;min-height:26px;padding:3px 5px;margin:1px 0;border-radius:5px;line-height:20px;transition:background .15s,color .15s}
3037
+ .dsh-super-code-feedback-step>span:last-child{flex:1;min-width:0;overflow-wrap:anywhere}
3038
+ .dsh-super-code-feedback-step[data-step-state=completed]{color:var(--sc-muted)}
3039
+ .dsh-super-code-feedback-step[data-step-state=completed]>span:last-child{text-decoration:line-through;text-decoration-color:color-mix(in srgb,var(--sc-muted) 40%,transparent)}
3040
+ .dsh-super-code-feedback-step[data-step-state=pending]{color:var(--sc-muted)}
3041
+ .dsh-super-code-feedback-step[data-step-state=in_progress]{background:color-mix(in srgb,var(--sc-accent) 7%,transparent);box-shadow:inset 2px 0 color-mix(in srgb,var(--sc-accent) 55%,transparent);color:var(--sc-text)}
3042
+ .dsh-super-code-feedback-mark{position:relative;display:inline-flex;align-items:center;justify-content:center;flex:none;width:14px;height:14px;margin-top:3px;color:var(--sc-muted);font-size:11px;line-height:14px}
3043
+ .dsh-super-code-feedback-mark>svg{width:14px;height:14px}
3044
+ .dsh-super-code-feedback-mark.pending:before,.dsh-super-code-feedback-mark.idle:before,.dsh-super-code-feedback-mark.unavailable:before{content:"";width:7px;height:7px;border:1px solid var(--sc-faint);border-radius:50%}
3045
+ .dsh-super-code-feedback-mark.active:before{content:"";position:absolute;inset:2px;border:1.5px solid color-mix(in srgb,var(--sc-accent) 24%,transparent);border-top-color:var(--sc-accent);border-right-color:var(--sc-accent);border-radius:50%;animation:sc-feedback-spin .9s linear infinite}
3046
+ .dsh-super-code-feedback-mark.success{color:var(--sc-success)}
3047
+ .dsh-super-code-feedback-mark.error{color:var(--sc-error);border:1px solid currentColor;border-radius:50%;transform:scale(.8)}
3048
+ [data-feedback-live=false] .dsh-super-code-feedback-step .dsh-super-code-feedback-mark.active:before{animation:none}
3049
+ @keyframes sc-feedback-spin{to{transform:rotate(360deg)}}
3050
+ @media(prefers-reduced-motion:reduce){.dsh-super-code-feedback-mark.active:before{animation:none!important}}
3051
+ .dsh-super-code-feedback-activity{display:flex;gap:7px;align-items:flex-start;min-height:28px;padding:4px 0;line-height:20px}
3052
+ .dsh-super-code-feedback-activity-text{min-width:0;flex:1;overflow-wrap:anywhere;white-space:pre-wrap}
3053
+ .dsh-super-code-feedback-activity>small{flex:none;font-size:10px;line-height:20px}
3054
+ .dsh-super-code-feedback-tool{max-width:84px;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}
3055
+ .dsh-super-code-feedback-list>li[data-feedback-kind=open]{padding-bottom:8px}
3056
+ .dsh-super-code-feedback-open{display:inline-flex;align-items:center;gap:4px;min-height:24px;border:0;border-radius:4px;background:transparent;color:var(--sc-muted);font:inherit;font-size:11px;padding:2px 4px}
3057
+ .dsh-super-code-feedback-open>.dsh-super-code-glyph{width:12px;height:12px;transition:transform .15s}
3058
+ .dsh-super-code-feedback-open:hover{color:var(--sc-accent);background:var(--sc-hover)}
3059
+ .dsh-super-code-feedback-open:hover>.dsh-super-code-glyph{transform:translateX(2px)}
3060
+ .dsh-super-code-feedback-files-heading{display:flex;align-items:center;gap:6px;font-size:12px;font-weight:600;margin:6px 0 0;padding:10px 0 3px;border-top:1px solid var(--sc-line)}
3061
+ .dsh-super-code-feedback-files-heading>span{font-size:11px;font-weight:400;color:var(--sc-muted);font-variant-numeric:tabular-nums}
3062
+ .dsh-super-code-feedback-file{display:flex;align-items:center;gap:7px;width:100%;min-height:30px;padding:4px;border:0;border-radius:5px;background:transparent;color:var(--sc-text);text-align:left;min-width:0;font:inherit;cursor:pointer}
3063
+ .dsh-super-code-feedback-file:hover{background:var(--sc-hover)}
3064
+ .dsh-super-code-feedback-file>.dsh-super-code-glyph{flex:none;width:14px;height:14px;color:var(--sc-muted)}
3065
+ .dsh-super-code-feedback-file strong{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:400}
3066
+ .dsh-super-code-feedback-file small{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
3067
+ .dsh-super-code-feedback-file-operation{flex:none;margin-left:auto;font-size:10px;color:var(--sc-success)}
3068
+ .dsh-super-code-feedback button:focus-visible,.dsh-super-code-feedback-scroll:focus-visible{outline:2px solid var(--sc-accent);outline-offset:-2px}
3069
+
2068
3070
  /* Memory library: geometry stays at 76px per row. */
2069
3071
  .dsh-super-code-memory{height:100%;min-height:0;display:flex;flex-direction:column;container-type:inline-size}
2070
3072
  .dsh-super-code-memory>header{padding:16px;border-bottom:1px solid var(--sc-line);flex:none}
@@ -2404,5 +3406,5 @@ function apply(ctx) {
2404
3406
  ctx.effect(() => disposeMemoryType, 'super-code: memory library tab')
2405
3407
  }
2406
3408
 
2407
- return { inject, apply, budgetDraft, budgetConfiguration, synchronizePresetDisplay, agentSessionState, buildAgentView, visibleAgentRows, agentTreeDisplay, agentTreeTotals, agentTreeLayout, agentExecutionStatus, agentDetailAddress, agentDetailTarget, agentPath, detailRecord, createAgentInspector, agentHistoryRange, agentHistoryRows, agentHistoryWindow, memoryLibraryAddress, memoryLibraryTarget, memoryLibraryNavigation, memoryLibraryWindow, createMemoryEditor }
3409
+ return { inject, apply, taskFeedbackRows, feedbackStatus, feedbackListItems, feedbackListLayout, feedbackListIndex, feedbackListWindow, feedbackListAnchor, connectionSessions, createConnectionAutosave, connectionDraft, connectionConfiguration, budgetDraft, budgetConfiguration, synchronizePresetDisplay, agentSessionState, buildAgentView, visibleAgentRows, agentTreeDisplay, agentTreeTotals, agentTreeLayout, agentExecutionStatus, agentDetailAddress, agentDetailTarget, agentPath, detailRecord, createAgentInspector, agentHistoryRange, agentHistoryRows, agentHistoryWindow, memoryLibraryAddress, memoryLibraryTarget, memoryLibraryNavigation, memoryLibraryWindow, createMemoryEditor }
2408
3410
  } })