dsh-ptc-plus 0.4.2 → 0.4.8

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 (89) hide show
  1. package/README.en.md +170 -76
  2. package/README.md +171 -77
  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-icon.svg +5 -0
  6. package/client.js +599 -310
  7. package/compiler-core.cjs +162 -160
  8. package/compiler-core.cjs.map +3 -3
  9. package/docs/README.md +4 -1
  10. package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +49 -11
  11. package/docs/adr/0008-define-agent-operating-contract.md +2 -2
  12. package/docs/adr/0010-session-log-derived-recovery-tips.md +7 -0
  13. package/docs/adr/0016-archive-resolved-review-findings.md +3 -3
  14. package/docs/adr/0017-track-the-latest-dsh-public-surface.md +5 -1
  15. package/docs/adr/0019-plugin-settings-and-kill-switch.md +3 -3
  16. package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +3 -1
  17. package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +14 -3
  18. package/docs/adr/0023-global-user-bindings.md +1 -1
  19. package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +23 -7
  20. package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +4 -0
  21. package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +26 -6
  22. package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +2 -2
  23. package/docs/adr/0031-close-semantic-obligations-by-owner.md +29 -0
  24. package/docs/adr/README.md +4 -4
  25. package/docs/architecture.md +8 -5
  26. package/docs/binding-review-placement-design.md +1 -1
  27. package/docs/client-ui.md +8 -4
  28. package/docs/durability-design.md +7 -7
  29. package/docs/installation.md +48 -12
  30. package/docs/publishing.md +4 -4
  31. package/docs/release-notes.md +109 -0
  32. package/docs/runtime-reference.md +25 -12
  33. package/docs/semantic-validation.md +7 -3
  34. package/docs/user-bindings.en.md +7 -6
  35. package/docs/user-bindings.md +6 -5
  36. package/docs/value-wire.md +1 -1
  37. package/docs/verification.md +43 -2
  38. package/index.js +39 -34
  39. package/internal/async-local-storage-intrinsics.js +12 -7
  40. package/internal/callable-source-facts.js +37 -1
  41. package/internal/cell-analysis.js +64 -9
  42. package/internal/config-spec.js +1 -1
  43. package/internal/cordis-tools-owner.js +81 -15
  44. package/internal/direct-surface-owner.js +331 -7
  45. package/internal/edit-transport-owner.js +80 -20
  46. package/internal/execution-seam-compat.js +192 -30
  47. package/internal/kernel-worker.js +162 -72
  48. package/internal/message-sources.js +14 -0
  49. package/internal/module-compilation.js +2 -1
  50. package/internal/rejected-cell-editor.js +11 -21
  51. package/internal/repl-memory-projection.js +10 -0
  52. package/internal/repl-scope-normalizer.js +72 -35
  53. package/internal/repl-value-observer.js +141 -45
  54. package/internal/runtime-config.js +62 -2
  55. package/internal/runtime-intrinsics.js +3 -0
  56. package/internal/runtime-messages.js +6 -4
  57. package/internal/session-cell-executor.js +24 -1
  58. package/internal/session-journal-recovery.js +267 -67
  59. package/internal/session-journal.js +466 -40
  60. package/internal/session-log-view.js +5 -4
  61. package/internal/session-runtime.js +45 -21
  62. package/internal/session-state.js +7 -29
  63. package/internal/settings-compat.js +66 -1
  64. package/internal/source-position-map.js +19 -0
  65. package/internal/user-binding-console-worker.js +23 -9
  66. package/internal/user-binding-draft-projection.js +3 -2
  67. package/internal/user-bindings-owner.js +2 -1
  68. package/internal/user-bindings.js +6 -1
  69. package/internal/validated-parse-repair.js +6 -2
  70. package/internal/value-wire.js +95 -30
  71. package/internal/worker-client.js +99 -8
  72. package/internal/worker-cwd-virtualization.js +10 -9
  73. package/internal/worker-output-capture.js +187 -0
  74. package/internal/worker-output-fence.js +26 -0
  75. package/internal/worker-realm-surfaces.js +197 -0
  76. package/internal/worker-repl-realm.js +10 -4
  77. package/package.json +42 -4
  78. package/src/client-activity.js +4 -1
  79. package/src/client-authoring-view.js +121 -62
  80. package/src/client-console.js +4 -2
  81. package/src/client-copy.js +4 -2
  82. package/src/client-feature-gates.js +7 -9
  83. package/src/client-host-compat.js +104 -27
  84. package/src/client-repl-view.js +3 -1
  85. package/src/client-styles.js +12 -1
  86. package/src/client-tool-view.js +3 -1
  87. package/src/client-transport.js +2 -2
  88. package/src/client-workbench.js +10 -7
  89. package/src/client.js +56 -17
@@ -35,6 +35,19 @@ export const LEGACY_EXECUTION_SEAM_SERVICE = 'codeRuntime'
35
35
 
36
36
  const SEAM_SERVICES = Object.freeze([EXECUTION_SEAM_SERVICE, LEGACY_EXECUTION_SEAM_SERVICE])
37
37
 
38
+ /**
39
+ * How long the plugin waits for the host's execution seam before it reports the
40
+ * deployment as unsupported.
41
+ *
42
+ * Both seam names belong to the host's code-runtime row, so a composed host
43
+ * publishes one while the loader settles its entries. A profile that never
44
+ * registers either one cannot be served, and its activation promise must settle
45
+ * with a diagnostic: the host awaits loader settlement before it becomes
46
+ * usable, so an unbounded wait would park the whole host instead of failing
47
+ * this entry.
48
+ */
49
+ const SEAM_ATTACHMENT_TIMEOUT_MS = 30_000
50
+
38
51
  /**
39
52
  * Descriptors the plugin replaces with the capabilities of its own execution.
40
53
  * `executionInstructions` describes the wrapped provider's program model, which
@@ -105,15 +118,8 @@ export function createExecutionSeam(service, serviceName) {
105
118
  if (installed !== undefined) throw new Error('ptc-plus: execution seam already taken over')
106
119
  const saved = new Map()
107
120
  const written = new Map()
108
- const define = (name, descriptor) => {
109
- const previous = Object.getOwnPropertyDescriptor(service, name)
110
- if (!saved.has(name)) saved.set(name, previous)
111
- // Preserve the shadowed property's visibility so a consumer that
112
- // enumerates the provider sees the same member set.
113
- Object.defineProperty(service, name, { enumerable: previous?.enumerable ?? false, ...descriptor })
114
- written.set(name, Object.getOwnPropertyDescriptor(service, name))
115
- }
116
- define('run', {
121
+ const requested = new Map()
122
+ requested.set('run', {
117
123
  configurable: true,
118
124
  writable: true,
119
125
  value: current
@@ -125,7 +131,32 @@ export function createExecutionSeam(service, serviceName) {
125
131
  : request => execute(request),
126
132
  })
127
133
  for (const name of WITHHELD_DESCRIPTORS) {
128
- define(name, { configurable: true, value: name === 'executionInstructions' ? '' : undefined })
134
+ requested.set(name, { configurable: true, value: name === 'executionInstructions' ? '' : undefined })
135
+ }
136
+ const extensible = Object.isExtensible(service)
137
+ for (const [name, descriptor] of requested) {
138
+ const previous = Object.getOwnPropertyDescriptor(service, name)
139
+ saved.set(name, previous)
140
+ const next = { enumerable: previous?.enumerable ?? false, ...descriptor }
141
+ requested.set(name, next)
142
+ const probe = {}
143
+ if (previous !== undefined) Object.defineProperty(probe, name, previous)
144
+ if (!extensible) Object.preventExtensions(probe)
145
+ Object.defineProperty(probe, name, next)
146
+ }
147
+ try {
148
+ for (const [name, descriptor] of requested) {
149
+ Object.defineProperty(service, name, descriptor)
150
+ written.set(name, Object.getOwnPropertyDescriptor(service, name))
151
+ }
152
+ } catch (error) {
153
+ for (const name of [...written.keys()].reverse()) {
154
+ if (!descriptorsMatch(Object.getOwnPropertyDescriptor(service, name), written.get(name))) continue
155
+ const descriptor = saved.get(name)
156
+ if (descriptor === undefined) delete service[name]
157
+ else Object.defineProperty(service, name, descriptor)
158
+ }
159
+ throw error
129
160
  }
130
161
  const restore = () => {
131
162
  if (installed !== restore) return
@@ -145,9 +176,10 @@ export function createExecutionSeam(service, serviceName) {
145
176
  /**
146
177
  * Wait for the host's execution seam and attach the plugin to the live one.
147
178
  *
148
- * A deployment registers one generation, so the current service is preferred
149
- * whenever both are live; the preceding generation attaches only when the
150
- * current one is absent. The injected scope requires the plugin's own core
179
+ * The current service is preferred whenever both are live; the preceding
180
+ * generation attaches only when the current one is absent, and a current
181
+ * service registered later retires the live legacy injection before attaching.
182
+ * The injected scope requires the plugin's own core
151
183
  * services as well, so the callback receives a context the plugin can work in,
152
184
  * and everything the plugin registers there belongs to the seam the host
153
185
  * registered: unloading that service unloads the plugin, and registering it
@@ -157,16 +189,107 @@ export function createExecutionSeam(service, serviceName) {
157
189
  * @param {object} ctx - the plugin context.
158
190
  * @param {object} options - `services` are the plugin's own required services,
159
191
  * and `attach` is invoked with the injected scope and the seam handle.
160
- * @returns {Promise<unknown>} settles with the attach result.
192
+ * `attachmentTimeoutMs` bounds the wait for a seam that never appears.
193
+ * @returns {Promise<void>} settles after attachment with a valid Cordis effect value.
161
194
  */
162
- export function installExecutionSeam(ctx, { services = [], attach }) {
195
+ export function installExecutionSeam(ctx, {
196
+ services = [],
197
+ attach,
198
+ reportFailure,
199
+ attachmentTimeoutMs = SEAM_ATTACHMENT_TIMEOUT_MS,
200
+ }) {
163
201
  let live
202
+ let stopping = false
203
+ let startWatcher
204
+ let unusableCurrent
205
+ const report = (error, name) => {
206
+ try {
207
+ reportFailure?.(error, name)
208
+ } catch {
209
+ // A reporting failure cannot replace the seam failure it describes.
210
+ }
211
+ }
212
+ const registerLifecycle = (entry, name, scope) => scope.effect?.(() => () => {
213
+ if (live !== entry) return
214
+ if (entry.retiring !== undefined) return
215
+ live = undefined
216
+ if (!stopping && name === EXECUTION_SEAM_SERVICE
217
+ && readService(ctx, EXECUTION_SEAM_SERVICE) === undefined
218
+ && readService(ctx, LEGACY_EXECUTION_SEAM_SERVICE) !== undefined) {
219
+ startWatcher(LEGACY_EXECUTION_SEAM_SERVICE)
220
+ }
221
+ }, 'ptc-plus execution seam lifecycle')
164
222
  const select = (name, scope) => {
165
- if (live !== undefined) return live.attached
166
- if (name === LEGACY_EXECUTION_SEAM_SERVICE && readService(ctx, EXECUTION_SEAM_SERVICE) !== undefined) {
223
+ const currentService = readService(ctx, EXECUTION_SEAM_SERVICE)
224
+ if (unusableCurrent !== undefined && currentService !== unusableCurrent) {
225
+ unusableCurrent = undefined
226
+ }
227
+ if (name === EXECUTION_SEAM_SERVICE && currentService === unusableCurrent) {
228
+ // A current service whose attach already failed cannot supersede the live
229
+ // legacy attachment. A replacement service object clears the marker above.
230
+ return live?.attached
231
+ }
232
+ if (live !== undefined) {
233
+ if (name !== EXECUTION_SEAM_SERVICE || live.name === EXECUTION_SEAM_SERVICE) return live.attached
234
+ const previous = live
235
+ if (previous.retiring !== undefined) return previous.retiring
236
+ const disposeLegacy = previous.scope?.fiber?.dispose
237
+ if (typeof disposeLegacy !== 'function') {
238
+ throw new Error('ptc-plus: live codeRuntime attachment cannot be superseded without a disposable injected scope')
239
+ }
240
+ let resolveActivation
241
+ let rejectActivation
242
+ const activation = new Promise((resolve, reject) => {
243
+ resolveActivation = resolve
244
+ rejectActivation = reject
245
+ })
246
+ previous.retiring = activation
247
+ let retirement
248
+ try {
249
+ retirement = disposeLegacy.call(previous.scope.fiber)
250
+ } catch (error) {
251
+ previous.retiring = undefined
252
+ rejectActivation(error)
253
+ return activation
254
+ }
255
+ let entry
256
+ const operation = Promise.resolve(retirement).then(() => {
257
+ if (live !== previous) return undefined
258
+ previous.retiring = undefined
259
+ entry = { name, scope }
260
+ live = entry
261
+ registerLifecycle(entry, name, scope)
262
+ const attached = attach(scope, createExecutionSeam(scope?.[name] ?? readService(ctx, name), name))
263
+ entry.attached = attached
264
+ return attached
265
+ })
266
+ operation.then(resolveActivation, error => {
267
+ if (live === previous) previous.retiring = undefined
268
+ else if (live === entry) {
269
+ // The current attach failed after the legacy fiber was retired. Keep
270
+ // the plugin attached by re-arming the preceding generation instead
271
+ // of silently returning run_code to the host provider.
272
+ live = undefined
273
+ unusableCurrent = readService(ctx, EXECUTION_SEAM_SERVICE)
274
+ if (readService(ctx, LEGACY_EXECUTION_SEAM_SERVICE) !== undefined) {
275
+ try {
276
+ startWatcher(LEGACY_EXECUTION_SEAM_SERVICE)
277
+ } catch (fallbackError) {
278
+ report(fallbackError, LEGACY_EXECUTION_SEAM_SERVICE)
279
+ }
280
+ }
281
+ }
282
+ report(error, name)
283
+ rejectActivation(error)
284
+ })
285
+ return activation
286
+ }
287
+ if (name === LEGACY_EXECUTION_SEAM_SERVICE
288
+ && currentService !== undefined
289
+ && currentService !== unusableCurrent) {
167
290
  return undefined
168
291
  }
169
- const entry = { name }
292
+ const entry = { name, scope }
170
293
  live = entry
171
294
  try {
172
295
  entry.attached = attach(scope, createExecutionSeam(scope?.[name] ?? readService(ctx, name), name))
@@ -174,20 +297,59 @@ export function installExecutionSeam(ctx, { services = [], attach }) {
174
297
  live = undefined
175
298
  throw error
176
299
  }
177
- scope.effect?.(() => () => {
178
- if (live === entry) live = undefined
179
- }, 'ptc-plus execution seam lifecycle')
300
+ registerLifecycle(entry, name, scope)
180
301
  return entry.attached
181
302
  }
303
+ startWatcher = (name, settlement) => ctx.inject([...services, name], scope => {
304
+ let selected
305
+ try {
306
+ selected = select(name, scope)
307
+ } catch (error) {
308
+ settlement?.reject(error)
309
+ report(error, name)
310
+ throw error
311
+ }
312
+ if (selected !== null && typeof selected === 'object' && typeof selected.then === 'function') {
313
+ const operation = Promise.resolve(selected)
314
+ operation.then(
315
+ value => settlement?.resolve(value),
316
+ error => settlement?.reject(error),
317
+ )
318
+ return operation.then(() => undefined)
319
+ }
320
+ settlement?.resolve(selected)
321
+ return undefined
322
+ })
182
323
  const pending = SEAM_SERVICES.map(name => new Promise((resolve, reject) => {
183
- ctx.inject([...services, name], scope => {
184
- try {
185
- resolve(select(name, scope))
186
- } catch (error) {
187
- reject(error)
188
- throw error
189
- }
190
- })
324
+ startWatcher(name, { resolve, reject })
191
325
  }))
192
- return Promise.race(pending)
326
+ ctx.effect?.(() => () => { stopping = true }, 'ptc-plus execution seam watcher lifecycle')
327
+ const bound = seamWaitBound(ctx, attachmentTimeoutMs)
328
+ return Promise.race([Promise.race(pending), bound.promise])
329
+ .then(() => undefined)
330
+ .finally(bound.cancel)
331
+ }
332
+
333
+ /**
334
+ * Bound the wait for a seam the deployment never registers.
335
+ *
336
+ * @param {object} ctx - the plugin context owning the timer.
337
+ * @param {number} timeoutMs - positive bound, or a non-positive value for none.
338
+ * @returns the rejection promise and the disposer that clears its timer.
339
+ */
340
+ function seamWaitBound(ctx, timeoutMs) {
341
+ if (!(timeoutMs > 0)) return { promise: new Promise(() => {}), cancel: () => {} }
342
+ let timer
343
+ const promise = new Promise((_resolve, reject) => {
344
+ timer = setTimeout(() => reject(new Error(
345
+ `ptc-plus: no host execution seam (${SEAM_SERVICES.join(' or ')}) is registered; this deployment does not publish a code runtime`,
346
+ )), timeoutMs)
347
+ })
348
+ // The timer is not unref'd: this rejection is the only thing that settles the
349
+ // activation promise on a deployment that never registers a seam, and an
350
+ // unref'd timer would let the process drain before it fires. It is cleared as
351
+ // soon as the race settles so an attached seam never holds the loop open.
352
+ const cancel = () => clearTimeout(timer)
353
+ ctx.effect?.(() => () => cancel(), 'ptc-plus execution seam wait bound')
354
+ return { promise, cancel }
193
355
  }