dsh-ptc-plus 0.4.8 → 0.4.10
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 +11 -3
- package/README.md +11 -3
- package/client.js +1151 -529
- package/cordis.patch.yml +12 -2
- package/docs/README.md +1 -0
- package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +19 -0
- package/docs/adr/0019-plugin-settings-and-kill-switch.md +13 -2
- package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +1 -1
- package/docs/adr/0023-global-user-bindings.md +1 -1
- package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +82 -18
- package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +3 -3
- package/docs/adr/0032-own-session-log-facts-in-a-host-only-projection.md +27 -0
- package/docs/adr/0033-gate-client-visual-usability-in-a-browser.md +23 -0
- package/docs/architecture.md +3 -3
- package/docs/binding-review-placement-design.md +29 -21
- package/docs/client-ui.md +20 -8
- package/docs/client-visual-contract.md +48 -0
- package/docs/durability-design.md +12 -0
- package/docs/installation.md +27 -6
- package/docs/runtime-reference.md +1 -1
- package/docs/user-bindings.en.md +2 -2
- package/docs/user-bindings.md +3 -3
- package/docs/verification-optimization-work.md +26 -0
- package/docs/verification.md +2 -0
- package/index.js +28 -7
- package/internal/compiler-platform.js +5 -3
- package/internal/compiler-service.js +2 -9
- package/internal/cordis-tools-owner.js +22 -19
- package/internal/direct-surface-owner.js +2 -0
- package/internal/edit-transport-owner.js +8 -5
- package/internal/execution-provider-owner.js +157 -0
- package/internal/execution-seam-compat.js +69 -113
- package/internal/isolated-worker.js +12 -8
- package/internal/recovery-tips.js +50 -21
- package/internal/runtime-bridge-owner.js +88 -57
- package/internal/runtime-contexts.js +13 -13
- package/internal/session-events.js +40 -4
- package/internal/session-journal-recovery.js +678 -181
- package/internal/session-log-view.js +189 -43
- package/internal/session-runtime.js +58 -10
- package/internal/settings-compat.js +12 -2
- package/internal/user-bindings-owner.js +6 -5
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +13 -14
- package/src/client-authoring-view.js +222 -141
- package/src/client-binding-review.js +2 -0
- package/src/client-code-editor.js +6 -0
- package/src/client-copy.js +10 -0
- package/src/client-feature-gates.js +12 -5
- package/src/client-host-compat.js +23 -2
- package/src/client-primitives.js +249 -0
- package/src/client-repl-view.js +3 -3
- package/src/client-settings-view.js +2 -29
- package/src/client-styles.js +92 -33
- package/src/client-tool-view.js +8 -3
- package/src/client-workbench.js +3 -3
- package/src/client.js +72 -46
- package/assets/ptc-plus-bindings-en.png +0 -0
- package/assets/ptc-plus-bindings-zh.png +0 -0
- package/assets/ptc-plus-repair-en.png +0 -0
- package/assets/ptc-plus-repair-zh.png +0 -0
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
advanceSessionTimeline, createSessionTimelineState, replaySessionTimelineFact, validateSessionTimelineState,
|
|
3
3
|
} from './session-journal-recovery.js'
|
|
4
4
|
import { isCanonicalSequence } from './session-journal.js'
|
|
5
|
-
import { sessionEvents } from './session-events.js'
|
|
5
|
+
import { advanceSessionSurface, readSessionLog, sessionEvents } from './session-events.js'
|
|
6
6
|
import { readRuntimeMessage } from './runtime-messages.js'
|
|
7
|
+
import { deepEqualJson, isJsonValue } from '@deepseek-ai/dsh-util-values'
|
|
8
|
+
import { deepFreeze, isRecord } from './record-utils.js'
|
|
9
|
+
import { advanceRuntimeHistory, createRuntimeHistory } from './recovery-tips.js'
|
|
7
10
|
|
|
8
11
|
import { isHostRuntimeContextSource } from './message-sources.js'
|
|
9
12
|
const SYSTEM_PROMPT_CLEARED = 'Current runtime context: none. Earlier runtime-context snapshots no longer apply.'
|
|
@@ -35,30 +38,185 @@ function isContextStep(event) {
|
|
|
35
38
|
|| (event?.type === 'user/message' && event.data?.source?.kind === 'user')
|
|
36
39
|
}
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
export const SESSION_LOG_PROJECTION_KEY = 'ptcPlusSessionLog'
|
|
42
|
+
|
|
43
|
+
function emptyLogState() {
|
|
44
|
+
return { timeline: createSessionTimelineState(), contextStep: 0,
|
|
45
|
+
runtimeHistory: createRuntimeHistory(), visibleRuntimeFacts: {},
|
|
46
|
+
systemPromptSnapshots: [], ptcMessages: [], runtimeMessagesBySeq: {}, visibleRuntimeMessages: [], surfaceNodes: [], logFacts: [] }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function runtimeMessageFacts(records, initial = {}) {
|
|
50
|
+
const facts = { ...initial }
|
|
51
|
+
for (const record of records) {
|
|
52
|
+
if (record.producer === 'aggregate') facts.aggregate = record
|
|
53
|
+
else if (record.form === 'snapshot') facts.snapshot = record
|
|
54
|
+
else if (record.form === 'catalog') facts.catalog = record
|
|
55
|
+
}
|
|
56
|
+
return facts
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function presentationEvent(event) {
|
|
60
|
+
const recognized = event?.type === 'user/message'
|
|
61
|
+
&& (systemPromptSnapshotSections(event.data) !== undefined || readRuntimeMessage(event.data) !== undefined)
|
|
62
|
+
const data = recognized ? { source: event.data.source, content: event.data.content }
|
|
63
|
+
: event?.type === 'user/message' && typeof event.data?.source?.kind === 'string'
|
|
64
|
+
? { source: { kind: event.data.source.kind } } : undefined
|
|
65
|
+
return JSON.parse(JSON.stringify({ seq: event.seq, type: event.type,
|
|
66
|
+
...(data === undefined ? {} : { data }),
|
|
67
|
+
...(event.surfaceOp === undefined ? {} : { surfaceOp: event.surfaceOp }),
|
|
68
|
+
}))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function applyLogEvent(state, event, index = event?.seq,
|
|
72
|
+
timeline = advanceSessionTimeline(state.timeline, event, index)) {
|
|
73
|
+
const sections = event?.type === 'user/message' ? systemPromptSnapshotSections(event.data) : undefined
|
|
74
|
+
const ptc = event?.type === 'user/message' ? readRuntimeMessage(event.data) : undefined
|
|
75
|
+
const contextStep = state.contextStep + (isContextStep(event) ? 1 : 0)
|
|
76
|
+
const surfaceNodes = advanceSessionSurface(state.surfaceNodes, event)
|
|
77
|
+
let visibleRuntimeMessages = state.visibleRuntimeMessages
|
|
78
|
+
if (timeline === state.timeline && sections === undefined && ptc === undefined && contextStep === state.contextStep
|
|
79
|
+
&& surfaceNodes === state.surfaceNodes) return state
|
|
80
|
+
const next = { ...state, timeline, contextStep,
|
|
81
|
+
logFacts: [...state.logFacts, deepFreeze({ index, event: presentationEvent(event) })],
|
|
82
|
+
}
|
|
83
|
+
next.runtimeHistory = advanceRuntimeHistory(state.runtimeHistory, {
|
|
84
|
+
sections, notice: ptc?.form === 'notice' ? ptc.name : undefined,
|
|
85
|
+
snapshot: ptc?.form === 'snapshot', catalog: ptc?.form === 'catalog',
|
|
86
|
+
index, contextStep: state.contextStep, lastSuccessfulRunIndex: timeline.lastSuccessfulRunIndex,
|
|
87
|
+
resetSuccess: timeline.lastSuccessfulRunIndex !== state.timeline.lastSuccessfulRunIndex,
|
|
88
|
+
})
|
|
89
|
+
if (sections !== undefined) {
|
|
90
|
+
const snapshot = Object.freeze({ index, contextStep: state.contextStep, sections })
|
|
91
|
+
next.systemPromptSnapshots = [...state.systemPromptSnapshots, snapshot]
|
|
92
|
+
next.runtimeMessagesBySeq = { ...state.runtimeMessagesBySeq,
|
|
93
|
+
[`k${event.seq}`]: Object.freeze({ ...snapshot, producer: 'aggregate', form: 'snapshot' }) }
|
|
94
|
+
}
|
|
95
|
+
if (ptc !== undefined) {
|
|
96
|
+
const record = Object.freeze({ ...ptc, index, contextStep: state.contextStep, producer: 'ptc-plus' })
|
|
97
|
+
next.ptcMessages = [...state.ptcMessages, record]
|
|
98
|
+
next.runtimeMessagesBySeq = { ...next.runtimeMessagesBySeq, [`k${event.seq}`]: record }
|
|
99
|
+
}
|
|
100
|
+
if (event?.surfaceOp === 'append') {
|
|
101
|
+
const record = next.runtimeMessagesBySeq[`k${event.seq}`]
|
|
102
|
+
if (record !== undefined) {
|
|
103
|
+
visibleRuntimeMessages = [...visibleRuntimeMessages, record]
|
|
104
|
+
next.visibleRuntimeFacts = runtimeMessageFacts([record], state.visibleRuntimeFacts)
|
|
47
105
|
}
|
|
106
|
+
} else if (surfaceNodes !== state.surfaceNodes) {
|
|
107
|
+
visibleRuntimeMessages = surfaceNodes.flatMap(seq => {
|
|
108
|
+
const record = next.runtimeMessagesBySeq[`k${seq}`]
|
|
109
|
+
return record === undefined ? [] : [record]
|
|
110
|
+
})
|
|
111
|
+
next.visibleRuntimeFacts = runtimeMessageFacts(visibleRuntimeMessages)
|
|
48
112
|
}
|
|
49
|
-
|
|
113
|
+
next.surfaceNodes = surfaceNodes
|
|
114
|
+
next.visibleRuntimeMessages = Object.freeze(visibleRuntimeMessages)
|
|
115
|
+
return next
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function createSessionLogProjection() {
|
|
119
|
+
return Object.freeze({
|
|
120
|
+
key: SESSION_LOG_PROJECTION_KEY,
|
|
121
|
+
stateVersion: 8,
|
|
122
|
+
stateSchema: Object.freeze({ parse(value) {
|
|
123
|
+
if (!isJsonValue(value) || !isRecord(value) || !isRecord(value.timeline)
|
|
124
|
+
|| typeof value.timeline.openTurn !== 'boolean' || typeof value.timeline.found !== 'boolean'
|
|
125
|
+
|| !Number.isSafeInteger(value.timeline.scope) || value.timeline.scope < 0
|
|
126
|
+
|| !Number.isSafeInteger(value.contextStep) || value.contextStep < 0
|
|
127
|
+
|| !Array.isArray(value.systemPromptSnapshots) || !Array.isArray(value.ptcMessages)
|
|
128
|
+
|| !Array.isArray(value.visibleRuntimeMessages)
|
|
129
|
+
|| !isRecord(value.runtimeHistory) || !isRecord(value.runtimeHistory.tips)
|
|
130
|
+
|| !isRecord(value.runtimeHistory.seenNames) || !isRecord(value.visibleRuntimeFacts)
|
|
131
|
+
|| !Array.isArray(value.surfaceNodes) || !value.surfaceNodes.every(isCanonicalSequence)
|
|
132
|
+
|| new Set(value.surfaceNodes).size !== value.surfaceNodes.length
|
|
133
|
+
|| !Array.isArray(value.timeline.calls) || !Array.isArray(value.timeline.boundaries)
|
|
134
|
+
|| !isRecord(value.runtimeMessagesBySeq) || !isRecord(value.timeline.cordisTranscript)) {
|
|
135
|
+
throw new TypeError('invalid PTC session-log projection checkpoint')
|
|
136
|
+
}
|
|
137
|
+
validateSessionTimelineState(value.timeline)
|
|
138
|
+
if (!Array.isArray(value.logFacts)) throw new TypeError('invalid PTC session-log projection checkpoint facts')
|
|
139
|
+
const timelineFacts = new Map(value.timeline.eventFacts.map(fact => [fact.eventIndex, fact]))
|
|
140
|
+
let reconstructed = emptyLogState()
|
|
141
|
+
let lastIndex = -1
|
|
142
|
+
let timelineCount = 0
|
|
143
|
+
for (const fact of value.logFacts) {
|
|
144
|
+
if (!isRecord(fact) || Object.keys(fact).some(key => !['index', 'event'].includes(key))
|
|
145
|
+
|| !isCanonicalSequence(fact.index) || fact.index <= lastIndex
|
|
146
|
+
|| !isRecord(fact.event) || !isCanonicalSequence(fact.event.seq) || typeof fact.event.type !== 'string'
|
|
147
|
+
|| !deepEqualJson(presentationEvent(fact.event), fact.event)) {
|
|
148
|
+
throw new TypeError('invalid PTC session-log projection checkpoint facts')
|
|
149
|
+
}
|
|
150
|
+
lastIndex = fact.index
|
|
151
|
+
const timelineFact = timelineFacts.get(fact.index)
|
|
152
|
+
if (timelineFact !== undefined) {
|
|
153
|
+
if (!deepEqualJson(presentationEvent(timelineFact.event), fact.event)) {
|
|
154
|
+
throw new TypeError('inconsistent PTC session-log projection checkpoint event identity')
|
|
155
|
+
}
|
|
156
|
+
timelineCount += 1
|
|
157
|
+
}
|
|
158
|
+
if (timelineFact === undefined
|
|
159
|
+
&& advanceSessionTimeline(reconstructed.timeline, fact.event, fact.index) !== reconstructed.timeline) {
|
|
160
|
+
throw new TypeError('inconsistent PTC session-log projection checkpoint missing timeline facts')
|
|
161
|
+
}
|
|
162
|
+
const timeline = timelineFact === undefined ? reconstructed.timeline
|
|
163
|
+
: replaySessionTimelineFact(reconstructed.timeline, timelineFact)
|
|
164
|
+
reconstructed = applyLogEvent(reconstructed, fact.event, fact.index, timeline)
|
|
165
|
+
}
|
|
166
|
+
if (timelineCount !== timelineFacts.size || !deepEqualJson(reconstructed, value)) {
|
|
167
|
+
throw new TypeError('inconsistent PTC session-log projection checkpoint facts')
|
|
168
|
+
}
|
|
169
|
+
return deepFreeze(value)
|
|
170
|
+
} }),
|
|
171
|
+
init: emptyLogState,
|
|
172
|
+
apply: applyLogEvent,
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function createSessionLogOwner(ctx) {
|
|
177
|
+
const projection = createSessionLogProjection()
|
|
178
|
+
let registeredService
|
|
179
|
+
const injection = ctx.inject(['sessionProjections'], scope => {
|
|
180
|
+
try {
|
|
181
|
+
scope.effect(() => {
|
|
182
|
+
const registry = scope.sessionProjections
|
|
183
|
+
const unregister = registry.register(projection)
|
|
184
|
+
if (typeof unregister !== 'function') throw new Error('ptc-plus: sessionProjections.register did not return a disposer')
|
|
185
|
+
registeredService = registry
|
|
186
|
+
return async () => {
|
|
187
|
+
await unregister()
|
|
188
|
+
if (registeredService === registry) registeredService = undefined
|
|
189
|
+
}
|
|
190
|
+
}, 'ptc-plus host-only session-log projection')
|
|
191
|
+
} catch (error) {
|
|
192
|
+
ctx.logger?.warn?.('ptc-plus: session-log projection unavailable', error)
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
ctx.effect(() => () => typeof injection === 'function' ? injection() : injection?.dispose?.(),
|
|
196
|
+
'ptc-plus session-log projection injection lifecycle')
|
|
197
|
+
const service = name => typeof ctx.get === 'function' ? ctx.get(name) : ctx[name]
|
|
198
|
+
return Object.freeze({
|
|
199
|
+
read: (session, signal) => readSessionLog(session, service('sessionQuery'), signal),
|
|
200
|
+
project(agent, requestedEdit) {
|
|
201
|
+
if (Array.isArray(agent?.session?.events)) return projectSessionLog(agent, requestedEdit)
|
|
202
|
+
const registry = registeredService ?? service('sessionProjections')
|
|
203
|
+
const state = agent?.session === undefined ? undefined : registry?.stateOf?.(agent.session, SESSION_LOG_PROJECTION_KEY)
|
|
204
|
+
return projectSessionLog(agent, requestedEdit, state)
|
|
205
|
+
},
|
|
206
|
+
})
|
|
50
207
|
}
|
|
51
208
|
|
|
52
209
|
/** Project session events into immutable facts consumed by prompt presentation. */
|
|
53
|
-
export function projectSessionLog(agent, requestedEdit) {
|
|
54
|
-
const events = sessionEvents(agent?.session)
|
|
55
|
-
if (!Array.isArray(events)) {
|
|
210
|
+
export function projectSessionLog(agent, requestedEdit, projectedState) {
|
|
211
|
+
const events = projectedState === undefined ? sessionEvents(agent?.session) : undefined
|
|
212
|
+
if (projectedState === undefined && !Array.isArray(events)) {
|
|
56
213
|
return Object.freeze({
|
|
57
214
|
openTurn: false,
|
|
58
215
|
contextStep: 0,
|
|
59
216
|
systemPromptSnapshots: Object.freeze([]),
|
|
60
217
|
ptcMessages: Object.freeze([]),
|
|
61
218
|
visibleRuntimeMessages: undefined,
|
|
219
|
+
runtimeHistory: Object.freeze(createRuntimeHistory()),
|
|
62
220
|
lastSuccessfulRunIndex: undefined,
|
|
63
221
|
latestRun: undefined,
|
|
64
222
|
editableRun: undefined,
|
|
@@ -66,45 +224,30 @@ export function projectSessionLog(agent, requestedEdit) {
|
|
|
66
224
|
cordisTranscript: Object.freeze({ calls: 0, inspections: 0 }),
|
|
67
225
|
})
|
|
68
226
|
}
|
|
69
|
-
let
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const runtimeMessagesBySeq = new Map()
|
|
73
|
-
for (let index = 0; index < events.length; index += 1) {
|
|
74
|
-
const event = events[index]
|
|
75
|
-
const sections = event?.type === 'user/message' ? systemPromptSnapshotSections(event.data) : undefined
|
|
76
|
-
if (sections !== undefined) {
|
|
77
|
-
const snapshot = Object.freeze({ index, contextStep, sections })
|
|
78
|
-
systemPromptSnapshots.push(snapshot)
|
|
79
|
-
runtimeMessagesBySeq.set(event.seq, Object.freeze({ ...snapshot, producer: 'aggregate', form: 'snapshot' }))
|
|
80
|
-
}
|
|
81
|
-
const ptc = event?.type === 'user/message' ? readRuntimeMessage(event.data) : undefined
|
|
82
|
-
if (ptc !== undefined) {
|
|
83
|
-
const record = Object.freeze({ ...ptc, index, contextStep, producer: 'ptc-plus' })
|
|
84
|
-
ptcMessages.push(record)
|
|
85
|
-
runtimeMessagesBySeq.set(event.seq, record)
|
|
86
|
-
}
|
|
87
|
-
if (isContextStep(event)) contextStep += 1
|
|
227
|
+
let state = projectedState ?? emptyLogState()
|
|
228
|
+
if (projectedState === undefined) {
|
|
229
|
+
for (let index = 0; index < events.length; index += 1) state = applyLogEvent(state, events[index], index)
|
|
88
230
|
}
|
|
89
|
-
const timeline =
|
|
231
|
+
const { timeline, contextStep, systemPromptSnapshots, ptcMessages, runtimeMessagesBySeq } = state
|
|
90
232
|
const requestedEntry = requestedEdit === undefined
|
|
91
233
|
? undefined
|
|
92
|
-
: timeline.executableCalls
|
|
234
|
+
: timeline.executableCalls[`k${requestedEdit.callSeq}`]?.[1]
|
|
93
235
|
const requestedEditTarget = requestedEdit !== undefined
|
|
94
236
|
&& requestedEntry?.event.data?.callId === requestedEdit.callId
|
|
95
237
|
&& requestedEntry.event.data.name === 'edit_run_code'
|
|
96
|
-
? timeline.editTargets
|
|
238
|
+
? timeline.editTargets[`k${requestedEdit.callSeq}`]?.[1] ?? undefined
|
|
97
239
|
: undefined
|
|
98
240
|
const latestRun = timeline.openTurn ? timeline.latestRun : undefined
|
|
99
241
|
const editableRun = timeline.openTurn ? timeline.editableRun : undefined
|
|
100
|
-
const cordisTranscript =
|
|
101
|
-
let visibleRuntimeMessages
|
|
102
|
-
try {
|
|
242
|
+
const cordisTranscript = Object.freeze(timeline.cordisTranscript)
|
|
243
|
+
let visibleRuntimeMessages = projectedState === undefined ? undefined : state.visibleRuntimeMessages
|
|
244
|
+
if (projectedState === undefined) try {
|
|
103
245
|
const nodes = agent?.session?.surface?.nodes
|
|
104
|
-
const knownSeqs = new Set(events.map(event => event?.seq))
|
|
105
|
-
if (Array.isArray(nodes) && nodes.every(seq => isCanonicalSequence(seq)
|
|
246
|
+
const knownSeqs = events === undefined ? undefined : new Set(events.map(event => event?.seq))
|
|
247
|
+
if (Array.isArray(nodes) && nodes.every(seq => isCanonicalSequence(seq)
|
|
248
|
+
&& (knownSeqs === undefined ? seq < agent.session.seq : knownSeqs.has(seq)))) {
|
|
106
249
|
visibleRuntimeMessages = Object.freeze(nodes.flatMap(seq => {
|
|
107
|
-
const record = runtimeMessagesBySeq
|
|
250
|
+
const record = runtimeMessagesBySeq[`k${seq}`]
|
|
108
251
|
return record === undefined ? [] : [record]
|
|
109
252
|
}))
|
|
110
253
|
}
|
|
@@ -115,6 +258,9 @@ export function projectSessionLog(agent, requestedEdit) {
|
|
|
115
258
|
systemPromptSnapshots: Object.freeze(systemPromptSnapshots),
|
|
116
259
|
ptcMessages: Object.freeze(ptcMessages),
|
|
117
260
|
visibleRuntimeMessages,
|
|
261
|
+
visibleRuntimeFacts: projectedState === undefined && visibleRuntimeMessages !== undefined
|
|
262
|
+
? runtimeMessageFacts(visibleRuntimeMessages) : state.visibleRuntimeFacts,
|
|
263
|
+
runtimeHistory: state.runtimeHistory,
|
|
118
264
|
lastSuccessfulRunIndex: timeline.lastSuccessfulRunIndex,
|
|
119
265
|
latestRun,
|
|
120
266
|
editableRun,
|
|
@@ -244,6 +244,7 @@ class SessionKernel {
|
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
async execute(request, config) {
|
|
247
|
+
const observedSession = request.sessionLog ?? this.session
|
|
247
248
|
const recoveryBoundaries = this.initialRecoveryBoundary === undefined
|
|
248
249
|
? []
|
|
249
250
|
: [this.initialRecoveryBoundary]
|
|
@@ -256,7 +257,7 @@ class SessionKernel {
|
|
|
256
257
|
if (this.surfaceProvenanceRequired
|
|
257
258
|
&& (currentGeneration !== this.surfaceGeneration || hasLiveState)) {
|
|
258
259
|
this.surfaceGeneration = currentGeneration
|
|
259
|
-
const visibleCallSeqs = visibleExecutableCallSeqs(
|
|
260
|
+
const visibleCallSeqs = visibleExecutableCallSeqs(observedSession)
|
|
260
261
|
const contractsLiveState = [...this.liveCallSeqs].some(callSeq => (
|
|
261
262
|
!(visibleCallSeqs instanceof Set) || !visibleCallSeqs.has(callSeq)
|
|
262
263
|
))
|
|
@@ -267,7 +268,7 @@ class SessionKernel {
|
|
|
267
268
|
if (config.durableReplay) {
|
|
268
269
|
this.recoveryNotice = undefined
|
|
269
270
|
try {
|
|
270
|
-
this.history = recoverJournal(
|
|
271
|
+
this.history = recoverJournal(observedSession, request.callSeq, {
|
|
271
272
|
visibleCallSeqs,
|
|
272
273
|
})
|
|
273
274
|
} catch (error) {
|
|
@@ -309,9 +310,9 @@ class SessionKernel {
|
|
|
309
310
|
if (!(error instanceof ReplayFailure)) throw error
|
|
310
311
|
const previousPathLength = pathToHead(this.history).length
|
|
311
312
|
const boundary = recoveryBoundaryForHistory(this.history, error.node)
|
|
312
|
-
const recovered = recoverJournal(
|
|
313
|
+
const recovered = recoverJournal(observedSession, request.callSeq, {
|
|
313
314
|
extraBoundaries: [boundary],
|
|
314
|
-
visibleCallSeqs: visibleExecutableCallSeqs(
|
|
315
|
+
visibleCallSeqs: visibleExecutableCallSeqs(observedSession),
|
|
315
316
|
})
|
|
316
317
|
const nextPathLength = pathToHead(recovered).length
|
|
317
318
|
if (nextPathLength >= previousPathLength) throw new Error('recovery did not contract the historical frontier')
|
|
@@ -631,6 +632,8 @@ export class SessionRuntime {
|
|
|
631
632
|
this.kernels = new Map()
|
|
632
633
|
this.pendingNoops = new Map()
|
|
633
634
|
this.settlements = new WeakSet()
|
|
635
|
+
this.preparingCells = new Set()
|
|
636
|
+
this.submissions = new WeakSet()
|
|
634
637
|
this.disposed = false
|
|
635
638
|
this.userBindingsCwd = options.userBindingsCwd ?? process.cwd()
|
|
636
639
|
if (typeof this.userBindingsCwd !== 'string' || !isAbsolute(this.userBindingsCwd)) {
|
|
@@ -638,6 +641,7 @@ export class SessionRuntime {
|
|
|
638
641
|
}
|
|
639
642
|
this.withInitiator = typeof options.withInitiator === 'function' ? options.withInitiator : undefined
|
|
640
643
|
this.observeSession = options.observeSession ?? (() => false)
|
|
644
|
+
this.readSessionLog = options.readSessionLog ?? (async session => session)
|
|
641
645
|
this.disposal = undefined
|
|
642
646
|
}
|
|
643
647
|
|
|
@@ -647,8 +651,31 @@ export class SessionRuntime {
|
|
|
647
651
|
return execution.result
|
|
648
652
|
}
|
|
649
653
|
|
|
654
|
+
beginSubmission(sessionContext, signal) {
|
|
655
|
+
const admission = { sessionId: sessionOf(sessionContext).id, config: this.config,
|
|
656
|
+
signal, cancelled: this.disposed }
|
|
657
|
+
this.submissions.add(admission)
|
|
658
|
+
if (!this.disposed) this.preparingCells.add(admission)
|
|
659
|
+
return admission
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
releaseSubmission(admission) {
|
|
663
|
+
this.preparingCells.delete(admission)
|
|
664
|
+
this.submissions.delete(admission)
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
assertSubmission(admission) {
|
|
668
|
+
if (this.disposed) throw new Error('PTC runtime disposed')
|
|
669
|
+
if (admission.cancelled || !this.submissions.has(admission)) throw new Error('PTC session disposed')
|
|
670
|
+
}
|
|
671
|
+
|
|
650
672
|
reconfigure(config) {
|
|
651
673
|
const resolved = resolvedRuntimeConfig(config)
|
|
674
|
+
for (const admission of this.preparingCells) {
|
|
675
|
+
if (!admission.signal?.aborted && resolved.maxOldGenerationSizeMb !== admission.config.maxOldGenerationSizeMb) {
|
|
676
|
+
throw new Error('ptc-plus: maxOldGenerationSizeMb cannot change while a session worker is active; retry after the session is disposed')
|
|
677
|
+
}
|
|
678
|
+
}
|
|
652
679
|
const kernels = [...this.kernels.values()]
|
|
653
680
|
for (const kernel of kernels) kernel.assertReconfigurationAllowed(resolved)
|
|
654
681
|
for (const kernel of kernels) kernel.reconfigure(resolved)
|
|
@@ -665,7 +692,6 @@ export class SessionRuntime {
|
|
|
665
692
|
async runTentative(sessionContext, request) {
|
|
666
693
|
const completed = result => Object.freeze({ result, settlement: undefined })
|
|
667
694
|
if (this.disposed) return completed({ logs: [], error: { kind: 'abort', message: 'PTC runtime disposed' } })
|
|
668
|
-
const cellConfig = this.config
|
|
669
695
|
let bindingDescriptors
|
|
670
696
|
try {
|
|
671
697
|
bindingDescriptors = normalizeBindingDescriptors(request?.bindings)
|
|
@@ -683,34 +709,49 @@ export class SessionRuntime {
|
|
|
683
709
|
} = sessionOf(sessionContext)
|
|
684
710
|
let callSeq
|
|
685
711
|
let sourceCallSeq
|
|
712
|
+
let sessionLog
|
|
713
|
+
const admission = sessionContext?.submission ?? this.beginSubmission(sessionContext, request.signal)
|
|
714
|
+
if (!this.submissions.has(admission) || admission.sessionId !== sessionId) {
|
|
715
|
+
return completed({ logs: [], error: { kind: 'exception', message: 'PTC submission does not belong to this session runtime' } })
|
|
716
|
+
}
|
|
717
|
+
const cellConfig = admission.config
|
|
718
|
+
const aborted = () => this.disposed || admission.cancelled
|
|
719
|
+
const abortResult = () => completed({ logs: [], error: { kind: 'abort',
|
|
720
|
+
message: request.signal?.aborted ? String(request.signal.reason)
|
|
721
|
+
: this.disposed ? 'PTC runtime disposed' : 'PTC session disposed' } })
|
|
686
722
|
try {
|
|
723
|
+
sessionLog = sessionContext?.sessionLog ?? await this.readSessionLog(session, request.signal)
|
|
724
|
+
if (aborted()) return abortResult()
|
|
687
725
|
if (persistedCallSeq !== undefined
|
|
688
726
|
&& !isCanonicalSequence(persistedCallSeq)) {
|
|
689
727
|
throw new Error('persisted tool call sequence must be a non-negative safe integer')
|
|
690
728
|
}
|
|
691
729
|
if (cellConfig.durableReplay) {
|
|
692
|
-
sourceCallSeq = liveToolCallSeq(
|
|
730
|
+
sourceCallSeq = liveToolCallSeq(sessionLog, callId, 'run_code')
|
|
693
731
|
callSeq = persistedCallSeq ?? sourceCallSeq
|
|
694
732
|
} else {
|
|
695
733
|
sourceCallSeq = persistedCallSeq
|
|
696
734
|
if (sourceCallSeq === undefined) {
|
|
697
735
|
try {
|
|
698
|
-
sourceCallSeq = liveToolCallSeq(
|
|
736
|
+
sourceCallSeq = liveToolCallSeq(sessionLog, callId, 'run_code')
|
|
699
737
|
} catch {
|
|
700
738
|
sourceCallSeq = undefined
|
|
701
739
|
}
|
|
702
740
|
}
|
|
703
741
|
}
|
|
704
742
|
} catch (error) {
|
|
743
|
+
if (aborted() || request.signal?.aborted) return abortResult()
|
|
705
744
|
return completed({ logs: [], error: { kind: 'recovery', message: `cannot identify current run_code call in session log: ${messageOf(error)}` } })
|
|
745
|
+
} finally {
|
|
746
|
+
this.releaseSubmission(admission)
|
|
706
747
|
}
|
|
707
748
|
let kernel = this.kernels.get(sessionId)
|
|
708
749
|
if (kernel === undefined) {
|
|
709
750
|
let history
|
|
710
751
|
try {
|
|
711
752
|
history = cellConfig.durableReplay
|
|
712
|
-
? recoverJournal(
|
|
713
|
-
visibleCallSeqs: visibleExecutableCallSeqs(
|
|
753
|
+
? recoverJournal(sessionLog, callSeq, {
|
|
754
|
+
visibleCallSeqs: visibleExecutableCallSeqs(sessionLog),
|
|
714
755
|
})
|
|
715
756
|
: emptyHistory()
|
|
716
757
|
} catch (error) {
|
|
@@ -742,7 +783,7 @@ export class SessionRuntime {
|
|
|
742
783
|
const workerReservation = kernel.reserveWorkerConfiguration(cellConfig)
|
|
743
784
|
let result
|
|
744
785
|
try {
|
|
745
|
-
result = await kernel.run({ ...request, journal, callSeq, sourceCallSeq }, cellConfig)
|
|
786
|
+
result = await kernel.run({ ...request, journal, callSeq, sourceCallSeq, sessionLog }, cellConfig)
|
|
746
787
|
} finally {
|
|
747
788
|
kernel.releaseWorkerConfiguration(workerReservation)
|
|
748
789
|
}
|
|
@@ -793,6 +834,12 @@ export class SessionRuntime {
|
|
|
793
834
|
|
|
794
835
|
async disposeSession(sessionId) {
|
|
795
836
|
const id = String(sessionId)
|
|
837
|
+
for (const admission of this.preparingCells) {
|
|
838
|
+
if (admission.sessionId === id) {
|
|
839
|
+
admission.cancelled = true
|
|
840
|
+
this.preparingCells.delete(admission)
|
|
841
|
+
}
|
|
842
|
+
}
|
|
796
843
|
const kernel = this.kernels.get(id)
|
|
797
844
|
this.pendingNoops.delete(id)
|
|
798
845
|
if (kernel === undefined) return
|
|
@@ -815,6 +862,7 @@ export class SessionRuntime {
|
|
|
815
862
|
|
|
816
863
|
async #dispose() {
|
|
817
864
|
this.disposed = true
|
|
865
|
+
this.preparingCells.clear()
|
|
818
866
|
const entries = [...this.kernels]
|
|
819
867
|
const results = await Promise.allSettled(entries.map(async ([id, kernel]) => {
|
|
820
868
|
await kernel.dispose()
|
|
@@ -63,7 +63,14 @@ export function installSettingsSectionCompat({
|
|
|
63
63
|
}) {
|
|
64
64
|
ctx.inject(['settings'], settingsContext => {
|
|
65
65
|
const provider = settingsContext.settings
|
|
66
|
-
|
|
66
|
+
const sectionInstalled = typeof provider?.installSection === 'function'
|
|
67
|
+
|| typeof settingsModule?.installSettingsSection === 'function'
|
|
68
|
+
const writeNamespace = sectionInstalled ? namespace : (ownerFiber ?? ctx.fiber)?.entry?.options?.id
|
|
69
|
+
const writer = typeof provider?.update === 'function'
|
|
70
|
+
&& typeof writeNamespace === 'string' && writeNamespace.length > 0
|
|
71
|
+
? Object.freeze({ update: patch => provider.update(writeNamespace, patch) }) : undefined
|
|
72
|
+
const unbind = onProvider(provider, writer)
|
|
73
|
+
if (typeof unbind === 'function') settingsContext.effect(() => unbind, 'ptc-plus settings writer')
|
|
67
74
|
if (typeof provider?.installSection === 'function') {
|
|
68
75
|
provider.installSection(ctx, namespace, schema, entry, hooks)
|
|
69
76
|
return
|
|
@@ -79,7 +86,10 @@ export function installSettingsSectionCompat({
|
|
|
79
86
|
// fiber, so the owner is the plugin's entry context rather than the
|
|
80
87
|
// scope this installation happens to run in.
|
|
81
88
|
const dispose = provider.configure({ auto: false }, ownerFiber ?? ctx.fiber)
|
|
82
|
-
|
|
89
|
+
// The policy belongs to the settings surface that accepted it, so its
|
|
90
|
+
// disposer is released when that surface goes away instead of
|
|
91
|
+
// accumulating on the plugin scope across settings reloads.
|
|
92
|
+
settingsContext.effect?.(() => dispose, 'ptc-plus settings page policy')
|
|
83
93
|
}
|
|
84
94
|
return
|
|
85
95
|
}
|
|
@@ -274,7 +274,7 @@ export function createUserBindingsOwner(ctx, options = {}) {
|
|
|
274
274
|
entry: current.entry,
|
|
275
275
|
})
|
|
276
276
|
const candidateView = current => Object.freeze({ requestId: current.requestId, commandId: current.commandId, ...draftView(current) })
|
|
277
|
-
const settleDraft = (current, state, enabled = false) => {
|
|
277
|
+
const settleDraft = async (current, state, enabled = false) => {
|
|
278
278
|
const retainReview = draftsByCapability.get(current.capability) === current
|
|
279
279
|
removeDraft(current)
|
|
280
280
|
const action = Object.freeze({ requestId: current.requestId, id: current.entry.id, state, enabled })
|
|
@@ -282,7 +282,8 @@ export function createUserBindingsOwner(ctx, options = {}) {
|
|
|
282
282
|
reviewsByCapability.set(current.capability, { agent: current.agent, candidate: candidateView(current), action })
|
|
283
283
|
}
|
|
284
284
|
try {
|
|
285
|
-
const
|
|
285
|
+
const sessionLog = await (options.readSessionLog ?? (async session => session))(current.agent.session)
|
|
286
|
+
const accepting = sessionEvents(sessionLog)?.find(event => (
|
|
286
287
|
event.type === 'tool/result' && event.surfaceOp === 'append'
|
|
287
288
|
&& event.data?.meta?.[USER_BINDING_DRAFT_META_KEY]?.candidate?.requestId === current.requestId
|
|
288
289
|
))
|
|
@@ -303,7 +304,7 @@ export function createUserBindingsOwner(ctx, options = {}) {
|
|
|
303
304
|
drafts.delete(current.sessionId)
|
|
304
305
|
}
|
|
305
306
|
}
|
|
306
|
-
const clearDraft = (capability, version) => {
|
|
307
|
+
const clearDraft = async (capability, version) => {
|
|
307
308
|
const current = draftFor(capability)
|
|
308
309
|
if (current === null) return null
|
|
309
310
|
if (!Number.isSafeInteger(version) || version < 1 || version !== current.version) {
|
|
@@ -314,7 +315,7 @@ export function createUserBindingsOwner(ctx, options = {}) {
|
|
|
314
315
|
if (current.state !== 'ready') {
|
|
315
316
|
throw Object.assign(new Error('binding draft is being saved'), { code: 'BINDINGS_BUSY' })
|
|
316
317
|
}
|
|
317
|
-
settleDraft(current, 'discarded')
|
|
318
|
+
await settleDraft(current, 'discarded')
|
|
318
319
|
return null
|
|
319
320
|
}
|
|
320
321
|
const saveDraft = async (capability, version, expectedRevision, activate = false) => {
|
|
@@ -333,7 +334,7 @@ export function createUserBindingsOwner(ctx, options = {}) {
|
|
|
333
334
|
const result = current.mode === 'new'
|
|
334
335
|
? await store.create(entry, expectedRevision)
|
|
335
336
|
: await store.update(entry, expectedRevision)
|
|
336
|
-
settleDraft(current, 'saved', activate === true)
|
|
337
|
+
await settleDraft(current, 'saved', activate === true)
|
|
337
338
|
return result
|
|
338
339
|
} catch (error) {
|
|
339
340
|
if (drafts.get(current.sessionId) === current && current.state === 'saving') {
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-ptc-plus",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.10",
|
|
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",
|
|
7
7
|
"main": "./index.js",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": "./index.js",
|
|
10
|
+
"./execution-provider": "./internal/execution-provider-owner.js",
|
|
10
11
|
"./client": "./client.js",
|
|
11
|
-
"./package.json": "./package.json"
|
|
12
|
+
"./package.json": "./package.json",
|
|
13
|
+
"./locale/*.json": "./locale/*.json"
|
|
12
14
|
},
|
|
13
15
|
"files": [
|
|
14
16
|
"index.js",
|
|
@@ -29,15 +31,12 @@
|
|
|
29
31
|
"assets/ptc-plus-icon.svg",
|
|
30
32
|
"assets/dsh-ptc-plus-banner-en.webp",
|
|
31
33
|
"assets/dsh-ptc-plus-banner-zh.webp",
|
|
32
|
-
"assets/ptc-plus-bindings-en.png",
|
|
33
|
-
"assets/ptc-plus-bindings-zh.png",
|
|
34
|
-
"assets/ptc-plus-repair-zh.png",
|
|
35
|
-
"assets/ptc-plus-repair-en.png",
|
|
36
34
|
"assets/ptc-plus-repl-workspace-en.png",
|
|
37
35
|
"assets/ptc-plus-repl-workspace-zh.png",
|
|
38
36
|
"assets/ptc-plus-settings-en.png",
|
|
39
37
|
"assets/ptc-plus-settings-zh.png",
|
|
40
38
|
"cordis.patch.yml",
|
|
39
|
+
"locale",
|
|
41
40
|
"README.md",
|
|
42
41
|
"README.en.md",
|
|
43
42
|
"LICENSE"
|
|
@@ -64,6 +63,7 @@
|
|
|
64
63
|
"@codemirror/state": "^6.7.4",
|
|
65
64
|
"@codemirror/view": "^6.43.11",
|
|
66
65
|
"@deepseek-ai/schemastery": "^3.18.3",
|
|
66
|
+
"@deepseek-ai/dsh-util-values": "^0.2.0-rc.2 || ^0.2.1-alpha.1",
|
|
67
67
|
"@jridgewell/trace-mapping": "^0.3.31",
|
|
68
68
|
"@lezer/highlight": "^1.2.3",
|
|
69
69
|
"acorn": "^8.15.0",
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
"codemirror": "^6.0.2",
|
|
72
72
|
"fflate": "^0.8.3",
|
|
73
73
|
"pathe": "^2.0.3",
|
|
74
|
+
"react-is": "^19.3.0",
|
|
74
75
|
"tslib": "^2.8.1",
|
|
75
76
|
"typescript": "^6.0.3"
|
|
76
77
|
},
|
|
@@ -88,9 +89,11 @@
|
|
|
88
89
|
"build": "node scripts/client-bundle.mjs build && node scripts/compiler-bundle.mjs build",
|
|
89
90
|
"build:check": "node scripts/client-bundle.mjs check && node scripts/compiler-bundle.mjs check",
|
|
90
91
|
"test:client": "vitest run --config scripts/client-test.config.mjs",
|
|
92
|
+
"test:client:visual": "node scripts/client-visual-check.mjs",
|
|
91
93
|
"test:semantics": "node --test --test-concurrency=1 test/semantic-test262.test.js test/semantic-dialect.test.js test/semantic-boundaries.test.js",
|
|
92
94
|
"semantic:check": "node scripts/semantic-obligations.mjs",
|
|
93
95
|
"test:client:web": "node scripts/client-web-smoke.mjs",
|
|
96
|
+
"test:client:fallback": "node scripts/client-fallback-smoke.mjs",
|
|
94
97
|
"test:client:layout": "node scripts/client-binding-layout.mjs",
|
|
95
98
|
"host:baseline:update": "node scripts/dsh-dependency-baseline.mjs update",
|
|
96
99
|
"host:baseline:verify": "node scripts/dsh-dependency-baseline.mjs verify",
|
|
@@ -110,8 +113,8 @@
|
|
|
110
113
|
"coverage": "node scripts/coverage.mjs",
|
|
111
114
|
"coverage:focus": "node scripts/coverage.mjs --focus",
|
|
112
115
|
"test:ci:runtime": "node --test --test-concurrency=1 test/package-contract.test.js test/compiler-platform.test.js test/direct-surface-policy.test.js test/isolated-entrance.test.js test/plugin-native-call-transport.test.js",
|
|
113
|
-
"verify": "npm run review:validate && npm run host:baseline:verify && npm run semantic:check && npm run build:check && node --check index.js && node --check internal/kernel-worker.js && node --check scripts/migrate-session-log.mjs && node --check scripts/expensive-headless-acceptance.mjs && node --check scripts/ab-headless-trajectories.mjs && npm run test:client && npm run coverage",
|
|
114
|
-
"verify:ci": "npm run review:validate && npm run host:baseline:verify && npm run semantic:check && npm run build:check && node --check index.js && node --check internal/kernel-worker.js && node --check scripts/migrate-session-log.mjs && npm run test:client && npm run test:ci:runtime",
|
|
116
|
+
"verify": "npm run review:validate && npm run host:baseline:verify && npm run semantic:check && npm run build:check && node --check index.js && node --check internal/kernel-worker.js && node --check scripts/migrate-session-log.mjs && node --check scripts/expensive-headless-acceptance.mjs && node --check scripts/ab-headless-trajectories.mjs && npm run test:client:visual && npm run coverage",
|
|
117
|
+
"verify:ci": "npm run review:validate && npm run host:baseline:verify && npm run semantic:check && npm run build:check && node --check index.js && node --check internal/kernel-worker.js && node --check scripts/migrate-session-log.mjs && npm run test:client:visual && npm run test:ci:runtime",
|
|
115
118
|
"check": "node scripts/review-findings.mjs check",
|
|
116
119
|
"test:expensive": "node scripts/expensive-headless-acceptance.mjs",
|
|
117
120
|
"test:ab": "node scripts/ab-headless-trajectories.mjs"
|
|
@@ -139,19 +142,15 @@
|
|
|
139
142
|
},
|
|
140
143
|
"client": {
|
|
141
144
|
"platform": "web",
|
|
142
|
-
"
|
|
143
|
-
"react"
|
|
144
|
-
],
|
|
145
|
+
"immediately": true,
|
|
145
146
|
"inject": [
|
|
146
147
|
"@deepseek-ai/dsh-api-gateway",
|
|
147
148
|
"@deepseek-ai/dsh-api-remotes",
|
|
148
|
-
"@deepseek-ai/dsh-client-runtime",
|
|
149
149
|
"@deepseek-ai/dsh-client-locale",
|
|
150
150
|
"@deepseek-ai/dsh-client-ui-settings",
|
|
151
151
|
"@deepseek-ai/dsh-client-ui-settings-plugins",
|
|
152
152
|
"@deepseek-ai/dsh-client-ui-conversation",
|
|
153
|
-
"@deepseek-ai/dsh-client-connection"
|
|
154
|
-
"@deepseek-ai/dsh-client-ui-primitives"
|
|
153
|
+
"@deepseek-ai/dsh-client-connection"
|
|
155
154
|
]
|
|
156
155
|
}
|
|
157
156
|
},
|