dsh-super-code 0.3.5 → 0.3.7

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 (52) hide show
  1. package/README.md +91 -57
  2. package/client/index.js +717 -36
  3. package/cordis.patch.yml +2 -2
  4. package/docs/Changelog.md +16 -0
  5. package/docs/Compatibility.md +56 -0
  6. package/docs/EvaluationResults.md +134 -58
  7. package/docs/UsageGuide.md +103 -0
  8. package/lib/core/deletion-command.d.ts +3 -0
  9. package/lib/core/deletion-command.d.ts.map +1 -0
  10. package/lib/core/deletion-command.js +169 -0
  11. package/lib/core/deletion-command.js.map +1 -0
  12. package/lib/dsh/budget-settings.d.ts +51 -0
  13. package/lib/dsh/budget-settings.d.ts.map +1 -0
  14. package/lib/dsh/budget-settings.js +188 -0
  15. package/lib/dsh/budget-settings.js.map +1 -0
  16. package/lib/dsh/budgets.d.ts +39 -0
  17. package/lib/dsh/budgets.d.ts.map +1 -0
  18. package/lib/dsh/budgets.js +76 -0
  19. package/lib/dsh/budgets.js.map +1 -0
  20. package/lib/dsh/compaction.d.ts +40 -0
  21. package/lib/dsh/compaction.d.ts.map +1 -0
  22. package/lib/dsh/compaction.js +146 -0
  23. package/lib/dsh/compaction.js.map +1 -0
  24. package/lib/dsh/deletion-protection.d.ts +13 -0
  25. package/lib/dsh/deletion-protection.d.ts.map +1 -0
  26. package/lib/dsh/deletion-protection.js +68 -0
  27. package/lib/dsh/deletion-protection.js.map +1 -0
  28. package/lib/dsh/index.d.ts +4 -1
  29. package/lib/dsh/index.d.ts.map +1 -1
  30. package/lib/dsh/index.js +11 -1
  31. package/lib/dsh/index.js.map +1 -1
  32. package/lib/dsh/official-budgets.d.ts +3 -0
  33. package/lib/dsh/official-budgets.d.ts.map +1 -0
  34. package/lib/dsh/official-budgets.js +114 -0
  35. package/lib/dsh/official-budgets.js.map +1 -0
  36. package/lib/dsh/ptc-budget.d.ts +5 -0
  37. package/lib/dsh/ptc-budget.d.ts.map +1 -0
  38. package/lib/dsh/ptc-budget.js +65 -0
  39. package/lib/dsh/ptc-budget.js.map +1 -0
  40. package/lib/dsh/super-code.d.ts.map +1 -1
  41. package/lib/dsh/super-code.js +26 -8
  42. package/lib/dsh/super-code.js.map +1 -1
  43. package/lib/index.d.ts +5 -1
  44. package/lib/index.d.ts.map +1 -1
  45. package/lib/index.js +5 -2
  46. package/lib/index.js.map +1 -1
  47. package/lib/task-feedback-projection.d.ts +68 -0
  48. package/lib/task-feedback-projection.d.ts.map +1 -0
  49. package/lib/task-feedback-projection.js +176 -0
  50. package/lib/task-feedback-projection.js.map +1 -0
  51. package/package.json +17 -7
  52. package/presets/super-code/agent.cordis.yml +2 -2
package/client/index.js CHANGED
@@ -256,13 +256,77 @@ const ActionButton = React.forwardRef(function ActionButton({ children, title, .
256
256
 
257
257
  const LOCALE_NS = 'dshSuperCode'
258
258
  const zh = {
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.presetTab': '预设状态', 'settings.contextTab': '上下文配置', 'settings.tabs': 'Super Code 配置',
296
+ 'budget.common': '通用预算', 'budget.superCode': 'Super Code 专属预算',
297
+ 'budget.applyToOfficialPresets': '同时应用到官方预设', 'budget.officialHint': '开启后,通用预算也用于标准、PTC、极简和创造模式;不包含下方的 Super Code 专属预算。',
298
+ 'budget.superCodeHint': '仅用于 Super Code 的任务记录和任务记忆,始终不影响官方预设。',
299
+ 'budget.defaultValue': '(默认:{value})', 'budget.off': '关闭', 'budget.managed': '宿主管理',
300
+ 'delete.title': '删除保护', 'delete.mode': '保护模式', 'delete.off': '关闭', 'delete.relaxed': '宽松', 'delete.strict': '严格', 'delete.official': '同时应用到官方预设',
301
+ 'delete.hint.off': '不添加删除保护提示,不额外拦截命令。宿主原有权限检查仍然有效。',
302
+ 'delete.hint.relaxed': '默认使用 trash,脚本中也建议使用;用户明确要求时可以使用 rm,无需插件额外确认。',
303
+ 'delete.hint.strict': '默认使用 trash,脚本也禁止直接使用 rm。识别到永久删除命令时,仅清理临时文件或构建产物可经用户逐次二次确认放行。',
304
+ 'delete.officialHint': '默认仅保护 Super Code。此开关独立于预算的官方预设开关;保存后立即作用于后续命令。',
305
+ 'budget.title': '上下文配置', 'budget.scope': '预算保存后对新建 Agent 生效,运行中的任务保留原配置。',
306
+ 'budget.defaults': '留空沿用默认值,括号内显示默认值;通用预算随宿主版本变化。恢复默认会清除这里的覆盖,回到插件加载配置;未配置时跟随宿主。',
307
+ 'budget.context': '上下文压缩', 'budget.execution': '批量工具执行', 'budget.advanced': '更多预算',
308
+ 'budget.pressureBasis': '压缩预算基准', 'budget.inherit': '跟随默认', 'budget.host': '宿主策略', 'budget.window': '完整上下文窗口',
309
+ 'budget.thresholdRatio': '压缩触发比例(%)', 'budget.retainRatio': '最近原文保留比例(%)', 'budget.summaryMaxTokens': '摘要输出上限(Token)',
310
+ 'budget.compactionRetries': '压缩追加尝试次数', 'budget.maxOverflowRetries': '上下文溢出重试次数',
311
+ 'budget.ptcTimeoutMs': '默认执行时限(秒)', 'budget.ptcMaxTimeoutMs': '执行时限上限(秒)',
312
+ 'budget.maxTasks': '活跃任务记录上限', 'budget.maxContextBytes': '任务记忆上下文(字节)',
313
+ 'budget.contextHint': '保留比例需小于触发比例。较大的预算会占用更多上下文。',
314
+ 'budget.ptcHint': '包含工具等待时间;默认时限不能超过上限,也不能超过宿主允许值。',
315
+ 'budget.ptcUnavailable': '此宿主的 PTC 计时由宿主管理;其他预算仍可配置。', 'budget.ptcCeiling': '宿主允许的最长时限:{seconds} 秒。',
316
+ 'budget.save': '保存设置', 'budget.reset': '恢复默认', 'budget.reload': '重新读取', 'budget.saving': '正在保存…',
317
+ 'budget.saved': '已保存。删除保护立即生效,预算对新建 Agent 生效。', 'budget.resetDone': '已恢复默认。删除保护立即生效,预算对新建 Agent 生效。',
318
+ 'budget.error.connection': '暂时无法读取预算,请重试。', 'budget.error.invalid': '请检查数值范围、保留比例和执行时限。',
319
+ 'budget.error.retain': '保留比例必须小于压缩触发比例;未填写时分别按 16% 和 80% 校验。', 'budget.error.ptc-order': '默认执行时限不能超过执行时限上限。',
320
+ 'budget.error.conflict': '预算已在别处修改。草稿已保留,请重新读取后再修改。', 'budget.error.read-only': '当前宿主的设置只读,无法保存。',
321
+ 'budget.error.ptc-unavailable': '此宿主不支持覆盖 PTC 总执行时限。', 'budget.error.ptc-ceiling': '执行时限超过了宿主允许的上限。',
322
+ 'budget.error.save-failed': '保存失败,草稿已保留,请重试。',
259
323
  'memory.add': '新增记忆', 'memory.edit': '编辑', 'memory.cancel': '取消', 'memory.save': '保存', 'memory.delete': '删除', 'memory.deleting': '正在删除…', 'memory.saving': '正在保存…',
260
324
  'memory.topic': '主题', 'memory.kind': '类型', 'memory.summaryLabel': '标题', 'memory.detailLabel': '内容', 'memory.manual': '手动维护',
261
325
  'memory.topicHint': '字母、数字、点、下划线或短横线,最多 96 个字符。',
262
326
  'memory.deleteTitle': '删除记忆', 'memory.deleteHint': '确定删除“{summary}”吗?删除后无法恢复。',
263
327
  'memory.writeError': '操作失败,内容已保留,请重试。', 'memory.conflict': '这条记忆已在别处更改,请取消后刷新再编辑。',
264
328
  'memory.missingEdit': '这条记忆已被删除,输入内容已保留。', 'memory.full': '该主题或记忆库已满,请删除不再需要的条目。',
265
- 'workspace.title': 'Super Code', 'workspace.description': 'Agent 执行树与记忆库', 'workspace.resize': '调整执行树与记忆库高度',
329
+ 'workspace.title': 'Super Code', 'workspace.description': '任务状态、Agent 执行树与记忆库', 'workspace.resize': '调整执行树与记忆库高度',
266
330
  'tree.collapsedLabel': '已收起', 'tree.organizeLabel': '自动整理',
267
331
  'detail.toolCall': '工具调用', 'detail.toolResult': '工具结果', 'detail.toolError': '工具失败',
268
332
  'detail.cache': '缓存', 'detail.info': '详情', 'tree.countCompact': '{shown}/{total} · 运行 {running}',
@@ -326,13 +390,77 @@ const zh = {
326
390
  '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': '[图片]'
327
391
  }
328
392
  const en = {
393
+ 'feedback.title': 'Task status',
394
+ 'feedback.refresh': 'Refresh task status',
395
+ 'feedback.noGoal': 'No recorded objective',
396
+ 'feedback.source.record': 'Recorded objective',
397
+ 'feedback.source.request': 'User request',
398
+ 'feedback.latest': 'Latest request',
399
+ 'feedback.progress': 'Steps {done} / {total}',
400
+ 'feedback.progressPartial': 'Recorded steps {done} / {total}',
401
+ 'feedback.steps': 'Step progress',
402
+ 'feedback.todo.pending': 'Pending',
403
+ 'feedback.todo.in_progress': 'In progress',
404
+ 'feedback.todo.completed': 'Completed',
405
+ 'feedback.activity.running': 'Running',
406
+ 'feedback.activity.succeeded': 'Succeeded',
407
+ 'feedback.activity.failed': 'Failed',
408
+ 'feedback.activity.interrupted': 'Interrupted',
409
+ 'feedback.waiting': 'Awaiting approval',
410
+ 'feedback.error': 'Execution error',
411
+ 'feedback.interrupted': 'Interrupted',
412
+ 'feedback.noSteps': 'No recorded steps or tool activity',
413
+ 'feedback.loading': 'Loading records…',
414
+ 'feedback.recentOnly': 'Recent activity only; open details for the full record.',
415
+ 'feedback.open': 'View execution log',
416
+ 'feedback.openFileRecord': 'View file operation log',
417
+ 'feedback.files': 'File operations',
418
+ 'feedback.filesHint': 'Recorded successful operations; terminal changes may be absent.',
419
+ 'feedback.filesEmpty': 'No confirmed file operations yet',
420
+ 'feedback.filesUnknown': 'File records are incomplete',
421
+ 'feedback.file.write': 'Written',
422
+ 'feedback.file.edit': 'Edited',
423
+ 'feedback.file.create': 'Created',
424
+ 'feedback.file.patch': 'Patched',
425
+ 'feedback.partial': 'Some Agent or history records are not loaded; showing known facts.',
426
+ 'feedback.agentsPartial': 'The task list reached its limit; view other Agents in the execution tree.',
427
+ 'status.blocked': 'Blocked',
428
+
429
+ 'settings.presetTab': 'Preset status', 'settings.contextTab': 'Context settings', 'settings.tabs': 'Super Code settings',
430
+ 'budget.common': 'Common budgets', 'budget.superCode': 'Super Code budgets',
431
+ '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.',
432
+ 'budget.superCodeHint': 'Task records and task memory for Super Code. These limits never affect official presets.',
433
+ 'budget.defaultValue': '(default: {value})', 'budget.off': 'off', 'budget.managed': 'host-managed',
434
+ 'delete.title': 'Deletion protection', 'delete.mode': 'Protection mode', 'delete.off': 'Off', 'delete.relaxed': 'Relaxed', 'delete.strict': 'Strict', 'delete.official': 'Also apply to official presets',
435
+ 'delete.hint.off': 'Adds no deletion instructions or command interception. Host permission checks still apply.',
436
+ 'delete.hint.relaxed': 'Prefer trash, including in scripts. Explicit user requests may use rm without extra plugin confirmation.',
437
+ '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.',
438
+ 'delete.officialHint': 'Protects Super Code by default. This switch is independent of official-preset budgets; saved changes apply to subsequent commands immediately.',
439
+ 'budget.title': 'Context settings', 'budget.scope': 'Budget changes apply to new Agents. Running tasks keep their current settings.',
440
+ '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.',
441
+ 'budget.context': 'Context compression', 'budget.execution': 'Batched tool execution', 'budget.advanced': 'More budgets',
442
+ 'budget.pressureBasis': 'Compression budget basis', 'budget.inherit': 'Inherit default', 'budget.host': 'Host policy', 'budget.window': 'Full context window',
443
+ 'budget.thresholdRatio': 'Compress at (%)', 'budget.retainRatio': 'Keep recent context (%)', 'budget.summaryMaxTokens': 'Summary output limit (tokens)',
444
+ 'budget.compactionRetries': 'Additional compression attempts', 'budget.maxOverflowRetries': 'Context overflow retries',
445
+ 'budget.ptcTimeoutMs': 'Default deadline (seconds)', 'budget.ptcMaxTimeoutMs': 'Maximum deadline (seconds)',
446
+ 'budget.maxTasks': 'Active task record limit', 'budget.maxContextBytes': 'Task-memory context (bytes)',
447
+ 'budget.contextHint': 'Retention must be below the compression threshold. Larger budgets use more context.',
448
+ 'budget.ptcHint': 'Includes tool waits. The default deadline must fit within both the configured maximum and the host ceiling.',
449
+ 'budget.ptcUnavailable': 'This host manages PTC timing. Other budgets remain configurable.', 'budget.ptcCeiling': 'Host deadline ceiling: {seconds} seconds.',
450
+ 'budget.save': 'Save settings', 'budget.reset': 'Reset to defaults', 'budget.reload': 'Reload settings', 'budget.saving': 'Saving…',
451
+ '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.',
452
+ 'budget.error.connection': 'Budgets could not be loaded. Try again.', 'budget.error.invalid': 'Check the ranges, retention ratio, and deadlines.',
453
+ '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.',
454
+ 'budget.error.conflict': 'Budgets changed elsewhere. Your draft is retained; reload before editing again.', 'budget.error.read-only': 'This host has read-only settings.',
455
+ 'budget.error.ptc-unavailable': 'This host cannot override PTC elapsed deadlines.', 'budget.error.ptc-ceiling': 'The deadline exceeds the host ceiling.',
456
+ 'budget.error.save-failed': 'Save failed. Your draft is retained; try again.',
329
457
  'memory.add': 'Add memory', 'memory.edit': 'Edit', 'memory.cancel': 'Cancel', 'memory.save': 'Save', 'memory.delete': 'Delete', 'memory.deleting': 'Deleting…', 'memory.saving': 'Saving…',
330
458
  'memory.topic': 'Topic', 'memory.kind': 'Type', 'memory.summaryLabel': 'Title', 'memory.detailLabel': 'Content', 'memory.manual': 'Manually maintained',
331
459
  'memory.topicHint': 'Letters, digits, dots, underscores or hyphens; up to 96 characters.',
332
460
  'memory.deleteTitle': 'Delete memory', 'memory.deleteHint': 'Delete “{summary}”? This cannot be undone.',
333
461
  'memory.writeError': 'The operation failed. Your input is retained; try again.', 'memory.conflict': 'This memory changed elsewhere. Cancel and refresh before editing again.',
334
462
  'memory.missingEdit': 'This memory was deleted. Your input is retained.', 'memory.full': 'This topic or library is full. Remove entries you no longer need.',
335
- 'workspace.title': 'Super Code', 'workspace.description': 'Agent tree and memory library', 'workspace.resize': 'Resize agent tree and memory library',
463
+ 'workspace.title': 'Super Code', 'workspace.description': 'Task status, Agent tree and memory library', 'workspace.resize': 'Resize agent tree and memory library',
336
464
  'tree.collapsedLabel': 'Collapsed', 'tree.organizeLabel': 'Organize',
337
465
  'detail.toolCall': 'Tool call', 'detail.toolResult': 'Tool result', 'detail.toolError': 'Tool failed',
338
466
  'detail.cache': 'Cache', 'detail.info': 'Details', 'tree.countCompact': '{shown}/{total} · Running {running}',
@@ -717,12 +845,299 @@ function AgentTree({ useSessions, useSessionStatus, sessionId, useTabInfo, treeS
717
845
  return React.createElement(AgentTreeBody, { key: view.root, state, view, saved, openDetail, refresh, watchCatalog, onToggle, t })
718
846
  }
719
847
 
848
+ /** Join only the selected conversation's known descendants; never infer a plan. */
849
+ function taskFeedbackRows(view) {
850
+ const { rows, more } = visibleAgentRows(view, () => true, view.nodes.size)
851
+ const tasks = rows.map(row => {
852
+ const node = view.nodes.get(row.id), projections = node.projectionValues || {}, facts = projections.superCodeFeedback
853
+ const current = projections.superCodeTasks?.current
854
+ const recorded = current?.kind === 'task' ? current : undefined
855
+ const goal = recorded?.goal || (facts?.goal?.kind === 'recorded' ? facts.goal.text : '')
856
+ const title = recorded?.title || goal || facts?.request || ''
857
+ const todos = facts?.todos || projections.todos || []
858
+ return { ...row, node, facts, title, goal, source: recorded || facts?.goal?.kind === 'recorded' ? 'record' : facts?.request ? 'request' : 'none',
859
+ owner: node.title || node.displayTitle || '', todos, done: todos.filter(item => item.status === 'completed').length,
860
+ total: todos.length, complete: !facts?.omittedTodos, taskStatus: recorded?.status || (facts?.goal?.kind === 'recorded' ? facts.goal.status : '') }
861
+ })
862
+ const files = new Map()
863
+ // File identity remains independent of the virtual viewport.
864
+ for (const node of view.nodes.values()) for (const file of node.projectionValues?.superCodeFeedback?.files || []) {
865
+ const row = files.get(file.path) || { path: file.path, operations: new Set(), sources: [] }
866
+ row.operations.add(file.operation); row.sources.push({ node, seq: file.seq, tool: file.tool })
867
+ files.set(file.path, row)
868
+ }
869
+ return { tasks, more, files: [...files.values()].sort((a, b) => a.path.localeCompare(b.path)).map(file => ({ ...file, ...feedbackFileLabel(file) })),
870
+ partial: [...view.nodes.values()].some(node => !node.projectionValues?.superCodeFeedback || node.projectionValues.superCodeFeedback.omittedFiles) }
871
+ }
872
+ /** Presentation only: retain the full path while shortening workspace-owned labels. */
873
+ function feedbackFileLabel(file) {
874
+ const path = file.path.replace(/\\/g, '/')
875
+ const cwd = file.sources.map(source => source.node.cwd || source.node.projectionValues?.workingDirectory || '')
876
+ .find(value => value && path.startsWith(value.replace(/\\/g, '/').replace(/\/$/, '') + '/'))
877
+ const relative = cwd ? path.slice(cwd.replace(/\\/g, '/').replace(/\/$/, '').length + 1) : path
878
+ const slash = relative.lastIndexOf('/')
879
+ return { name: relative.slice(slash + 1) || relative, directory: slash < 0 ? '' : relative.slice(0, slash) }
880
+ }
881
+ function FeedbackMark({ tone, label }) {
882
+ return React.createElement('span', { className: 'dsh-super-code-feedback-mark ' + tone, role: 'img', 'aria-label': label },
883
+ tone === 'success' && React.createElement('svg', { viewBox: '0 0 16 16', fill: 'none', stroke: 'currentColor', strokeWidth: 1.6, 'aria-hidden': true },
884
+ React.createElement('path', { d: 'm3.5 8 3 3 6-6', strokeLinecap: 'round', strokeLinejoin: 'round' })),
885
+ tone === 'error' && React.createElement('span', { 'aria-hidden': true }, '!'))
886
+ }
887
+ function FeedbackActivity({ activity, t }) {
888
+ const tone = { running: 'active', failed: 'error', succeeded: 'success', interrupted: 'idle' }[activity.status] || 'idle'
889
+ return React.createElement('div', { className: 'dsh-super-code-feedback-activity', 'data-activity-state': activity.status },
890
+ React.createElement(FeedbackMark, { tone, label: text(t, 'feedback.activity.' + activity.status) }),
891
+ React.createElement('span', { className: 'dsh-super-code-feedback-activity-text', title: activity.description || activity.tool }, activity.description || activity.tool),
892
+ activity.description && React.createElement('small', { className: 'dsh-super-code-feedback-tool', title: activity.tool }, activity.tool),
893
+ ['failed', 'interrupted'].includes(activity.status) && React.createElement('small', null, text(t, 'feedback.activity.' + activity.status)))
894
+ }
895
+ /** Flatten presentation rows only; no task, step, or loaded activity is folded. */
896
+ function feedbackListItems(model, root, partial) {
897
+ const items = []
898
+ const add = (kind, key, data = {}, row) => items.push({ kind, key: JSON.stringify(key), row, ...data,
899
+ signature: JSON.stringify([kind, data.content, data.todo, data.activity, data.label, data.count,
900
+ data.file && [data.file.name, data.file.directory, [...data.file.operations]], kind === 'task' &&
901
+ [row.title, row.owner, row.done, row.total, row.complete, row.taskStatus, row.facts?.phase, row.node.running]]) })
902
+ for (const row of model.tasks) {
903
+ const facts = row.facts
904
+ add('task', [row.id, 'task'], {}, row)
905
+ if (row.goal && row.goal !== row.title) add('text', [row.id, 'goal'], { content: row.goal }, row)
906
+ if (facts?.latestRequest && facts.latestRequest !== facts.request) add('request', [row.id, 'request'], { content: facts.latestRequest }, row)
907
+ row.todos.forEach((todo, index) => add('step', [row.id, 'step', index], { todo }, row))
908
+ for (const activity of facts?.activities || []) add('activity', [row.id, 'activity', activity.callId, activity.seq], { activity }, row)
909
+ if (!row.total && !facts?.activities?.length) add('note', [row.id, 'empty'], { label: facts ? 'feedback.noSteps' : 'feedback.loading' }, row)
910
+ if (facts?.omittedActivities) add('note', [row.id, 'recent'], { label: 'feedback.recentOnly' }, row)
911
+ add('open', [row.id, 'open'], { focusable: !row.node.unavailable }, row)
912
+ }
913
+ if (root) {
914
+ add('files', ['files'], { count: model.files.length })
915
+ add('note', ['filesHint'], { label: 'feedback.filesHint' })
916
+ if (!model.files.length) add('note', ['filesEmpty'], { label: partial ? 'feedback.filesUnknown' : 'feedback.filesEmpty' })
917
+ for (const file of model.files) add('file', ['file', file.path], { file, focusable: !file.sources[0].node.unavailable })
918
+ }
919
+ if (partial) add('note', ['partial'], { label: 'feedback.partial' })
920
+ return items
921
+ }
922
+
923
+ /** Prefix heights allow binary-search windows without mounting offscreen rows. */
924
+ function feedbackListLayout(items, heights = new Map()) {
925
+ const estimates = { task: 64, text: 48, request: 70, step: 28, activity: 30, open: 34, files: 40, file: 30, note: 26 }
926
+ const offsets = [0], byKey = new Map()
927
+ items.forEach((item, index) => {
928
+ const measured = heights.get(item.key)
929
+ const height = measured?.signature === item.signature ? measured.height : estimates[item.kind]
930
+ offsets.push(offsets[index] + Math.max(1, height)); byKey.set(item.key, index)
931
+ })
932
+ return { offsets, byKey, height: offsets.at(-1) }
933
+ }
934
+ function feedbackListIndex(layout, top) {
935
+ let low = 0, high = layout.offsets.length - 1
936
+ while (low < high) {
937
+ const middle = Math.floor((low + high) / 2)
938
+ if (layout.offsets[middle + 1] <= top) low = middle + 1; else high = middle
939
+ }
940
+ return Math.min(low, Math.max(0, layout.offsets.length - 2))
941
+ }
942
+ function feedbackListWindow(layout, scrollTop, height) {
943
+ const top = Math.max(0, Math.min(scrollTop, Math.max(0, layout.height - height)))
944
+ return { top, start: feedbackListIndex(layout, Math.max(0, top - 240)),
945
+ end: layout.height ? Math.min(layout.offsets.length - 1, feedbackListIndex(layout, top + height + 240) + 1) : 0 }
946
+ }
947
+ /** Keep the same visible fact in place when earlier rows resize or arrive. */
948
+ function feedbackListAnchor(previous, next, scrollTop, height) {
949
+ if (!previous || !previous.layout.height) return Math.max(0, Math.min(scrollTop, Math.max(0, next.layout.height - height)))
950
+ const index = feedbackListIndex(previous.layout, scrollTop), key = previous.items[index]?.key
951
+ const nextIndex = next.layout.byKey.get(key)
952
+ const top = nextIndex === undefined ? scrollTop : next.layout.offsets[nextIndex] + Math.min(
953
+ Math.max(0, scrollTop - previous.layout.offsets[index]), next.layout.offsets[nextIndex + 1] - next.layout.offsets[nextIndex] - 1)
954
+ return Math.max(0, Math.min(top, Math.max(0, next.layout.height - height)))
955
+ }
956
+ function FeedbackRowContent({ item, root, openDetail, t }) {
957
+ const row = item.row
958
+ if (item.kind === 'task') {
959
+ const status = feedbackStatus({ ...row, root }, t)
960
+ const progressLabel = text(t, row.complete ? 'feedback.progress' : 'feedback.progressPartial', { done: row.done, total: row.total })
961
+ return React.createElement('div', { className: 'dsh-super-code-feedback-task-heading' },
962
+ React.createElement('div', { className: 'dsh-super-code-feedback-heading' },
963
+ React.createElement('h3', { title: row.source === 'none' ? '' : text(t, 'feedback.source.' + row.source) }, row.title || text(t, 'feedback.noGoal')),
964
+ React.createElement('div', { className: 'dsh-super-code-feedback-meta' },
965
+ React.createElement('small', null, row.owner || (row.id === root ? text(t, 'main.agent') : row.id)),
966
+ React.createElement('span', { className: 'dsh-super-code-feedback-status', 'data-tone': status.tone }, status.label))),
967
+ row.total > 0 && React.createElement('span', { className: 'dsh-super-code-feedback-count', 'aria-label': progressLabel, title: progressLabel }, `${row.done}/${row.total}${row.complete ? '' : '+'}`),
968
+ row.total > 0 && row.complete && React.createElement('progress', { max: row.total, value: row.done, 'aria-label': text(t, 'feedback.steps') }))
969
+ }
970
+ if (item.kind === 'step') return React.createElement('div', { className: 'dsh-super-code-feedback-step', 'data-step-state': item.todo.status },
971
+ React.createElement(FeedbackMark, { tone: item.todo.status === 'completed' ? 'success' : item.todo.status === 'in_progress' ? 'active' : 'pending', label: text(t, 'feedback.todo.' + item.todo.status) }),
972
+ React.createElement('span', null, item.todo.content))
973
+ if (item.kind === 'activity') return React.createElement(FeedbackActivity, { activity: item.activity, t })
974
+ if (item.kind === 'open') return React.createElement(ActionButton, { type: 'button', className: 'dsh-super-code-feedback-open', disabled: !item.focusable,
975
+ title: text(t, 'feedback.open'), onClick: () => openDetail(row.node) }, text(t, 'feedback.open'), React.createElement(Glyph, { type: 'arrow' }))
976
+ 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))
977
+ if (item.kind === 'file') {
978
+ const file = item.file
979
+ return React.createElement(ActionButton, { type: 'button', className: 'dsh-super-code-feedback-file', title: file.path,
980
+ 'aria-label': text(t, 'feedback.openFileRecord') + ' ' + file.path, disabled: !item.focusable, onClick: () => openDetail(file.sources[0].node) },
981
+ React.createElement(Glyph, { type: 'code' }), React.createElement('strong', null, file.name),
982
+ file.directory && React.createElement('small', null, file.directory),
983
+ React.createElement('span', { className: 'dsh-super-code-feedback-file-operation' }, [...file.operations].map(operation => text(t, 'feedback.file.' + operation)).join(' · ')))
984
+ }
985
+ 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))
986
+ return React.createElement('p', { className: 'dsh-super-code-feedback-text' },
987
+ item.kind === 'request' && React.createElement('small', null, text(t, 'feedback.latest')), item.content)
988
+ }
989
+
990
+ /** One measured, variable-height viewport for tasks, steps, activity and files. */
991
+ function FeedbackVirtualList({ items, root, saved, openDetail, t, watchCatalog }) {
992
+ const viewport = useRef(null), list = useRef(null), previous = useRef(null), pendingFocus = useRef('')
993
+ const language = localeCode(t)
994
+ const [scroll, setScroll] = useState({ top: saved.feedbackTop || 0, height: 640, width: saved.feedbackWidth || 0 })
995
+ const measurements = useRef(saved.feedbackHeights || new Map())
996
+ const [heightRevision, setHeightRevision] = useState(0)
997
+ const [focused, setFocused] = useState('')
998
+ const layout = useMemo(() => feedbackListLayout(items, measurements.current), [items, heightRevision])
999
+ const range = feedbackListWindow(layout, scroll.top, scroll.height)
1000
+ const indices = Array.from({ length: range.end - range.start }, (_, index) => range.start + index)
1001
+ const focusIndex = layout.byKey.get(focused)
1002
+ if (focusIndex !== undefined && !indices.includes(focusIndex)) indices.push(focusIndex)
1003
+ indices.sort((a, b) => a - b)
1004
+ const renderKey = indices.join(',')
1005
+ const watched = new Set(root ? [root] : [])
1006
+ for (const index of indices) if (items[index].row) watched.add(items[index].row.id)
1007
+ useLayoutEffect(() => {
1008
+ const element = viewport.current
1009
+ const measure = () => {
1010
+ const width = element.clientWidth
1011
+ if (width !== saved.feedbackWidth || language !== saved.feedbackLanguage) {
1012
+ saved.feedbackWidth = width; saved.feedbackLanguage = language
1013
+ saved.feedbackHeights = new Map(); measurements.current = saved.feedbackHeights; setHeightRevision(value => value + 1)
1014
+ }
1015
+ setScroll(value => value.top === element.scrollTop && value.height === element.clientHeight && value.width === width
1016
+ ? value : { top: element.scrollTop, height: element.clientHeight, width })
1017
+ }
1018
+ element.scrollTop = saved.feedbackTop || 0; measure()
1019
+ const observer = new ResizeObserver(measure); observer.observe(element)
1020
+ return () => observer.disconnect()
1021
+ }, [saved, language])
1022
+ useLayoutEffect(() => {
1023
+ const element = viewport.current
1024
+ const top = feedbackListAnchor(previous.current, { items, layout }, previous.current?.top ?? saved.feedbackTop ?? 0, element.clientHeight)
1025
+ element.scrollTop = top; saved.feedbackTop = element.scrollTop
1026
+ previous.current = { items, layout, top: element.scrollTop }
1027
+ setScroll(value => value.top === element.scrollTop ? value : { ...value, top: element.scrollTop })
1028
+ // Retain only current identities; long-running sessions must not leak measurements.
1029
+ for (const key of measurements.current.keys()) if (!layout.byKey.has(key)) measurements.current.delete(key)
1030
+ saved.feedbackHeights = measurements.current
1031
+ }, [items, layout, saved])
1032
+ useLayoutEffect(() => {
1033
+ const rows = [...list.current.children]
1034
+ let active = true
1035
+ const measure = () => {
1036
+ if (!active) return
1037
+ const current = measurements.current
1038
+ let next = current
1039
+ for (const element of rows) {
1040
+ const key = element.dataset.feedbackKey, item = items[layout.byKey.get(key)], height = element.getBoundingClientRect().height
1041
+ if (!item || height <= 0 || current.get(key)?.signature === item.signature && Math.abs(current.get(key).height - height) < .5) continue
1042
+ if (next === current) next = new Map(current)
1043
+ next.set(key, { signature: item.signature, height })
1044
+ }
1045
+ if (next === current) return
1046
+ measurements.current = next; saved.feedbackHeights = next
1047
+ setHeightRevision(value => value + 1)
1048
+ }
1049
+ measure()
1050
+ const observer = new ResizeObserver(measure); rows.forEach(element => observer.observe(element))
1051
+ return () => { active = false; observer.disconnect() }
1052
+ }, [items, renderKey, scroll.width, language, saved])
1053
+ useLayoutEffect(() => {
1054
+ if (!pendingFocus.current) return
1055
+ const element = [...list.current.children].find(row => row.dataset.feedbackKey === pendingFocus.current)
1056
+ const button = element?.querySelector('button')
1057
+ if (button) { button.focus({ preventScroll: true }); pendingFocus.current = '' }
1058
+ }, [renderKey, items, layout, focused])
1059
+ const tab = event => {
1060
+ if (event.key !== 'Tab') return
1061
+ const current = layout.byKey.get(focused)
1062
+ if (current === undefined) return
1063
+ const direction = event.shiftKey ? -1 : 1
1064
+ for (let index = current + direction; index >= 0 && index < items.length; index += direction) {
1065
+ if (!items[index].focusable) continue
1066
+ event.preventDefault(); pendingFocus.current = items[index].key; setFocused(items[index].key)
1067
+ const top = Math.max(0, Math.min(layout.offsets[index], Math.max(0, layout.height - viewport.current.clientHeight)))
1068
+ viewport.current.scrollTop = top; saved.feedbackTop = top
1069
+ if (previous.current) previous.current.top = top
1070
+ setScroll(value => ({ ...value, top })); return
1071
+ }
1072
+ }
1073
+ return React.createElement('div', { ref: viewport, className: 'dsh-super-code-feedback-scroll', tabIndex: 0, 'aria-label': text(t, 'feedback.title'), onKeyDown: tab,
1074
+ onScroll: event => {
1075
+ const top = event.currentTarget.scrollTop; saved.feedbackTop = top
1076
+ if (previous.current) previous.current.top = top
1077
+ setScroll(value => value.top === top ? value : { ...value, top })
1078
+ }, onFocusCapture: event => setFocused(event.target.closest('[data-feedback-key]')?.dataset.feedbackKey || ''),
1079
+ onBlurCapture: event => { if (!event.currentTarget.contains(event.relatedTarget)) setFocused('') } },
1080
+ [...watched].map(id => React.createElement(AgentCatalogWatch, { key: id, id, watchCatalog })),
1081
+ 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 },
1082
+ indices.map(index => {
1083
+ const item = items[index], row = item.row, header = item.kind === 'task'
1084
+ const indent = row ? Math.min(Math.max(0, row.level - 1), 4) * 16 + (header ? 0 : 14) : 0
1085
+ return React.createElement('li', { key: item.key, 'data-feedback-key': item.key, 'data-feedback-kind': item.kind,
1086
+ 'data-feedback-agent': row?.id, 'data-feedback-depth': row?.level, 'data-feedback-live': row ? row.node.running === true || row.facts?.phase === 'running' : undefined,
1087
+ 'aria-posinset': index + 1, 'aria-setsize': items.length,
1088
+ style: { transform: `translateY(${layout.offsets[index]}px)`, paddingLeft: indent, '--sc-feedback-indent': indent + 'px' } }, React.createElement(FeedbackRowContent, { item, root, openDetail, t }))
1089
+ })))
1090
+ }
1091
+ function feedbackStatus(row, t = key => zh[key] || key) {
1092
+ if (row.node.unavailable) return { tone: 'unavailable', label: text(t, 'tree.legend.unavailable') }
1093
+ const phase = row.facts?.phase
1094
+ if (row.node.running === true) return { tone: 'active', label: text(t, phase === 'waiting' ? 'feedback.waiting' : 'tree.legend.active') }
1095
+ if (phase === 'error' || phase === 'interrupted') return { tone: 'error', label: text(t, `feedback.${phase}`) }
1096
+ if (['completed', 'cancelled', 'paused', 'blocked'].includes(row.taskStatus)) return { tone: row.taskStatus === 'completed' ? 'success' : 'idle', label: text(t, `status.${row.taskStatus}`) }
1097
+ // Host inactivity or a completed turn does not establish task completion.
1098
+ return agentExecutionStatus(row.node, row.id === row.root, t)
1099
+ }
1100
+ function TaskFeedback({ useSessions, useSessionStatus, sessionId, useTabInfo, treeStates, openDetail, refresh, watchCatalog, onToggle, t }) {
1101
+ const catalog = useSessions(s => s), statuses = useSessionStatus ? useSessionStatus(s => s) : undefined
1102
+ const state = useMemo(() => agentSessionState(catalog, sessionId, statuses), [catalog, sessionId, statuses])
1103
+ const view = useMemo(() => buildAgentView(state), [state.byId, state.subagentsByParent, state.current, state.currentAddress])
1104
+ const { tab } = useTabInfo()
1105
+ const saved = useMemo(() => {
1106
+ let value = treeStates.get(tab.signal)
1107
+ 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) }
1108
+ return value
1109
+ }, [treeStates, tab.signal, view.root])
1110
+ const [busy, setBusy] = useState(false), [error, setError] = useState(false)
1111
+ const model = useMemo(() => taskFeedbackRows(view), [view])
1112
+ const partial = model.partial || Object.values(state.subagentsByParent || {}).some(value => value.state === 'error')
1113
+ const items = useMemo(() => {
1114
+ const rows = feedbackListItems(model, view.root, partial)
1115
+ if (!view.root) rows.unshift({ kind: 'note', key: 'empty', label: 'tree.empty', signature: 'empty' })
1116
+ if (error) rows.unshift({ kind: 'note', key: 'error', label: 'tree.loadError', signature: 'error' })
1117
+ return rows
1118
+ }, [model, view.root, partial, error])
1119
+ const reload = async () => {
1120
+ setBusy(true); setError(false)
1121
+ try {
1122
+ const ids = [...view.nodes.keys()]
1123
+ for (let start = 0; start < ids.length; start += 20) await Promise.all(ids.slice(start, start + 20).map(id => refresh(id)))
1124
+ }
1125
+ catch { setError(true) }
1126
+ finally { setBusy(false) }
1127
+ }
1128
+ return React.createElement('section', { className: 'dsh-super-code-feedback', 'data-task-feedback': true },
1129
+ React.createElement('header', null,
1130
+ React.createElement(WorkspaceSectionToggle, { section: 'tasks', expanded: true, onToggle, t }),
1131
+ 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))),
1132
+ React.createElement(FeedbackVirtualList, { key: view.root, items, root: view.root, saved, openDetail, t, watchCatalog }))
1133
+ }
1134
+
720
1135
  function WorkspaceSectionToggle({ section, expanded, onToggle, children, t }) {
721
- const label = text(t, section === 'tree' ? 'tree.title' : 'memory.title')
1136
+ const label = text(t, section === 'tasks' ? 'feedback.title' : section === 'tree' ? 'tree.title' : 'memory.title')
722
1137
  return React.createElement(ActionButton, { type: 'button', className: 'dsh-super-code-section-toggle',
723
1138
  'data-workspace-section': section, 'aria-label': label, 'aria-expanded': expanded, onClick: onToggle },
724
1139
  React.createElement(Glyph, { type: 'chevron' }),
725
- React.createElement(Glyph, { type: section === 'tree' ? 'branch' : 'memory' }),
1140
+ React.createElement(Glyph, { type: section === 'tasks' ? 'context' : section === 'tree' ? 'branch' : 'memory' }),
726
1141
  React.createElement('strong', null, label), children)
727
1142
  }
728
1143
 
@@ -733,7 +1148,7 @@ function SuperCodeWorkspace(props) {
733
1148
  const navigationKey = `${address}/${tab.navigation?.revision || 0}`
734
1149
  const memoryTarget = memoryLibraryTarget(address, '')
735
1150
  const saved = useMemo(() => {
736
- const value = props.workspaceStates.get(tab.signal) || { active: 'tree' }
1151
+ const value = props.workspaceStates.get(tab.signal) || { active: 'tasks' }
737
1152
  if (value.navigationKey !== navigationKey) {
738
1153
  value.navigationKey = navigationKey
739
1154
  if (memoryTarget.id) value.active = 'memory'
@@ -741,10 +1156,10 @@ function SuperCodeWorkspace(props) {
741
1156
  props.workspaceStates.set(tab.signal, value)
742
1157
  return value
743
1158
  }, [props.workspaceStates, tab.signal])
744
- const [active, setActive] = useState(saved.active === 'memory' ? 'memory' : 'tree')
1159
+ const [active, setActive] = useState(['tasks', 'tree', 'memory'].includes(saved.active) ? saved.active : 'tasks')
745
1160
  const layout = useRef(null), focusSection = useRef('')
746
- const toggle = () => {
747
- const next = active === 'tree' ? 'memory' : 'tree'
1161
+ const toggle = section => {
1162
+ const next = active === section ? section === 'tasks' ? 'tree' : 'tasks' : section
748
1163
  saved.active = next; focusSection.current = next; setActive(next)
749
1164
  }
750
1165
  useLayoutEffect(() => {
@@ -759,14 +1174,13 @@ function SuperCodeWorkspace(props) {
759
1174
  }, [active])
760
1175
  return React.createElement('section', { className: 'dsh-super-code-workspace', 'aria-label': text(props.t, 'workspace.title') },
761
1176
  React.createElement('div', { ref: layout, className: 'dsh-super-code-workspace-layout', 'data-active-section': active, style: {
762
- gridTemplateRows: active === 'tree' ? 'minmax(280px, 1fr) 44px' : '44px minmax(280px, 1fr)',
763
- } },
764
- active === 'tree' ? React.createElement(AgentTree, { ...props, key: 'tree', onToggle: toggle })
765
- : React.createElement('header', { key: 'tree-collapsed', className: 'dsh-super-code-workspace-collapsed' },
766
- React.createElement(WorkspaceSectionToggle, { section: 'tree', expanded: false, onToggle: toggle, t: props.t })),
767
- active === 'memory' ? React.createElement(MemoryLibrary, { ...props, key: 'memory', onToggle: toggle })
768
- : React.createElement('header', { key: 'memory-collapsed', className: 'dsh-super-code-workspace-collapsed' },
769
- React.createElement(WorkspaceSectionToggle, { section: 'memory', expanded: false, onToggle: toggle, t: props.t }))))
1177
+ gridTemplateRows: ['tasks', 'tree', 'memory'].map(section => active === section ? 'minmax(280px, 1fr)' : '44px').join(' '),
1178
+ } }, ['tasks', 'tree', 'memory'].map(section => {
1179
+ const onToggle = () => toggle(section)
1180
+ return active === section ? React.createElement(section === 'tasks' ? TaskFeedback : section === 'tree' ? AgentTree : MemoryLibrary, { ...props, key: section, onToggle })
1181
+ : React.createElement('header', { key: section + '-collapsed', className: 'dsh-super-code-workspace-collapsed' },
1182
+ React.createElement(WorkspaceSectionToggle, { section, expanded: false, onToggle, t: props.t }))
1183
+ })))
770
1184
  }
771
1185
 
772
1186
  function AgentCatalogWatch({ id, watchCatalog }) {
@@ -1533,27 +1947,200 @@ const memoryRemoteContribution = {
1533
1947
  })),
1534
1948
  }
1535
1949
 
1950
+ // Budget settings use the same public Remote transport as the preset settings.
1951
+ const budgetRemoteContribution = {
1952
+ package: 'dsh-super-code', descriptors: ['readConfig', 'saveConfig', 'resetConfig'].map(method => ({
1953
+ id: `dsh-super-code:superCodeBudgets/${method}`, service: 'superCodeBudgets', namespace: 'superCodeBudgets', method, invocation: { kind: 'direct' },
1954
+ parameters: (method === 'saveConfig' ? ['content', 'expected'] : method === 'resetConfig' ? ['expected'] : [])
1955
+ .map(name => ({ name, wire: name, source: 'json', codec: remoteStringCodec })), result: { mode: 'src-json' },
1956
+ })),
1957
+ }
1958
+
1959
+ const budgetFields = [
1960
+ { key: 'thresholdRatio', group: 'context', factor: 0.01, min: 1, max: 100, step: 'any' },
1961
+ { key: 'retainRatio', group: 'context', factor: 0.01, min: 0, max: 100, step: 'any' },
1962
+ { key: 'summaryMaxTokens', group: 'context', factor: 1, min: 1, step: 1 },
1963
+ { key: 'ptcTimeoutMs', group: 'execution', factor: 1000, min: 0.001, step: 0.001 },
1964
+ { key: 'ptcMaxTimeoutMs', group: 'execution', factor: 1000, min: 0.001, step: 0.001 },
1965
+ { key: 'compactionRetries', group: 'advanced', factor: 1, min: 0, max: 10, step: 1 },
1966
+ { key: 'maxOverflowRetries', group: 'advanced', factor: 1, min: 0, max: 10, step: 1 },
1967
+ { key: 'maxTasks', group: 'superCode', root: true, factor: 1, min: 1, max: 128, step: 1 },
1968
+ { key: 'maxContextBytes', group: 'superCode', root: true, factor: 1, min: 1024, max: 262144, step: 1 },
1969
+ ]
1970
+ function budgetDraft(configuration) {
1971
+ const deletion = configuration.deletionProtection
1972
+ return Object.fromEntries([['deletionProtection', deletion === true ? 'strict' : deletion === false ? 'off' : deletion ?? 'relaxed'], ['deletionProtectionForOfficialPresets', configuration.deletionProtectionForOfficialPresets ?? false], ['applyToOfficialPresets', configuration.applyToOfficialPresets ?? false], ['pressureBasis', configuration.budgets?.pressureBasis ?? ''],
1973
+ ...budgetFields.map(field => {
1974
+ const value = field.root ? configuration[field.key] : configuration.budgets?.[field.key]
1975
+ return [field.key, value === undefined ? '' : String(value / field.factor)]
1976
+ })])
1977
+ }
1978
+ function budgetConfiguration(draft) {
1979
+ const config = {}, budgets = {}
1980
+ if (draft.deletionProtection !== undefined) {
1981
+ if (!['off', 'relaxed', 'strict'].includes(draft.deletionProtection)) return { ok: false, error: 'invalid' }
1982
+ config.deletionProtection = draft.deletionProtection
1983
+ }
1984
+ if (draft.deletionProtectionForOfficialPresets !== undefined) {
1985
+ if (typeof draft.deletionProtectionForOfficialPresets !== 'boolean') return { ok: false, error: 'invalid' }
1986
+ config.deletionProtectionForOfficialPresets = draft.deletionProtectionForOfficialPresets
1987
+ }
1988
+ if (draft.applyToOfficialPresets !== undefined) {
1989
+ if (typeof draft.applyToOfficialPresets !== 'boolean') return { ok: false, error: 'invalid' }
1990
+ config.applyToOfficialPresets = draft.applyToOfficialPresets
1991
+ }
1992
+ if (draft.pressureBasis) {
1993
+ if (!['host', 'window'].includes(draft.pressureBasis)) return { ok: false, error: 'invalid' }
1994
+ budgets.pressureBasis = draft.pressureBasis
1995
+ }
1996
+ for (const field of budgetFields) {
1997
+ const input = draft[field.key]?.trim()
1998
+ if (!input) continue
1999
+ const value = Number(input)
2000
+ if (!Number.isFinite(value) || value < field.min || (field.max !== undefined && value > field.max)) return { ok: false, error: 'invalid' }
2001
+ const scaled = value * field.factor
2002
+ const selected = field.step === 'any' ? scaled : Math.round(scaled)
2003
+ if (field.step !== 'any' && (Math.abs(selected - scaled) > 1e-7 || !Number.isSafeInteger(selected))) return { ok: false, error: 'invalid' }
2004
+ ;(field.root ? config : budgets)[field.key] = selected
2005
+ }
2006
+ if ((budgets.retainRatio ?? 0.16) >= (budgets.thresholdRatio ?? 0.8)) return { ok: false, error: 'retain' }
2007
+ if (budgets.ptcTimeoutMs !== undefined && budgets.ptcMaxTimeoutMs !== undefined && budgets.ptcTimeoutMs > budgets.ptcMaxTimeoutMs) return { ok: false, error: 'ptc-order' }
2008
+ if (Object.keys(budgets).length) config.budgets = budgets
2009
+ return { ok: true, configuration: config }
2010
+ }
2011
+
2012
+ function BudgetSettings({ api, t, active }) {
2013
+ const [snapshot, setSnapshot] = useState(null)
2014
+ const [draft, setDraft] = useState(() => budgetDraft({})), [busy, setBusy] = useState(false)
2015
+ const [error, setError] = useState(''), [notice, setNotice] = useState('')
2016
+ const alive = useRef(false), pending = useRef(false)
2017
+ const receive = value => { setSnapshot(value); setDraft(budgetDraft(value.configuration)) }
2018
+ const read = async () => {
2019
+ if (pending.current) return
2020
+ pending.current = true; setBusy(true); setError(''); setNotice('')
2021
+ try {
2022
+ const response = await api.readConfig()
2023
+ if (!response.ok) throw new Error('Remote unavailable')
2024
+ if (alive.current) receive(response.value)
2025
+ } catch { if (alive.current) setError('connection') }
2026
+ finally { pending.current = false; if (alive.current) setBusy(false) }
2027
+ }
2028
+ useEffect(() => { alive.current = true; return () => { alive.current = false } }, [api])
2029
+ useEffect(() => { if (active && !snapshot && !error) read() }, [api, active])
2030
+ const save = async reset => {
2031
+ if (pending.current || !snapshot?.writable) return
2032
+ const parsed = reset ? { ok: true } : budgetConfiguration(draft)
2033
+ if (!parsed.ok) { setError(parsed.error); setNotice(''); return }
2034
+ pending.current = true; setBusy(true); setError(''); setNotice('')
2035
+ try {
2036
+ const response = reset ? await api.resetConfig(String(snapshot.revision))
2037
+ : await api.saveConfig(JSON.stringify(parsed.configuration), String(snapshot.revision))
2038
+ if (!response.ok) throw new Error('Remote unavailable')
2039
+ if (!alive.current) return
2040
+ if (response.value.ok) { receive(response.value.snapshot); setNotice(reset ? 'resetDone' : 'saved') }
2041
+ else setError(response.value.error)
2042
+ } catch { if (alive.current) setError('save-failed') }
2043
+ finally { pending.current = false; if (alive.current) setBusy(false) }
2044
+ }
2045
+ const disabled = busy || !snapshot?.writable
2046
+ const dirty = snapshot && JSON.stringify(draft) !== JSON.stringify(budgetDraft(snapshot.configuration))
2047
+ const edit = (key, value) => { setDraft(current => ({ ...current, [key]: value })); setError(''); setNotice('') }
2048
+ const defaultLabel = (key, field) => {
2049
+ const value = field?.root ? snapshot?.defaults?.[key] : snapshot?.defaults?.budgets?.[key]
2050
+ const shown = key === 'pressureBasis' ? t('budget.host') : value === undefined ? t('budget.managed') : Number(value / field.factor).toLocaleString('en-US', { maximumFractionDigits: 3 })
2051
+ return React.createElement('span', { className: 'dsh-super-code-budget-default' }, text(t, 'budget.defaultValue', { value: shown }))
2052
+ }
2053
+ const fields = group => budgetFields.filter(field => field.group === group).map(field => {
2054
+ const id = `super-code-budget-${field.key}`
2055
+ return React.createElement('div', { className: 'dsh-super-code-settings-field', key: field.key },
2056
+ React.createElement('label', { htmlFor: id }, t(`budget.${field.key}`), ' ', defaultLabel(field.key, field)),
2057
+ React.createElement('input', { id, type: 'number', inputMode: 'decimal', value: draft[field.key],
2058
+ min: field.min, max: group === 'execution' && snapshot?.ptcMaxTimeoutMs ? snapshot.ptcMaxTimeoutMs / 1000 : field.max,
2059
+ step: field.step, placeholder: t('budget.inherit'), disabled: disabled || (group === 'execution' && snapshot?.ptcMaxTimeoutMs === false),
2060
+ onChange: event => edit(field.key, event.target.value) }))
2061
+ })
2062
+ return React.createElement('section', { className: 'dsh-super-code-budgets' },
2063
+ React.createElement('form', { 'aria-label': t('budget.title'), 'aria-busy': busy, onSubmit: event => { event.preventDefault(); save(false) } },
2064
+ React.createElement('section', { className: 'dsh-super-code-budget-group', 'aria-labelledby': 'super-code-deletion-title' },
2065
+ React.createElement('h3', { id: 'super-code-deletion-title' }, t('delete.title')),
2066
+ React.createElement('div', { className: 'dsh-super-code-settings-field' },
2067
+ React.createElement('label', { id: 'super-code-deletion-mode-label' }, t('delete.mode')),
2068
+ React.createElement(SelectControl, { id: 'super-code-deletion-protection', label: t('delete.mode'), value: draft.deletionProtection,
2069
+ choices: ['off', 'relaxed', 'strict'].map(mode => [mode, t(`delete.${mode}`)]), disabled,
2070
+ onChange: value => edit('deletionProtection', value) })),
2071
+ React.createElement('p', { id: 'super-code-deletion-hint', className: 'dsh-super-code-budget-hint' }, t(`delete.hint.${draft.deletionProtection}`)),
2072
+ React.createElement('label', { className: 'dsh-super-code-budget-switch' },
2073
+ React.createElement('input', { id: 'super-code-deletion-official', type: 'checkbox', role: 'switch', checked: draft.deletionProtectionForOfficialPresets, disabled: disabled || draft.deletionProtection === 'off',
2074
+ 'aria-describedby': 'super-code-deletion-official-hint', onChange: event => edit('deletionProtectionForOfficialPresets', event.target.checked) }),
2075
+ React.createElement('span', null, t('delete.official'))),
2076
+ React.createElement('p', { id: 'super-code-deletion-official-hint', className: 'dsh-super-code-budget-hint' }, t('delete.officialHint'))),
2077
+ React.createElement('p', { className: 'dsh-super-code-budget-hint' }, t('budget.scope')),
2078
+ React.createElement('section', { className: 'dsh-super-code-budget-group', 'aria-labelledby': 'super-code-common-title' },
2079
+ React.createElement('h3', { id: 'super-code-common-title' }, t('budget.common')),
2080
+ React.createElement('label', { className: 'dsh-super-code-budget-switch' },
2081
+ React.createElement('input', { type: 'checkbox', role: 'switch', checked: draft.applyToOfficialPresets, disabled,
2082
+ 'aria-describedby': 'super-code-official-hint', onChange: event => edit('applyToOfficialPresets', event.target.checked) }),
2083
+ React.createElement('span', null, t('budget.applyToOfficialPresets'), ' ', React.createElement('span', { className: 'dsh-super-code-budget-default' }, text(t, 'budget.defaultValue', { value: t('budget.off') })))),
2084
+ React.createElement('p', { id: 'super-code-official-hint', className: 'dsh-super-code-budget-hint' }, t('budget.officialHint')),
2085
+ React.createElement('p', { className: 'dsh-super-code-budget-hint' }, t('budget.defaults')),
2086
+ React.createElement('fieldset', null, React.createElement('legend', null, t('budget.context')),
2087
+ React.createElement('div', { className: 'dsh-super-code-budget-grid' },
2088
+ React.createElement('div', { className: 'dsh-super-code-settings-field' },
2089
+ React.createElement('label', { id: 'super-code-budget-basis-label' }, t('budget.pressureBasis'), ' ', defaultLabel('pressureBasis')),
2090
+ React.createElement(SelectControl, { id: 'super-code-budget-basis', label: t('budget.pressureBasis'), value: draft.pressureBasis,
2091
+ choices: [['', t('budget.inherit')], ['host', t('budget.host')], ['window', t('budget.window')]], disabled,
2092
+ onChange: value => edit('pressureBasis', value) })), fields('context')),
2093
+ React.createElement('small', null, t('budget.contextHint'))),
2094
+ React.createElement('fieldset', null, React.createElement('legend', null, t('budget.execution')),
2095
+ React.createElement('div', { className: 'dsh-super-code-budget-grid' }, fields('execution')),
2096
+ React.createElement('small', null, t(snapshot?.ptcMaxTimeoutMs === false ? 'budget.ptcUnavailable' : 'budget.ptcHint')),
2097
+ snapshot?.ptcMaxTimeoutMs && React.createElement('small', null, text(t, 'budget.ptcCeiling', { seconds: snapshot.ptcMaxTimeoutMs / 1000 }))),
2098
+ React.createElement('details', { className: 'dsh-super-code-budget-more' },
2099
+ React.createElement('summary', null, t('budget.advanced')),
2100
+ React.createElement('div', { className: 'dsh-super-code-budget-grid' }, fields('advanced')))),
2101
+ React.createElement('section', { className: 'dsh-super-code-budget-group dsh-super-code-budget-exclusive', 'aria-labelledby': 'super-code-exclusive-title' },
2102
+ React.createElement('h3', { id: 'super-code-exclusive-title' }, t('budget.superCode')),
2103
+ React.createElement('p', { className: 'dsh-super-code-budget-hint' }, t('budget.superCodeHint')),
2104
+ React.createElement('div', { className: 'dsh-super-code-budget-grid' }, fields('superCode'))),
2105
+ !busy && snapshot && !snapshot.writable && React.createElement('small', null, t('budget.error.read-only')),
2106
+ error && React.createElement('p', { role: 'alert' }, t(`budget.error.${error}`)),
2107
+ notice && React.createElement('p', { role: 'status', className: 'dsh-super-code-budget-hint' }, t(`budget.${notice}`)),
2108
+ React.createElement('div', { className: 'dsh-super-code-settings-form-actions' },
2109
+ error && React.createElement(ActionButton, { type: 'button', disabled: busy, onClick: read }, t('budget.reload')),
2110
+ React.createElement(ActionButton, { type: 'button', disabled, onClick: () => save(true) }, t('budget.reset')),
2111
+ React.createElement(ActionButton, { type: 'submit', className: 'dsh-super-code-primary-button', disabled: disabled || !dirty }, t(busy ? 'budget.saving' : 'budget.save')))))
2112
+ }
2113
+
1536
2114
  // Old hosts render file metadata. Synchronize through our own Remote, then
1537
2115
  // reload their cached roster only when a managed field actually changed.
1538
2116
  function synchronizePresetDisplay(api, locale, reload, onError, preferenceScope) {
1539
2117
  let disposed = false, running = false, pending = '', last = '', needsReload = false
2118
+ let observed = locale.getSnapshot().active, awaitingPreference = false, settled = false
1540
2119
  const changed = () => {
1541
- // A provisional browser locale can precede the host's saved preference.
1542
- // Do not rewrite the preset or reload while that public scope is loading.
1543
- if (preferenceScope?.getSnapshot().status === 'loading') return
1544
- pending = locale.getSnapshot().active
1545
- if (running || pending === last) return
2120
+ const language = locale.getSnapshot().active
2121
+ if (language !== observed) { observed = language; awaitingPreference = !!preferenceScope }
2122
+ const preference = preferenceScope?.getSnapshot()
2123
+ // Locale publishes optimistically, before its host write starts. A reload
2124
+ // must wait for that preference to appear in the accepted public snapshot.
2125
+ settled = preference?.status !== 'loading'
2126
+ if (preference?.status === 'ready' && preference.mode !== 'memory') {
2127
+ if (preference.value?.preference === language) awaitingPreference = false
2128
+ else if (awaitingPreference || preference.value?.preference !== undefined) settled = false
2129
+ }
2130
+ if (!settled) return
2131
+ pending = language
2132
+ if (running || (pending === last && !needsReload)) return
1546
2133
  running = true
1547
2134
  void (async () => {
1548
2135
  try {
1549
- while (!disposed && pending !== last) {
2136
+ while (!disposed && settled && pending !== last) {
1550
2137
  const language = pending
1551
2138
  const result = await api.synchronize(language)
1552
2139
  if (!result.ok) throw new Error('Preset display synchronization failed')
1553
2140
  last = language
1554
2141
  needsReload ||= result.value.changed
1555
- if (!disposed && needsReload && pending === language) { reload(); return }
1556
2142
  }
2143
+ if (!disposed && settled && needsReload && pending === last) reload()
1557
2144
  } catch (error) { if (!disposed) onError(error) }
1558
2145
  finally { running = false }
1559
2146
  })()
@@ -1564,7 +2151,8 @@ function synchronizePresetDisplay(api, locale, reload, onError, preferenceScope)
1564
2151
  return () => { disposed = true; unsubscribe(); unsubscribePreference() }
1565
2152
  }
1566
2153
 
1567
- function PresetSettings({ api, t }) {
2154
+ function PresetSettings({ api, budgetApi, t }) {
2155
+ const [tab, setTab] = useState('preset')
1568
2156
  const [status, setStatus] = useState(null), [customId, setCustomId] = useState(null)
1569
2157
  const reinstall = status?.reinstallable ?? status?.state === 'installed'
1570
2158
  const name = customId ?? (reinstall ? status.id : 'super-code')
@@ -1630,12 +2218,22 @@ function PresetSettings({ api, t }) {
1630
2218
  const available = status && ['available', 'installed'].includes(status.state)
1631
2219
  const versionState = status?.presetVersion ? status.presetVersion === status.pluginVersion ? 'match' : 'mismatch' : 'unknown'
1632
2220
  return React.createElement('section', { className: 'dsh-super-code-settings' },
1633
- React.createElement('header', { className: 'dsh-super-code-settings-header' },
1634
- React.createElement('span', { className: 'dsh-super-code-settings-mark', 'aria-hidden': true }, React.createElement(Glyph, { type: 'code' })),
1635
- React.createElement('div', { className: 'dsh-super-code-settings-head-text' },
1636
- React.createElement('h2', { className: 'dsh-super-code-settings-name' }, t('preset.title')),
1637
- React.createElement('p', { className: 'dsh-super-code-settings-description' }, t('preset.description')))),
2221
+ React.createElement('div', { className: 'dsh-super-code-settings-tabs', role: 'tablist', 'aria-label': t('settings.tabs'), onKeyDown: event => {
2222
+ if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return
2223
+ event.preventDefault()
2224
+ const selected = event.key === 'Home' ? 'preset' : event.key === 'End' ? 'context' : tab === 'preset' ? 'context' : 'preset'
2225
+ setTab(selected)
2226
+ event.currentTarget.querySelector(`#super-code-tab-${selected}`)?.focus()
2227
+ } }, ...['preset', 'context'].map(value => React.createElement('button', { key: value, type: 'button', role: 'tab',
2228
+ id: `super-code-tab-${value}`, 'aria-selected': tab === value, 'aria-controls': `super-code-panel-${value}`,
2229
+ tabIndex: tab === value ? 0 : -1, onClick: () => setTab(value) }, t(`settings.${value}Tab`)))),
1638
2230
  React.createElement('div', { className: 'dsh-super-code-settings-body' },
2231
+ React.createElement('div', { role: 'tabpanel', id: 'super-code-panel-preset', 'aria-labelledby': 'super-code-tab-preset', hidden: tab !== 'preset' },
2232
+ React.createElement('header', { className: 'dsh-super-code-settings-header' },
2233
+ React.createElement('span', { className: 'dsh-super-code-settings-mark', 'aria-hidden': true }, React.createElement(Glyph, { type: 'code' })),
2234
+ React.createElement('div', { className: 'dsh-super-code-settings-head-text' },
2235
+ React.createElement('h2', { className: 'dsh-super-code-settings-name' }, t('preset.title')),
2236
+ React.createElement('p', { className: 'dsh-super-code-settings-description' }, t('preset.description')))),
1639
2237
  React.createElement('div', { className: 'dsh-super-code-settings-summary' },
1640
2238
  React.createElement('div', { className: 'dsh-super-code-settings-status-row' },
1641
2239
  status ? React.createElement('p', { className: 'dsh-super-code-preset-status', role: 'status', 'data-available': available },
@@ -1672,6 +2270,8 @@ function PresetSettings({ api, t }) {
1672
2270
  status?.delivery === 'declaration' && !status.authorable && React.createElement('p', { role: 'note' }, t('preset.declaredReadonly')),
1673
2271
  status?.delivery === 'directory' && !status.authorable && React.createElement('p', { role: 'note' }, t('preset.readonly'))),
1674
2272
  error && React.createElement('p', { role: 'alert' }, t(error))),
2273
+ React.createElement('div', { role: 'tabpanel', id: 'super-code-panel-context', 'aria-labelledby': 'super-code-tab-context', hidden: tab !== 'context' },
2274
+ React.createElement(BudgetSettings, { api: budgetApi, t, active: tab === 'context' }))),
1675
2275
  confirmation && React.createElement('dialog', { ref: dialog, className: 'dsh-super-code-confirm',
1676
2276
  'aria-labelledby': 'super-code-confirm-title', 'aria-describedby': 'super-code-confirm-hint',
1677
2277
  onKeyDown: event => { if (event.key === 'Escape') event.stopPropagation() },
@@ -1709,20 +2309,20 @@ function apply(ctx) {
1709
2309
  // $mount creates a lifecycle effect on this same fiber; awaiting it from
1710
2310
  // another effect would stall the namespace and the settings contribution.
1711
2311
  void ctx.remote.$mount({ package: 'dsh-super-code', descriptors: [
1712
- ...presetRemoteContribution.descriptors, ...memoryRemoteContribution.descriptors,
2312
+ ...presetRemoteContribution.descriptors, ...memoryRemoteContribution.descriptors, ...budgetRemoteContribution.descriptors,
1713
2313
  ] }).catch(error => console.warn('Super Code Remote:', error))
1714
2314
  const memoryStates = new WeakMap(), workspaceStates = new WeakMap()
1715
2315
  // Locale already requires its host's preference service. Declare that same
1716
2316
  // service here: Cordis rejects even optional reads without an injection.
1717
2317
  const preferenceService = 'configForms' in ctx ? 'configForms' : 'settingsScope'
1718
- ctx.inject(['remote.superCodePresets', preferenceService], ready => {
2318
+ ctx.inject(['remote.superCodePresets', 'remote.superCodeBudgets', preferenceService], ready => {
1719
2319
  const preferenceScope = preferenceService === 'configForms'
1720
2320
  ? ready.configForms.get('locale') : ready.settingsScope.bind({ namespace: 'locale' })
1721
2321
  ready.effect(() => synchronizePresetDisplay(ready.remote.superCodePresets, locale,
1722
2322
  () => window.location.reload(), error => console.warn('Super Code preset display:', error), preferenceScope), 'super-code: preset display')
1723
2323
  ready.slots.inject('settings.section', () => ready.slots.register({
1724
2324
  name: 'settings.section', id: 'super-code', order: 120, label: () => t('preset.title'), locale: LOCALE_NS,
1725
- inject: () => ({ api: ready.remote.superCodePresets }),
2325
+ inject: () => ({ api: ready.remote.superCodePresets, budgetApi: ready.remote.superCodeBudgets }),
1726
2326
  }, PresetSettings))
1727
2327
  })
1728
2328
  ctx.effect(() => {
@@ -1766,7 +2366,7 @@ function apply(ctx) {
1766
2366
 
1767
2367
  /* Settings. */
1768
2368
  .dsh-super-code-settings{container-type:inline-size;border:1px solid var(--sc-line);border-radius:12px;background:var(--sc-bg);overflow:hidden}
1769
- .dsh-super-code-settings-header{display:flex;align-items:center;gap:12px;padding:20px 24px;border-bottom:1px solid var(--sc-line)}
2369
+ .dsh-super-code-settings-header{display:flex;align-items:center;gap:12px;padding-bottom:20px;margin-bottom:20px;border-bottom:1px solid var(--sc-line)}
1770
2370
  .dsh-super-code-settings-mark{display:grid;place-items:center;flex:none;width:40px;height:40px;background:var(--sc-surface);border:1px solid var(--sc-line);border-radius:10px}
1771
2371
  .dsh-super-code-settings-mark svg{width:22px;height:22px;color:var(--sc-text)}
1772
2372
  .dsh-super-code-settings-head-text{display:grid;gap:4px;min-width:0}
@@ -1803,8 +2403,29 @@ function apply(ctx) {
1803
2403
  .dsh-super-code-settings-guidance:empty{display:none}
1804
2404
  .dsh-super-code-settings-guidance{margin-top:20px;padding-top:12px;border-top:1px solid var(--sc-line);color:var(--sc-muted);font-size:12px}
1805
2405
  .dsh-super-code-settings-guidance p{margin:0 0 4px}
2406
+ .dsh-super-code-settings-tabs{display:flex;gap:24px;padding:0 24px;border-bottom:1px solid var(--sc-line)}
2407
+ .dsh-super-code-settings-tabs>[role=tab]{border:0;border-bottom:2px solid transparent;background:none;color:var(--sc-muted);padding:12px 0;font-size:13px;cursor:pointer}
2408
+ .dsh-super-code-settings-tabs>[role=tab][aria-selected=true]{border-bottom-color:var(--sc-text);color:var(--sc-text);font-weight:600}
2409
+ .dsh-super-code-settings-tabs>[role=tab]:focus-visible{outline:2px solid var(--sc-active);outline-offset:3px;border-radius:2px}
2410
+ .dsh-super-code-settings [role=tabpanel][hidden]{display:none}
2411
+ .dsh-super-code-budgets form{gap:24px}
2412
+ .dsh-super-code-budget-hint{margin:0;color:var(--sc-muted);font-size:12px;line-height:1.65}
2413
+ .dsh-super-code-budget-group{display:flex;flex-direction:column;gap:16px;min-width:0}
2414
+ .dsh-super-code-budget-group h3{margin:0;font-size:14px;font-weight:600}
2415
+ .dsh-super-code-budget-exclusive{border-top:1px solid var(--sc-line);padding-top:24px}
2416
+ .dsh-super-code-budget-switch{display:flex;align-items:center;gap:10px;font-size:13px;cursor:pointer}
2417
+ .dsh-super-code-budget-switch input{width:30px;height:17px;flex:none;accent-color:var(--sc-active);cursor:pointer}
2418
+ .dsh-super-code-budget-default{color:var(--sc-muted);font-weight:400;font-size:12px}
2419
+ .dsh-super-code-budgets fieldset{min-width:0;margin:0;border:0;padding:0}
2420
+ .dsh-super-code-budgets legend{font-size:13px;font-weight:500;margin-bottom:12px;padding:0}
2421
+ .dsh-super-code-budget-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px 16px}
2422
+ .dsh-super-code-budgets fieldset>small{margin-top:8px}
2423
+ .dsh-super-code-budget-more>summary{cursor:pointer;color:var(--sc-muted);font-size:13px;margin-bottom:12px}
2424
+ .dsh-super-code-budgets .dsh-super-code-settings-form-actions>button{margin-left:0}
2425
+ .dsh-super-code-budgets .dsh-super-code-settings-form-actions{border-top:1px solid var(--sc-line);padding-top:16px}
2426
+ @container(max-width:420px){.dsh-super-code-budget-grid{grid-template-columns:1fr}.dsh-super-code-settings-tabs{padding:0 16px}}
1806
2427
  .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}
1807
- @container(max-width:420px){.dsh-super-code-settings-header{padding: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%}}
2428
+ @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%}}
1808
2429
 
1809
2430
  /* Confirmation. */
1810
2431
  .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}
@@ -1823,7 +2444,7 @@ function apply(ctx) {
1823
2444
 
1824
2445
  /* Exactly one section fills the pane; the other keeps its clickable heading. */
1825
2446
  .dsh-super-code-workspace{height:100%;min-height:0;overflow:auto;background:var(--sc-bg)}
1826
- .dsh-super-code-workspace-layout{display:grid;height:100%;min-height:324px;min-width:0}
2447
+ .dsh-super-code-workspace-layout{display:grid;height:100%;min-height:368px;min-width:0}
1827
2448
  .dsh-super-code-workspace-collapsed{display:flex;align-items:center;border-block:1px solid var(--sc-line);background:var(--sc-bg);padding:0 12px}
1828
2449
  .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}
1829
2450
  .dsh-super-code-section-toggle>.dsh-super-code-tree-count{margin-left:auto}
@@ -1834,6 +2455,66 @@ function apply(ctx) {
1834
2455
  .dsh-super-code-section-toggle:hover{background:var(--sc-hover)}
1835
2456
  .dsh-super-code-workspace-collapsed>.dsh-super-code-section-toggle{width:100%;height:100%}
1836
2457
 
2458
+
2459
+ /* Read-only journal feedback, independent of the graph camera and model context. */
2460
+ .dsh-super-code-feedback{height:100%;min-height:0;min-width:0;display:flex;flex-direction:column}
2461
+ .dsh-super-code-feedback>header{display:flex;align-items:center;gap:8px;padding:4px 12px;border-bottom:1px solid var(--sc-line)}
2462
+ .dsh-super-code-feedback-scroll{flex:1;min-height:0;overflow:auto;padding:8px 12px 12px;overscroll-behavior:contain;overflow-anchor:none}
2463
+ .dsh-super-code-feedback-list{position:relative;list-style:none;padding:0;margin:0}
2464
+ .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}
2465
+ .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)}
2466
+ .dsh-super-code-feedback-task-heading{display:flex;flex-wrap:wrap;align-items:flex-start;gap:2px 6px;padding:8px 4px 6px}
2467
+ .dsh-super-code-feedback-heading{flex:1;min-width:0}
2468
+ .dsh-super-code-feedback-heading h3{overflow-wrap:anywhere;font-size:13px;line-height:20px;font-weight:600;margin:0}
2469
+ .dsh-super-code-feedback small{color:var(--sc-muted);font-size:11px;line-height:18px}
2470
+ .dsh-super-code-feedback-meta{display:flex;align-items:center;gap:7px;min-width:0;margin-top:1px}
2471
+ .dsh-super-code-feedback-meta>small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
2472
+ .dsh-super-code-feedback-status{display:flex;align-items:center;gap:5px;flex:none;font-size:11px;line-height:18px;color:var(--sc-muted)}
2473
+ .dsh-super-code-feedback-status:before{content:"";width:3px;height:3px;border-radius:50%;background:var(--sc-faint)}
2474
+ .dsh-super-code-feedback-status[data-tone=active]{color:var(--sc-accent)}
2475
+ .dsh-super-code-feedback-status[data-tone=error]{color:var(--sc-error)}
2476
+ .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}
2477
+ .dsh-super-code-feedback-muted{font-size:11px;color:var(--sc-muted);line-height:18px;margin:3px 0;white-space:pre-wrap}
2478
+ .dsh-super-code-feedback-text{margin:3px 0;white-space:pre-wrap}
2479
+ .dsh-super-code-feedback-text>small{display:block}
2480
+ .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}
2481
+ .dsh-super-code-feedback progress::-webkit-progress-bar{background:var(--sc-line)}
2482
+ .dsh-super-code-feedback progress::-webkit-progress-value{background:var(--sc-accent);transition:width .3s ease}
2483
+ .dsh-super-code-feedback progress::-moz-progress-bar{background:var(--sc-accent)}
2484
+ .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}
2485
+ .dsh-super-code-feedback-step>span:last-child{flex:1;min-width:0;overflow-wrap:anywhere}
2486
+ .dsh-super-code-feedback-step[data-step-state=completed]{color:var(--sc-muted)}
2487
+ .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)}
2488
+ .dsh-super-code-feedback-step[data-step-state=pending]{color:var(--sc-muted)}
2489
+ .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)}
2490
+ .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}
2491
+ .dsh-super-code-feedback-mark>svg{width:14px;height:14px}
2492
+ .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%}
2493
+ .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}
2494
+ .dsh-super-code-feedback-mark.success{color:var(--sc-success)}
2495
+ .dsh-super-code-feedback-mark.error{color:var(--sc-error);border:1px solid currentColor;border-radius:50%;transform:scale(.8)}
2496
+ [data-feedback-live=false] .dsh-super-code-feedback-step .dsh-super-code-feedback-mark.active:before{animation:none}
2497
+ @keyframes sc-feedback-spin{to{transform:rotate(360deg)}}
2498
+ @media(prefers-reduced-motion:reduce){.dsh-super-code-feedback-mark.active:before{animation:none!important}}
2499
+ .dsh-super-code-feedback-activity{display:flex;gap:7px;align-items:flex-start;min-height:28px;padding:4px 0;line-height:20px}
2500
+ .dsh-super-code-feedback-activity-text{min-width:0;flex:1;overflow-wrap:anywhere;white-space:pre-wrap}
2501
+ .dsh-super-code-feedback-activity>small{flex:none;font-size:10px;line-height:20px}
2502
+ .dsh-super-code-feedback-tool{max-width:84px;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}
2503
+ .dsh-super-code-feedback-list>li[data-feedback-kind=open]{padding-bottom:8px}
2504
+ .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}
2505
+ .dsh-super-code-feedback-open>.dsh-super-code-glyph{width:12px;height:12px;transition:transform .15s}
2506
+ .dsh-super-code-feedback-open:hover{color:var(--sc-accent);background:var(--sc-hover)}
2507
+ .dsh-super-code-feedback-open:hover>.dsh-super-code-glyph{transform:translateX(2px)}
2508
+ .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)}
2509
+ .dsh-super-code-feedback-files-heading>span{font-size:11px;font-weight:400;color:var(--sc-muted);font-variant-numeric:tabular-nums}
2510
+ .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}
2511
+ .dsh-super-code-feedback-file:hover{background:var(--sc-hover)}
2512
+ .dsh-super-code-feedback-file>.dsh-super-code-glyph{flex:none;width:14px;height:14px;color:var(--sc-muted)}
2513
+ .dsh-super-code-feedback-file strong{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:400}
2514
+ .dsh-super-code-feedback-file small{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
2515
+ .dsh-super-code-feedback-file-operation{flex:none;margin-left:auto;font-size:10px;color:var(--sc-success)}
2516
+ .dsh-super-code-feedback button:focus-visible,.dsh-super-code-feedback-scroll:focus-visible{outline:2px solid var(--sc-accent);outline-offset:-2px}
2517
+
1837
2518
  /* Memory library: geometry stays at 76px per row. */
1838
2519
  .dsh-super-code-memory{height:100%;min-height:0;display:flex;flex-direction:column;container-type:inline-size}
1839
2520
  .dsh-super-code-memory>header{padding:16px;border-bottom:1px solid var(--sc-line);flex:none}
@@ -2173,5 +2854,5 @@ function apply(ctx) {
2173
2854
  ctx.effect(() => disposeMemoryType, 'super-code: memory library tab')
2174
2855
  }
2175
2856
 
2176
- return { inject, apply, synchronizePresetDisplay, agentSessionState, buildAgentView, visibleAgentRows, agentTreeDisplay, agentTreeTotals, agentTreeLayout, agentExecutionStatus, agentDetailAddress, agentDetailTarget, agentPath, detailRecord, createAgentInspector, agentHistoryRange, agentHistoryRows, agentHistoryWindow, memoryLibraryAddress, memoryLibraryTarget, memoryLibraryNavigation, memoryLibraryWindow, createMemoryEditor }
2857
+ return { inject, apply, taskFeedbackRows, feedbackStatus, feedbackListItems, feedbackListLayout, feedbackListIndex, feedbackListWindow, feedbackListAnchor, budgetDraft, budgetConfiguration, synchronizePresetDisplay, agentSessionState, buildAgentView, visibleAgentRows, agentTreeDisplay, agentTreeTotals, agentTreeLayout, agentExecutionStatus, agentDetailAddress, agentDetailTarget, agentPath, detailRecord, createAgentInspector, agentHistoryRange, agentHistoryRows, agentHistoryWindow, memoryLibraryAddress, memoryLibraryTarget, memoryLibraryNavigation, memoryLibraryWindow, createMemoryEditor }
2177
2858
  } })