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.
Files changed (125) hide show
  1. package/README.en.md +65 -127
  2. package/README.md +65 -127
  3. package/assets/dsh-ptc-plus-banner-en.webp +0 -0
  4. package/assets/dsh-ptc-plus-banner-zh.webp +0 -0
  5. package/assets/ptc-plus-repl-workspace-en.png +0 -0
  6. package/assets/ptc-plus-repl-workspace-zh.png +0 -0
  7. package/assets/ptc-plus-settings-en.png +0 -0
  8. package/assets/ptc-plus-settings-zh.png +0 -0
  9. package/client.cjs +42702 -0
  10. package/cordis.patch.yml +1 -0
  11. package/docs/README.md +6 -2
  12. package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +31 -198
  13. package/docs/adr/0008-define-agent-operating-contract.md +5 -1
  14. package/docs/adr/0009-bound-expensive-acceptance-default.md +2 -2
  15. package/docs/adr/0016-archive-resolved-review-findings.md +1 -1
  16. package/docs/adr/0017-track-the-latest-dsh-public-surface.md +4 -4
  17. package/docs/adr/0019-plugin-settings-and-kill-switch.md +1 -1
  18. package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +1 -1
  19. package/docs/adr/0022-stage-npm-releases-with-oidc.md +4 -2
  20. package/docs/adr/0023-global-user-bindings.md +2 -0
  21. package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +2 -0
  22. package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +25 -23
  23. package/docs/adr/0031-close-semantic-obligations-by-owner.md +1 -1
  24. package/docs/adr/0034-keep-program-values-separate-from-output-projections.md +29 -0
  25. package/docs/adr/0035-run-coverage-only-before-a-new-release.md +27 -0
  26. package/docs/adr/0036-separate-focused-development-from-delivery-verification.md +29 -0
  27. package/docs/adr/0037-keep-tool-hover-previews-passive.md +52 -0
  28. package/docs/adr/0038-freeze-session-capability-plans.md +71 -0
  29. package/docs/adr/0039-verify-documentation-without-runtime-suites.md +29 -0
  30. package/docs/adr/0040-separate-capability-configurations-from-session-observations.md +39 -0
  31. package/docs/adr/0041-record-cell-execution-directories.md +65 -0
  32. package/docs/adr/0042-persist-provider-availability-and-handback.md +41 -0
  33. package/docs/adr/0043-compose-capability-and-context-policies.md +37 -0
  34. package/docs/adr/0044-coexist-with-the-official-persistent-repl.md +35 -0
  35. package/docs/adr/README.md +8 -0
  36. package/docs/architecture.md +23 -25
  37. package/docs/capability-planning-components.md +93 -0
  38. package/docs/capability-projection.md +7 -0
  39. package/docs/client-ui.md +70 -23
  40. package/docs/client-visual-contract.md +26 -9
  41. package/docs/context-management.md +22 -0
  42. package/docs/context-optimization-plan.md +353 -0
  43. package/docs/context-public-interface-coverage.md +25 -0
  44. package/docs/durability-design.md +20 -15
  45. package/docs/evaluation.md +41 -6
  46. package/docs/evaluations/command-goat-deepseek-v4.1-flash-20261006.md +126 -0
  47. package/docs/evaluations/command-goat-deepseek-v4.1-flash.md +281 -0
  48. package/docs/installation.md +33 -10
  49. package/docs/program-data-plane.md +5 -3
  50. package/docs/publishing.md +7 -7
  51. package/docs/release-coverage.md +73 -0
  52. package/docs/release-notes.md +1 -1
  53. package/docs/review-procedure.md +56 -0
  54. package/docs/runtime-reference.md +53 -16
  55. package/docs/semantic-validation.md +14 -2
  56. package/docs/stateful-binding-updates-proposal.md +2 -2
  57. package/docs/user-bindings.md +1 -1
  58. package/docs/value-wire.md +11 -1
  59. package/docs/verification-optimization-work.md +26 -15
  60. package/docs/verification.md +30 -88
  61. package/index.js +146 -20
  62. package/internal/binding-toggle-receipt.js +24 -0
  63. package/internal/capability-command-receipt.js +31 -0
  64. package/internal/capability-environment.js +1253 -0
  65. package/internal/capability-plan.js +285 -0
  66. package/internal/capability-policy.js +96 -0
  67. package/internal/capability-statistics-store.js +180 -0
  68. package/internal/capability-statistics.js +214 -0
  69. package/internal/config-spec.js +89 -10
  70. package/internal/direct-surface-owner.js +27 -29
  71. package/internal/dynamic-environment-runtime.js +9 -9
  72. package/internal/edit-transport-owner.js +195 -140
  73. package/internal/execution-provider-owner.js +40 -12
  74. package/internal/execution-seam-compat.js +26 -8
  75. package/internal/kernel-worker.js +104 -32
  76. package/internal/native-root-dynamic.js +12 -13
  77. package/internal/official-repl-priority.js +51 -0
  78. package/internal/program-bindings.js +190 -0
  79. package/internal/recovery-tips.js +3 -2
  80. package/internal/rejected-cell-editor.js +8 -8
  81. package/internal/repl-observation-interest.js +11 -3
  82. package/internal/rpc-contract.js +1 -0
  83. package/internal/runtime-bridge-owner.js +275 -34
  84. package/internal/runtime-config.js +58 -0
  85. package/internal/runtime-contexts.js +74 -5
  86. package/internal/runtime-intrinsics.js +1 -0
  87. package/internal/runtime-messages.js +41 -0
  88. package/internal/sdk-projection.js +10 -82
  89. package/internal/session-cell-executor.js +42 -19
  90. package/internal/session-events.js +34 -0
  91. package/internal/session-journal-recovery.js +234 -11
  92. package/internal/session-journal-schema.js +6 -3
  93. package/internal/session-journal.js +31 -2
  94. package/internal/session-log-view.js +1 -1
  95. package/internal/session-runtime.js +119 -15
  96. package/internal/settings-compat.js +12 -5
  97. package/internal/stateful-module-compiler.js +3 -2
  98. package/internal/stateful-module-runtime.js +7 -3
  99. package/internal/stateful-root-runtime.js +9 -4
  100. package/internal/user-binding-authoring.js +2 -7
  101. package/internal/user-bindings-owner.js +19 -2
  102. package/internal/validated-parse-repair.js +1 -1
  103. package/internal/value-wire.js +68 -21
  104. package/internal/worker-cwd-virtualization.js +20 -18
  105. package/internal/worker-realm-surfaces.js +3 -3
  106. package/package.json +49 -30
  107. package/src/client-activity.js +3 -2
  108. package/src/client-authoring-view.js +67 -30
  109. package/src/client-binding-review.js +2 -1
  110. package/src/client-binding-toggle.js +18 -0
  111. package/src/client-capabilities-view.js +1555 -0
  112. package/src/client-capability-sort.js +29 -0
  113. package/src/client-copy.js +923 -8
  114. package/src/client-feature-gates.js +3 -0
  115. package/src/client-indicator-view.js +4 -6
  116. package/src/client-primitives.js +33 -2
  117. package/src/client-repl-view.js +20 -13
  118. package/src/client-settings-view.js +411 -41
  119. package/src/client-styles.js +112 -15
  120. package/src/client-tool-hover.js +185 -0
  121. package/src/client-tool-view.js +45 -9
  122. package/src/client-transport.js +44 -9
  123. package/src/client-workbench.js +6 -7
  124. package/src/client.js +76 -22
  125. 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 messages = projectRuntimeMessages(
102
- sessionLogView(payload.agent), contextsForRequest(request.context), decision.messages,
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 host program SDK without weakening the declared direct tool surface. */
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 incompatible(message, cause = undefined) {
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.includes(excludedName)) return 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
- return projected
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
- encodeValue,
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 declared for the current request and the correction is small and localized, use it to avoid resending this long source; otherwise retry only this cell with corrected source in run_code']
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 declared, prefer the direct tool call ${repair.invocation} with its target guard to run the complete corrected cell; validation proves syntax/preflight acceptance, not intended behavior; otherwise submit corrected source in run_code`,
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 executes the complete corrected cell, not only the failing expression']
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; correct the failed initializer and continue with the same binding names']
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
- 'reduce the returned graph when it exceeds the configured value budget',
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?.message === actual.message ? recorded : actual
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 moduleDirectory = kernel.cwd ?? process.cwd()
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('cannot save a durable REPL state from a volatile segment; restore a durable state first')
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 failure = replayFailureDiagnostic(actualFailure, active.replay)
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 ? prepareValueWire(message.value, active.valueLimits) : undefined
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 ? { value: completion.projectedValue } : {}),
955
+ ...(message.hasValue && active.request.programCompletion !== true
956
+ ? { value: completion.projectedValue } : {}),
936
957
  })
937
958
  } catch (error) {
938
- const failure = replayFailureDiagnostic(invalidOutputDiagnostic(messageOf(error)), active.replay)
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 = encodeValue(value, active.valueLimits)
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 {