dsh-plan-completion 0.1.4 → 0.1.5

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/CHANGELOG.md CHANGED
@@ -3,6 +3,18 @@
3
3
  All notable changes to this project are documented here.
4
4
  Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), versioning: SemVer.
5
5
 
6
+ ## [0.1.5] - 2026-11-02
7
+
8
+ ### Fixed
9
+ - Detect a plan that stopped tracking the conversation: after **two consecutive `op=none`**
10
+ generation outcomes (e.g. an edited plan whose coarse step needs splitting), the published
11
+ suggestion becomes `/replan 按最近进展重排计划,把已完成项标记完成` and the plan is marked
12
+ stale, so the user can fix the plan with one Tab fill instead of seeing a repeated stale hint.
13
+ - `/reset` and forced `/replan` clear the none-streak counter.
14
+
15
+ ### Changed
16
+ - `npm test` now reports **133/133** tests.
17
+
6
18
  ## [0.1.4] - 2026-11-02
7
19
 
8
20
  ### Fixed
package/cordis.patch.yml CHANGED
@@ -1,16 +1,16 @@
1
- - insert:
2
- - id: plan-completion
3
- name: 'dsh-plan-completion'
4
- config:
5
- evidenceChars: 1500
6
- maxTokensPatch: 400
7
- maxTokensRewrite: 1200
8
- warmBackfill: true
9
- modelCalls: true # false: heuristic-only; no plugin model requests
10
- # Plans + last suggestion are persisted per session so a DSH restart restores the UI.
11
- persistPlans: true
12
- storageDir: '~/.dsh/plan-completion-data'
13
- # Debug endpoints (debug/diag/theme/experiment/autojob/steps/reset) stay off in production.
14
- enableDebugRoutes: false
15
- # Optional: set a local directory to persist debug logs / experiment data.
16
- # debugDir: '<local debug directory>'
1
+ - insert:
2
+ - id: plan-completion
3
+ name: 'dsh-plan-completion'
4
+ config:
5
+ evidenceChars: 1500
6
+ maxTokensPatch: 400
7
+ maxTokensRewrite: 1200
8
+ warmBackfill: true
9
+ modelCalls: true # false: heuristic-only; no plugin model requests
10
+ # Plans + last suggestion are persisted per session so a DSH restart restores the UI.
11
+ persistPlans: true
12
+ storageDir: '~/.dsh/plan-completion-data'
13
+ # Debug endpoints (debug/diag/theme/experiment/autojob/steps/reset) stay off in production.
14
+ enableDebugRoutes: false
15
+ # Optional: set a local directory to persist debug logs / experiment data.
16
+ # debugDir: '<local debug directory>'
@@ -1,7 +1,9 @@
1
1
  # GitHub 发布评估 · 会话计划补全插件(2026-11-02)
2
2
 
3
- > 历史文档:以下保留 v0.1.0 的交付/评估/计划记录。**当前最新为 v0.1.3**(核对进度 + 本地计划持久化 + 鉴权/状态一致性/隐私边界修复),详见 [security.md](security.md) 与 [CHANGELOG](../CHANGELOG.md);旧记录中的“无鉴权”、原始诊断字段与独立评审不代表当前实现。
3
+ > 历史文档:以下保留 v0.1.0 的交付/评估/计划记录。**当前最新为 v0.1.4**(建议文本保真 + 核对进度 + 本地计划持久化 + 鉴权/状态一致性/隐私边界修复),详见 [security.md](security.md) 与 [CHANGELOG](../CHANGELOG.md);旧记录中的“无鉴权”、原始诊断字段与独立评审不代表当前实现。
4
4
  >
5
+ > v0.1.4 发布记录(2026-11-02):建议文本保真修复(标识符/路径/命令保留 + 语义化截断)。tag `v0.1.4` + [Release](https://github.com/Xumi0920/dsh-plan-completion/releases/tag/v0.1.4);GitHub Actions [run 41389845733](https://github.com/Xumi0920/dsh-plan-completion/actions/runs/41389845733)(四 job 全绿,132/132 tests);npm `dsh-plan-completion@0.1.4`(31 files,shasum `a6f8953445e330081243f9f9d6b429fd4402b341`)。
6
+ >
5
7
  > v0.1.3 发布记录(2026-11-02):tag `v0.1.3` + [Release](https://github.com/Xumi0920/dsh-plan-completion/releases/tag/v0.1.3);GitHub Actions [run 41368609382](https://github.com/Xumi0920/dsh-plan-completion/actions/runs/41368609382)(Ubuntu/Windows × Node20/22 四 job 全绿,126/126 tests);npm `dsh-plan-completion@0.1.3`(31 files,shasum `11adee0fe9acff199a180be511ca8bc600afdf6f`);canary 验证 `/plan-completion/verify` 未认证 401、客户端鉴权请求正常。
6
8
  >
7
9
  > v0.1.1 发布记录(2026-11-02):tag `v0.1.1` + [Release](https://github.com/Xumi0920/dsh-plan-completion/releases/tag/v0.1.1);GitHub Actions [run 41366651557](https://github.com/Xumi0920/dsh-plan-completion/actions/runs/41366651557)(Ubuntu/Windows × Node20/22 四 job 全绿);npm `dsh-plan-completion@0.1.1`(31 files,shasum `18989b0f50b72d5c8c332f3fdee7f43ccb5fd91a`)。
@@ -105,3 +107,4 @@ git tag v0.1.0 && git push --tags
105
107
  > 建议:**先把 P0 B1~B8 全部打勾,再公开仓库**;否则许可证与数据问题会成为不可逆的历史提交。
106
108
 
107
109
 
110
+
package/index.js CHANGED
@@ -215,7 +215,7 @@ const HOST_GLOBAL_KEY = '__dsh_plan_completion_host__'
215
215
  * up with two live handler sets answering different state.
216
216
  */
217
217
  function hostGlobal() {
218
- const fallback = { states: new Map(), sessions: new Map(), handledTurns: new Map(), warmAttempts: new Map(), disposers: [] }
218
+ const fallback = { states: new Map(), sessions: new Map(), handledTurns: new Map(), warmAttempts: new Map(), noneStreaks: new Map(), disposers: [] }
219
219
  if (typeof globalThis === 'undefined') return { ...fallback, store: createMemoryStore() }
220
220
  if (!globalThis[HOST_GLOBAL_KEY] || typeof globalThis[HOST_GLOBAL_KEY] !== 'object') {
221
221
  globalThis[HOST_GLOBAL_KEY] = fallback
@@ -225,6 +225,7 @@ function hostGlobal() {
225
225
  if (!(shared.sessions instanceof Map)) shared.sessions = new Map()
226
226
  if (!(shared.handledTurns instanceof Map)) shared.handledTurns = new Map()
227
227
  if (!(shared.warmAttempts instanceof Map)) shared.warmAttempts = new Map()
228
+ if (!(shared.noneStreaks instanceof Map)) shared.noneStreaks = new Map()
228
229
  if (!Array.isArray(shared.disposers)) shared.disposers = []
229
230
  if (!shared.store || typeof shared.store.get !== 'function' || typeof shared.store.put !== 'function') {
230
231
  shared.store = createMemoryStore()
@@ -754,12 +755,22 @@ export function apply(ctx, config = {}) {
754
755
  const withinSuggestionDeadline = elapsed <= suggestionDeadline
755
756
  const finalSuggestion = withinSuggestionDeadline ? suggestion ?? provisionalSuggestion : provisionalSuggestion
756
757
 
758
+ // A long `op=none` streak means the visible plan stopped tracking the conversation
759
+ // (edited-by-user plans cannot be rewritten automatically, and one coarse step may need
760
+ // splitting). Surface an actionable replan suggestion instead of repeating a stale one.
761
+ const priorNoneStreak = noneStreaks.get(sessionId) ?? 0
762
+ const nextNoneStreak = force ? 0 : outcome === 'none' && nextPlan ? priorNoneStreak + 1 : 0
763
+ const replanSuggested = nextNoneStreak >= 2 && Boolean(nextPlan)
764
+ const publishedSuggestion = replanSuggested
765
+ ? { text: '/replan 按最近进展重排计划,把已完成项标记完成', alternates: [] }
766
+ : finalSuggestion
767
+
757
768
  if (nextPlan) {
758
769
  try {
759
770
  // Check CAS even for an unchanged plan; an old snapshot is never an
760
771
  // unconditional write. null also protects the first commit from races.
761
772
  // The last suggestion is persisted in the same write so a restart can restore it.
762
- const committed = await store.put(nextPlan, { cas: storedVersion, suggestion: finalSuggestion })
773
+ const committed = await store.put(nextPlan, { cas: storedVersion, suggestion: publishedSuggestion })
763
774
  if (!committed) {
764
775
  debugLog({ phase: 'store-cas-miss', sessionId })
765
776
  const winner = typeof store.getState === 'function'
@@ -779,13 +790,23 @@ export function apply(ctx, config = {}) {
779
790
  }
780
791
  }
781
792
 
793
+ noneStreaks.set(sessionId, nextNoneStreak)
782
794
  publishState(sessionId, {
783
795
  plan: nextPlan,
784
- suggestion: finalSuggestion,
785
- stale: false,
796
+ suggestion: publishedSuggestion,
797
+ stale: replanSuggested,
786
798
  })
787
- debug.lastJob = { sessionId, turn, elapsed, outcome, suggestion: Boolean(suggestion), withinSuggestionDeadline }
788
- debugLog({ phase: 'job-done', sessionId, turn, elapsed, outcome, withinSuggestionDeadline })
799
+ debug.lastJob = {
800
+ sessionId,
801
+ turn,
802
+ elapsed,
803
+ outcome,
804
+ suggestion: Boolean(suggestion),
805
+ withinSuggestionDeadline,
806
+ noneStreak: nextNoneStreak,
807
+ replanSuggested,
808
+ }
809
+ debugLog({ phase: 'job-done', sessionId, turn, elapsed, outcome, withinSuggestionDeadline, replanSuggested })
789
810
  } catch (error) {
790
811
  if (!isCurrent()) return
791
812
  debugLog({ phase: 'job-failed', sessionId, turn, error: String(error) })
@@ -800,6 +821,7 @@ export function apply(ctx, config = {}) {
800
821
 
801
822
  // ── Session event subscription: one plan job per completed turn ──────────
802
823
  const handledTurns = shared.handledTurns // sessionId -> last handled turn number
824
+ const noneStreaks = shared.noneStreaks // sessionId -> consecutive op=none outcomes
803
825
 
804
826
  function triggerTurnEnd(session, seq, turn, source) {
805
827
  if (!session?.id || disposed) return
@@ -1161,6 +1183,7 @@ export function apply(ctx, config = {}) {
1161
1183
  states.delete(sessionId)
1162
1184
  warmAttempts.delete(sessionId)
1163
1185
  handledTurns.delete(sessionId)
1186
+ noneStreaks.delete(sessionId)
1164
1187
  debugLog({ phase: 'state-reset', sessionId, existed })
1165
1188
  jsonResponse(res, 200, { ok: true, sessionId, existed })
1166
1189
  return
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plan-completion",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Implicit session plans with Tab-completable next-message suggestions for DeepSeek Harness",