fix(archive): pure-export semantics + auto-unarchive migration (#169)#170
Conversation
Archive used to combine 'export to Markdown' with a soft-hide that removed the session from the sidebar. Users expected archive to mean 'snapshot the conversation as a Markdown file', not 'hide the conversation'. The old soft-hide also made the 'click again to un-archive' code path unreachable from the UI because hidden sessions never reappear in the list. Changes: - session-store.js archive(id): drop unarchive branch, drop archived=true write, drop sessionId reset. Only render to Markdown + toast. - session-store.js constructor: one-shot idempotent migration flips every previously-hidden session back to visible, gated by archiveSemanticsV2Migrated flag in globalState. Errors are swallowed so a failed migration cannot block activation. After upgrade, sessions hidden by the old archive action reappear in the sidebar. Repeated archive on the same session produces multiple Markdown snapshots (timestamp + _writeUnique suffix on collision).
| } | ||
| } | ||
|
|
||
| // ─── Raw storage ──────────────────────────────────────────────────────── |
There was a problem hiding this comment.
在 _migrateArchivedFlagIfNeeded 方法中,虽然错误被捕获并记录,但没有对错误进行进一步处理。建议在捕获错误后,考虑是否需要进行重试或记录更多上下文信息,以便后续排查。同时,建议确保 this._gs 的获取和使用是安全的,避免潜在的空指针异常。
| if (!savedPath) return; // user cancelled | ||
|
|
||
| this._notifyArchived(savedPath); | ||
| } |
There was a problem hiding this comment.
在 archive 方法中,虽然进行了错误处理,但在调用 exportSessionToMarkdown 时,如果发生错误,应该确保 s.archived 状态不被改变。建议在 try-catch 块中明确处理状态的变化,确保在任何情况下都能保持状态一致性。此外,建议对 this.all() 的调用进行空值检查,以防止潜在的空指针异常。
There was a problem hiding this comment.
Pull request overview
This PR updates DeepCopilot’s “Archive” action semantics to match user expectations from #169: archiving becomes a pure export to Markdown, no longer a “soft-hide” toggle. It also adds a one-time migration to make previously hidden (archived: true) sessions visible again after upgrade.
Changes:
- Make
SessionStore.archive(id)export-only: remove any mutation ofarchived,sessionId, persistence, and list broadcasts. - Add a one-shot, idempotent migration on
SessionStoreconstruction to flip storedarchived: truesessions back tofalse, guarded by aglobalStateflag.
| } catch (err) { | ||
| // Non-fatal: next launch will retry. | ||
| // eslint-disable-next-line no-console | ||
| console.warn('[deep-copilot] archive-v2 migration failed:', err && err.message || err); | ||
| } |
When the model watches a bg job (e.g. long training) that was started in an earlier turn, run._sessionStartedBgJobs is empty and the BG_WAIT_SKIPPED_MODEL_DONE guard ends the turn right after the model says "I will keep monitoring" -- silently dropping the watchdog. Track jobs the model inspects via read_terminal(terminal: "deepseek-job-*") in run._monitoredBgJobs. The guard now also refuses to end the turn while any monitored job is still active, so the loop keeps polling/snapshotting until completion or the 4h per-turn budget.
| } | ||
| }; | ||
| onBgJobEnded(_bgJobEndHandler); | ||
|
|
There was a problem hiding this comment.
新增的 run._monitoredBgJobs 集合用于跟踪模型在当前运行中主动检查的后台作业,但未对其进行初始化检查。在使用前应确保其存在且为 Set 类型,以避免潜在的空指针异常。此外,建议在注释中详细说明该集合的用途和生命周期,以提高可维护性。
| monitoredJobs: _monitored ? [..._monitored] : [], | ||
| }); | ||
| break; | ||
| } |
There was a problem hiding this comment.
在检查 run._monitoredBgJobs 时,虽然进行了类型检查,但建议在使用前确保该集合已被正确初始化。此外,建议在条件判断中添加日志记录,以便在出现问题时能够追踪到具体的作业状态。
| } | ||
| if (tcId) { | ||
| ctx.onStreamDelta = (delta) => { | ||
| if (!delta) return; |
There was a problem hiding this comment.
此段代码引入了对终端的监控机制,但在处理 args.terminal 时缺乏有效的输入验证,可能导致命令注入风险。建议在使用 args.terminal 之前,增加对其内容的严格校验,确保其符合预期格式。此外,run._monitoredBgJobs 的初始化逻辑应考虑并发访问的安全性,避免潜在的竞态条件。
| // Watchdog/monitor detection: when the model inspects an existing | ||
| // deepseek-job-* terminal via read_terminal in this run, treat that | ||
| // as an active monitoring commitment. agent-loop's | ||
| // BG_WAIT_SKIPPED_MODEL_DONE guard refuses to end the turn while a | ||
| // monitored job is still alive — preventing the model from | ||
| // "promising to keep watching" and then immediately stopping when | ||
| // the underlying job was spawned in a previous turn. | ||
| if (name === 'read_terminal' && run && args && typeof args.terminal === 'string') { | ||
| const _jobId = args.terminal; | ||
| if (/^deepseek-job-/.test(_jobId)) { | ||
| if (!(run._monitoredBgJobs instanceof Set)) run._monitoredBgJobs = new Set(); | ||
| run._monitoredBgJobs.add(_jobId); | ||
| } | ||
| } |
| } catch (err) { | ||
| // Non-fatal: next launch will retry. | ||
| // eslint-disable-next-line no-console | ||
| console.warn('[deep-copilot] archive-v2 migration failed:', err && err.message || err); | ||
| } |
| // Fire-and-forget: any postList() call after the next tick will see | ||
| // the migrated data. |
…-migration comment Addresses Copilot review on PR #170: - Replace console.warn (and the no-console eslint suppression) in the archive-v2 migration error path with Logger.info('ARCHIVE_V2_MIGRATION_FAILED'). Diagnostics now respect deepseekAgent.enableDebugLog and surface in the "Deep Copilot Debug" output channel. - Reword the constructor comment: the migration is fire-and-forget and triggers postList() itself on completion; the sidebar refreshes when the migrated data lands, not necessarily on the next tick.
| }); | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
在捕获异常时,建议对错误进行更详细的处理,尤其是在日志记录时。虽然使用 Logger 记录错误信息是一个好的改进,但在某些情况下,可能需要对错误进行更细致的分类或处理,以便后续的调试和维护。此外,确保 Logger 的实现是线程安全的,以避免潜在的竞态条件。
- Archive becomes a pure Markdown export; one-shot idempotent migration un-hides legacy soft-archived sessions (Issue #169 / PR #170). - Watchdog turns now track read_terminal targets in run._monitoredBgJobs so the BG_WAIT_SKIPPED_MODEL_DONE guard no longer cuts off long-running monitoring turns started in an earlier turn. - Migration failures route through Logger (replacing console.warn). - .vscodeignore filters .tmp-*.json/.txt/.md to keep PR-review scratch caches out of the shipped VSIX. Release notes: release/notes_v0416.md
Closes #169.
改动
本 PR 含两组改动:
A. Archive 语义改为「纯导出」 (#169)
把
Archive从「软隐藏 + 导出」改成「纯导出」。src/chat/session-store.js中archive(id):s.archived = true写入;sessionId = null+sessionLoaded空消息广播)的副作用;postList()调用(不再有状态变更需要广播);exportSessionToMarkdown(s)+ 错误/取消处理 +_notifyArchived(savedPath)。src/chat/session-store.js构造函数新增一次性迁移_migrateArchivedFlagIfNeeded():globalState['deepseekAgent.archiveSemanticsV2Migrated']守护,幂等;archived: true翻回false;Logger.info('ARCHIVE_V2_MIGRATION_FAILED', ...),不阻塞激活;postList(),避免无谓刷新。注意:
session-store.js#L125的.filter(s => !s.archived)和provider.js#L208的find(s => !s.archived)保留,作为向后兼容。迁移完成后所有记录都archived = false,过滤器自然变成 no-op。后续 minor 版本可移除字段。B. Watchdog 任务不再提前结束会话
模型在执行「值守」类任务(例如长时间训练)时,如果 bg job 是在更早的轮次启动的,
run._sessionStartedBgJobs在当前 run 是空集,BG_WAIT_SKIPPED_MODEL_DONE守卫就会在模型刚说完「我会持续监控」之后立刻结束 turn,把值守静默掐断。src/chat/tool-executor.js:当模型调用read_terminal(terminal: "deepseek-job-*")时,把该 jobId 记入run._monitoredBgJobs,作为「当前 run 在主动监控该任务」的信号。src/chat/agent-loop.js:run._monitoredBgJobs = new Set();BG_WAIT_SKIPPED_MODEL_DONE守卫新增_hasMonitoredRunningJob条件 —— 任何被监控且仍活着的 bg job 都会阻止 turn 提前退出,循环继续走 4 分钟快照 / 等结束事件,直到任务完成或命中 4h 本轮预算。使用场景对比
测试
npm run build✅。npm run lint✅ 0 errors(warnings 全是仓库现存的 indent 风格告警,未在本次新增)。archive()路径:导出成功 → toast;导出取消 → 静默;导出抛错 → toast;任何分支都不再改动 session state。archived字段并写 flag;第二次进入即 short-circuit;失败路径走Logger.info。read_terminal(deepseek-job-X)后,模型即便给出最终回复也不会触发BG_WAIT_SKIPPED_MODEL_DONE,循环继续。兼容性 / 风险
archived字段保留,迁移只翻值。.deep-copilot/archives/*.md文件原封不动。read_terminal过的 bg job(例如旁路 dev server)依然走老的 short-circuit。关联