dsh-ptc-plus 0.4.8 → 0.5.0

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 (139) hide show
  1. package/README.en.md +61 -123
  2. package/README.md +61 -123
  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 +42336 -0
  10. package/cordis.patch.yml +13 -2
  11. package/docs/README.md +7 -2
  12. package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +31 -179
  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 +2 -2
  17. package/docs/adr/0019-plugin-settings-and-kill-switch.md +14 -3
  18. package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -2
  19. package/docs/adr/0022-stage-npm-releases-with-oidc.md +4 -2
  20. package/docs/adr/0023-global-user-bindings.md +3 -1
  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 +101 -35
  23. package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +3 -3
  24. package/docs/adr/0031-close-semantic-obligations-by-owner.md +1 -1
  25. package/docs/adr/0032-own-session-log-facts-in-a-host-only-projection.md +27 -0
  26. package/docs/adr/0033-gate-client-visual-usability-in-a-browser.md +23 -0
  27. package/docs/adr/0034-keep-program-values-separate-from-output-projections.md +29 -0
  28. package/docs/adr/0035-run-coverage-only-before-a-new-release.md +27 -0
  29. package/docs/adr/0036-separate-focused-development-from-delivery-verification.md +29 -0
  30. package/docs/adr/0037-keep-tool-hover-previews-passive.md +52 -0
  31. package/docs/adr/0038-freeze-session-capability-plans.md +71 -0
  32. package/docs/adr/0039-verify-documentation-without-runtime-suites.md +29 -0
  33. package/docs/adr/0040-separate-capability-configurations-from-session-observations.md +39 -0
  34. package/docs/adr/0041-record-cell-execution-directories.md +65 -0
  35. package/docs/adr/0042-persist-provider-availability-and-handback.md +41 -0
  36. package/docs/adr/0043-compose-capability-and-context-policies.md +37 -0
  37. package/docs/adr/README.md +6 -0
  38. package/docs/architecture.md +25 -27
  39. package/docs/binding-review-placement-design.md +29 -21
  40. package/docs/capability-planning-components.md +93 -0
  41. package/docs/capability-projection.md +7 -0
  42. package/docs/client-ui.md +88 -29
  43. package/docs/client-visual-contract.md +65 -0
  44. package/docs/context-management.md +22 -0
  45. package/docs/context-optimization-plan.md +353 -0
  46. package/docs/context-public-interface-coverage.md +25 -0
  47. package/docs/durability-design.md +31 -14
  48. package/docs/evaluation.md +41 -6
  49. package/docs/evaluations/command-goat-deepseek-v4.1-flash-20261006.md +126 -0
  50. package/docs/evaluations/command-goat-deepseek-v4.1-flash.md +281 -0
  51. package/docs/installation.md +50 -8
  52. package/docs/program-data-plane.md +5 -3
  53. package/docs/publishing.md +5 -5
  54. package/docs/release-coverage.md +73 -0
  55. package/docs/release-notes.md +1 -1
  56. package/docs/review-procedure.md +56 -0
  57. package/docs/runtime-reference.md +48 -16
  58. package/docs/semantic-validation.md +14 -2
  59. package/docs/stateful-binding-updates-proposal.md +2 -2
  60. package/docs/user-bindings.en.md +2 -2
  61. package/docs/user-bindings.md +3 -3
  62. package/docs/value-wire.md +11 -1
  63. package/docs/verification-optimization-work.md +52 -15
  64. package/docs/verification.md +30 -86
  65. package/index.js +147 -23
  66. package/internal/binding-toggle-receipt.js +24 -0
  67. package/internal/capability-command-receipt.js +29 -0
  68. package/internal/capability-environment.js +1247 -0
  69. package/internal/capability-plan.js +285 -0
  70. package/internal/capability-policy.js +96 -0
  71. package/internal/capability-statistics-store.js +180 -0
  72. package/internal/capability-statistics.js +210 -0
  73. package/internal/compiler-platform.js +5 -3
  74. package/internal/compiler-service.js +2 -9
  75. package/internal/config-spec.js +79 -9
  76. package/internal/cordis-tools-owner.js +22 -19
  77. package/internal/direct-surface-owner.js +29 -29
  78. package/internal/dynamic-environment-runtime.js +9 -9
  79. package/internal/edit-transport-owner.js +183 -139
  80. package/internal/execution-provider-owner.js +160 -0
  81. package/internal/execution-seam-compat.js +76 -120
  82. package/internal/isolated-worker.js +12 -8
  83. package/internal/kernel-worker.js +104 -32
  84. package/internal/native-root-dynamic.js +12 -13
  85. package/internal/program-bindings.js +190 -0
  86. package/internal/recovery-tips.js +53 -23
  87. package/internal/rejected-cell-editor.js +8 -8
  88. package/internal/repl-observation-interest.js +11 -3
  89. package/internal/rpc-contract.js +1 -0
  90. package/internal/runtime-bridge-owner.js +351 -82
  91. package/internal/runtime-config.js +58 -0
  92. package/internal/runtime-contexts.js +85 -16
  93. package/internal/runtime-intrinsics.js +1 -0
  94. package/internal/runtime-messages.js +41 -0
  95. package/internal/sdk-projection.js +10 -82
  96. package/internal/session-cell-executor.js +42 -19
  97. package/internal/session-events.js +74 -4
  98. package/internal/session-journal-recovery.js +904 -184
  99. package/internal/session-journal-schema.js +6 -3
  100. package/internal/session-journal.js +31 -2
  101. package/internal/session-log-view.js +189 -43
  102. package/internal/session-runtime.js +175 -23
  103. package/internal/settings-compat.js +23 -6
  104. package/internal/stateful-module-compiler.js +3 -2
  105. package/internal/stateful-module-runtime.js +7 -3
  106. package/internal/stateful-root-runtime.js +9 -4
  107. package/internal/user-binding-authoring.js +2 -7
  108. package/internal/user-bindings-owner.js +47 -7
  109. package/internal/validated-parse-repair.js +1 -1
  110. package/internal/value-wire.js +68 -21
  111. package/internal/worker-cwd-virtualization.js +20 -18
  112. package/internal/worker-realm-surfaces.js +3 -3
  113. package/locale/en.json +6 -0
  114. package/locale/zh.json +6 -0
  115. package/package.json +55 -38
  116. package/src/client-authoring-view.js +286 -168
  117. package/src/client-binding-review.js +4 -1
  118. package/src/client-binding-toggle.js +18 -0
  119. package/src/client-capabilities-view.js +1341 -0
  120. package/src/client-capability-sort.js +29 -0
  121. package/src/client-code-editor.js +6 -0
  122. package/src/client-copy.js +881 -8
  123. package/src/client-feature-gates.js +15 -5
  124. package/src/client-host-compat.js +23 -2
  125. package/src/client-indicator-view.js +4 -6
  126. package/src/client-primitives.js +280 -0
  127. package/src/client-repl-view.js +23 -16
  128. package/src/client-settings-view.js +412 -69
  129. package/src/client-styles.js +193 -41
  130. package/src/client-tool-hover.js +185 -0
  131. package/src/client-tool-view.js +45 -11
  132. package/src/client-transport.js +44 -9
  133. package/src/client-workbench.js +9 -10
  134. package/src/client.js +148 -68
  135. package/assets/ptc-plus-bindings-en.png +0 -0
  136. package/assets/ptc-plus-bindings-zh.png +0 -0
  137. package/assets/ptc-plus-repair-en.png +0 -0
  138. package/assets/ptc-plus-repair-zh.png +0 -0
  139. package/client.js +0 -31857
@@ -1,4 +1,5 @@
1
1
  import { PTC_MESSAGE_SOURCE_KIND } from './message-sources.js'
2
+ import { bindingToggleMessage, readBindingToggle } from './binding-toggle-receipt.js'
2
3
  import { RPC_CONTRACTS } from './rpc-contract.js'
3
4
  import { randomUUID } from 'node:crypto'
4
5
  import { dirname } from 'node:path'
@@ -185,6 +186,7 @@ export function createUserBindingsOwner(ctx, options = {}) {
185
186
  valueLimits: options.valueLimits,
186
187
  }
187
188
  let disposed = false
189
+ const pendingConfigurationReceipts = new WeakMap()
188
190
  const codeConsole = new UserBindingConsole(currentOptions)
189
191
  const commandRegistrations = new Map()
190
192
  const sessionAgents = new Map()
@@ -274,7 +276,7 @@ export function createUserBindingsOwner(ctx, options = {}) {
274
276
  entry: current.entry,
275
277
  })
276
278
  const candidateView = current => Object.freeze({ requestId: current.requestId, commandId: current.commandId, ...draftView(current) })
277
- const settleDraft = (current, state, enabled = false) => {
279
+ const settleDraft = async (current, state, enabled = false) => {
278
280
  const retainReview = draftsByCapability.get(current.capability) === current
279
281
  removeDraft(current)
280
282
  const action = Object.freeze({ requestId: current.requestId, id: current.entry.id, state, enabled })
@@ -282,7 +284,8 @@ export function createUserBindingsOwner(ctx, options = {}) {
282
284
  reviewsByCapability.set(current.capability, { agent: current.agent, candidate: candidateView(current), action })
283
285
  }
284
286
  try {
285
- const accepting = sessionEvents(current.agent.session)?.find(event => (
287
+ const sessionLog = await (options.readSessionLog ?? (async session => session))(current.agent.session)
288
+ const accepting = sessionEvents(sessionLog)?.find(event => (
286
289
  event.type === 'tool/result' && event.surfaceOp === 'append'
287
290
  && event.data?.meta?.[USER_BINDING_DRAFT_META_KEY]?.candidate?.requestId === current.requestId
288
291
  ))
@@ -303,7 +306,7 @@ export function createUserBindingsOwner(ctx, options = {}) {
303
306
  drafts.delete(current.sessionId)
304
307
  }
305
308
  }
306
- const clearDraft = (capability, version) => {
309
+ const clearDraft = async (capability, version) => {
307
310
  const current = draftFor(capability)
308
311
  if (current === null) return null
309
312
  if (!Number.isSafeInteger(version) || version < 1 || version !== current.version) {
@@ -314,7 +317,7 @@ export function createUserBindingsOwner(ctx, options = {}) {
314
317
  if (current.state !== 'ready') {
315
318
  throw Object.assign(new Error('binding draft is being saved'), { code: 'BINDINGS_BUSY' })
316
319
  }
317
- settleDraft(current, 'discarded')
320
+ await settleDraft(current, 'discarded')
318
321
  return null
319
322
  }
320
323
  const saveDraft = async (capability, version, expectedRevision, activate = false) => {
@@ -333,7 +336,7 @@ export function createUserBindingsOwner(ctx, options = {}) {
333
336
  const result = current.mode === 'new'
334
337
  ? await store.create(entry, expectedRevision)
335
338
  : await store.update(entry, expectedRevision)
336
- settleDraft(current, 'saved', activate === true)
339
+ await settleDraft(current, 'saved', activate === true)
337
340
  return result
338
341
  } catch (error) {
339
342
  if (drafts.get(current.sessionId) === current && current.state === 'saving') {
@@ -377,13 +380,33 @@ export function createUserBindingsOwner(ctx, options = {}) {
377
380
  }
378
381
  // Fixed endpoint tables grouped by domain; the dispatcher only looks up the
379
382
  // operation, rejects unknown endpoints and hands over the validated input.
383
+ const setEnabled = async (input, requested) => {
384
+ const next = await store.setEnabled(input.id, requested, input.expectedRevision)
385
+ const entry = next.entries.find(entry => entry.id === input.id)
386
+ if (entry === undefined) throw new Error('saved binding missing from confirmed catalog')
387
+ const sessions = new Set((ctx.agents?.list?.() ?? []).map(agent => agent.session).filter(Boolean))
388
+ for (const session of sessions) {
389
+ try {
390
+ const message = createUserMessage(bindingToggleMessage(entry, next.revision))
391
+ // The Host alone establishes the first system head. Before that accepted
392
+ // boundary, keep UI configuration facts pending without waking the Agent.
393
+ if (session.deriveMessages()[0]?.role === 'system') session.append('user/message', message, { surfaceOp: 'append' })
394
+ else {
395
+ const pending = pendingConfigurationReceipts.get(session) ?? []
396
+ pending.push(message)
397
+ pendingConfigurationReceipts.set(session, pending)
398
+ }
399
+ } catch (error) { reportContainedFailure('ptc-plus: binding configuration saved but its session receipt could not be appended', error) }
400
+ }
401
+ return next
402
+ }
380
403
  const storageEndpoints = Object.freeze({
381
404
  list: () => store.list(),
382
405
  load: input => store.entry(input.id),
383
406
  reload: () => store.reload(),
384
407
  save: saveEntry,
385
- enable: input => store.setEnabled(input.id, true, input.expectedRevision),
386
- disable: input => store.setEnabled(input.id, false, input.expectedRevision),
408
+ enable: input => setEnabled(input, true),
409
+ disable: input => setEnabled(input, false),
387
410
  remove: input => store.remove(input.id, input.expectedRevision),
388
411
  import: input => store.importFile(input.path, input.expectedRevision, input.options),
389
412
  validate: input => validateEntry(input.entry),
@@ -893,6 +916,23 @@ export function createUserBindingsOwner(ctx, options = {}) {
893
916
  mountAuthoring()
894
917
 
895
918
  return Object.freeze({
919
+ committedEvent(session, event) {
920
+ if (event.type !== 'user/message' || !readBindingToggle(event.data)) return
921
+ const pending = pendingConfigurationReceipts.get(session)
922
+ if (pending) pendingConfigurationReceipts.set(session, pending.filter(message => message.id !== event.data.id))
923
+ },
924
+ async preStep(payload, next) {
925
+ const decision = await next()
926
+ if (disposed || decision?.kind !== 'enter' || payload.signal?.aborted || payload.agent?.signal?.aborted) return decision
927
+ const session = payload.agent?.session
928
+ const pending = pendingConfigurationReceipts.get(session)
929
+ if (!pending?.length) return decision
930
+ const visible = new Set(session.deriveMessages().map(message => message.id))
931
+ const remaining = pending.filter(message => !visible.has(message.id))
932
+ pendingConfigurationReceipts.set(session, remaining)
933
+ const proposed = new Set((decision.messages ?? []).map(message => message.id))
934
+ return { ...decision, messages: [...(decision.messages ?? []), ...remaining.filter(message => !proposed.has(message.id))] }
935
+ },
896
936
  path: store.filename,
897
937
  cwd: bindingsCwd,
898
938
  async snapshot() {
@@ -53,7 +53,7 @@ function renderInvocation(argumentsValue) {
53
53
  const json = JSON.stringify(argumentsValue)
54
54
  .replaceAll('\u2028', '\\u2028')
55
55
  .replaceAll('\u2029', '\\u2029')
56
- return `edit_run_code(${json})`
56
+ return `await tools.edit_run_code(${json})`
57
57
  }
58
58
 
59
59
  /** Requires exactly one valid suffix across all one- and two-token EOF closures. */
@@ -1,3 +1,4 @@
1
+ import { types } from 'node:util'
1
2
  import { assertFields, isRecord } from './record-utils.js'
2
3
  import { runtimeIntrinsics as internal } from './runtime-intrinsics.js'
3
4
  import {
@@ -23,6 +24,9 @@ const intrinsicFunctionToString = Function.prototype.toString
23
24
  const reflectApply = Reflect.apply
24
25
  const bufferByteLength = internal.bufferByteLength
25
26
  const jsonStringify = JSON.stringify
27
+ const { isProxy, isDate, isMap, isSet, isRegExp, isAnyArrayBuffer, isTypedArray,
28
+ isDataView, isNativeError, isPromise, isWeakMap, isWeakSet } = types
29
+ const encodingFailures = new WeakMap()
26
30
 
27
31
  function limitsOf(options = {}) {
28
32
  const limits = { ...DEFAULT_VALUE_LIMITS, ...options }
@@ -36,19 +40,16 @@ function limitsOf(options = {}) {
36
40
  }
37
41
 
38
42
  function hasIntrinsicConstructor(prototype, name) {
43
+ if (isProxy(prototype)) return false
39
44
  const constructor = Object.getOwnPropertyDescriptor(prototype, 'constructor')?.value
40
- if (typeof constructor !== 'function') return false
41
- try {
42
- return constructor.name === name
43
- && constructor.prototype === prototype
44
- && reflectApply(intrinsicFunctionToString, constructor, []) === `function ${name}() { [native code] }`
45
- } catch {
46
- return false
47
- }
45
+ if (typeof constructor !== 'function' || isProxy(constructor)) return false
46
+ return Object.getOwnPropertyDescriptor(constructor, 'name')?.value === name
47
+ && Object.getOwnPropertyDescriptor(constructor, 'prototype')?.value === prototype
48
+ && reflectApply(intrinsicFunctionToString, constructor, []) === `function ${name}() { [native code] }`
48
49
  }
49
50
 
50
51
  function isIntrinsicObjectPrototype(value) {
51
- return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor(value, 'Object')
52
+ return !isProxy(value) && Object.getPrototypeOf(value) === null && hasIntrinsicConstructor(value, 'Object')
52
53
  }
53
54
 
54
55
  function plainObjectPrototype(value) {
@@ -66,8 +67,53 @@ function hasPlainArrayPrototype(value) {
66
67
  && isIntrinsicObjectPrototype(objectPrototype)
67
68
  }
68
69
 
69
- function invalid(path, detail) {
70
- throw new TypeError(`value at ${path} is not PTC Value V1: ${detail}`)
70
+ function invalid(path, detail, kind = 'shape', legacyDetail = detail, legacyMessage = `value at ${path} is not PTC Value V1: ${legacyDetail}`) {
71
+ const error = new TypeError(`value at ${path} is not PTC Value V1: ${detail}`)
72
+ weakMapSet(encodingFailures, error, { kind, path, detail, legacyMessage })
73
+ throw error
74
+ }
75
+
76
+ /** Brand checks do not read constructor, toStringTag, getters, or Proxy traps. */
77
+ function unsupportedObject(value) {
78
+ if (isProxy(value)) return 'Proxy'
79
+ if (isDate(value)) return 'Date'
80
+ if (isMap(value)) return 'Map'
81
+ if (isSet(value)) return 'Set'
82
+ if (isRegExp(value)) return 'RegExp'
83
+ if (isAnyArrayBuffer(value)) return 'ArrayBuffer/SharedArrayBuffer'
84
+ if (isTypedArray(value)) return 'TypedArray/Buffer'
85
+ if (isDataView(value)) return 'DataView'
86
+ if (isNativeError(value)) return 'Error'
87
+ if (isPromise(value)) return 'Promise'
88
+ if (isWeakMap(value)) return 'WeakMap'
89
+ if (isWeakSet(value)) return 'WeakSet'
90
+ return 'unknown prototype'
91
+ }
92
+
93
+ export function valueEncodingFailure(error) {
94
+ return weakMapGet(encodingFailures, error)
95
+ }
96
+
97
+ export function valueEncodingHelp(failure) {
98
+ return failure?.kind === 'budget'
99
+ ? 'reduce the transferred graph to fit the reported value budget'
100
+ : 'select needed data explicitly as supported primitives, plain objects or arrays; keep unsupported live values in bindings (Date: explicit ISO text or timestamp; Map/Set: explicit entries or values); do not JSON.stringify a graph containing BigInt'
101
+ }
102
+
103
+ /** Only codec-owned failures acquire boundary facts; owner exceptions remain untouched. */
104
+ export function encodeValueAtBoundary(value, options, boundary) {
105
+ try {
106
+ return encodeValue(value, options)
107
+ } catch (error) {
108
+ const failure = valueEncodingFailure(error)
109
+ if (failure === undefined) throw error
110
+ const fact = boundary === 'parameter'
111
+ ? 'program binding parameter was not dispatched'
112
+ : 'program binding call completed, but its result could not be transferred; external effects may already have occurred; do not repeat the operation solely to obtain its result'
113
+ const wrapped = new TypeError(`${fact}: ${error.message}; ${valueEncodingHelp(failure)}`)
114
+ weakMapSet(encodingFailures, wrapped, failure)
115
+ throw wrapped
116
+ }
71
117
  }
72
118
 
73
119
  function stringBytes(value) {
@@ -86,7 +132,7 @@ export function encodeValue(value, options = {}) {
86
132
 
87
133
  const accountText = (text, path) => {
88
134
  textBytes += stringBytes(text)
89
- if (textBytes > limits.maxStringBytes) invalid(path, `string budget exceeds ${limits.maxStringBytes} bytes`)
135
+ if (textBytes > limits.maxStringBytes) invalid(path, `string budget exceeds ${limits.maxStringBytes} bytes`, 'budget')
90
136
  }
91
137
 
92
138
  const atom = (current, path) => {
@@ -106,14 +152,14 @@ export function encodeValue(value, options = {}) {
106
152
  if (typeof current === 'bigint') {
107
153
  const text = toString(current)
108
154
  const digits = text[0] === '-' ? text.length - 1 : text.length
109
- if (digits > limits.maxBigIntDigits) invalid(path, `BigInt exceeds ${limits.maxBigIntDigits} digits`)
155
+ if (digits > limits.maxBigIntDigits) invalid(path, `BigInt exceeds ${limits.maxBigIntDigits} digits`, 'budget')
110
156
  accountText(text, path)
111
157
  return { tag: 'bigint', value: text }
112
158
  }
113
- if (typeof current !== 'object') invalid(path, typeof current)
159
+ if (typeof current !== 'object') invalid(path, typeof current, 'unsupported')
114
160
  const prior = weakMapGet(seen, current)
115
161
  if (prior !== undefined) return { tag: 'reference', index: prior }
116
- if (nodes.length >= limits.maxNodes) invalid(path, `node budget exceeds ${limits.maxNodes}`)
162
+ if (nodes.length >= limits.maxNodes) invalid(path, `node budget exceeds ${limits.maxNodes}`, 'budget')
117
163
  const index = nodes.length
118
164
  weakMapSet(seen, current, index)
119
165
  appendArray(nodes, undefined)
@@ -127,10 +173,10 @@ export function encodeValue(value, options = {}) {
127
173
  const current = item.value
128
174
  if (isArray(current)) {
129
175
  if (!hasPlainArrayPrototype(current)) invalid(item.path, 'non-plain array')
130
- if (current.length > limits.maxArrayLength) invalid(item.path, `array length exceeds ${limits.maxArrayLength}`)
176
+ if (current.length > limits.maxArrayLength) invalid(item.path, `array length exceeds ${limits.maxArrayLength}`, 'budget')
131
177
  arraySlotCount += current.length
132
178
  if (arraySlotCount > limits.maxArrayLength) {
133
- invalid(item.path, `array slot budget exceeds ${limits.maxArrayLength}`)
179
+ invalid(item.path, `array slot budget exceeds ${limits.maxArrayLength}`, 'budget')
134
180
  }
135
181
  const entries = []
136
182
  const keys = Reflect.ownKeys(current)
@@ -147,7 +193,7 @@ export function encodeValue(value, options = {}) {
147
193
  invalid(`${item.path}[${key}]`, 'array index must be an enumerable data property')
148
194
  }
149
195
  edgeCount += 1
150
- if (edgeCount > limits.maxEdges) invalid(item.path, `edge budget exceeds ${limits.maxEdges}`)
196
+ if (edgeCount > limits.maxEdges) invalid(item.path, `edge budget exceeds ${limits.maxEdges}`, 'budget')
151
197
  appendArray(entries, [index, atom(descriptor.value, `${item.path}[${key}]`)])
152
198
  }
153
199
  nodes[item.index] = { type: 'array', length: current.length, entries }
@@ -155,7 +201,7 @@ export function encodeValue(value, options = {}) {
155
201
  }
156
202
 
157
203
  const prototype = plainObjectPrototype(current)
158
- if (prototype === undefined) invalid(item.path, 'non-plain object')
204
+ if (prototype === undefined) invalid(item.path, `non-plain object (${unsupportedObject(current)})`, 'unsupported', 'non-plain object')
159
205
  const entries = []
160
206
  const keys = Reflect.ownKeys(current)
161
207
  for (let keyIndex = 0; keyIndex < keys.length; keyIndex += 1) {
@@ -167,7 +213,7 @@ export function encodeValue(value, options = {}) {
167
213
  }
168
214
  accountText(key, `${item.path}.${key}`)
169
215
  edgeCount += 1
170
- if (edgeCount > limits.maxEdges) invalid(item.path, `edge budget exceeds ${limits.maxEdges}`)
216
+ if (edgeCount > limits.maxEdges) invalid(item.path, `edge budget exceeds ${limits.maxEdges}`, 'budget')
171
217
  appendArray(entries, [key, atom(descriptor.value, `${item.path}.${key}`)])
172
218
  }
173
219
  nodes[item.index] = { type: 'object', prototype, entries }
@@ -442,7 +488,8 @@ function renderCanonicalValue(normalized, limits) {
442
488
  let bytes = 0
443
489
  const append = (text) => {
444
490
  bytes += stringBytes(text)
445
- if (bytes > limits.maxStringBytes) throw new TypeError(`rendered PTC value exceeds ${limits.maxStringBytes} bytes`)
491
+ if (bytes > limits.maxStringBytes) invalid('$', `rendered PTC value exceeds ${limits.maxStringBytes} bytes`, 'budget', undefined,
492
+ `rendered PTC value exceeds ${limits.maxStringBytes} bytes`)
446
493
  appendArray(chunks, text)
447
494
  }
448
495
  const emitted = new Set()
@@ -7,7 +7,7 @@ import {
7
7
  installProjectedProcessCwd,
8
8
  } from './worker-realm-surfaces.js'
9
9
 
10
- const { Reflect: privateReflect, appendArray, bufferConcat, bufferIsBuffer, bufferToString,
10
+ const { Reflect: privateReflect, appendArray, bufferConcat, bufferFrom, bufferIsBuffer, bufferToString,
11
11
  copyArray, isArray, regexpTest } = internal
12
12
  const TRAILING_SEPARATOR = /[\\/]$/
13
13
 
@@ -108,29 +108,32 @@ export function projectChildProcessArguments(name, args, sessionCwd) {
108
108
  : withDefaultOptions(2)
109
109
  }
110
110
 
111
- /** Install one session's cwd projection into Node builtins loaded by the worker. */
112
- export function installWorkerCwdVirtualization(sessionCwd, originalRequire, markVolatile) {
111
+ /** Install stable Node wrappers; the execution owner supplies each cell's cwd. */
112
+ export function installWorkerCwdVirtualization(sessionCwd, originalRequire, markVolatile, readCwd = () => sessionCwd) {
113
+ const nativeCwd = process.cwd
114
+ const currentCwd = () => readCwd() ?? privateReflect.apply(nativeCwd, process, [])
113
115
  if (sessionCwd === undefined) {
114
- const nativeCwd = process.cwd
115
116
  assignWorkerRealmProperty(WORKER_REALM_MUTATION.STABLE, 'process.cwd', process, 'cwd', () => {
116
- markVolatile('process.cwd')
117
- return privateReflect.apply(nativeCwd, process, [])
117
+ if (readCwd() === undefined) markVolatile('process.cwd')
118
+ return currentCwd()
118
119
  })
119
- return
120
- }
120
+ } else installProjectedProcessCwd(process, currentCwd)
121
121
  const nativeResolve = resolve
122
- const bufferPrefix = Buffer.from(sessionCwd + (regexpTest(TRAILING_SEPARATOR, sessionCwd) ? '' : sep))
122
+ const directoryPrefix = () => {
123
+ const cwd = currentCwd()
124
+ return cwd + (regexpTest(TRAILING_SEPARATOR, cwd) ? '' : sep)
125
+ }
123
126
  const sessionPath = (value) => {
124
- if (typeof value === 'string') return isAbsolute(value) ? value : nativeResolve(sessionCwd, value)
127
+ if (typeof value === 'string') return isAbsolute(value) ? value : nativeResolve(currentCwd(), value)
125
128
  if (bufferIsBuffer(value)) {
126
129
  const absolute = isAbsolute(bufferToString(value, 'utf8', 0, 3))
127
- return absolute ? value : bufferConcat([bufferPrefix, value])
130
+ return absolute ? value : bufferConcat([bufferFrom(directoryPrefix()), value])
128
131
  }
129
132
  return value
130
133
  }
131
134
  const sessionPathPrefix = (value) => {
132
135
  if (typeof value !== 'string' || isAbsolute(value) || parse(value).root !== '') return sessionPath(value)
133
- return sessionCwd + (regexpTest(TRAILING_SEPARATOR, sessionCwd) ? '' : sep) + value
136
+ return directoryPrefix() + value
134
137
  }
135
138
  const projectPaths = (args, indices) => {
136
139
  const next = copyArray(args)
@@ -148,18 +151,17 @@ export function installWorkerCwdVirtualization(sessionCwd, originalRequire, mark
148
151
  const sessionGlobOptions = (args) => {
149
152
  if (typeof args[1] === 'function') {
150
153
  const next = copyArray(args, 0, 1)
151
- appendArray(next, { cwd: sessionCwd })
154
+ appendArray(next, { cwd: currentCwd() })
152
155
  return copyArray(args, 1, args.length, next)
153
156
  }
154
157
  const next = copyArray(args)
155
- if (next[1] === undefined) next[1] = { cwd: sessionCwd }
158
+ if (next[1] === undefined) next[1] = { cwd: currentCwd() }
156
159
  else if (next[1] !== null && typeof next[1] === 'object' && next[1].cwd === undefined) {
157
- next[1] = { ...next[1], cwd: sessionCwd }
160
+ next[1] = { ...next[1], cwd: currentCwd() }
158
161
  }
159
162
  return next
160
163
  }
161
164
 
162
- installProjectedProcessCwd(process, sessionCwd)
163
165
  const fs = originalRequire('node:fs')
164
166
  const path = originalRequire('node:path')
165
167
  for (const [name, indices] of Object.entries(FILE_SYSTEM_PATH_ARGUMENTS)) {
@@ -174,10 +176,10 @@ export function installWorkerCwdVirtualization(sessionCwd, originalRequire, mark
174
176
  wrapBuiltin(fs, name, sessionGlobOptions)
175
177
  wrapBuiltin(fs.promises, name, sessionGlobOptions)
176
178
  }
177
- wrapBuiltin(path, 'resolve', args => copyArray(args, 0, args.length, [sessionCwd]))
179
+ wrapBuiltin(path, 'resolve', args => copyArray(args, 0, args.length, [currentCwd()]))
178
180
  const childProcess = originalRequire('node:child_process')
179
181
  for (const name of ['exec', 'execFile', 'execFileSync', 'execSync', 'fork', 'spawn', 'spawnSync']) {
180
182
  wrapBuiltin(childProcess, name,
181
- args => projectChildProcessArguments(name, args, sessionCwd), CHILD_PROCESS_PROJECTED_PROPERTIES[name])
183
+ args => projectChildProcessArguments(name, args, currentCwd()), CHILD_PROCESS_PROJECTED_PROPERTIES[name])
182
184
  }
183
185
  }
@@ -41,7 +41,7 @@ export const WORKER_REALM_SURFACE_CONTRACT = objectFreeze([
41
41
  surface: 'process.cwd',
42
42
  owner: 'internal/worker-cwd-virtualization.js',
43
43
  classification: WORKER_REALM_MUTATION.STABLE,
44
- contract: 'recorded cwd; immutable and non-configurable when a session cwd exists',
44
+ contract: 'current recorded cell cwd; callable immutable and non-configurable when a session cwd exists',
45
45
  reference: 'plain Node worker process descriptor',
46
46
  verification: 'cwd-descriptor',
47
47
  }),
@@ -49,7 +49,7 @@ export const WORKER_REALM_SURFACE_CONTRACT = objectFreeze([
49
49
  surface: 'cwd-projected Node builtins',
50
50
  owner: 'internal/worker-cwd-virtualization.js',
51
51
  classification: WORKER_REALM_MUTATION.STABLE,
52
- contract: 'relative fs, path, and child-process calls use the recorded cwd while retaining callable descriptors',
52
+ contract: 'relative fs, path, and new child-process calls use the current recorded cell cwd while retaining callable descriptors',
53
53
  reference: 'plain Node worker fs, fs/promises, path, and child_process callable descriptors',
54
54
  verification: 'cwd-projected-builtins',
55
55
  }),
@@ -148,7 +148,7 @@ export function installProjectedProcessCwd(processObject, cwd) {
148
148
  configurable: false,
149
149
  enumerable: true,
150
150
  writable: false,
151
- value: () => cwd,
151
+ value: typeof cwd === 'function' ? cwd : () => cwd,
152
152
  })
153
153
  }
154
154
 
package/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "PTC Plus",
4
+ "description": "A session-bound agent-native REPL for DeepSeek Harness PTC mode."
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "PTC Plus",
4
+ "description": "面向 DeepSeek Harness PTC 模式的会话级 Agent 原生 REPL。"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,18 +1,20 @@
1
1
  {
2
2
  "name": "dsh-ptc-plus",
3
- "version": "0.4.8",
3
+ "version": "0.5.0",
4
4
  "icon": "assets/ptc-plus-icon.svg",
5
5
  "description": "A session-bound agent-native REPL for DeepSeek Harness PTC mode.",
6
6
  "type": "module",
7
7
  "main": "./index.js",
8
8
  "exports": {
9
9
  ".": "./index.js",
10
- "./client": "./client.js",
11
- "./package.json": "./package.json"
10
+ "./execution-provider": "./internal/execution-provider-owner.js",
11
+ "./client": "./client.cjs",
12
+ "./package.json": "./package.json",
13
+ "./locale/*.json": "./locale/*.json"
12
14
  },
13
15
  "files": [
14
16
  "index.js",
15
- "client.js",
17
+ "client.cjs",
16
18
  "compiler-core.cjs",
17
19
  "compiler-core.cjs.map",
18
20
  "compiler-platform.cjs",
@@ -29,15 +31,12 @@
29
31
  "assets/ptc-plus-icon.svg",
30
32
  "assets/dsh-ptc-plus-banner-en.webp",
31
33
  "assets/dsh-ptc-plus-banner-zh.webp",
32
- "assets/ptc-plus-bindings-en.png",
33
- "assets/ptc-plus-bindings-zh.png",
34
- "assets/ptc-plus-repair-zh.png",
35
- "assets/ptc-plus-repair-en.png",
36
34
  "assets/ptc-plus-repl-workspace-en.png",
37
35
  "assets/ptc-plus-repl-workspace-zh.png",
38
36
  "assets/ptc-plus-settings-en.png",
39
37
  "assets/ptc-plus-settings-zh.png",
40
38
  "cordis.patch.yml",
39
+ "locale",
41
40
  "README.md",
42
41
  "README.en.md",
43
42
  "LICENSE"
@@ -50,36 +49,24 @@
50
49
  },
51
50
  "dependencies": {
52
51
  "@babel/code-frame": "^7.29.7",
53
- "@babel/core": "^7.29.7",
54
- "@babel/generator": "^7.29.8",
55
- "@babel/helper-create-class-features-plugin": "^7.29.7",
56
52
  "@babel/parser": "^7.29.0",
57
- "@babel/plugin-proposal-decorators": "^7.29.7",
58
- "@babel/plugin-transform-explicit-resource-management": "^7.29.7",
59
- "@babel/plugin-transform-optional-chaining": "^7.29.7",
60
- "@babel/plugin-transform-typescript": "^7.29.7",
61
- "@babel/traverse": "^7.29.0",
62
- "@codemirror/lang-javascript": "^6.2.5",
63
- "@codemirror/language": "^6.12.4",
64
- "@codemirror/state": "^6.7.4",
65
- "@codemirror/view": "^6.43.11",
53
+ "@deepseek-ai/dsh-util-values": "^0.2.0-rc.2 || ^0.2.1-alpha.1",
66
54
  "@deepseek-ai/schemastery": "^3.18.3",
67
55
  "@jridgewell/trace-mapping": "^0.3.31",
68
- "@lezer/highlight": "^1.2.3",
69
- "acorn": "^8.15.0",
70
- "amaro": "1.1.11",
71
- "codemirror": "^6.0.2",
72
- "fflate": "^0.8.3",
73
- "pathe": "^2.0.3",
74
- "tslib": "^2.8.1",
75
- "typescript": "^6.0.3"
56
+ "acorn": "^8.15.0"
76
57
  },
77
58
  "peerDependencies": {
59
+ "@deepseek-ai/cordis": "*",
78
60
  "@deepseek-ai/dsh-atomic-write": "*",
79
61
  "@deepseek-ai/dsh-home-paths": "*",
80
62
  "@deepseek-ai/dsh-llm": "*",
63
+ "@deepseek-ai/dsh-scope": "*",
64
+ "@deepseek-ai/dsh-session": "*",
65
+ "@deepseek-ai/dsh-session-format-catalog": "*",
81
66
  "@deepseek-ai/dsh-settings": "*",
67
+ "@deepseek-ai/dsh-skill": "*",
82
68
  "@deepseek-ai/dsh-skill-filesystem": "*",
69
+ "@deepseek-ai/dsh-spill-policy": "*",
83
70
  "@deepseek-ai/dsh-tool-cordis": "*",
84
71
  "@deepseek-ai/dsh-tools": "*",
85
72
  "@deepseek-ai/dsh-typert-protocol": "*"
@@ -88,10 +75,18 @@
88
75
  "build": "node scripts/client-bundle.mjs build && node scripts/compiler-bundle.mjs build",
89
76
  "build:check": "node scripts/client-bundle.mjs check && node scripts/compiler-bundle.mjs check",
90
77
  "test:client": "vitest run --config scripts/client-test.config.mjs",
91
- "test:semantics": "node --test --test-concurrency=1 test/semantic-test262.test.js test/semantic-dialect.test.js test/semantic-boundaries.test.js",
78
+ "test:client:focus": "node scripts/client-focused-tests.mjs",
79
+ "test:client:fixtures": "vitest run --config scripts/client-test.config.mjs --testNamePattern=\"^(REPL observations and shared workbench remain separate from session execution|saved cards retain exact source across remount and locale changes; catalog conflicts keep drafts actionable)$\"",
80
+ "test:client:visual": "node scripts/client-visual-check.mjs",
81
+ "test:semantics": "node scripts/test-suite-files.mjs --file test/semantic-test262.test.js --file test/semantic-dialect.test.js --file test/semantic-boundaries.test.js --file test/semantic-boundary-storage.test.js --file test/semantic-boundary-promises.test.js --file test/semantic-boundary-reflection.test.js",
92
82
  "semantic:check": "node scripts/semantic-obligations.mjs",
93
83
  "test:client:web": "node scripts/client-web-smoke.mjs",
84
+ "test:client:tool-hover:focus": "node scripts/client-tool-hover.mjs --focus",
85
+ "test:client:fallback:focus": "node scripts/client-fallback-smoke.mjs --focus",
86
+ "test:client:layout:focus": "node scripts/client-binding-layout.mjs --focus",
87
+ "test:client:fallback": "node scripts/client-fallback-smoke.mjs",
94
88
  "test:client:layout": "node scripts/client-binding-layout.mjs",
89
+ "readme:screenshots": "node scripts/readme-screenshots.mjs",
95
90
  "host:baseline:update": "node scripts/dsh-dependency-baseline.mjs update",
96
91
  "host:baseline:verify": "node scripts/dsh-dependency-baseline.mjs verify",
97
92
  "test:host-contract": "node scripts/dsh-host-contract-suite.mjs",
@@ -109,12 +104,16 @@
109
104
  "session:migrate": "node scripts/migrate-session-log.mjs",
110
105
  "coverage": "node scripts/coverage.mjs",
111
106
  "coverage:focus": "node scripts/coverage.mjs --focus",
107
+ "test:backend:focus": "node scripts/test-suite-files.mjs --focus",
108
+ "test:backend": "node scripts/test-suite-files.mjs",
112
109
  "test:ci:runtime": "node --test --test-concurrency=1 test/package-contract.test.js test/compiler-platform.test.js test/direct-surface-policy.test.js test/isolated-entrance.test.js test/plugin-native-call-transport.test.js",
113
- "verify": "npm run review:validate && npm run host:baseline:verify && npm run semantic:check && npm run build:check && node --check index.js && node --check internal/kernel-worker.js && node --check scripts/migrate-session-log.mjs && node --check scripts/expensive-headless-acceptance.mjs && node --check scripts/ab-headless-trajectories.mjs && npm run test:client && npm run coverage",
114
- "verify:ci": "npm run review:validate && npm run host:baseline:verify && npm run semantic:check && npm run build:check && node --check index.js && node --check internal/kernel-worker.js && node --check scripts/migrate-session-log.mjs && npm run test:client && npm run test:ci:runtime",
110
+ "verify": "npm run review:validate && npm run host:baseline:verify && npm run semantic:check && npm run build:check && node --check index.js && node --check internal/kernel-worker.js && node --check scripts/migrate-session-log.mjs && node --check scripts/expensive-headless-acceptance.mjs && node --check scripts/ab-headless-trajectories.mjs && node scripts/deterministic-test-groups.mjs",
111
+ "verify:ci": "npm run review:validate && npm run host:baseline:verify && npm run semantic:check && npm run build:check && node --check index.js && node --check internal/kernel-worker.js && node --check scripts/migrate-session-log.mjs && node scripts/deterministic-test-groups.mjs --ci",
115
112
  "check": "node scripts/review-findings.mjs check",
113
+ "verify:docs": "node scripts/review-findings.mjs docs",
116
114
  "test:expensive": "node scripts/expensive-headless-acceptance.mjs",
117
- "test:ab": "node scripts/ab-headless-trajectories.mjs"
115
+ "test:ab": "node scripts/ab-headless-trajectories.mjs",
116
+ "test:package:consumer": "node scripts/npm-consumer-smoke.mjs"
118
117
  },
119
118
  "keywords": [
120
119
  "deepseek-harness",
@@ -139,23 +138,31 @@
139
138
  },
140
139
  "client": {
141
140
  "platform": "web",
142
- "external": [
143
- "react"
144
- ],
141
+ "immediately": true,
145
142
  "inject": [
146
143
  "@deepseek-ai/dsh-api-gateway",
147
144
  "@deepseek-ai/dsh-api-remotes",
148
- "@deepseek-ai/dsh-client-runtime",
149
145
  "@deepseek-ai/dsh-client-locale",
150
146
  "@deepseek-ai/dsh-client-ui-settings",
151
147
  "@deepseek-ai/dsh-client-ui-settings-plugins",
152
148
  "@deepseek-ai/dsh-client-ui-conversation",
153
- "@deepseek-ai/dsh-client-connection",
154
- "@deepseek-ai/dsh-client-ui-primitives"
149
+ "@deepseek-ai/dsh-client-connection"
155
150
  ]
156
151
  }
157
152
  },
158
153
  "devDependencies": {
154
+ "@babel/core": "^7.29.7",
155
+ "@babel/generator": "^7.29.8",
156
+ "@babel/helper-create-class-features-plugin": "^7.29.7",
157
+ "@babel/plugin-proposal-decorators": "^7.29.7",
158
+ "@babel/plugin-transform-explicit-resource-management": "^7.29.7",
159
+ "@babel/plugin-transform-optional-chaining": "^7.29.7",
160
+ "@babel/plugin-transform-typescript": "^7.29.7",
161
+ "@babel/traverse": "^7.29.0",
162
+ "@codemirror/lang-javascript": "^6.2.5",
163
+ "@codemirror/language": "^6.12.4",
164
+ "@codemirror/state": "^6.7.4",
165
+ "@codemirror/view": "^6.43.11",
159
166
  "@deepseek-ai/dsh": "next",
160
167
  "@deepseek-ai/dsh-agent-loop": "next",
161
168
  "@deepseek-ai/dsh-api-gateway": "next",
@@ -170,6 +177,7 @@
170
177
  "@deepseek-ai/dsh-client-ui-session": "next",
171
178
  "@deepseek-ai/dsh-client-ui-slots": "next",
172
179
  "@deepseek-ai/dsh-commands": "next",
180
+ "@deepseek-ai/dsh-compaction-image-offload": "next",
173
181
  "@deepseek-ai/dsh-home-paths": "next",
174
182
  "@deepseek-ai/dsh-llm": "next",
175
183
  "@deepseek-ai/dsh-session-format-catalog": "next",
@@ -182,14 +190,19 @@
182
190
  "@deepseek-ai/dsh-typert-protocol": "next",
183
191
  "@deepseek-ai/dsh-typert-registry": "next",
184
192
  "@deepseek-ai/dsh-util-workspace-path": "next",
193
+ "@lezer/highlight": "^1.2.3",
194
+ "@radix-ui/react-select": "^2.2.6",
185
195
  "@shikijs/langs": "^4.3.1",
186
196
  "@testing-library/react": "^16.3.3",
197
+ "amaro": "1.1.11",
187
198
  "anser": "^2.3.5",
188
199
  "c8": "^12.0.0",
189
200
  "clsx": "^2.0.0",
201
+ "codemirror": "^6.0.2",
190
202
  "convert-source-map": "^2.0.0",
191
203
  "diff": "^9.0.0",
192
204
  "esbuild": "0.28.2",
205
+ "fflate": "^0.8.3",
193
206
  "immer": "^10.1.1",
194
207
  "jsdom": "^29.1.1",
195
208
  "katex": "^0.16.47",
@@ -204,11 +217,15 @@
204
217
  "micromark-util-classify-character": "^2.0.1",
205
218
  "micromark-util-sanitize-uri": "^2.0.1",
206
219
  "micromark-util-symbol": "^2.0.1",
220
+ "pathe": "^2.0.3",
207
221
  "playwright": "^1.63.0",
208
222
  "react": "^18.3.1",
209
223
  "react-dom": "^18.3.1",
224
+ "react-is": "^19.3.0",
210
225
  "shiki": "^4.3.1",
211
226
  "simple-icons": "16.31.0",
227
+ "tslib": "^2.8.1",
228
+ "typescript": "^6.0.3",
212
229
  "vitest": "^5.0.0",
213
230
  "yaml": "^2.9.0",
214
231
  "zustand": "~4.4.7"