dsh-super-code 0.3.6 → 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.
package/client/index.js CHANGED
@@ -256,12 +256,53 @@ 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
+
259
295
  'settings.presetTab': '预设状态', 'settings.contextTab': '上下文配置', 'settings.tabs': 'Super Code 配置',
260
296
  'budget.common': '通用预算', 'budget.superCode': 'Super Code 专属预算',
261
297
  'budget.applyToOfficialPresets': '同时应用到官方预设', 'budget.officialHint': '开启后,通用预算也用于标准、PTC、极简和创造模式;不包含下方的 Super Code 专属预算。',
262
298
  'budget.superCodeHint': '仅用于 Super Code 的任务记录和任务记忆,始终不影响官方预设。',
263
299
  'budget.defaultValue': '(默认:{value})', 'budget.off': '关闭', 'budget.managed': '宿主管理',
264
- 'budget.title': '上下文配置', 'budget.scope': '保存后对新建 Agent 生效,运行中的任务保留原配置。',
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 生效,运行中的任务保留原配置。',
265
306
  'budget.defaults': '留空沿用默认值,括号内显示默认值;通用预算随宿主版本变化。恢复默认会清除这里的覆盖,回到插件加载配置;未配置时跟随宿主。',
266
307
  'budget.context': '上下文压缩', 'budget.execution': '批量工具执行', 'budget.advanced': '更多预算',
267
308
  'budget.pressureBasis': '压缩预算基准', 'budget.inherit': '跟随默认', 'budget.host': '宿主策略', 'budget.window': '完整上下文窗口',
@@ -272,8 +313,8 @@ const zh = {
272
313
  'budget.contextHint': '保留比例需小于触发比例。较大的预算会占用更多上下文。',
273
314
  'budget.ptcHint': '包含工具等待时间;默认时限不能超过上限,也不能超过宿主允许值。',
274
315
  'budget.ptcUnavailable': '此宿主的 PTC 计时由宿主管理;其他预算仍可配置。', 'budget.ptcCeiling': '宿主允许的最长时限:{seconds} 秒。',
275
- 'budget.save': '保存预算', 'budget.reset': '恢复默认', 'budget.reload': '重新读取', 'budget.saving': '正在保存…',
276
- 'budget.saved': '已保存,对新建 Agent 生效。', 'budget.resetDone': '已恢复默认,对新建 Agent 生效。',
316
+ 'budget.save': '保存设置', 'budget.reset': '恢复默认', 'budget.reload': '重新读取', 'budget.saving': '正在保存…',
317
+ 'budget.saved': '已保存。删除保护立即生效,预算对新建 Agent 生效。', 'budget.resetDone': '已恢复默认。删除保护立即生效,预算对新建 Agent 生效。',
277
318
  'budget.error.connection': '暂时无法读取预算,请重试。', 'budget.error.invalid': '请检查数值范围、保留比例和执行时限。',
278
319
  'budget.error.retain': '保留比例必须小于压缩触发比例;未填写时分别按 16% 和 80% 校验。', 'budget.error.ptc-order': '默认执行时限不能超过执行时限上限。',
279
320
  'budget.error.conflict': '预算已在别处修改。草稿已保留,请重新读取后再修改。', 'budget.error.read-only': '当前宿主的设置只读,无法保存。',
@@ -285,7 +326,7 @@ const zh = {
285
326
  'memory.deleteTitle': '删除记忆', 'memory.deleteHint': '确定删除“{summary}”吗?删除后无法恢复。',
286
327
  'memory.writeError': '操作失败,内容已保留,请重试。', 'memory.conflict': '这条记忆已在别处更改,请取消后刷新再编辑。',
287
328
  'memory.missingEdit': '这条记忆已被删除,输入内容已保留。', 'memory.full': '该主题或记忆库已满,请删除不再需要的条目。',
288
- 'workspace.title': 'Super Code', 'workspace.description': 'Agent 执行树与记忆库', 'workspace.resize': '调整执行树与记忆库高度',
329
+ 'workspace.title': 'Super Code', 'workspace.description': '任务状态、Agent 执行树与记忆库', 'workspace.resize': '调整执行树与记忆库高度',
289
330
  'tree.collapsedLabel': '已收起', 'tree.organizeLabel': '自动整理',
290
331
  'detail.toolCall': '工具调用', 'detail.toolResult': '工具结果', 'detail.toolError': '工具失败',
291
332
  'detail.cache': '缓存', 'detail.info': '详情', 'tree.countCompact': '{shown}/{total} · 运行 {running}',
@@ -349,12 +390,53 @@ const zh = {
349
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': '[图片]'
350
391
  }
351
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
+
352
429
  'settings.presetTab': 'Preset status', 'settings.contextTab': 'Context settings', 'settings.tabs': 'Super Code settings',
353
430
  'budget.common': 'Common budgets', 'budget.superCode': 'Super Code budgets',
354
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.',
355
432
  'budget.superCodeHint': 'Task records and task memory for Super Code. These limits never affect official presets.',
356
433
  '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.',
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.',
358
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.',
359
441
  'budget.context': 'Context compression', 'budget.execution': 'Batched tool execution', 'budget.advanced': 'More budgets',
360
442
  'budget.pressureBasis': 'Compression budget basis', 'budget.inherit': 'Inherit default', 'budget.host': 'Host policy', 'budget.window': 'Full context window',
@@ -365,8 +447,8 @@ const en = {
365
447
  'budget.contextHint': 'Retention must be below the compression threshold. Larger budgets use more context.',
366
448
  'budget.ptcHint': 'Includes tool waits. The default deadline must fit within both the configured maximum and the host ceiling.',
367
449
  '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.',
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.',
370
452
  'budget.error.connection': 'Budgets could not be loaded. Try again.', 'budget.error.invalid': 'Check the ranges, retention ratio, and deadlines.',
371
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.',
372
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.',
@@ -378,7 +460,7 @@ const en = {
378
460
  'memory.deleteTitle': 'Delete memory', 'memory.deleteHint': 'Delete “{summary}”? This cannot be undone.',
379
461
  'memory.writeError': 'The operation failed. Your input is retained; try again.', 'memory.conflict': 'This memory changed elsewhere. Cancel and refresh before editing again.',
380
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.',
381
- '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',
382
464
  'tree.collapsedLabel': 'Collapsed', 'tree.organizeLabel': 'Organize',
383
465
  'detail.toolCall': 'Tool call', 'detail.toolResult': 'Tool result', 'detail.toolError': 'Tool failed',
384
466
  'detail.cache': 'Cache', 'detail.info': 'Details', 'tree.countCompact': '{shown}/{total} · Running {running}',
@@ -763,12 +845,299 @@ function AgentTree({ useSessions, useSessionStatus, sessionId, useTabInfo, treeS
763
845
  return React.createElement(AgentTreeBody, { key: view.root, state, view, saved, openDetail, refresh, watchCatalog, onToggle, t })
764
846
  }
765
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
+
766
1135
  function WorkspaceSectionToggle({ section, expanded, onToggle, children, t }) {
767
- 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')
768
1137
  return React.createElement(ActionButton, { type: 'button', className: 'dsh-super-code-section-toggle',
769
1138
  'data-workspace-section': section, 'aria-label': label, 'aria-expanded': expanded, onClick: onToggle },
770
1139
  React.createElement(Glyph, { type: 'chevron' }),
771
- React.createElement(Glyph, { type: section === 'tree' ? 'branch' : 'memory' }),
1140
+ React.createElement(Glyph, { type: section === 'tasks' ? 'context' : section === 'tree' ? 'branch' : 'memory' }),
772
1141
  React.createElement('strong', null, label), children)
773
1142
  }
774
1143
 
@@ -779,7 +1148,7 @@ function SuperCodeWorkspace(props) {
779
1148
  const navigationKey = `${address}/${tab.navigation?.revision || 0}`
780
1149
  const memoryTarget = memoryLibraryTarget(address, '')
781
1150
  const saved = useMemo(() => {
782
- const value = props.workspaceStates.get(tab.signal) || { active: 'tree' }
1151
+ const value = props.workspaceStates.get(tab.signal) || { active: 'tasks' }
783
1152
  if (value.navigationKey !== navigationKey) {
784
1153
  value.navigationKey = navigationKey
785
1154
  if (memoryTarget.id) value.active = 'memory'
@@ -787,10 +1156,10 @@ function SuperCodeWorkspace(props) {
787
1156
  props.workspaceStates.set(tab.signal, value)
788
1157
  return value
789
1158
  }, [props.workspaceStates, tab.signal])
790
- const [active, setActive] = useState(saved.active === 'memory' ? 'memory' : 'tree')
1159
+ const [active, setActive] = useState(['tasks', 'tree', 'memory'].includes(saved.active) ? saved.active : 'tasks')
791
1160
  const layout = useRef(null), focusSection = useRef('')
792
- const toggle = () => {
793
- const next = active === 'tree' ? 'memory' : 'tree'
1161
+ const toggle = section => {
1162
+ const next = active === section ? section === 'tasks' ? 'tree' : 'tasks' : section
794
1163
  saved.active = next; focusSection.current = next; setActive(next)
795
1164
  }
796
1165
  useLayoutEffect(() => {
@@ -805,14 +1174,13 @@ function SuperCodeWorkspace(props) {
805
1174
  }, [active])
806
1175
  return React.createElement('section', { className: 'dsh-super-code-workspace', 'aria-label': text(props.t, 'workspace.title') },
807
1176
  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 }))))
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
+ })))
816
1184
  }
817
1185
 
818
1186
  function AgentCatalogWatch({ id, watchCatalog }) {
@@ -1600,7 +1968,8 @@ const budgetFields = [
1600
1968
  { key: 'maxContextBytes', group: 'superCode', root: true, factor: 1, min: 1024, max: 262144, step: 1 },
1601
1969
  ]
1602
1970
  function budgetDraft(configuration) {
1603
- return Object.fromEntries([['applyToOfficialPresets', configuration.applyToOfficialPresets ?? false], ['pressureBasis', configuration.budgets?.pressureBasis ?? ''],
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 ?? ''],
1604
1973
  ...budgetFields.map(field => {
1605
1974
  const value = field.root ? configuration[field.key] : configuration.budgets?.[field.key]
1606
1975
  return [field.key, value === undefined ? '' : String(value / field.factor)]
@@ -1608,6 +1977,14 @@ function budgetDraft(configuration) {
1608
1977
  }
1609
1978
  function budgetConfiguration(draft) {
1610
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
+ }
1611
1988
  if (draft.applyToOfficialPresets !== undefined) {
1612
1989
  if (typeof draft.applyToOfficialPresets !== 'boolean') return { ok: false, error: 'invalid' }
1613
1990
  config.applyToOfficialPresets = draft.applyToOfficialPresets
@@ -1684,6 +2061,19 @@ function BudgetSettings({ api, t, active }) {
1684
2061
  })
1685
2062
  return React.createElement('section', { className: 'dsh-super-code-budgets' },
1686
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'))),
1687
2077
  React.createElement('p', { className: 'dsh-super-code-budget-hint' }, t('budget.scope')),
1688
2078
  React.createElement('section', { className: 'dsh-super-code-budget-group', 'aria-labelledby': 'super-code-common-title' },
1689
2079
  React.createElement('h3', { id: 'super-code-common-title' }, t('budget.common')),
@@ -2054,7 +2444,7 @@ function apply(ctx) {
2054
2444
 
2055
2445
  /* Exactly one section fills the pane; the other keeps its clickable heading. */
2056
2446
  .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}
2447
+ .dsh-super-code-workspace-layout{display:grid;height:100%;min-height:368px;min-width:0}
2058
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}
2059
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}
2060
2450
  .dsh-super-code-section-toggle>.dsh-super-code-tree-count{margin-left:auto}
@@ -2065,6 +2455,66 @@ function apply(ctx) {
2065
2455
  .dsh-super-code-section-toggle:hover{background:var(--sc-hover)}
2066
2456
  .dsh-super-code-workspace-collapsed>.dsh-super-code-section-toggle{width:100%;height:100%}
2067
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
+
2068
2518
  /* Memory library: geometry stays at 76px per row. */
2069
2519
  .dsh-super-code-memory{height:100%;min-height:0;display:flex;flex-direction:column;container-type:inline-size}
2070
2520
  .dsh-super-code-memory>header{padding:16px;border-bottom:1px solid var(--sc-line);flex:none}
@@ -2404,5 +2854,5 @@ function apply(ctx) {
2404
2854
  ctx.effect(() => disposeMemoryType, 'super-code: memory library tab')
2405
2855
  }
2406
2856
 
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 }
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 }
2408
2858
  } })