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.
- package/README.en.md +6 -2
- package/README.md +6 -2
- package/client.cjs +13 -23
- package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +18 -12
- package/docs/adr/0044-coexist-with-the-official-persistent-repl.md +2 -0
- package/docs/adr/0051-persist-explicit-host-environment-resets.md +29 -0
- package/docs/adr/README.md +1 -0
- package/docs/architecture.md +2 -2
- package/docs/durability-design.md +10 -0
- package/docs/installation.md +2 -2
- package/docs/releases/v0.5.6.md +7 -3
- package/docs/releases/v0.5.7.md +51 -0
- package/docs/runtime-reference.md +4 -4
- package/internal/config-spec.js +2 -2
- package/internal/cordis-tools-owner.js +32 -28
- package/internal/execution-seam-compat.js +13 -15
- package/internal/runtime-bridge-owner.js +7 -1
- package/internal/runtime-messages.js +8 -0
- package/internal/session-journal-recovery.js +68 -25
- package/internal/session-journal-schema.js +10 -0
- package/internal/session-journal.js +25 -13
- package/internal/session-log-view.js +15 -8
- package/internal/session-runtime.js +13 -0
- package/package.json +1 -1
- package/src/client-activity.js +4 -1
- package/src/client-settings-view.js +3 -10
|
@@ -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
|
-
//
|
|
175
|
-
//
|
|
176
|
-
|
|
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
package/src/client-activity.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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`))),
|