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
@@ -0,0 +1,210 @@
1
+ const CATEGORIES = new Set(['tool', 'skill', 'resource', 'resource-template', 'task'])
2
+ const OUTCOMES = new Set(['complete', 'failed', 'cancelled', 'unknown'])
3
+ const USE_EVIDENCE = new Map([
4
+ ['tool', 'host-execution-result'], ['task', 'host-execution-result'], ['skill', 'skill-body-accepted'],
5
+ ['resource', 'resource-access'], ['resource-template', 'resource-access'],
6
+ ])
7
+
8
+ function fail(label) { throw new TypeError(`ptc-plus: invalid capability statistics ${label}`) }
9
+ function record(value, label) {
10
+ if (!value || typeof value !== 'object' || Array.isArray(value)) fail(label)
11
+ return value
12
+ }
13
+ function identity(value, label) {
14
+ if (typeof value !== 'string' || !value.trim() || value.length > 4096) fail(label)
15
+ return value
16
+ }
17
+ function integer(value, label, minimum = 0) {
18
+ if (!Number.isSafeInteger(value) || value < minimum) fail(label)
19
+ return value
20
+ }
21
+ function key(value) { return JSON.stringify(value) }
22
+ function orderSamples(a, b) { return b.eligibleOrder - a.eligibleOrder }
23
+ function freeze(value) {
24
+ if (value && typeof value === 'object') {
25
+ for (const item of Object.values(value)) freeze(item)
26
+ Object.freeze(value)
27
+ }
28
+ return value
29
+ }
30
+
31
+ /** Bounds are explicit calibration inputs; no observation, clock or process directory is inferred. */
32
+ export function createCapabilityStatistics(configuration) {
33
+ const options = record(configuration, 'configuration')
34
+ const limits = {}
35
+ for (const name of ['windowSize', 'maxFamilies', 'maxScopes', 'maxCapabilitiesPerFamily']) limits[name] = integer(options[name], name, 1)
36
+ return freeze({ schema: 'ptc-capability-statistics/v1', status: 'usable', limits, families: [], windows: [] })
37
+ }
38
+
39
+ function observation(raw) {
40
+ const value = record(raw, 'availability observation')
41
+ const category = value.category
42
+ if (!CATEGORIES.has(category)) fail('category')
43
+ if (!['full', 'on-demand', 'host'].includes(value.disclosure)) fail('availability disclosure')
44
+ const useEvidence = value.useEvidence ?? null
45
+ if (useEvidence !== null && useEvidence !== USE_EVIDENCE.get(category)) fail('owner use evidence')
46
+ const outcome = useEvidence === null ? null : value.outcome ?? 'unknown'
47
+ if (outcome !== null && !OUTCOMES.has(outcome)) fail('owner outcome evidence')
48
+ return {
49
+ workspaceId: identity(value.workspaceId, 'workspace identity'),
50
+ id: identity(value.id, 'capability identity'), revision: identity(value.revision, 'capability revision'), category,
51
+ disclosure: value.disclosure, planRevision: identity(value.planRevision, 'plan revision'),
52
+ used: useEvidence !== null, outcome,
53
+ }
54
+ }
55
+
56
+ function sampleObservation(value) {
57
+ const item = record(value, 'retained observation')
58
+ identity(item.workspaceId, 'retained workspace identity')
59
+ identity(item.id, 'retained capability identity')
60
+ identity(item.revision, 'retained capability revision')
61
+ if (!CATEGORIES.has(item.category) || typeof item.used !== 'boolean' || !Array.isArray(item.exposures) || item.exposures.length < 1) fail('retained evidence')
62
+ if (!Array.isArray(item.outcomes) || item.outcomes.some(outcome => !OUTCOMES.has(outcome))
63
+ || new Set(item.outcomes).size !== item.outcomes.length || item.used !== (item.outcomes.length > 0)) fail('retained outcomes')
64
+ for (const exposure of item.exposures) {
65
+ record(exposure, 'retained exposure')
66
+ if (!['full', 'on-demand', 'host'].includes(exposure.disclosure)) fail('retained disclosure')
67
+ identity(exposure.planRevision, 'retained plan revision')
68
+ }
69
+ }
70
+
71
+ /** Validate retained state before it can supply an automatic-allocation denominator. */
72
+ export function validateCapabilityStatistics(state) {
73
+ const value = record(state, 'state')
74
+ if (value.schema !== 'ptc-capability-statistics/v1' || !['usable', 'capacity-exceeded'].includes(value.status)) fail('state schema or status')
75
+ const template = createCapabilityStatistics(value.limits)
76
+ if (!Array.isArray(value.families) || !Array.isArray(value.windows)
77
+ || value.families.length > template.limits.maxFamilies || value.windows.length > template.limits.maxScopes) fail('state bounds')
78
+ const familyKeys = new Set()
79
+ const orders = new Set()
80
+ for (const family of value.families) {
81
+ record(family, 'retained family')
82
+ const familyKey = key([identity(family.userId, 'user identity'), identity(family.familyId, 'family identity')])
83
+ if (familyKeys.has(familyKey)) fail('duplicate family')
84
+ familyKeys.add(familyKey)
85
+ integer(family.observationRevision, 'observation revision')
86
+ if (!['active', 'settled', 'unknown'].includes(family.settlement)) fail('family settlement')
87
+ if (family.eligibleOrder !== null) {
88
+ integer(family.eligibleOrder, 'qualification order', 1)
89
+ const orderKey = key([family.userId, family.eligibleOrder])
90
+ if (orders.has(orderKey)) fail('duplicate qualification order')
91
+ orders.add(orderKey)
92
+ } else if (family.settlement === 'settled') fail('settled family without qualification order')
93
+ if (!Array.isArray(family.observations) || family.observations.length > value.limits.maxCapabilitiesPerFamily) fail('family observation bounds')
94
+ const observationKeys = new Set()
95
+ for (const item of family.observations) {
96
+ sampleObservation(item)
97
+ const observationKey = key([item.workspaceId, item.id, item.revision])
98
+ if (observationKeys.has(observationKey)) fail('duplicate observation')
99
+ observationKeys.add(observationKey)
100
+ if (item.exposures.length > value.limits.maxCapabilitiesPerFamily) fail('exposure bounds')
101
+ }
102
+ }
103
+ const windowKeys = new Set()
104
+ for (const window of value.windows) {
105
+ record(window, 'window')
106
+ identity(window.userId, 'window user identity')
107
+ if (window.workspaceId !== null) identity(window.workspaceId, 'window workspace identity')
108
+ identity(window.id, 'window capability identity')
109
+ identity(window.revision, 'window capability revision')
110
+ integer(window.floor, 'evicted qualification frontier')
111
+ const windowKey = key([window.userId, window.workspaceId, window.id, window.revision])
112
+ if (windowKeys.has(windowKey)) fail('duplicate window')
113
+ windowKeys.add(windowKey)
114
+ }
115
+ return value
116
+ }
117
+
118
+ function windowKey(userId, workspaceId, item) { return key([userId, workspaceId, item.id, item.revision]) }
119
+ function qualifies(family, window) {
120
+ return family.userId === window.userId && family.settlement === 'settled' && family.eligibleOrder > window.floor
121
+ && family.observations.some(item => item.id === window.id && item.revision === window.revision
122
+ && (window.workspaceId === null || item.workspaceId === window.workspaceId))
123
+ }
124
+
125
+ /**
126
+ * Public adapters supply actual family/workspace identities, monotone observation revisions,
127
+ * and an immutable qualification order. Observations are cumulative additions, never log guesses.
128
+ * A resumed or unknown family contributes no negative sample until owner-proven settlement.
129
+ */
130
+ export function reduceCapabilityStatistics(previous, event) {
131
+ validateCapabilityStatistics(previous)
132
+ if (previous.status !== 'usable') return previous
133
+ const value = record(event, 'event')
134
+ if (value.type !== 'family-observation') fail('event type')
135
+ const userId = identity(value.userId, 'user identity')
136
+ const familyId = identity(value.familyId, 'family identity')
137
+ const observationRevision = integer(value.observationRevision, 'observation revision')
138
+ const settlement = value.settlement
139
+ if (!['active', 'settled', 'unknown'].includes(settlement)) fail('family settlement')
140
+ const eligibleOrder = value.eligibleOrder == null ? null : integer(value.eligibleOrder, 'qualification order', 1)
141
+ if (settlement === 'settled' && eligibleOrder === null) fail('settlement qualification order')
142
+ if (!Array.isArray(value.observations) || value.observations.length > previous.limits.maxCapabilitiesPerFamily) fail('observation bounds')
143
+ const additions = value.observations.map(observation)
144
+ const state = structuredClone(previous)
145
+ let family = state.families.find(item => item.userId === userId && item.familyId === familyId)
146
+ if (family) {
147
+ if (family.eligibleOrder !== null && eligibleOrder !== family.eligibleOrder) fail('changed qualification order')
148
+ if (observationRevision <= family.observationRevision) return previous
149
+ family.observationRevision = observationRevision
150
+ family.settlement = settlement
151
+ family.eligibleOrder ??= eligibleOrder
152
+ } else {
153
+ if (eligibleOrder !== null && state.families.some(item => item.userId === userId && item.eligibleOrder === eligibleOrder)) fail('qualification order collision')
154
+ family = { userId, familyId, observationRevision, settlement, eligibleOrder, observations: [] }
155
+ state.families.push(family)
156
+ }
157
+ for (const addition of additions) {
158
+ let retained = family.observations.find(item => item.workspaceId === addition.workspaceId && item.id === addition.id && item.revision === addition.revision)
159
+ if (!retained) {
160
+ retained = { workspaceId: addition.workspaceId, id: addition.id, revision: addition.revision, category: addition.category, used: false, exposures: [], outcomes: [] }
161
+ family.observations.push(retained)
162
+ } else if (retained.category !== addition.category) fail('changed capability category')
163
+ retained.used ||= addition.used
164
+ if (addition.outcome !== null && !retained.outcomes.includes(addition.outcome)) retained.outcomes.push(addition.outcome)
165
+ const exposure = { disclosure: addition.disclosure, planRevision: addition.planRevision }
166
+ if (!retained.exposures.some(item => item.disclosure === exposure.disclosure && item.planRevision === exposure.planRevision)) retained.exposures.push(exposure)
167
+ for (const workspaceId of [addition.workspaceId, null]) {
168
+ const targetKey = windowKey(userId, workspaceId, addition)
169
+ if (!state.windows.some(item => windowKey(item.userId, item.workspaceId, item) === targetKey)) {
170
+ state.windows.push({ userId, workspaceId, id: addition.id, revision: addition.revision, floor: 0 })
171
+ }
172
+ }
173
+ }
174
+ const retainedFamilies = new Set()
175
+ for (const window of state.windows) {
176
+ const eligible = state.families.filter(item => qualifies(item, window)).sort(orderSamples)
177
+ const selected = eligible.slice(0, state.limits.windowSize)
178
+ for (const item of selected) retainedFamilies.add(key([item.userId, item.familyId]))
179
+ for (const item of eligible.slice(state.limits.windowSize)) window.floor = Math.max(window.floor, item.eligibleOrder)
180
+ }
181
+ state.families = state.families.filter(item => item.settlement !== 'settled' || retainedFamilies.has(key([item.userId, item.familyId])))
182
+ if (state.families.length > state.limits.maxFamilies || state.windows.length > state.limits.maxScopes
183
+ || state.families.some(item => item.observations.length > state.limits.maxCapabilitiesPerFamily
184
+ || item.observations.some(entry => entry.exposures.length > state.limits.maxCapabilitiesPerFamily))) {
185
+ return freeze({ ...structuredClone(previous), status: 'capacity-exceeded' })
186
+ }
187
+ validateCapabilityStatistics(state)
188
+ return freeze(state)
189
+ }
190
+
191
+ /** Per-capability eligible windows; workspace and global evidence are never added together. */
192
+ export function capabilityCoverage(state, query) {
193
+ validateCapabilityStatistics(state)
194
+ const options = record(query, 'coverage query')
195
+ const userId = identity(options.userId, 'coverage user identity')
196
+ const id = identity(options.id, 'coverage capability identity')
197
+ const revision = identity(options.revision, 'coverage capability revision')
198
+ const workspaceId = options.workspaceId === null ? null : identity(options.workspaceId, 'coverage workspace identity')
199
+ if (state.status !== 'usable') return { status: state.status, windowSize: state.limits.windowSize, eligible: null, used: null, coverage: null }
200
+ const window = state.windows.find(item => item.userId === userId && item.workspaceId === workspaceId && item.id === id && item.revision === revision)
201
+ const families = window ? state.families.filter(item => qualifies(item, window)).sort(orderSamples).slice(0, state.limits.windowSize) : []
202
+ // A resumed family can expose a gap behind the irreversible eviction frontier.
203
+ // Surviving samples do not prove the recent settled window until it is full again.
204
+ if (window?.floor > 0 && families.length < state.limits.windowSize) {
205
+ return { status: 'incomplete-window', windowSize: state.limits.windowSize, eligible: null, used: null, coverage: null }
206
+ }
207
+ const used = families.filter(family => family.observations.some(item => item.id === id && item.revision === revision && item.used
208
+ && (workspaceId === null || item.workspaceId === workspaceId))).length
209
+ return { status: 'usable', windowSize: state.limits.windowSize, eligible: families.length, used, coverage: families.length ? used / families.length : null }
210
+ }
@@ -20,6 +20,7 @@ const nativePathToFileURL = pathToFileURL
20
20
  const nativeStripTypeScriptTypes = stripTypeScriptTypes
21
21
  const runInContext = Script.prototype.runInContext
22
22
  const NativeUint8Array = Uint8Array
23
+ const typedArrayLength = descriptor(Object.getPrototypeOf(NativeUint8Array.prototype), 'length').get
23
24
  const NativeWeakRef = WeakRef, deref = WeakRef.prototype.deref
24
25
  const NativeError = Error
25
26
  const parseJson = JSON.parse
@@ -65,11 +66,12 @@ export const compilerPlatformBridge = {
65
66
  fileURLToPath: input => nativeFileURLToPath(nativeUrl(input)),
66
67
  pathToFileURL: input => ownUrl(nativePathToFileURL(input)),
67
68
  encode(source, encoding) {
68
- const buffer = new NativeUint8Array(byteLength(source, encoding))
69
- const length = apply(codecs[encoding].write, buffer, [source, 0, buffer.length])
69
+ const size = byteLength(source, encoding)
70
+ const buffer = new NativeUint8Array(size)
71
+ const length = apply(codecs[encoding].write, buffer, [source, 0, size])
70
72
  return { buffer, length }
71
73
  },
72
- decode: (buffer, encoding) => apply(codecs[encoding].slice, buffer, [0, buffer.length]),
74
+ decode: (buffer, encoding) => apply(codecs[encoding].slice, buffer, [0, apply(typedArrayLength, buffer, [])]),
73
75
  textEncoder() {
74
76
  const encoder = new NativeTextEncoder()
75
77
  return { encode: source => apply(encoderEncode, encoder, [source]),
@@ -2,7 +2,6 @@ import { readFileSync } from 'node:fs'
2
2
  import { constants, createContext, Script } from 'node:vm'
3
3
  import { fileURLToPath } from 'node:url'
4
4
  import { workerData } from 'node:worker_threads'
5
- import { deserialize } from 'node:v8'
6
5
  import { copyCompilerData } from './compiler-data.js'
7
6
  import { PreflightError } from './cell-analysis-contract.js'
8
7
  import { ModuleRewriteError } from './cell-error.js'
@@ -29,10 +28,7 @@ const define = compilerDescriptors.defineProperty
29
28
  const keys = Object.keys
30
29
  let compiler
31
30
  let compilerScript
32
- const coverageEnabled = Boolean(process.env.NODE_V8_COVERAGE)
33
- const bytecodeFile = process.env.DSH_PTC_COMPILER_BYTECODE
34
- let workerCache = bytecodeFile ? deserialize(readFileSync(bytecodeFile)) : undefined
35
- if (workerCache?.source !== compilerSource) workerCache = undefined
31
+ let workerCache
36
32
  // Bytecode contains no compiler realm or program state. Match the complete
37
33
  // source: V8's cache validation alone does not prove source text equality.
38
34
  let cachedData = workerData?.compilerCache?.source === compilerSource
@@ -43,9 +39,6 @@ if (workerData !== null && typeof workerData === 'object') delete workerData.com
43
39
  /** Reuse compiled compiler code across fresh workers, never their realm state. */
44
40
  export function compilerWorkerCache() {
45
41
  if (compilerScript === undefined) return undefined
46
- // Instrumented compiler instances neither produce nor consume bytecode.
47
- // Coverage's runner supplies a snapshot from its uninstrumented preparation.
48
- if (workerCache === undefined && coverageEnabled) return undefined
49
42
  workerCache ??= { source: compilerSource, data: apply(createCachedData, compilerScript, []) }
50
43
  return { source: workerCache.source, data: new NativeUint8Array(workerCache.data) }
51
44
  }
@@ -58,7 +51,7 @@ function compilerEntry() {
58
51
  // Workers can execute host-prepared cells without compiling dynamic source.
59
52
  // Capture source and platform before user code, but parse only on first use.
60
53
  compilerScript = new NativeScript(compilerSource, { __proto__: null, filename, displayErrors: false,
61
- cachedData: coverageEnabled ? undefined : cachedData })
54
+ cachedData })
62
55
  cachedData = undefined
63
56
  const context = nativeCreateContext(dontContextify)
64
57
  define(context, '__compilerBaseUrl', { value: import.meta.url })
@@ -1,3 +1,5 @@
1
+ import { CAPABILITY_POLICY_DEFAULT } from './capability-policy.js'
2
+
1
3
  /** Configuration fields shared by the Host schema and settings card. */
2
4
  export const MAX_TIMER_DELAY_MS = 2_147_483_647
3
5
 
@@ -11,6 +13,54 @@ export const SETTINGS_NAMESPACE = 'ptc-plus'
11
13
  * exists in both locales or in neither.
12
14
  */
13
15
  export const CONFIG_FIELDS = Object.freeze([
16
+ { key: 'capabilityContextEnabled', type: 'boolean', default: true,
17
+ label: '能力与上下文', labelEn: 'Capabilities and context',
18
+ description: '整体暂停能力规则、统计采集、说明恢复和结果预览,保留各项设置;新会话或明确刷新后生效。',
19
+ descriptionEn: 'Pauses capability rules, statistics collection, description recovery and result previews while retaining each setting. Applies to new sessions or explicit refreshes.' },
20
+ { key: 'capabilityManagementEnabled', type: 'boolean', default: false,
21
+ label: '实验性能力管理', labelEn: 'Experimental capability management',
22
+ description: '为新 PTC 会话应用可用性与说明规则;已有会话使用 /capabilities refresh 生效。收益尚未经过模型对照验收。',
23
+ descriptionEn: 'Applies availability and disclosure rules to new PTC sessions. Use /capabilities refresh for existing sessions. Model cost benefits remain unverified.' },
24
+ { key: 'capabilityPolicy', preserveOmission: true, type: 'string', default: CAPABILITY_POLICY_DEFAULT,
25
+ label: '能力规则', labelEn: 'Capability rules',
26
+ description: '分别设置可用性、说明方式以及常用集、统计和预算分配;默认、类别、MCP 来源、单项依次覆盖。',
27
+ descriptionEn: 'Choose availability, disclosure and baseline, statistics and budget allocation separately. Default, category, MCP source and item rules apply in order.' },
28
+ { key: 'capabilityRecoveryEnabled', preserveOmission: true, type: 'boolean', default: false,
29
+ label: '压缩后恢复已使用的 API 说明', labelEn: 'Recover used API descriptions after compaction',
30
+ description: '只补回已展开、仍有效且不可见的近期 API 说明,不恢复 Skill 正文。',
31
+ descriptionEn: 'Restores only expanded, current, no longer visible API descriptions from recent use; never Skill bodies.' },
32
+ { key: 'modelPreviewEnabled', preserveOmission: true, type: 'boolean', default: false,
33
+ label: '限制模型侧结果与日志展示', labelEn: 'Bound model-facing results and logs',
34
+ description: '独立使用插件预览预算;关闭时沿用宿主展示,程序值始终完整。',
35
+ descriptionEn: 'Independently selects the plugin preview budget. Otherwise retains Host presentation; program values remain complete.' },
36
+ { key: 'capabilityBaseline', type: 'string', default: '[]',
37
+ label: '初始常用能力', labelEn: 'Initial common capabilities',
38
+ description: '从能力列表勾选常用项;adaptive 选用常用集依据时参与分配,不生成使用记录。',
39
+ descriptionEn: 'Select common capabilities as an optional allocation basis for adaptive disclosure. Creates no usage records.' },
40
+ { key: 'capabilityOverrides', type: 'string', default: '{}',
41
+ label: '旧版能力选择', labelEn: 'Legacy capability selections',
42
+ description: '旧配置迁移输入;新规则由能力工作台分别保存可用性与说明方式。',
43
+ descriptionEn: 'Migration input for older settings. The workbench saves availability and disclosure separately in the policy.' },
44
+ { key: 'capabilityStatistics', type: 'string',
45
+ default: '{"enabled":false,"windowSize":100,"minimumSamples":10,"fullThreshold":0.5}',
46
+ label: '能力统计设置', labelEn: 'Capability statistics settings',
47
+ description: '独立采集使用统计,并设置样本窗口、最低样本数和完整披露阈值;关闭或不可用时保留手动基线。',
48
+ descriptionEn: 'Collect usage statistics independently and set the sample window, minimum samples and full-disclosure threshold. Disabled or unavailable statistics preserve the manual baseline.' },
49
+ { key: 'capabilityProfiles', type: 'string', default: '{"schema":"ptc-capability-profiles/v1","profiles":[]}',
50
+ label: '保存的能力配置', labelEn: 'Saved capability configurations',
51
+ description: '在能力工作台命名保存、导入导出和切换配置;已有会话需要主动刷新。',
52
+ descriptionEn: 'Name, save, import, export and switch configurations in the capability workbench. Existing sessions require explicit refresh.' },
53
+ { key: 'capabilityBudgetBytes', type: 'integer', default: 16384, min: 1, max: Number.MAX_SAFE_INTEGER,
54
+ label: '完整说明预算 (UTF-8 bytes)', labelEn: 'Full contract budget (UTF-8 bytes)',
55
+ description: '显式 full 可以超出此预算;不截断调用契约。', descriptionEn: 'Explicit full selections may exceed this budget; calling contracts are never truncated.' },
56
+ { key: 'capabilityDirectoryBytes', type: 'integer', default: 2048, min: 512, max: Number.MAX_SAFE_INTEGER,
57
+ label: '能力导航预算 (UTF-8 bytes)', labelEn: 'Capability navigation budget (UTF-8 bytes)', description: '', descriptionEn: '' },
58
+ { key: 'capabilityRecoveryBytes', type: 'integer', default: 8192, min: 1, max: Number.MAX_SAFE_INTEGER,
59
+ label: '压缩后说明恢复预算 (UTF-8 bytes)', labelEn: 'Description recovery budget (UTF-8 bytes)', description: '', descriptionEn: '' },
60
+ { key: 'modelPreviewTokens', type: 'integer', default: 4096, min: 1, max: Number.MAX_SAFE_INTEGER,
61
+ label: '模型结果预览预算 (estimated tokens)', labelEn: 'Model result preview budget (estimated tokens)',
62
+ description: '选用模型侧预览时复用 DSH spill policy;完整内容需要宿主 spill store,程序值保持完整。',
63
+ descriptionEn: 'Uses DSH spill policy when model-facing previews are selected. Complete content requires a host spill store; program values remain complete.' },
14
64
  {
15
65
  key: 'legacyBindingSettings',
16
66
  type: 'boolean',
@@ -38,6 +88,15 @@ export const CONFIG_FIELDS = Object.freeze([
38
88
  description: '开启时使用增强工具卡片;关闭后使用 DSH 原生工具卡片。',
39
89
  descriptionEn: 'Uses enhanced tool cards when enabled and DSH native tool cards when disabled.',
40
90
  },
91
+ {
92
+ key: 'toolHoverPreviewEnabled',
93
+ type: 'boolean',
94
+ default: true,
95
+ label: '悬停预览执行内容',
96
+ labelEn: 'Preview execution content on hover',
97
+ description: '在增强执行卡片上停留或键盘聚焦时显示简短预览;滚动时自动隐藏。',
98
+ descriptionEn: 'Shows a quick preview on enhanced tool cards after hovering or keyboard focus; hides while scrolling.',
99
+ },
41
100
  {
42
101
  key: 'replViewEnabled',
43
102
  type: 'boolean',
@@ -53,8 +112,8 @@ export const CONFIG_FIELDS = Object.freeze([
53
112
  default: true,
54
113
  label: '显示 PTC Plus 快捷入口',
55
114
  labelEn: 'Show the PTC Plus shortcut',
56
- description: '在输入框旁显示 PTC Plus 快捷入口:查看和启停全局绑定、编写新绑定,并直接打开插件设置窗口;隐藏后仍可使用 /binding 命令。',
57
- descriptionEn: 'Shows the PTC Plus shortcut beside the composer: global binding access and toggles, authoring, and direct access to the plugin settings dialog. Hiding it keeps /binding commands available.',
115
+ description: '在输入框旁显示星光菜单:打开插件设置、刷新当前会话能力,并在全局绑定开启时查看、启停和编写绑定;隐藏后仍可使用斜杠命令。',
116
+ descriptionEn: 'Shows the sparkle menu beside the composer: open settings, refresh current-session capabilities, and manage or author global bindings when enabled. Hiding it does not remove slash commands.',
58
117
  },
59
118
  {
60
119
  key: 'autoDescribeRunCode',
@@ -296,12 +355,6 @@ export const CONFIG_GROUPS = Object.freeze([
296
355
  labelEn: 'Plugin switch',
297
356
  fields: Object.freeze(['enabled']),
298
357
  },
299
- {
300
- key: 'calls',
301
- label: '调用容错',
302
- labelEn: 'Tool call tolerance',
303
- fields: Object.freeze(['autoDescribeRunCode', 'canonicalizeToolCalls']),
304
- },
305
358
  {
306
359
  key: 'syntax',
307
360
  label: 'REPL 语法',
@@ -322,11 +375,17 @@ export const CONFIG_GROUPS = Object.freeze([
322
375
  labelEn: 'Tool extensions',
323
376
  fields: Object.freeze(['userBindingsEnabled', 'cordisToolsEnabled']),
324
377
  },
378
+ {
379
+ key: 'calls',
380
+ label: '调用容错',
381
+ labelEn: 'Tool call tolerance',
382
+ fields: Object.freeze(['autoDescribeRunCode', 'canonicalizeToolCalls']),
383
+ },
325
384
  {
326
385
  key: 'interface',
327
386
  label: '界面显示',
328
387
  labelEn: 'Interface display',
329
- fields: Object.freeze(['enhancedToolView', 'replViewEnabled', 'bindingAuthorButtonVisible']),
388
+ fields: Object.freeze(['enhancedToolView', 'toolHoverPreviewEnabled', 'replViewEnabled', 'bindingAuthorButtonVisible']),
330
389
  },
331
390
  {
332
391
  key: 'limits',
@@ -344,8 +403,19 @@ export const CONFIG_GROUPS = Object.freeze([
344
403
  'maxValueBigIntDigits',
345
404
  ]),
346
405
  },
406
+ { key: 'capabilities', label: '能力与上下文', labelEn: 'Capabilities and context',
407
+ fields: Object.freeze(['capabilityContextEnabled', 'capabilityManagementEnabled', 'capabilityBaseline', 'capabilityOverrides',
408
+ 'capabilityStatistics', 'capabilityPolicy', 'capabilityRecoveryEnabled', 'modelPreviewEnabled', 'capabilityProfiles', 'capabilityBudgetBytes', 'capabilityDirectoryBytes', 'capabilityRecoveryBytes', 'modelPreviewTokens']) },
347
409
  ])
348
410
 
411
+ /** Functional modules share the same section order across every settings entry. */
412
+ export const CONFIG_MODULES = Object.freeze([
413
+ { key: 'workspace', label: '核心工作区', labelEn: 'Core workspace', groups: Object.freeze(['syntax', 'recovery']) },
414
+ { key: 'extensions', label: '扩展与容错', labelEn: 'Extensions and tolerance', groups: Object.freeze(['extensions', 'calls']) },
415
+ { key: 'presentation', label: '界面与资源', labelEn: 'Interface and resources', groups: Object.freeze(['interface', 'limits']) },
416
+ { key: 'experimental', label: '实验性', labelEn: 'Experimental', groups: Object.freeze(['capabilities']) },
417
+ ].map(Object.freeze))
418
+
349
419
  /** Resolved defaults, derived once from the field definitions. */
350
420
  export const CONFIG_DEFAULTS = Object.freeze(
351
421
  Object.fromEntries(CONFIG_FIELDS.map(field => [field.key, field.default])),
@@ -149,8 +149,9 @@ function containsCompanionSkill(directory, fileExists) {
149
149
 
150
150
  /** Resolve the official Cordis companion Skill across DSH preset generations. */
151
151
  export async function resolveCompanionSkillDirectory(agentPresets, options = {}) {
152
- const fileExists = options.fileExists ?? existsSync
153
152
  const preset = await agentPresets.resolve(CORDIS_PRESET_ID)
153
+ const resolution = typeof options === 'function' ? options() : options
154
+ const fileExists = resolution.fileExists ?? existsSync
154
155
  if (typeof preset?.broken === 'string' && preset.broken.length > 0) {
155
156
  throw new Error(`ptc-plus: DSH cordis preset is unavailable: ${preset.broken}`)
156
157
  }
@@ -166,7 +167,7 @@ export async function resolveCompanionSkillDirectory(agentPresets, options = {})
166
167
  }
167
168
 
168
169
  for (const layout of COMPANION_PACKAGE_LAYOUTS) {
169
- const pkg = options.packageOf?.(layout.name, options.packageBaseUrl)
170
+ const pkg = resolution.packageOf?.(layout.name, resolution.packageBaseUrl)
170
171
  if (pkg === undefined) continue
171
172
  if (pkg?.name !== layout.name || typeof pkg.dir !== 'string' || !isAbsolute(pkg.dir)) {
172
173
  throw new Error(`ptc-plus: DSH pluginPackages returned an invalid ${layout.name} package resource`)
@@ -175,10 +176,10 @@ export async function resolveCompanionSkillDirectory(agentPresets, options = {})
175
176
  if (containsCompanionSkill(directory, fileExists)) return directory
176
177
  }
177
178
 
178
- if (options.packageOf === undefined && options.resolveLegacyPackageManifest !== undefined) {
179
+ if (resolution.packageOf === undefined && resolution.resolveLegacyPackageManifest !== undefined) {
179
180
  let manifest
180
181
  try {
181
- manifest = options.resolveLegacyPackageManifest('@deepseek-ai/dsh-agent-presets/package.json')
182
+ manifest = resolution.resolveLegacyPackageManifest('@deepseek-ai/dsh-agent-presets/package.json')
182
183
  } catch (error) {
183
184
  if (error?.code !== 'MODULE_NOT_FOUND' && error?.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') throw error
184
185
  }
@@ -320,22 +321,24 @@ export function createCordisToolsOwner(
320
321
  const cordisInspectLeases = createCordisInspectLeases(ctx)
321
322
  const scopedCordisPlugin = cordisInspectLeases.plugin(cordisPlugin)
322
323
  const scopedSkillPlugin = exactCompanionSkillPlugin(skillFilesystemPlugin)
323
- const pluginPackages = ctx.get?.('pluginPackages')
324
324
  const packageBaseUrl = ctx.baseUrl
325
- const skillResolution = companionResolution ?? {
326
- packageOf: pluginPackages === undefined
327
- ? undefined
328
- : (name, parentURL) => {
329
- if (typeof pluginPackages?.packageOf !== 'function') {
330
- throw new Error('ptc-plus: cordisToolsEnabled requires the DSH pluginPackages.packageOf API')
331
- }
332
- return pluginPackages.packageOf(name, parentURL)
333
- },
334
- packageBaseUrl,
335
- resolveLegacyPackageManifest: pluginPackages === undefined && typeof packageBaseUrl === 'string'
336
- ? specifier => createRequire(packageBaseUrl).resolve(specifier)
337
- : undefined,
338
- }
325
+ const skillResolution = companionResolution ?? (() => {
326
+ const pluginPackages = ctx.get?.('pluginPackages')
327
+ return {
328
+ packageOf: pluginPackages === undefined
329
+ ? undefined
330
+ : (name, parentURL) => {
331
+ if (typeof pluginPackages?.packageOf !== 'function') {
332
+ throw new Error('ptc-plus: cordisToolsEnabled requires the DSH pluginPackages.packageOf API')
333
+ }
334
+ return pluginPackages.packageOf(name, parentURL)
335
+ },
336
+ packageBaseUrl,
337
+ resolveLegacyPackageManifest: pluginPackages === undefined && typeof packageBaseUrl === 'string'
338
+ ? specifier => createRequire(packageBaseUrl).resolve(specifier)
339
+ : undefined,
340
+ }
341
+ })
339
342
  let disposed = false
340
343
 
341
344
  const leafErrors = error => error instanceof AggregateError