dsh-ptc-plus 0.4.10 → 0.5.1
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 +65 -127
- package/README.md +65 -127
- package/assets/dsh-ptc-plus-banner-en.webp +0 -0
- package/assets/dsh-ptc-plus-banner-zh.webp +0 -0
- package/assets/ptc-plus-repl-workspace-en.png +0 -0
- package/assets/ptc-plus-repl-workspace-zh.png +0 -0
- package/assets/ptc-plus-settings-en.png +0 -0
- package/assets/ptc-plus-settings-zh.png +0 -0
- package/client.cjs +42702 -0
- package/cordis.patch.yml +1 -0
- package/docs/README.md +6 -2
- package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +31 -198
- package/docs/adr/0008-define-agent-operating-contract.md +5 -1
- package/docs/adr/0009-bound-expensive-acceptance-default.md +2 -2
- package/docs/adr/0016-archive-resolved-review-findings.md +1 -1
- package/docs/adr/0017-track-the-latest-dsh-public-surface.md +4 -4
- package/docs/adr/0019-plugin-settings-and-kill-switch.md +1 -1
- package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +1 -1
- package/docs/adr/0022-stage-npm-releases-with-oidc.md +4 -2
- package/docs/adr/0023-global-user-bindings.md +2 -0
- package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +2 -0
- package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +25 -23
- package/docs/adr/0031-close-semantic-obligations-by-owner.md +1 -1
- package/docs/adr/0034-keep-program-values-separate-from-output-projections.md +29 -0
- package/docs/adr/0035-run-coverage-only-before-a-new-release.md +27 -0
- package/docs/adr/0036-separate-focused-development-from-delivery-verification.md +29 -0
- package/docs/adr/0037-keep-tool-hover-previews-passive.md +52 -0
- package/docs/adr/0038-freeze-session-capability-plans.md +71 -0
- package/docs/adr/0039-verify-documentation-without-runtime-suites.md +29 -0
- package/docs/adr/0040-separate-capability-configurations-from-session-observations.md +39 -0
- package/docs/adr/0041-record-cell-execution-directories.md +65 -0
- package/docs/adr/0042-persist-provider-availability-and-handback.md +41 -0
- package/docs/adr/0043-compose-capability-and-context-policies.md +37 -0
- package/docs/adr/0044-coexist-with-the-official-persistent-repl.md +35 -0
- package/docs/adr/README.md +8 -0
- package/docs/architecture.md +23 -25
- package/docs/capability-planning-components.md +93 -0
- package/docs/capability-projection.md +7 -0
- package/docs/client-ui.md +70 -23
- package/docs/client-visual-contract.md +26 -9
- package/docs/context-management.md +22 -0
- package/docs/context-optimization-plan.md +353 -0
- package/docs/context-public-interface-coverage.md +25 -0
- package/docs/durability-design.md +20 -15
- package/docs/evaluation.md +41 -6
- package/docs/evaluations/command-goat-deepseek-v4.1-flash-20261006.md +126 -0
- package/docs/evaluations/command-goat-deepseek-v4.1-flash.md +281 -0
- package/docs/installation.md +33 -10
- package/docs/program-data-plane.md +5 -3
- package/docs/publishing.md +7 -7
- package/docs/release-coverage.md +73 -0
- package/docs/release-notes.md +1 -1
- package/docs/review-procedure.md +56 -0
- package/docs/runtime-reference.md +53 -16
- package/docs/semantic-validation.md +14 -2
- package/docs/stateful-binding-updates-proposal.md +2 -2
- package/docs/user-bindings.md +1 -1
- package/docs/value-wire.md +11 -1
- package/docs/verification-optimization-work.md +26 -15
- package/docs/verification.md +30 -88
- package/index.js +146 -20
- package/internal/binding-toggle-receipt.js +24 -0
- package/internal/capability-command-receipt.js +31 -0
- package/internal/capability-environment.js +1253 -0
- package/internal/capability-plan.js +285 -0
- package/internal/capability-policy.js +96 -0
- package/internal/capability-statistics-store.js +180 -0
- package/internal/capability-statistics.js +214 -0
- package/internal/config-spec.js +89 -10
- package/internal/direct-surface-owner.js +27 -29
- package/internal/dynamic-environment-runtime.js +9 -9
- package/internal/edit-transport-owner.js +195 -140
- package/internal/execution-provider-owner.js +40 -12
- package/internal/execution-seam-compat.js +26 -8
- package/internal/kernel-worker.js +104 -32
- package/internal/native-root-dynamic.js +12 -13
- package/internal/official-repl-priority.js +51 -0
- package/internal/program-bindings.js +190 -0
- package/internal/recovery-tips.js +3 -2
- package/internal/rejected-cell-editor.js +8 -8
- package/internal/repl-observation-interest.js +11 -3
- package/internal/rpc-contract.js +1 -0
- package/internal/runtime-bridge-owner.js +275 -34
- package/internal/runtime-config.js +58 -0
- package/internal/runtime-contexts.js +74 -5
- package/internal/runtime-intrinsics.js +1 -0
- package/internal/runtime-messages.js +41 -0
- package/internal/sdk-projection.js +10 -82
- package/internal/session-cell-executor.js +42 -19
- package/internal/session-events.js +34 -0
- package/internal/session-journal-recovery.js +234 -11
- package/internal/session-journal-schema.js +6 -3
- package/internal/session-journal.js +31 -2
- package/internal/session-log-view.js +1 -1
- package/internal/session-runtime.js +119 -15
- package/internal/settings-compat.js +12 -5
- package/internal/stateful-module-compiler.js +3 -2
- package/internal/stateful-module-runtime.js +7 -3
- package/internal/stateful-root-runtime.js +9 -4
- package/internal/user-binding-authoring.js +2 -7
- package/internal/user-bindings-owner.js +19 -2
- package/internal/validated-parse-repair.js +1 -1
- package/internal/value-wire.js +68 -21
- package/internal/worker-cwd-virtualization.js +20 -18
- package/internal/worker-realm-surfaces.js +3 -3
- package/package.json +49 -30
- package/src/client-activity.js +3 -2
- package/src/client-authoring-view.js +67 -30
- package/src/client-binding-review.js +2 -1
- package/src/client-binding-toggle.js +18 -0
- package/src/client-capabilities-view.js +1555 -0
- package/src/client-capability-sort.js +29 -0
- package/src/client-copy.js +923 -8
- package/src/client-feature-gates.js +3 -0
- package/src/client-indicator-view.js +4 -6
- package/src/client-primitives.js +33 -2
- package/src/client-repl-view.js +20 -13
- package/src/client-settings-view.js +411 -41
- package/src/client-styles.js +112 -15
- package/src/client-tool-hover.js +185 -0
- package/src/client-tool-view.js +45 -9
- package/src/client-transport.js +44 -9
- package/src/client-workbench.js +6 -7
- package/src/client.js +76 -22
- package/client.js +0 -32479
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { capabilityPolicyForConfig } from './capability-policy.js'
|
|
1
2
|
import {
|
|
2
3
|
CONFIG_DEFAULTS,
|
|
3
4
|
CONFIG_FIELDS,
|
|
@@ -6,6 +7,43 @@ import {
|
|
|
6
7
|
|
|
7
8
|
export { MAX_TIMER_DELAY_MS }
|
|
8
9
|
|
|
10
|
+
/** Reusable configurations contain capability settings, never session evidence. */
|
|
11
|
+
export const CAPABILITY_CONFIGURATION_KEYS = Object.freeze(CONFIG_FIELDS
|
|
12
|
+
.filter(field => (field.key.startsWith('capability') && field.key !== 'capabilityProfiles') || ['modelPreviewTokens', 'modelPreviewEnabled'].includes(field.key))
|
|
13
|
+
.map(field => field.key))
|
|
14
|
+
|
|
15
|
+
export function capabilityProfileConfiguration(value, { allowInvalidStatistics = false } = {}) {
|
|
16
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)
|
|
17
|
+
|| Object.keys(value).some(key => !CAPABILITY_CONFIGURATION_KEYS.includes(key))) {
|
|
18
|
+
throw new TypeError('ptc-plus: invalid capability configuration fields')
|
|
19
|
+
}
|
|
20
|
+
const resolved = resolveConfig(value)
|
|
21
|
+
let statistics
|
|
22
|
+
try { statistics = JSON.parse(resolved.capabilityStatistics) } catch {}
|
|
23
|
+
if (!allowInvalidStatistics && (!statistics || typeof statistics.enabled !== 'boolean'
|
|
24
|
+
|| !Number.isSafeInteger(statistics.windowSize) || statistics.windowSize < 1
|
|
25
|
+
|| !Number.isSafeInteger(statistics.minimumSamples) || statistics.minimumSamples < 1 || statistics.minimumSamples > statistics.windowSize
|
|
26
|
+
|| !Number.isFinite(statistics.fullThreshold) || statistics.fullThreshold < 0 || statistics.fullThreshold > 1)) {
|
|
27
|
+
throw new TypeError('ptc-plus: invalid saved capability statistics settings')
|
|
28
|
+
}
|
|
29
|
+
return Object.fromEntries(CAPABILITY_CONFIGURATION_KEYS.map(key => [key, resolved[key]]))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function validateCapabilityProfiles(value) {
|
|
33
|
+
if (!value || value.schema !== 'ptc-capability-profiles/v1' || !Array.isArray(value.profiles) || value.profiles.length > 100) {
|
|
34
|
+
throw new TypeError('ptc-plus: invalid capability profiles document')
|
|
35
|
+
}
|
|
36
|
+
const ids = new Set()
|
|
37
|
+
const names = new Set()
|
|
38
|
+
return { schema: value.schema, profiles: value.profiles.map(profile => {
|
|
39
|
+
if (!profile || typeof profile.id !== 'string' || !profile.id.trim() || profile.id.length > 100
|
|
40
|
+
|| typeof profile.name !== 'string' || !profile.name.trim() || profile.name.length > 80
|
|
41
|
+
|| ids.has(profile.id) || names.has(profile.name.trim())) throw new TypeError('ptc-plus: invalid or duplicate capability profile')
|
|
42
|
+
ids.add(profile.id); names.add(profile.name.trim())
|
|
43
|
+
return { id: profile.id, name: profile.name.trim(), configuration: capabilityProfileConfiguration(profile.configuration) }
|
|
44
|
+
}) }
|
|
45
|
+
}
|
|
46
|
+
|
|
9
47
|
/** The wall-clock ceiling field carries the platform timer limit as its maximum. */
|
|
10
48
|
const MAX_WALL_MS_FIELD = CONFIG_FIELDS.find(field => field.key === 'maxWallMs')
|
|
11
49
|
|
|
@@ -21,6 +59,10 @@ function assertIntegerField(field, value) {
|
|
|
21
59
|
|
|
22
60
|
/** Validate one field against its CONFIG_FIELDS declaration. */
|
|
23
61
|
function assertField(field, value) {
|
|
62
|
+
if (field.type === 'string') {
|
|
63
|
+
if (typeof value !== 'string') throw new TypeError(`ptc-plus: ${field.key} must be a string`)
|
|
64
|
+
return
|
|
65
|
+
}
|
|
24
66
|
if (field.type === 'boolean') {
|
|
25
67
|
if (typeof value !== 'boolean') {
|
|
26
68
|
throw new TypeError(`ptc-plus: ${field.key} must be a boolean`)
|
|
@@ -74,6 +116,22 @@ export function resolveConfig(config = {}) {
|
|
|
74
116
|
resolved[field.key] = liveConfigValue(resolved[field.key], CONFIG_DEFAULTS[field.key])
|
|
75
117
|
}
|
|
76
118
|
for (const field of CONFIG_FIELDS) assertField(field, resolved[field.key])
|
|
119
|
+
validateCapabilityProfiles(JSON.parse(resolved.capabilityProfiles))
|
|
120
|
+
for (const [key, array] of [['capabilityBaseline', true], ['capabilityOverrides', false]]) {
|
|
121
|
+
let value
|
|
122
|
+
try { value = JSON.parse(resolved[key]) } catch { throw new TypeError(`ptc-plus: ${key} must contain valid JSON`) }
|
|
123
|
+
if (array ? !Array.isArray(value) || value.some(id => typeof id !== 'string' || !id.trim())
|
|
124
|
+
: value === null || typeof value !== 'object' || Array.isArray(value)
|
|
125
|
+
|| Object.entries(value).some(([id, selection]) => !id.trim() || !['full', 'on-demand', 'closed'].includes(selection)
|
|
126
|
+
|| (id.startsWith('mcpServers.') && (id === 'mcpServers.' || selection !== 'closed')))) {
|
|
127
|
+
throw new TypeError(`ptc-plus: invalid ${key}`)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
resolved.capabilityPolicy = JSON.stringify(capabilityPolicyForConfig(provided.has('capabilityPolicy') ? resolved : { ...resolved, capabilityPolicy: undefined }))
|
|
131
|
+
if (!provided.has('capabilityPolicy') && resolved.capabilityManagementEnabled) {
|
|
132
|
+
if (!provided.has('capabilityRecoveryEnabled')) resolved.capabilityRecoveryEnabled = true
|
|
133
|
+
if (!provided.has('modelPreviewEnabled')) resolved.modelPreviewEnabled = true
|
|
134
|
+
}
|
|
77
135
|
// Migrate complete old policies; mixed choices retain their explicit
|
|
78
136
|
// compatibility state until the user selects the unified language.
|
|
79
137
|
const legacyKeys = [
|
|
@@ -1,14 +1,77 @@
|
|
|
1
1
|
import { latestRecoveryTip, runtimeHistoryForView } from './recovery-tips.js'
|
|
2
|
+
import { foldSurface, deriveEventMessage, snapshotSessionEvent } from '@deepseek-ai/dsh-session'
|
|
3
|
+
import { currentSessionMessageProjections } from '@deepseek-ai/dsh-session-format-catalog/message-projections'
|
|
4
|
+
import { readSessionLog } from './session-events.js'
|
|
5
|
+
import { JOURNAL_KEY } from './session-journal.js'
|
|
2
6
|
import { projectSessionLog, runtimeMessageFacts, systemPromptSnapshotSections } from './session-log-view.js'
|
|
3
7
|
import {
|
|
4
8
|
PTC_BINDING_CATALOG, PTC_DELIVERY_CONTEXT, PTC_STATE_NAMES, readRuntimeMessage,
|
|
5
9
|
runtimeBindingCatalogMessage, runtimeNoticeMessage, runtimeStateMessage,
|
|
10
|
+
projectRuntimeAvailability,
|
|
11
|
+
runtimeAvailabilityMessage,
|
|
6
12
|
} from './runtime-messages.js'
|
|
7
13
|
|
|
8
14
|
const REWRITE_FEEDBACK = 'tools:ptc-plus-rewrite-info'
|
|
9
15
|
const CORDIS_RECOVERY = 'tools:ptc-plus-cordis-recovery'
|
|
10
16
|
const CORDIS_RECOVERY_TEXT = 'Recorded Cordis values in the recovered REPL are historical data, not proof that process-local Plugins, Runs, approvals, or Inspect observations still exist in the current DSH process. Before relying on prior Cordis IDs, state, or capability data, follow the current Cordis owner guidance and call the live read-only Cordis Inspect bindings through `tools.*`; do not rerun mutating Cordis calls merely to reconstruct history.'
|
|
11
17
|
|
|
18
|
+
// A historical plugin result proves that this provider was used, not that its
|
|
19
|
+
// bindings remain known, recoverable, initialized or currently alive.
|
|
20
|
+
function hadPluginComputation(session) {
|
|
21
|
+
return (session?.events ?? []).some(event => event.type === 'tool/result'
|
|
22
|
+
&& Object.hasOwn(event.data?.meta ?? {}, JOURNAL_KEY))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Persist actual provider release through public Session and single-writer owners. */
|
|
26
|
+
export async function handBackRuntimeSessions(ctx, owners) {
|
|
27
|
+
const service = name => owners ? owners[name] : typeof ctx.get === 'function' ? ctx.get(name) : ctx[name]
|
|
28
|
+
const sessions = service('sessions')
|
|
29
|
+
const persistence = service('sessionPersistence')
|
|
30
|
+
const query = service('sessionQuery')
|
|
31
|
+
if (!sessions?.list || !sessions?.flush || !persistence?.list || !persistence?.open) {
|
|
32
|
+
throw new Error('PTC Plus provider handback requires the public session and persistence owners')
|
|
33
|
+
}
|
|
34
|
+
const live = new Map(sessions.list().map(session => [session.id, session]))
|
|
35
|
+
const failures = []
|
|
36
|
+
const withdraw = (session, log) => {
|
|
37
|
+
if (!projectRuntimeAvailability(session.deriveMessages(), false, hadPluginComputation(log))) return undefined
|
|
38
|
+
return session.append('user/message', runtimeAvailabilityMessage('unloaded'), { surfaceOp: 'append' })
|
|
39
|
+
}
|
|
40
|
+
for (const session of live.values()) {
|
|
41
|
+
try {
|
|
42
|
+
const log = await readSessionLog(session, query)
|
|
43
|
+
const event = withdraw(session, log)
|
|
44
|
+
const pending = session.deriveMessages().some(message => message.source?.form === 'runtime-availability')
|
|
45
|
+
if ((event || pending) && await sessions.flush(session) !== true) throw new Error('No public durability listener confirmed the session handback')
|
|
46
|
+
} catch (error) { failures.push(new Error(`PTC Plus handback failed for session ${session.id}`, { cause: error })) }
|
|
47
|
+
}
|
|
48
|
+
const entries = await persistence.list()
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
const id = entry.header.id
|
|
51
|
+
if (live.has(id)) continue
|
|
52
|
+
try {
|
|
53
|
+
const handle = await persistence.open(id, 'write')
|
|
54
|
+
try {
|
|
55
|
+
const stored = await handle.read()
|
|
56
|
+
const definitions = new Map(currentSessionMessageProjections.map(item => [item.type, item]))
|
|
57
|
+
for (const definition of sessions.messageProjections ?? []) definitions.set(definition.type, definition)
|
|
58
|
+
const projections = [...definitions.values()]
|
|
59
|
+
const surface = foldSurface(stored.events, projections)
|
|
60
|
+
const messages = surface.nodes.map(seq => deriveEventMessage(stored.events[seq], surface.projectedMessages)).filter(Boolean)
|
|
61
|
+
if (!projectRuntimeAvailability(messages, false, hadPluginComputation({ events: stored.events }))) continue
|
|
62
|
+
// This is a new fact from the releasing provider, not a Session resume.
|
|
63
|
+
// The exclusive writer's next offset owns the new event's identity.
|
|
64
|
+
const event = snapshotSessionEvent({ type: 'user/message', seq: stored.events.length, time: Date.now(),
|
|
65
|
+
data: runtimeAvailabilityMessage('unloaded'), surfaceOp: 'append' })
|
|
66
|
+
foldSurface([...stored.events, event], projections)
|
|
67
|
+
await handle.append([event])
|
|
68
|
+
await handle.flush()
|
|
69
|
+
} finally { await handle.close() }
|
|
70
|
+
} catch (error) { failures.push(new Error(`PTC Plus handback failed for session ${id}`, { cause: error })) }
|
|
71
|
+
}
|
|
72
|
+
if (failures.length) throw new AggregateError(failures, 'PTC Plus provider released, but session handback is incomplete')
|
|
73
|
+
}
|
|
74
|
+
|
|
12
75
|
function continuationFeedback(view) {
|
|
13
76
|
const run = view.latestRun
|
|
14
77
|
const rewrites = run?.rewrites
|
|
@@ -74,7 +137,7 @@ export function projectRuntimeMessages(view, contexts, pending = []) {
|
|
|
74
137
|
}
|
|
75
138
|
|
|
76
139
|
/** Passive message delivery follows the actual public assembly and accepted step. */
|
|
77
|
-
export function createRuntimeMessageOwner(contextsForRequest, sessionLogView = projectSessionLog) {
|
|
140
|
+
export function createRuntimeMessageOwner(contextsForRequest, sessionLogView = projectSessionLog, activeForRequest, sessionQuery) {
|
|
78
141
|
const requests = new WeakMap()
|
|
79
142
|
const disposedAgents = new WeakSet()
|
|
80
143
|
let disposed = false
|
|
@@ -84,7 +147,7 @@ export function createRuntimeMessageOwner(contextsForRequest, sessionLogView = p
|
|
|
84
147
|
return Object.freeze({
|
|
85
148
|
async assemble(initial, context, next) {
|
|
86
149
|
const assembly = await next()
|
|
87
|
-
if (!disposed && context?.agent !== null && typeof context?.agent === 'object'
|
|
150
|
+
if (!disposed && context?.ptcPlusCapabilityObservation !== true && context?.agent !== null && typeof context?.agent === 'object'
|
|
88
151
|
&& !disposedAgents.has(context.agent)) requests.set(context.agent, {
|
|
89
152
|
context,
|
|
90
153
|
allowed: hasWitness(initial) && hasWitness(assembly),
|
|
@@ -98,9 +161,15 @@ export function createRuntimeMessageOwner(contextsForRequest, sessionLogView = p
|
|
|
98
161
|
const request = requests.get(payload.agent)
|
|
99
162
|
if (disposed || decision.kind !== 'enter' || payload.signal?.aborted === true
|
|
100
163
|
|| request?.allowed !== true || request.context.signal !== payload.signal) return decision
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
)
|
|
164
|
+
const view = sessionLogView(payload.agent)
|
|
165
|
+
const messages = projectRuntimeMessages(view, contextsForRequest(request.context), decision.messages)
|
|
166
|
+
if (activeForRequest !== undefined) {
|
|
167
|
+
const log = await readSessionLog(payload.agent.session, sessionQuery, payload.signal)
|
|
168
|
+
const availability = projectRuntimeAvailability([
|
|
169
|
+
...(payload.agent.session.deriveMessages?.() ?? []), ...decision.messages, ...messages,
|
|
170
|
+
], activeForRequest(request.context), hadPluginComputation(log))
|
|
171
|
+
if (availability) messages.push(availability)
|
|
172
|
+
}
|
|
104
173
|
return messages.length === 0 ? decision : { ...decision, messages: [...decision.messages, ...messages] }
|
|
105
174
|
},
|
|
106
175
|
disposeAgent(agent) { disposedAgents.add(agent); requests.delete(agent) },
|
|
@@ -98,6 +98,7 @@ export const runtimeIntrinsics = {
|
|
|
98
98
|
if (typeof Buffer === 'function') {
|
|
99
99
|
runtimeIntrinsics.bufferByteLength = Buffer.byteLength
|
|
100
100
|
runtimeIntrinsics.bufferConcat = Buffer.concat
|
|
101
|
+
runtimeIntrinsics.bufferFrom = Buffer.from
|
|
101
102
|
runtimeIntrinsics.bufferIsBuffer = Buffer.isBuffer
|
|
102
103
|
runtimeIntrinsics.bufferSubarray = uncurry(Buffer.prototype.subarray)
|
|
103
104
|
runtimeIntrinsics.bufferToString = uncurry(Buffer.prototype.toString)
|
|
@@ -23,6 +23,45 @@ const STATE_PREFIX = 'PTC Plus recovery status. Replaces earlier PTC recovery st
|
|
|
23
23
|
const CATALOG_PREFIX = 'Global binding API reference for run_code (replaces the previous global binding reference):'
|
|
24
24
|
const EMPTY_CATALOG = 'No entries documented.'
|
|
25
25
|
const PREVIOUS_EMPTY_CATALOG = 'No global binding API documentation is currently configured. Earlier binding catalogs no longer apply.'
|
|
26
|
+
const AVAILABILITY_TEXT = Object.freeze({
|
|
27
|
+
active: 'PTC Plus is the current continuous-computation provider. Follow its current tool schemas and capability plan; earlier withdrawals do not restore old computation state.',
|
|
28
|
+
disabled: 'PTC Plus is disabled. Earlier PTC Plus continuous-computation instructions, capability plans, discovery instructions, calling-contract disclosures and global binding references no longer describe the current runtime. Use the current Host tools and contracts. Historical computations and effects remain historical; no old PTC Plus heap has been restored. Tasks, Skill instructions, other producers and tool results are unchanged.',
|
|
29
|
+
unloaded: 'PTC Plus has released its computation provider. Earlier PTC Plus continuous-computation instructions, capability plans, discovery instructions, calling-contract disclosures and global binding references no longer describe the current runtime. Use the current Host tools and contracts. Historical computations and effects remain historical; no old PTC Plus heap has been restored. Tasks, Skill instructions, other producers and tool results are unchanged.',
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// Historical wording stays readable so recorded sessions keep their provenance;
|
|
33
|
+
// new messages use the current text above.
|
|
34
|
+
const PREVIOUS_AVAILABILITY_TEXT = Object.freeze({
|
|
35
|
+
active: ['PTC Plus is the current continuous-computation provider. Follow its current tool schemas and newly supplied capability plan; earlier withdrawals do not restore old computation state.'],
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
export function runtimeAvailabilityMessage(state) {
|
|
39
|
+
if (!Object.hasOwn(AVAILABILITY_TEXT, state)) throw new TypeError('invalid PTC provider availability')
|
|
40
|
+
return createUserMessage({ source: { kind: PTC_MESSAGE_SOURCE_KIND, form: 'runtime-availability', state },
|
|
41
|
+
content: [{ type: 'text', text: AVAILABILITY_TEXT[state] }] })
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function readRuntimeAvailability(message) {
|
|
45
|
+
const state = message?.source?.state
|
|
46
|
+
if (!isPtcMessageSource(message?.source) || message.source.form !== 'runtime-availability'
|
|
47
|
+
|| !Object.hasOwn(AVAILABILITY_TEXT, state) || message.content?.length !== 1
|
|
48
|
+
|| message.content[0]?.type !== 'text') return undefined
|
|
49
|
+
const text = message.content[0].text
|
|
50
|
+
return text === AVAILABILITY_TEXT[state]
|
|
51
|
+
|| (PREVIOUS_AVAILABILITY_TEXT[state] ?? []).includes(text) ? state : undefined
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Availability reconciles plugin declarations only, never tasks or other producers. */
|
|
55
|
+
export function projectRuntimeAvailability(messages, active, computationDeclared = false) {
|
|
56
|
+
const last = messages.map(readRuntimeAvailability).filter(Boolean).at(-1)
|
|
57
|
+
if (active) return last && last !== 'active' ? runtimeAvailabilityMessage('active') : undefined
|
|
58
|
+
if (last === 'disabled' || last === 'unloaded') return undefined
|
|
59
|
+
const declared = computationDeclared || last === 'active' || messages.some(message => isPtcMessageSource(message?.source)
|
|
60
|
+
&& (['capability-navigation', 'capability-descriptions', 'capability-contracts'].includes(message.source.form)
|
|
61
|
+
|| message.source.form === 'capability-plan' && message.source.snapshot?.plan?.enabled === true
|
|
62
|
+
|| readRuntimeMessage(message)?.form === 'catalog' && readRuntimeMessage(message).sections.length > 0))
|
|
63
|
+
return declared ? runtimeAvailabilityMessage('disabled') : undefined
|
|
64
|
+
}
|
|
26
65
|
|
|
27
66
|
export function recoveryTipIdentity(name) {
|
|
28
67
|
if (typeof name !== 'string') return undefined
|
|
@@ -60,6 +99,8 @@ export function readRuntimeMessage(message) {
|
|
|
60
99
|
|| !Array.isArray(message.content) || message.content.length !== 1
|
|
61
100
|
|| message.content[0]?.type !== 'text') return undefined
|
|
62
101
|
const text = message.content[0].text
|
|
102
|
+
const availability = readRuntimeAvailability(message)
|
|
103
|
+
if (availability !== undefined) return { form: 'runtime-availability', state: availability }
|
|
63
104
|
if (source.form === 'snapshot') {
|
|
64
105
|
const sections = stateSections(source.sections)
|
|
65
106
|
if (sections !== undefined && [STATE_PREFIX, PREVIOUS_STATE_PREFIX, LEGACY_STATE_PREFIX]
|
|
@@ -1,88 +1,16 @@
|
|
|
1
|
-
/** Adapt the
|
|
2
|
-
import { parse } from '@babel/parser'
|
|
1
|
+
/** Adapt the Host's TypeScript cell introduction while preserving its SDK members. */
|
|
3
2
|
|
|
4
|
-
const PROJECTION_ERROR = 'ptc-plus: incompatible tools SDK projection: '
|
|
5
3
|
const TYPESCRIPT_FENCE = '```ts\n'
|
|
6
4
|
|
|
7
|
-
function
|
|
8
|
-
return new Error(`${PROJECTION_ERROR}${message}`, cause === undefined ? undefined : { cause })
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function lineStart(source, offset) {
|
|
12
|
-
return source.lastIndexOf('\n', offset - 1) + 1
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function memberName(member) {
|
|
16
|
-
if (member.type !== 'TSPropertySignature') return undefined
|
|
17
|
-
if (member.key.type === 'Identifier') return member.key.name
|
|
18
|
-
if (member.key.type === 'StringLiteral') return member.key.value
|
|
19
|
-
return undefined
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function supportedFence(source) {
|
|
23
|
-
const starts = []
|
|
24
|
-
for (let offset = 0; offset < source.length;) {
|
|
25
|
-
const start = source.indexOf(TYPESCRIPT_FENCE, offset)
|
|
26
|
-
if (start < 0) break
|
|
27
|
-
if (start === 0 || source[start - 1] === '\n') starts.push(start)
|
|
28
|
-
offset = start + TYPESCRIPT_FENCE.length
|
|
29
|
-
}
|
|
30
|
-
if (starts.length !== 1) {
|
|
31
|
-
throw incompatible(`expected exactly one ${JSON.stringify(TYPESCRIPT_FENCE.trim())} fence`)
|
|
32
|
-
}
|
|
33
|
-
const codeStart = starts[0] + TYPESCRIPT_FENCE.length
|
|
34
|
-
const codeEnd = source.indexOf('\n```', codeStart)
|
|
35
|
-
if (codeEnd < 0) throw incompatible('the TypeScript fence is not closed')
|
|
36
|
-
return { codeStart, codeEnd }
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** Remove one direct-only tool from the host-generated program SDK. */
|
|
40
|
-
export function projectProgramSdk(nativeSdk, excludedName = 'edit_run_code') {
|
|
5
|
+
export function projectProgramSdk(nativeSdk, { languageSemantics = 'legacy-v1' } = {}) {
|
|
41
6
|
if (typeof nativeSdk !== 'string') return ''
|
|
42
|
-
if (!nativeSdk.
|
|
43
|
-
|
|
44
|
-
const { codeStart, codeEnd } = supportedFence(nativeSdk)
|
|
45
|
-
const code = nativeSdk.slice(codeStart, codeEnd)
|
|
46
|
-
let ast
|
|
47
|
-
try {
|
|
48
|
-
ast = parse(code, { sourceType: 'module', plugins: ['typescript'] })
|
|
49
|
-
} catch (error) {
|
|
50
|
-
throw incompatible('the TypeScript fence could not be parsed', error)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const removals = []
|
|
54
|
-
for (const interfaceName of ['ToolArgsMap', 'ToolOutputMap']) {
|
|
55
|
-
const declarations = ast.program.body.filter(statement => (
|
|
56
|
-
statement.type === 'TSInterfaceDeclaration' && statement.id.name === interfaceName
|
|
57
|
-
))
|
|
58
|
-
if (declarations.length !== 1) {
|
|
59
|
-
throw incompatible(`expected exactly one ${interfaceName} interface`)
|
|
60
|
-
}
|
|
61
|
-
const declaration = declarations[0]
|
|
62
|
-
const members = declaration.body.body.filter(member => memberName(member) === excludedName)
|
|
63
|
-
if (members.length !== 1) {
|
|
64
|
-
throw incompatible(`expected exactly one ${excludedName} member in ${interfaceName}`)
|
|
65
|
-
}
|
|
66
|
-
const member = members[0]
|
|
67
|
-
if (declaration.body.body.length === 1) {
|
|
68
|
-
removals.push([declaration.body.start + 1, declaration.body.end - 1])
|
|
69
|
-
continue
|
|
70
|
-
}
|
|
71
|
-
const commentStart = member.leadingComments?.[0]?.start
|
|
72
|
-
const startOffset = commentStart ?? member.start
|
|
73
|
-
const start = lineStart(code, startOffset)
|
|
74
|
-
let end = member.end
|
|
75
|
-
if (code[end] === '\n') end += 1
|
|
76
|
-
removals.push([start, end])
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
let projectedCode = code
|
|
80
|
-
for (const [start, end] of removals.sort((left, right) => right[0] - left[0])) {
|
|
81
|
-
projectedCode = projectedCode.slice(0, start) + projectedCode.slice(end)
|
|
82
|
-
}
|
|
83
|
-
const projected = nativeSdk.slice(0, codeStart) + projectedCode + nativeSdk.slice(codeEnd)
|
|
84
|
-
if (projected.includes(excludedName)) {
|
|
85
|
-
throw incompatible(`residual ${excludedName} token remains after projection`)
|
|
7
|
+
if (languageSemantics === 'legacy-v1' || !nativeSdk.startsWith('## Writing code for run_code\n')) {
|
|
8
|
+
return nativeSdk
|
|
86
9
|
}
|
|
87
|
-
|
|
10
|
+
const fence = nativeSdk.indexOf(TYPESCRIPT_FENCE)
|
|
11
|
+
if (fence < 0) return nativeSdk
|
|
12
|
+
return nativeSdk.slice(0, fence).replace(
|
|
13
|
+
'the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped)',
|
|
14
|
+
'a PTC Plus TypeScript cell (runtime `enum` and namespaces are supported; type annotations are advisory)',
|
|
15
|
+
) + nativeSdk.slice(fence)
|
|
88
16
|
}
|
|
@@ -2,7 +2,9 @@ import { resolve } from 'node:path'
|
|
|
2
2
|
import { pathToFileURL } from 'node:url'
|
|
3
3
|
import {
|
|
4
4
|
decodeValue,
|
|
5
|
-
|
|
5
|
+
encodeValueAtBoundary,
|
|
6
|
+
valueEncodingFailure,
|
|
7
|
+
valueEncodingHelp,
|
|
6
8
|
normalizeValueWire,
|
|
7
9
|
prepareValueWire,
|
|
8
10
|
valueWiresEqual,
|
|
@@ -88,11 +90,11 @@ function parseDiagnostic(error, source, position, repair) {
|
|
|
88
90
|
}),
|
|
89
91
|
help: repair === undefined
|
|
90
92
|
? source.length >= LONG_CELL_CODE_UNITS
|
|
91
|
-
? ['this cell was not executed; when edit_run_code is
|
|
93
|
+
? ['this cell was not executed; when tools.edit_run_code is available in the program SDK and the correction is small and localized, call it inside a new run_code cell to avoid resending this long source; otherwise retry only this cell with corrected source in run_code']
|
|
92
94
|
: ['this cell was not executed; correct the reported syntax and retry only this cell with run_code']
|
|
93
95
|
: [
|
|
94
96
|
`this cell was not executed; validated syntax repair: append ${JSON.stringify(repair.delimiter)} at the end of this cell`,
|
|
95
|
-
`when this correction matches your intent and edit_run_code is
|
|
97
|
+
`when this correction matches your intent and tools.edit_run_code is available in the program SDK, use ${repair.invocation} inside a new run_code cell with its target guard; the accepted edit runs the complete corrected cell after that new cell finishes; validation proves syntax/preflight acceptance, not intended behavior; otherwise submit corrected source in run_code`,
|
|
96
98
|
],
|
|
97
99
|
})
|
|
98
100
|
}
|
|
@@ -316,16 +318,16 @@ function exceptionDiagnostic({
|
|
|
316
318
|
&& error.toolName.startsWith('cordis_')
|
|
317
319
|
? ['bindings assigned before this Cordis failure remain live; reuse them instead of resending large source']
|
|
318
320
|
: longCellFailure
|
|
319
|
-
? ['inspect relevant live state in a new short `run_code` cell; edit_run_code
|
|
321
|
+
? ['inspect relevant live state in a new short `run_code` cell; tools.edit_run_code accepts a complete corrected-cell run after that new cell finishes; it reruns prior effects, so acceptance does not prove a retry is safe']
|
|
320
322
|
: languageSemantics !== 'legacy-v1'
|
|
321
|
-
? ['completed declarations and actual assignments remain available;
|
|
323
|
+
? ['completed declarations and actual assignments remain available; continue with the same binding names from the execution facts, inspecting relevant live values in a short cell when their state is uncertain']
|
|
322
324
|
: declared.size === 0 ? []
|
|
323
325
|
: ['use fresh names for one-off top-level bindings after partial execution; later declarations may be uninitialized']),
|
|
324
326
|
],
|
|
325
327
|
})
|
|
326
328
|
}
|
|
327
329
|
|
|
328
|
-
function invalidOutputDiagnostic(detail) {
|
|
330
|
+
function invalidOutputDiagnostic(detail, failure) {
|
|
329
331
|
return diagnostic({
|
|
330
332
|
code: 'PTC-O001',
|
|
331
333
|
severity: 'error',
|
|
@@ -334,15 +336,16 @@ function invalidOutputDiagnostic(detail) {
|
|
|
334
336
|
stateEffect: 'partially-applied',
|
|
335
337
|
help: [
|
|
336
338
|
'the worker remains live; inspect a retained binding in a later cell without rerunning the failed cell',
|
|
337
|
-
|
|
339
|
+
valueEncodingHelp(failure),
|
|
338
340
|
],
|
|
339
341
|
})
|
|
340
342
|
}
|
|
341
343
|
|
|
342
344
|
/** Retain validated recorded wording while still comparing the actual failure. */
|
|
343
|
-
function replayFailureDiagnostic(actual, replay) {
|
|
345
|
+
function replayFailureDiagnostic(actual, replay, legacy) {
|
|
344
346
|
const recorded = replay?.diagnostics?.find(item => item.code === actual.code)
|
|
345
|
-
return recorded
|
|
347
|
+
return recorded !== undefined && (recorded.message === actual.message
|
|
348
|
+
|| recorded.message === legacy?.message) ? recorded : actual
|
|
346
349
|
}
|
|
347
350
|
|
|
348
351
|
function stateArguments(value) {
|
|
@@ -417,7 +420,9 @@ export class SessionCellExecutor {
|
|
|
417
420
|
const priorBindingCatalog = userBindingPlan.catalog
|
|
418
421
|
const catalog = priorBindingCatalog.inputs()
|
|
419
422
|
const { bindingPolicy, rewritesEnabled, moduleSemantics, languageSemantics, moduleTransform } = executionPolicies(config, replayRecord)
|
|
420
|
-
const
|
|
423
|
+
const executionCwd = replayRecord === undefined ? request.cwd ?? kernel.cwd
|
|
424
|
+
: replayRecord.executionCwd ?? kernel.cwd
|
|
425
|
+
const moduleDirectory = executionCwd ?? process.cwd()
|
|
421
426
|
const moduleFilename = resolve(moduleDirectory, 'repl')
|
|
422
427
|
const prepareCell = program => prepareProgram(program, {
|
|
423
428
|
knownBindings: catalog.knownBindings,
|
|
@@ -446,7 +451,10 @@ export class SessionCellExecutor {
|
|
|
446
451
|
} catch (error) {
|
|
447
452
|
const result = earlyResult('exception', messageOf(error))
|
|
448
453
|
const position = parseCellPosition(error)
|
|
454
|
+
// sourceCallSeq proves the assistant ancestor, not the editable identity
|
|
455
|
+
// of derived text. A parse-rejected derived edit does not publish that text.
|
|
449
456
|
const repair = error instanceof PreflightError || replayRecord !== undefined
|
|
457
|
+
|| request.callSeq !== request.sourceCallSeq
|
|
450
458
|
? undefined
|
|
451
459
|
: validatedEofClosureRepair({
|
|
452
460
|
source: request.program,
|
|
@@ -474,6 +482,7 @@ export class SessionCellExecutor {
|
|
|
474
482
|
|
|
475
483
|
let worker
|
|
476
484
|
try {
|
|
485
|
+
await kernel.prepareWorker()
|
|
477
486
|
worker = await kernel.client.ensure(config.maxOldGenerationSizeMb)
|
|
478
487
|
} catch (error) {
|
|
479
488
|
const result = earlyResult('worker-exit', messageOf(error))
|
|
@@ -483,11 +492,12 @@ export class SessionCellExecutor {
|
|
|
483
492
|
}
|
|
484
493
|
if (request.signal?.aborted) {
|
|
485
494
|
const result = earlyResult('abort', String(request.signal.reason))
|
|
486
|
-
kernel.completeJournal(request.journal, 'discarded', result)
|
|
487
495
|
kernel.rollbackToDurable()
|
|
488
496
|
// A refused reclamation stays recorded on the kernel so disposal remains
|
|
489
497
|
// observable instead of becoming an unhandled rejection after this abort.
|
|
490
|
-
kernel.resetWorker(worker)
|
|
498
|
+
const reclamation = await kernel.resetWorker(worker)
|
|
499
|
+
if (reclamation.failed) result.error.message += `; helper reclamation failed: ${messageOf(reclamation.reason)}`
|
|
500
|
+
kernel.completeJournal(request.journal, 'discarded', result)
|
|
491
501
|
return result
|
|
492
502
|
}
|
|
493
503
|
|
|
@@ -586,6 +596,7 @@ export class SessionCellExecutor {
|
|
|
586
596
|
moduleLoads: prepared.moduleLoads,
|
|
587
597
|
languageSemantics,
|
|
588
598
|
moduleTransform,
|
|
599
|
+
cwd: executionCwd,
|
|
589
600
|
rootRuntimeName: prepared.rootRuntimeName,
|
|
590
601
|
rootBindings: prepared.rootBindings,
|
|
591
602
|
importBindingNamespaces: new Map([...catalog.importBindings].map(([name, binding]) => [name, binding.namespace])),
|
|
@@ -675,7 +686,7 @@ export class SessionCellExecutor {
|
|
|
675
686
|
}
|
|
676
687
|
if (action === 'save') {
|
|
677
688
|
if (active.durability.status === 'volatile') {
|
|
678
|
-
throw new Error(
|
|
689
|
+
throw new Error(`cannot save a durable REPL state from a volatile segment; volatile reason: ${active.durability.reason ?? 'replayability is unproved'}. This save rejection leaves live bindings usable in the current worker; continue computation from them. It does not undo external effects.`)
|
|
679
690
|
}
|
|
680
691
|
active.control.names.add(name)
|
|
681
692
|
active.journal.operations.push({ action, name })
|
|
@@ -896,7 +907,11 @@ export class SessionCellExecutor {
|
|
|
896
907
|
longCellFailure: active.request.program.length >= LONG_CELL_CODE_UNITS,
|
|
897
908
|
failureOrigin: message.failureOrigin,
|
|
898
909
|
})
|
|
899
|
-
const
|
|
910
|
+
const legacyFailure = message.valueFailure === undefined ? undefined : exceptionDiagnostic({
|
|
911
|
+
error: { ...rawError, message: message.valueFailure.legacyMessage },
|
|
912
|
+
languageSemantics: active.prepared.languageSemantics,
|
|
913
|
+
})
|
|
914
|
+
const failure = replayFailureDiagnostic(actualFailure, active.replay, legacyFailure)
|
|
900
915
|
const error = {
|
|
901
916
|
kind: 'exception',
|
|
902
917
|
message: renderDiagnostic(failure, active.request.program),
|
|
@@ -908,7 +923,8 @@ export class SessionCellExecutor {
|
|
|
908
923
|
return
|
|
909
924
|
}
|
|
910
925
|
if (typeof message.invalidOutput === 'string') {
|
|
911
|
-
const failure = replayFailureDiagnostic(invalidOutputDiagnostic(message.invalidOutput), active.replay
|
|
926
|
+
const failure = replayFailureDiagnostic(invalidOutputDiagnostic(message.invalidOutput, message.valueFailure), active.replay,
|
|
927
|
+
message.valueFailure === undefined ? undefined : invalidOutputDiagnostic(message.valueFailure.legacyMessage))
|
|
912
928
|
const error = { kind: 'invalid-output', message: renderDiagnostic(failure, active.request.program) }
|
|
913
929
|
active.diagnostics.push(failure)
|
|
914
930
|
active.resolve({ logs, error })
|
|
@@ -919,7 +935,11 @@ export class SessionCellExecutor {
|
|
|
919
935
|
|| (message.hasValue ? message.value === undefined : message.value !== undefined)) {
|
|
920
936
|
throw new TypeError('invalid PTC completion envelope')
|
|
921
937
|
}
|
|
922
|
-
const completion = message.hasValue
|
|
938
|
+
const completion = message.hasValue
|
|
939
|
+
? active.request.programCompletion === true
|
|
940
|
+
? { wire: normalizeValueWire(message.value, active.valueLimits) }
|
|
941
|
+
: prepareValueWire(message.value, active.valueLimits)
|
|
942
|
+
: undefined
|
|
923
943
|
active.completion = {
|
|
924
944
|
hasValue: message.hasValue,
|
|
925
945
|
...(message.hasValue ? { value: completion.wire } : {}),
|
|
@@ -932,10 +952,13 @@ export class SessionCellExecutor {
|
|
|
932
952
|
}
|
|
933
953
|
active.resolve({
|
|
934
954
|
logs,
|
|
935
|
-
...(message.hasValue
|
|
955
|
+
...(message.hasValue && active.request.programCompletion !== true
|
|
956
|
+
? { value: completion.projectedValue } : {}),
|
|
936
957
|
})
|
|
937
958
|
} catch (error) {
|
|
938
|
-
const
|
|
959
|
+
const valueFailure = valueEncodingFailure(error)
|
|
960
|
+
const failure = replayFailureDiagnostic(invalidOutputDiagnostic(messageOf(error), valueFailure), active.replay,
|
|
961
|
+
valueFailure === undefined ? undefined : invalidOutputDiagnostic(valueFailure.legacyMessage))
|
|
939
962
|
const invalid = { kind: 'invalid-output', message: renderDiagnostic(failure, active.request.program) }
|
|
940
963
|
active.diagnostics.push(failure)
|
|
941
964
|
active.resolve({ logs, error: invalid })
|
|
@@ -990,7 +1013,7 @@ export class SessionCellExecutor {
|
|
|
990
1013
|
const value = kernel.withInitiator === undefined || agent === undefined
|
|
991
1014
|
? await invoke()
|
|
992
1015
|
: await kernel.withInitiator(agent, invoke)
|
|
993
|
-
const valueWire =
|
|
1016
|
+
const valueWire = encodeValueAtBoundary(value, active.valueLimits, 'result')
|
|
994
1017
|
if (call !== undefined) {
|
|
995
1018
|
call.ok = true
|
|
996
1019
|
call.value = valueWire
|
|
@@ -18,6 +18,34 @@ export function advanceSessionSurface(nodes, event) {
|
|
|
18
18
|
return next
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/** Read model source from the Host projection, independently of execution history. */
|
|
22
|
+
export function sessionSourceMessages(session, events, nodes) {
|
|
23
|
+
if (Array.isArray(session?.surface?.sourceMessages)) {
|
|
24
|
+
return new Map(session.surface.sourceMessages)
|
|
25
|
+
}
|
|
26
|
+
const visible = new Set(nodes)
|
|
27
|
+
const sources = new Map()
|
|
28
|
+
const messages = typeof session?.deriveMessages === 'function'
|
|
29
|
+
? session.deriveMessages()
|
|
30
|
+
: undefined
|
|
31
|
+
for (const event of events) {
|
|
32
|
+
if (event.type !== 'assistant/message' || !visible.has(event.seq)) continue
|
|
33
|
+
let message
|
|
34
|
+
if (typeof session?.deriveEventMessage === 'function') {
|
|
35
|
+
message = session.deriveEventMessage(event)
|
|
36
|
+
} else if (messages !== undefined) {
|
|
37
|
+
const id = event.data?.message?.id
|
|
38
|
+
const matches = typeof id === 'string' ? messages.filter(item => item.id === id) : []
|
|
39
|
+
if (matches.length === 1) message = matches[0]
|
|
40
|
+
} else if (Array.isArray(session?.events)) {
|
|
41
|
+
// The historical array-backed contract has no pure message projections.
|
|
42
|
+
message = event.data?.message
|
|
43
|
+
}
|
|
44
|
+
if (message !== undefined && message !== null) sources.set(event.seq, message)
|
|
45
|
+
}
|
|
46
|
+
return sources
|
|
47
|
+
}
|
|
48
|
+
|
|
21
49
|
export async function readSessionLog(session, query, signal) {
|
|
22
50
|
if (session === undefined || session === null || Array.isArray(session.events)) return session
|
|
23
51
|
if (typeof query?.observeSession !== 'function') {
|
|
@@ -31,10 +59,16 @@ export async function readSessionLog(session, query, signal) {
|
|
|
31
59
|
nodes = []
|
|
32
60
|
for (const event of observation.events) nodes = advanceSessionSurface(nodes, event)
|
|
33
61
|
}
|
|
62
|
+
// Do not pair an immutable raw cut with a newer live message projection.
|
|
63
|
+
// A cut without exact model-source evidence cannot retain old bindings.
|
|
64
|
+
const sources = session.seq - 1 === observation.cursor
|
|
65
|
+
? sessionSourceMessages(session, observation.events, nodes)
|
|
66
|
+
: new Map()
|
|
34
67
|
return Object.freeze({
|
|
35
68
|
id: session.id, header: observation.header, events: observation.events,
|
|
36
69
|
surface: Object.freeze({
|
|
37
70
|
nodes: Object.freeze([...nodes]),
|
|
71
|
+
sourceMessages: Object.freeze([...sources].map(entry => Object.freeze(entry))),
|
|
38
72
|
}),
|
|
39
73
|
})
|
|
40
74
|
} finally {
|