dsh-ptc-plus 0.5.6 → 0.5.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.
@@ -1,3 +1,4 @@
1
+ import { isEnvironmentResetEvent } from './session-journal-schema.js'
1
2
  import {
2
3
  advanceSessionTimeline, createSessionTimelineState, factDigest, replaySessionTimelineFact, withFullRetention,
3
4
  retentionSummaryOf, validateRetentionSummary, validateSessionTimelineState, admissionReplayBudget,
@@ -101,7 +102,7 @@ export function runtimeMessageFacts(records, initial = {}) {
101
102
 
102
103
  function presentationEvent(event) {
103
104
  const recognized = event?.type === 'user/message'
104
- && (systemPromptSnapshotSections(event.data) !== undefined || readRuntimeMessage(event.data) !== undefined
105
+ && (isEnvironmentResetEvent(event) || systemPromptSnapshotSections(event.data) !== undefined || readRuntimeMessage(event.data) !== undefined
105
106
  || isBindingAdoptionStatement(event.data))
106
107
  const data = recognized ? { source: event.data.source, content: event.data.content }
107
108
  : event?.type === 'user/message' && typeof event.data?.source?.kind === 'string'
@@ -171,11 +172,9 @@ export function createSessionLogProjection(options = {}) {
171
172
  const replayBudget = admissionReplayBudget(options.replayBudgetBytes)
172
173
  return Object.freeze({
173
174
  key: SESSION_LOG_PROJECTION_KEY,
174
- // 14 records the binding-adoption statement this build cannot resolve: the fact it
175
- // keeps now carries the form its own replay needs, and `ptcMessages` gained a record
176
- // for it. A checkpoint written by an earlier fold answers a different question, so
177
- // the version moves and stored checkpoints are refolded rather than admitted.
178
- stateVersion: 14,
175
+ // 16 classifies damaged reset recovery targets separately from failed calls.
176
+ // Refold older caches to retain their reset corruption evidence.
177
+ stateVersion: 16,
179
178
  stateSchema: Object.freeze({ parse(value) {
180
179
  if (!isJsonValue(value) || !isRecord(value) || !isRecord(value.timeline)
181
180
  || typeof value.timeline.openTurn !== 'boolean' || typeof value.timeline.found !== 'boolean'
@@ -201,6 +200,7 @@ export function createSessionLogProjection(options = {}) {
201
200
  // ordered log facts whose indices agree with the timeline facts, each
202
201
  // timeline fact carrying a digest that matches the log fact it points at,
203
202
  // and a frontier that names the processed cut.
203
+ const timelineFacts = new Map(value.timeline.eventFacts.map(fact => [fact.eventIndex, fact]))
204
204
  let lastIndex = -1
205
205
  for (const fact of value.logFacts) {
206
206
  if (!isRecord(fact) || Object.keys(fact).some(key => !['index', 'event'].includes(key))
@@ -209,6 +209,12 @@ export function createSessionLogProjection(options = {}) {
209
209
  || !deepEqualJson(presentationEvent(fact.event), fact.event)) {
210
210
  throw new TypeError('invalid PTC session-log projection checkpoint facts')
211
211
  }
212
+ // Reset facts are never dropped with retained cell clusters. Their log
213
+ // identity must survive even when compaction prevents full replay below.
214
+ if (isEnvironmentResetEvent(fact.event)
215
+ && !isEnvironmentResetEvent(timelineFacts.get(fact.index)?.event)) {
216
+ throw new TypeError('inconsistent PTC session-log projection checkpoint missing reset fact')
217
+ }
212
218
  lastIndex = fact.index
213
219
  }
214
220
  // Strictly increasing indices are what makes this index one-to-one, so a
@@ -221,7 +227,9 @@ export function createSessionLogProjection(options = {}) {
221
227
  if (logFact === undefined) {
222
228
  throw new TypeError('inconsistent PTC session-log projection checkpoint event identity')
223
229
  }
224
- if (logFact.event.seq !== timelineFact.event.seq || logFact.event.type !== timelineFact.event.type) {
230
+ if (logFact.event.seq !== timelineFact.event.seq || logFact.event.type !== timelineFact.event.type
231
+ || (isEnvironmentResetEvent(timelineFact.event)
232
+ && !deepEqualJson(logFact.event.data?.source, timelineFact.event.data?.source))) {
225
233
  throw new TypeError('inconsistent PTC session-log projection checkpoint event identity')
226
234
  }
227
235
  }
@@ -241,7 +249,6 @@ export function createSessionLogProjection(options = {}) {
241
249
  // longer reach the stored state. Admission proves the summary instead (below)
242
250
  // and skips the replay entirely: it is both impossible to satisfy and the
243
251
  // most expensive thing this file does.
244
- const timelineFacts = new Map(value.timeline.eventFacts.map(fact => [fact.eventIndex, fact]))
245
252
  let replayBytes = 0
246
253
  let replayComplete = true
247
254
  let reconstructed = emptyLogState()
@@ -1,3 +1,4 @@
1
+ import { environmentResetMessage } from './runtime-messages.js'
1
2
  import { createHash } from 'node:crypto'
2
3
  import { isAbsolute } from 'node:path'
3
4
  import { isDeepStrictEqual } from 'node:util'
@@ -1623,6 +1624,18 @@ export class SessionRuntime {
1623
1624
  if (noops.size === 0) this.pendingNoops.delete(sessionId)
1624
1625
  }
1625
1626
 
1627
+ /** Explicit Host reset ends live state and every earlier replay/checkpoint branch. */
1628
+ async resetEnvironment(session) {
1629
+ if (typeof session?.append !== 'function') {
1630
+ throw new TypeError('ptc-plus: environment reset requires the public session append API')
1631
+ }
1632
+ const id = String(session.id)
1633
+ await this.#releaseKernel(id)
1634
+ await session.append('user/message', environmentResetMessage(), { surfaceOp: 'append' })
1635
+ this.releasedAgents.delete(id)
1636
+ this.disposedSessions.delete(id)
1637
+ }
1638
+
1626
1639
  async disposeSession(sessionId) {
1627
1640
  const id = String(sessionId)
1628
1641
  // Recorded before the early return: an identity whose owning session was
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-ptc-plus",
3
- "version": "0.5.6",
3
+ "version": "0.5.7",
4
4
  "icon": "assets/ptc-plus-icon.svg",
5
5
  "description": "A session-bound agent-native REPL for DeepSeek Harness PTC mode.",
6
6
  "type": "module",
@@ -29,6 +29,7 @@ import {
29
29
  EDIT_TARGET_FIELDS,
30
30
  DERIVED_RUN_FIELDS,
31
31
  RECOVERY_BOUNDARY_FIELDS,
32
+ RESET_RECOVERY_BOUNDARY_FIELDS,
32
33
  REWRITE_FIELDS,
33
34
  REWRITE_KINDS,
34
35
  USER_BINDINGS_REUSE_POLICIES,
@@ -419,7 +420,9 @@ function isValidDerivedRun(value) {
419
420
  function isValidRecoveryBoundaries(value) {
420
421
  try {
421
422
  return Array.isArray(value) && value.every(boundary => (
422
- hasExactFields(boundary, RECOVERY_BOUNDARY_FIELDS)
423
+ hasExactFields(boundary, RESET_RECOVERY_BOUNDARY_FIELDS)
424
+ && isSafeSequence(boundary.failedResetSeq) && boundary.frontierCallSeq === null
425
+ || hasExactFields(boundary, RECOVERY_BOUNDARY_FIELDS)
423
426
  && isSafeSequence(boundary.failedCallSeq)
424
427
  && (boundary.frontierCallSeq === null || isSafeSequence(boundary.frontierCallSeq))
425
428
  ))
@@ -116,13 +116,6 @@ export function createPtcSettingsView(React, deps) {
116
116
  onChange: event => onChange(field, event.target.checked),
117
117
  })
118
118
  }
119
- if (field.type === 'enum') {
120
- return h('input', {
121
- type: 'checkbox', role: 'switch', className: 'ptcPlusCheck', checked: value !== 'protected',
122
- disabled, 'aria-label': label,
123
- onChange: event => onChange(field, event.target.checked ? 'stateful' : 'protected'),
124
- })
125
- }
126
119
  return h('input', {
127
120
  type: 'number', className: 'ptcPlusInput',
128
121
  value: Number.isSafeInteger(value) ? String(value) : '',
@@ -402,9 +395,9 @@ export function createPtcSettingsView(React, deps) {
402
395
  h('div', { className: 'ptcPlusLabel' }, t(`${field.key}.label`)),
403
396
  field.description === '' ? null : h('div', { className: 'ptcPlusDetail' },
404
397
  t(migratingPolicy ? 'bindingPolicy.migrationDescription' : `${field.key}.description`))),
405
- migratingPolicy
406
- ? h(Select, { value: 'legacy', disabled: fieldDisabled(field), label: t(`${field.key}.label`),
407
- options: [{ value: 'legacy', label: t('bindingPolicy.legacy'), disabled: true },
398
+ field.type === 'enum'
399
+ ? h(Select, { value: migratingPolicy ? 'legacy' : value[field.key], disabled: fieldDisabled(field), label: t(`${field.key}.label`),
400
+ options: [...(migratingPolicy ? [{ value: 'legacy', label: t('bindingPolicy.legacy'), disabled: true }] : []),
408
401
  ...field.options.map(policy => ({ value: policy, label: t(`bindingPolicy.${policy}`) }))],
409
402
  onChange: next => persist(field, next) })
410
403
  : fieldInput(field, value[field.key], fieldDisabled(field), persist, t(`${field.key}.label`))),