dsh-ptc-plus 0.4.2 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +166 -76
- package/README.md +167 -77
- package/assets/dsh-ptc-plus-banner-en.webp +0 -0
- package/assets/dsh-ptc-plus-banner-zh.webp +0 -0
- package/assets/ptc-plus-icon.svg +5 -0
- package/client.js +593 -306
- package/compiler-core.cjs +162 -160
- package/compiler-core.cjs.map +3 -3
- package/docs/README.md +3 -1
- package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +8 -7
- package/docs/adr/0008-define-agent-operating-contract.md +2 -2
- package/docs/adr/0010-session-log-derived-recovery-tips.md +7 -0
- package/docs/adr/0016-archive-resolved-review-findings.md +3 -3
- package/docs/adr/0019-plugin-settings-and-kill-switch.md +3 -3
- package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -0
- package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +14 -3
- package/docs/adr/0023-global-user-bindings.md +1 -1
- package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +23 -7
- package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +4 -0
- package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +26 -6
- package/docs/adr/0031-close-semantic-obligations-by-owner.md +29 -0
- package/docs/adr/README.md +4 -4
- package/docs/architecture.md +6 -5
- package/docs/binding-review-placement-design.md +1 -1
- package/docs/client-ui.md +8 -4
- package/docs/durability-design.md +7 -7
- package/docs/installation.md +26 -8
- package/docs/runtime-reference.md +18 -11
- package/docs/semantic-validation.md +7 -3
- package/docs/user-bindings.en.md +7 -6
- package/docs/user-bindings.md +6 -5
- package/docs/value-wire.md +1 -1
- package/docs/verification.md +38 -1
- package/index.js +39 -34
- package/internal/async-local-storage-intrinsics.js +12 -7
- package/internal/callable-source-facts.js +37 -1
- package/internal/cell-analysis.js +64 -9
- package/internal/config-spec.js +1 -1
- package/internal/cordis-tools-owner.js +20 -10
- package/internal/edit-transport-owner.js +80 -20
- package/internal/execution-seam-compat.js +192 -30
- package/internal/kernel-worker.js +162 -72
- package/internal/message-sources.js +14 -0
- package/internal/module-compilation.js +2 -1
- package/internal/repl-memory-projection.js +10 -0
- package/internal/repl-scope-normalizer.js +72 -35
- package/internal/repl-value-observer.js +141 -45
- package/internal/runtime-config.js +62 -2
- package/internal/runtime-intrinsics.js +3 -0
- package/internal/runtime-messages.js +6 -4
- package/internal/session-cell-executor.js +24 -1
- package/internal/session-journal-recovery.js +267 -67
- package/internal/session-journal.js +466 -40
- package/internal/session-log-view.js +5 -4
- package/internal/session-runtime.js +45 -21
- package/internal/session-state.js +7 -29
- package/internal/settings-compat.js +66 -1
- package/internal/source-position-map.js +19 -0
- package/internal/user-binding-console-worker.js +23 -9
- package/internal/user-binding-draft-projection.js +3 -2
- package/internal/user-bindings-owner.js +2 -1
- package/internal/user-bindings.js +6 -1
- package/internal/validated-parse-repair.js +6 -2
- package/internal/value-wire.js +95 -30
- package/internal/worker-client.js +99 -8
- package/internal/worker-cwd-virtualization.js +10 -9
- package/internal/worker-output-capture.js +187 -0
- package/internal/worker-output-fence.js +26 -0
- package/internal/worker-realm-surfaces.js +197 -0
- package/internal/worker-repl-realm.js +10 -4
- package/package.json +36 -4
- package/src/client-activity.js +4 -1
- package/src/client-authoring-view.js +121 -62
- package/src/client-console.js +4 -2
- package/src/client-copy.js +4 -2
- package/src/client-feature-gates.js +7 -9
- package/src/client-host-compat.js +104 -27
- package/src/client-repl-view.js +3 -1
- package/src/client-styles.js +12 -1
- package/src/client-tool-view.js +3 -1
- package/src/client-transport.js +2 -2
- package/src/client-workbench.js +4 -3
- package/src/client.js +56 -17
|
@@ -23,7 +23,7 @@ import { supportsNativeUsing, transformTypeScriptSource } from './typescript-tra
|
|
|
23
23
|
import { createDynamicScopeAnalysis, varInitializerTarget } from './dynamic-scope-analysis.js'
|
|
24
24
|
import { visitSource } from './compiler-source-regions.js'
|
|
25
25
|
import { analyzeSourceDeclarations, resolveSourceBinding, COMMONJS_PARAMETERS } from './compiler-scope-facts.js'
|
|
26
|
-
import { callableMarkerPosition } from './callable-source-facts.js'
|
|
26
|
+
import { callableMarkerPosition, restoreMappedCallableSourceMarks } from './callable-source-facts.js'
|
|
27
27
|
import { createSourcePieces } from './compiler-source-pieces.js'
|
|
28
28
|
import { indexSourceRegions, validateRegionSource, sourceFeatures } from './compiler-region-output.js'
|
|
29
29
|
|
|
@@ -424,7 +424,7 @@ export function lowerStatefulDecorators(result, target, intrinsicContext) {
|
|
|
424
424
|
|
|
425
425
|
/** Erase type-only declarations and lower runtime TypeScript values before
|
|
426
426
|
* logical ownership and module export/link plans consume the source. */
|
|
427
|
-
export function normalizeTypeScriptValues(code, sourceMap, target) {
|
|
427
|
+
export function normalizeTypeScriptValues(code, sourceMap, target, callableSources) {
|
|
428
428
|
const tree=parse(code,{...parserOptionsForTarget(target),errorRecovery:true})
|
|
429
429
|
let parameterProperties = false
|
|
430
430
|
let tokens
|
|
@@ -521,20 +521,15 @@ export function normalizeTypeScriptValues(code, sourceMap, target) {
|
|
|
521
521
|
if(enumEdits.length>0) source=applySourceEdits(source,identitySourceMap(source.length),enumEdits).code
|
|
522
522
|
const transformed=transformTypeScriptSource(source,{module:target!=='commonjs',sourceMap:true,
|
|
523
523
|
transform:{tsEnumIsMutable:true,noEmptyExport:true}})
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
originalEnd:Math.min(owner.end,path.node.start+start+1)})
|
|
534
|
-
if(transformed.code[index]==='\n'){line++;column=1}else column++
|
|
535
|
-
}
|
|
536
|
-
const exported=owner!==path.node&&transformed.code.trim()!==''?`\nexport {${path.node.id.name}};`:''
|
|
537
|
-
edits.push({start:owner.start,end:owner.end,text:transformed.code+exported,mappings})
|
|
524
|
+
let mapped=mappedSourceTransform(source,identitySourceMap(source.length),transformed)
|
|
525
|
+
mapped=restoreMappedCallableSourceMarks(mapped,source,transformed,callableSources,parserOptionsForTarget(target))
|
|
526
|
+
const mappings=[...mapped.sourceMap].map(mapping=>({
|
|
527
|
+
...mapping,
|
|
528
|
+
originalStart:path.node.start+mapping.originalStart,
|
|
529
|
+
originalEnd:Math.min(owner.end,path.node.start+mapping.originalEnd),
|
|
530
|
+
}))
|
|
531
|
+
const exported=owner!==path.node&&mapped.code.trim()!==''?`\nexport {${path.node.id.name}};`:''
|
|
532
|
+
edits.push({start:owner.start,end:owner.end,text:mapped.code+exported,mappings})
|
|
538
533
|
path.skip()
|
|
539
534
|
},
|
|
540
535
|
})
|
|
@@ -1130,23 +1125,46 @@ export function preserveStatementBoundaries(code, sourceMap = identitySourceMap(
|
|
|
1130
1125
|
* The root compiler owns Program bindings and calls this pass after lowering
|
|
1131
1126
|
* those declarations. protected-v1 keeps native local binding policy.
|
|
1132
1127
|
*/
|
|
1128
|
+
const normalizationFailureInputs = new WeakMap()
|
|
1129
|
+
|
|
1130
|
+
function retainNormalizationFailureInput(error, input) {
|
|
1131
|
+
if ((typeof error === 'object' && error !== null) || typeof error === 'function') {
|
|
1132
|
+
if (!normalizationFailureInputs.has(error)) normalizationFailureInputs.set(error, input)
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
/** Latest mapped normalization stage established before an error was thrown. */
|
|
1137
|
+
export function normalizationFailureInput(error) {
|
|
1138
|
+
return (typeof error === 'object' && error !== null) || typeof error === 'function'
|
|
1139
|
+
? normalizationFailureInputs.get(error) : undefined
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1133
1142
|
export function normalizeStatefulScopes(code, sourceMap = identitySourceMap(code.length), options = {}) {
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1143
|
+
let diagnosticInput = { code, sourceMap }
|
|
1144
|
+
try {
|
|
1145
|
+
const { outputPlan, ...metadata } = normalizeStatefulScopeInput(code, sourceMap, options)
|
|
1146
|
+
let result = metadata
|
|
1147
|
+
if (outputPlan) {
|
|
1148
|
+
// Release declaration ASTs and scope ancestry before materializing the
|
|
1149
|
+
// expanded output. This phase retains only numeric pieces and source facts.
|
|
1150
|
+
const { pieces, root, code, sourceMap, regionFacts } = outputPlan
|
|
1151
|
+
diagnosticInput = { code, sourceMap }
|
|
1152
|
+
options.onPhase?.('mapping')
|
|
1153
|
+
const output = pieces.emit(root)
|
|
1154
|
+
result = { ...metadata, ...applySourceEdits(code, sourceMap,
|
|
1155
|
+
[{ start: 0, end: code.length, ...output }]),
|
|
1156
|
+
sourceRegions: { ...regionFacts, regions: output.regions } }
|
|
1157
|
+
}
|
|
1158
|
+
diagnosticInput = result
|
|
1159
|
+
options.onPhase?.('validation')
|
|
1160
|
+
if (options.target === 'module' || options.target === 'commonjs') validateRegionSource(result, parserOptionsForTarget(options.target))
|
|
1161
|
+
result = options.deferDecorators ? result : lowerStatefulDecorators(result, options.target, options.intrinsicContext)
|
|
1162
|
+
diagnosticInput = result
|
|
1163
|
+
return options.deferResources ? result : lowerStatefulResources(result, options)
|
|
1164
|
+
} catch (error) {
|
|
1165
|
+
retainNormalizationFailureInput(error, diagnosticInput)
|
|
1166
|
+
throw error
|
|
1145
1167
|
}
|
|
1146
|
-
options.onPhase?.('validation')
|
|
1147
|
-
if (options.target === 'module' || options.target === 'commonjs') validateRegionSource(result, parserOptionsForTarget(options.target))
|
|
1148
|
-
result = options.deferDecorators ? result : lowerStatefulDecorators(result, options.target, options.intrinsicContext)
|
|
1149
|
-
return options.deferResources ? result : lowerStatefulResources(result, options)
|
|
1150
1168
|
}
|
|
1151
1169
|
|
|
1152
1170
|
/** Resource lifetime is lowered only after declaration owners have consumed
|
|
@@ -1227,15 +1245,33 @@ export function hasModuleResources(code) {
|
|
|
1227
1245
|
node.type === 'VariableDeclaration' && (node.kind === 'using' || node.kind === 'await using'))
|
|
1228
1246
|
}
|
|
1229
1247
|
|
|
1230
|
-
function normalizeStatefulScopeInput(code, sourceMap, {
|
|
1231
|
-
|
|
1248
|
+
function normalizeStatefulScopeInput(code, sourceMap, options) {
|
|
1249
|
+
let diagnosticInput = { code, sourceMap }
|
|
1250
|
+
try {
|
|
1251
|
+
return normalizeStatefulScopeInputUnchecked(code, sourceMap, options, input => { diagnosticInput = input })
|
|
1252
|
+
} catch (error) {
|
|
1253
|
+
retainNormalizationFailureInput(error, diagnosticInput)
|
|
1254
|
+
throw error
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
function normalizeStatefulScopeInputUnchecked(code, sourceMap, { mode = 'stateful-v1', target, moduleImport, intrinsicContext, onPhase,
|
|
1259
|
+
callableSources, nativeJavaScript = false, nativeUsing = supportsNativeUsing() } = {}, updateDiagnosticInput) {
|
|
1232
1260
|
if (mode !== 'stateful-v1' && mode !== 'protected-v1') throw new TypeError('ptc-plus: unsupported scope semantics')
|
|
1233
1261
|
onPhase?.('typescript')
|
|
1234
|
-
|
|
1235
|
-
|
|
1262
|
+
let typescript
|
|
1263
|
+
if (nativeJavaScript) {
|
|
1264
|
+
typescript = { code, sourceMap }
|
|
1265
|
+
} else {
|
|
1266
|
+
const normalizedTypescript = normalizeTypeScriptValues(code,sourceMap,target,callableSources)
|
|
1267
|
+
updateDiagnosticInput(normalizedTypescript)
|
|
1268
|
+
typescript = lowerLegacyParameterDecorators(normalizedTypescript,target,intrinsicContext)
|
|
1269
|
+
}
|
|
1270
|
+
updateDiagnosticInput(typescript)
|
|
1236
1271
|
const separated = preserveStatementBoundaries(typescript.code, typescript.sourceMap, parserOptionsForTarget(target))
|
|
1237
1272
|
code=separated.code
|
|
1238
1273
|
sourceMap=separated.sourceMap
|
|
1274
|
+
updateDiagnosticInput(separated)
|
|
1239
1275
|
const finish = result => {
|
|
1240
1276
|
result = { ...result, deferredHelpers: typescript.deferredHelpers,
|
|
1241
1277
|
internalBindings: new Set([...typescript.internalBindings ?? [], ...result.internalBindings ?? []]) }
|
|
@@ -1269,6 +1305,7 @@ function normalizeStatefulScopeInput(code, sourceMap, { mode = 'stateful-v1', ta
|
|
|
1269
1305
|
const annex = introduceAnnexBVars(code, sourceMap, target, onPhase)
|
|
1270
1306
|
code = annex.code
|
|
1271
1307
|
sourceMap = annex.sourceMap
|
|
1308
|
+
updateDiagnosticInput(annex)
|
|
1272
1309
|
onPhase?.('facts')
|
|
1273
1310
|
const facts = analyzeLogicalScopes(code, { target, compact: true, compactNames: target === 'module' || target === 'commonjs', onPhase })
|
|
1274
1311
|
onPhase?.('planning')
|
|
@@ -1,116 +1,212 @@
|
|
|
1
1
|
import { types } from 'node:util'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { readFileSync } from 'node:fs'
|
|
3
|
+
import { createRequire } from 'node:module'
|
|
4
|
+
import { createContext, isContext, runInContext, runInThisContext } from 'node:vm'
|
|
4
5
|
import { randomUUID } from 'node:crypto'
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
6
|
+
import { runtimeIntrinsics as internal } from './runtime-intrinsics.js'
|
|
7
|
+
import { deleteWorkerRealmProperty, WORKER_REALM_MUTATION } from './worker-realm-surfaces.js'
|
|
7
8
|
|
|
8
9
|
const MAX_ENTRIES = 128
|
|
9
10
|
const MAX_TEXT = 512
|
|
10
11
|
const MAX_PROPERTIES = 5
|
|
11
|
-
const ownDescriptor = Object.getOwnPropertyDescriptor
|
|
12
|
-
const isArray = Array.isArray
|
|
13
12
|
const isProxy = types.isProxy
|
|
14
|
-
const stringify = JSON.stringify
|
|
15
13
|
const now = Date.now
|
|
16
|
-
const bigintText = BigInt.prototype.toString
|
|
17
14
|
const BIGINT_LIMIT = 10n ** 128n
|
|
15
|
+
const IDENTIFIER = /^[$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*$/u
|
|
16
|
+
const AST_METADATA_KEYS = new internal.Set(['start', 'end', 'loc', 'range'])
|
|
17
|
+
const {
|
|
18
|
+
Set: PrivateSet,
|
|
19
|
+
appendArray,
|
|
20
|
+
bigintToString,
|
|
21
|
+
isArray,
|
|
22
|
+
jsonStringify,
|
|
23
|
+
min,
|
|
24
|
+
objectCreate,
|
|
25
|
+
objectGetOwnPropertyDescriptor: ownDescriptor,
|
|
26
|
+
objectHasOwn,
|
|
27
|
+
objectIs,
|
|
28
|
+
objectKeys,
|
|
29
|
+
regexpTest,
|
|
30
|
+
replaceAllString,
|
|
31
|
+
setAdd,
|
|
32
|
+
setHas,
|
|
33
|
+
sliceString,
|
|
34
|
+
toString,
|
|
35
|
+
} = internal
|
|
18
36
|
const unreadable = () => ({ status: 'unreadable', text: '', truncated: false })
|
|
19
37
|
const runInRealm = (source, context, options) => isContext(context)
|
|
20
38
|
? runInContext(source, context, options)
|
|
21
39
|
: runInThisContext(source, options)
|
|
22
40
|
|
|
41
|
+
function createPrivateParser() {
|
|
42
|
+
const require = createRequire(import.meta.url)
|
|
43
|
+
const exports = objectCreate(null)
|
|
44
|
+
const module = objectCreate(null)
|
|
45
|
+
module.exports = exports
|
|
46
|
+
const context = createContext({ exports, module })
|
|
47
|
+
runInContext(readFileSync(require.resolve('acorn'), 'utf8'), context,
|
|
48
|
+
{ filename: require.resolve('acorn') })
|
|
49
|
+
return module.exports.parse
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const parse = createPrivateParser()
|
|
53
|
+
|
|
54
|
+
function parseProgram(program) {
|
|
55
|
+
const options = objectCreate(null)
|
|
56
|
+
options.ecmaVersion = 'latest'
|
|
57
|
+
options.allowAwaitOutsideFunction = true
|
|
58
|
+
return parse(program, options)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function containsAwait(node) {
|
|
62
|
+
if (node === null || typeof node !== 'object') return false
|
|
63
|
+
if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression'
|
|
64
|
+
|| node.type === 'ArrowFunctionExpression') return false
|
|
65
|
+
if (node.type === 'AwaitExpression' || node.await === true) return true
|
|
66
|
+
const keys = objectKeys(node)
|
|
67
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
68
|
+
const key = keys[index]
|
|
69
|
+
if (setHas(AST_METADATA_KEYS, key)) continue
|
|
70
|
+
const value = node[key]
|
|
71
|
+
if (isArray(value)) {
|
|
72
|
+
for (let child = 0; child < value.length; child += 1) {
|
|
73
|
+
if (containsAwait(value[child])) return true
|
|
74
|
+
}
|
|
75
|
+
} else if (containsAwait(value)) return true
|
|
76
|
+
}
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function recordBindingNames(pattern, names) {
|
|
81
|
+
if (pattern === null || typeof pattern !== 'object') return
|
|
82
|
+
if (pattern.type === 'Identifier') {
|
|
83
|
+
setAdd(names, pattern.name)
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
if (pattern.type === 'RestElement') return recordBindingNames(pattern.argument, names)
|
|
87
|
+
if (pattern.type === 'AssignmentPattern') return recordBindingNames(pattern.left, names)
|
|
88
|
+
if (pattern.type === 'ArrayPattern') {
|
|
89
|
+
for (let index = 0; index < pattern.elements.length; index += 1) {
|
|
90
|
+
recordBindingNames(pattern.elements[index], names)
|
|
91
|
+
}
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
if (pattern.type === 'ObjectPattern') {
|
|
95
|
+
for (let index = 0; index < pattern.properties.length; index += 1) {
|
|
96
|
+
const property = pattern.properties[index]
|
|
97
|
+
recordBindingNames(property.type === 'RestElement' ? property.argument : property.value, names)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
23
102
|
function primitiveText(value) {
|
|
24
|
-
if (typeof value === 'string') return
|
|
103
|
+
if (typeof value === 'string') return jsonStringify(sliceString(value, 0, MAX_TEXT))
|
|
25
104
|
if (typeof value === 'bigint') return value > -BIGINT_LIMIT && value < BIGINT_LIMIT
|
|
26
|
-
? `${
|
|
105
|
+
? `${bigintToString(value)}n` : '[bigint: more than 128 digits]'
|
|
27
106
|
if (typeof value === 'symbol' || typeof value === 'function') return `[${typeof value}: unreadable]`
|
|
28
107
|
if (value === null) return 'null'
|
|
29
108
|
if (typeof value === 'object') return isProxy(value) ? '[proxy: unreadable]' : '[object]'
|
|
30
|
-
return
|
|
109
|
+
return objectIs(value, -0) ? '-0' : toString(value)
|
|
31
110
|
}
|
|
32
111
|
|
|
33
112
|
/** Inspect own descriptors only, rejecting proxies before any reflective operation. */
|
|
34
113
|
export function previewBindingValue(value) {
|
|
35
|
-
if (
|
|
114
|
+
if (typeof value === 'function' || typeof value === 'symbol') return unreadable()
|
|
36
115
|
if (value === null || typeof value !== 'object') {
|
|
37
116
|
const text = primitiveText(value)
|
|
38
|
-
return { status: 'readable', text: text
|
|
117
|
+
return { status: 'readable', text: sliceString(text, 0, MAX_TEXT), truncated: text.length > MAX_TEXT
|
|
39
118
|
|| (typeof value === 'bigint' && (value <= -BIGINT_LIMIT || value >= BIGINT_LIMIT)) }
|
|
40
119
|
}
|
|
41
120
|
// Only arrays provide a finite slot range without enumerating the whole object.
|
|
42
121
|
if (isProxy(value) || !isArray(value)) return unreadable()
|
|
43
|
-
const keys =
|
|
44
|
-
const
|
|
122
|
+
const keys = []
|
|
123
|
+
const length = min(ownDescriptor(value, 'length').value, MAX_PROPERTIES)
|
|
124
|
+
for (let index = 0; index < length; index += 1) appendArray(keys, toString(index))
|
|
125
|
+
const properties = []
|
|
126
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
127
|
+
const key = keys[index]
|
|
45
128
|
const descriptor = ownDescriptor(value, key)
|
|
46
129
|
const text = descriptor === undefined ? '[empty]'
|
|
47
|
-
:
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
130
|
+
: objectHasOwn(descriptor, 'value') ? primitiveText(descriptor.value) : '[accessor: unreadable]'
|
|
131
|
+
appendArray(properties, `${jsonStringify(key)}: ${text}`)
|
|
132
|
+
}
|
|
133
|
+
let text = 'array { '
|
|
134
|
+
for (let index = 0; index < properties.length; index += 1) {
|
|
135
|
+
if (index > 0) text += ', '
|
|
136
|
+
text += properties[index]
|
|
137
|
+
}
|
|
138
|
+
text += ' }'
|
|
51
139
|
// Uninspected own properties remain unknown even when all indexed slots fit.
|
|
52
|
-
return { status: 'readable', text: text
|
|
140
|
+
return { status: 'readable', text: sliceString(text, 0, MAX_TEXT), truncated: true }
|
|
53
141
|
}
|
|
54
142
|
|
|
55
143
|
/** Probe the actual evaluator without consulting a Node version or any session value. */
|
|
56
144
|
export async function supportsAwaitLexicals(context, evaluate) {
|
|
57
|
-
const prefix = `__ptc_observation_${randomUUID()
|
|
145
|
+
const prefix = `__ptc_observation_${replaceAllString(randomUUID(), '-', '')}`
|
|
58
146
|
const names = [`${prefix}_let`, `${prefix}_const`]
|
|
59
147
|
try {
|
|
60
148
|
await evaluate(`let ${names[0]} = await Promise.resolve(41); const ${names[1]} = 42;`)
|
|
61
|
-
|
|
62
|
-
|
|
149
|
+
for (let index = 0; index < names.length; index += 1) {
|
|
150
|
+
if (ownDescriptor(context, names[index]) !== undefined
|
|
151
|
+
|| runInRealm(names[index], context, { timeout: 25 }) !== index + 41) return false
|
|
152
|
+
}
|
|
153
|
+
return true
|
|
63
154
|
} catch {
|
|
64
155
|
return false
|
|
65
156
|
} finally {
|
|
66
157
|
// Lexical probe names are private and never enter the binding inventory.
|
|
67
|
-
for (
|
|
158
|
+
for (let index = 0; index < names.length; index += 1) {
|
|
159
|
+
deleteWorkerRealmProperty(
|
|
160
|
+
WORKER_REALM_MUTATION.RESTORE,
|
|
161
|
+
'await lexical probe',
|
|
162
|
+
context,
|
|
163
|
+
names[index],
|
|
164
|
+
)
|
|
165
|
+
}
|
|
68
166
|
}
|
|
69
167
|
}
|
|
70
168
|
|
|
71
169
|
/** Successful programs prove storage; await lexical reads additionally require a runtime probe. */
|
|
72
170
|
export function createReplValueObserver(context, { awaitLexicals = false } = {}) {
|
|
73
|
-
const lexicals = new
|
|
74
|
-
const globals = new
|
|
171
|
+
const lexicals = new PrivateSet()
|
|
172
|
+
const globals = new PrivateSet()
|
|
75
173
|
return {
|
|
76
174
|
record(program) {
|
|
77
175
|
try {
|
|
78
|
-
const tree =
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (node.type === 'AwaitExpression' || node.await === true) awaits = true
|
|
83
|
-
})
|
|
84
|
-
if (awaits && !awaitLexicals) return
|
|
85
|
-
for (const statement of tree.body) {
|
|
176
|
+
const tree = parseProgram(program)
|
|
177
|
+
if (containsAwait(tree) && !awaitLexicals) return
|
|
178
|
+
for (let statementIndex = 0; statementIndex < tree.body.length; statementIndex += 1) {
|
|
179
|
+
const statement = tree.body[statementIndex]
|
|
86
180
|
if (statement.type !== 'VariableDeclaration') continue
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
181
|
+
const names = statement.kind === 'var' ? globals : lexicals
|
|
182
|
+
for (let index = 0; index < statement.declarations.length; index += 1) {
|
|
183
|
+
recordBindingNames(statement.declarations[index].id, names)
|
|
91
184
|
}
|
|
92
185
|
}
|
|
93
186
|
} catch {}
|
|
94
187
|
},
|
|
95
188
|
observe(names) {
|
|
96
189
|
const at = now()
|
|
97
|
-
const entries =
|
|
190
|
+
const entries = []
|
|
191
|
+
const length = min(names.length, MAX_ENTRIES)
|
|
192
|
+
for (let index = 0; index < length; index += 1) {
|
|
193
|
+
const name = names[index]
|
|
98
194
|
let preview = unreadable()
|
|
99
195
|
try {
|
|
100
|
-
if (lexicals
|
|
196
|
+
if (setHas(lexicals, name)) {
|
|
101
197
|
// Lexical storage shadows global properties, even while uninitialized.
|
|
102
|
-
if (
|
|
198
|
+
if (regexpTest(IDENTIFIER, name) && now() - at < 100) {
|
|
103
199
|
preview = previewBindingValue(runInRealm(name, context, { timeout: 25, displayErrors: false }))
|
|
104
200
|
}
|
|
105
|
-
} else if (globals
|
|
201
|
+
} else if (setHas(globals, name)) {
|
|
106
202
|
const descriptor = ownDescriptor(context, name)
|
|
107
|
-
if (descriptor !== undefined &&
|
|
203
|
+
if (descriptor !== undefined && objectHasOwn(descriptor, 'value')) {
|
|
108
204
|
preview = previewBindingValue(descriptor.value)
|
|
109
205
|
}
|
|
110
206
|
}
|
|
111
207
|
} catch {}
|
|
112
|
-
|
|
113
|
-
}
|
|
208
|
+
appendArray(entries, { name, ...preview })
|
|
209
|
+
}
|
|
114
210
|
return { at, entries }
|
|
115
211
|
},
|
|
116
212
|
}
|
|
@@ -43,8 +43,36 @@ function assertField(field, value) {
|
|
|
43
43
|
* @param config - raw config object.
|
|
44
44
|
* @returns a resolved config object.
|
|
45
45
|
*/
|
|
46
|
+
/**
|
|
47
|
+
* A volatile schema field validates to a live `{ get }` handle. A field that
|
|
48
|
+
* has no schema default arrives as a handle with an undefined live value and
|
|
49
|
+
* keeps the runtime default.
|
|
50
|
+
*/
|
|
51
|
+
function liveConfigValue(value, fallback) {
|
|
52
|
+
if (value !== null && typeof value === 'object'
|
|
53
|
+
&& typeof value.get === 'function' && Object.keys(value).length === 1) {
|
|
54
|
+
const live = value.get()
|
|
55
|
+
return live === undefined ? fallback : live
|
|
56
|
+
}
|
|
57
|
+
return value
|
|
58
|
+
}
|
|
59
|
+
|
|
46
60
|
export function resolveConfig(config = {}) {
|
|
61
|
+
const provided = new Set()
|
|
62
|
+
for (const field of CONFIG_FIELDS) {
|
|
63
|
+
if (!Object.hasOwn(config, field.key)) continue
|
|
64
|
+
const value = config[field.key]
|
|
65
|
+
if (value !== null && typeof value === 'object'
|
|
66
|
+
&& typeof value.get === 'function' && Object.keys(value).length === 1) {
|
|
67
|
+
if (value.get() !== undefined) provided.add(field.key)
|
|
68
|
+
} else {
|
|
69
|
+
provided.add(field.key)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
47
72
|
const resolved = { ...CONFIG_DEFAULTS, ...config }
|
|
73
|
+
for (const field of CONFIG_FIELDS) {
|
|
74
|
+
resolved[field.key] = liveConfigValue(resolved[field.key], CONFIG_DEFAULTS[field.key])
|
|
75
|
+
}
|
|
48
76
|
for (const field of CONFIG_FIELDS) assertField(field, resolved[field.key])
|
|
49
77
|
// Migrate complete old policies; mixed choices retain their explicit
|
|
50
78
|
// compatibility state until the user selects the unified language.
|
|
@@ -52,8 +80,8 @@ export function resolveConfig(config = {}) {
|
|
|
52
80
|
'looseTopLevelRedeclarations', 'looseTopLevelFunctionClassRedeclarations',
|
|
53
81
|
'autoRewriteImports', 'autoStripExports', 'autoSplitRedeclarations',
|
|
54
82
|
]
|
|
55
|
-
if (!
|
|
56
|
-
&& legacyKeys.some(key =>
|
|
83
|
+
if (!provided.has('bindingUpdates')
|
|
84
|
+
&& legacyKeys.some(key => provided.has(key))) {
|
|
57
85
|
if (legacyKeys.every(key => resolved[key] === true)) {
|
|
58
86
|
resolved.bindingUpdates = 'stateful'
|
|
59
87
|
} else if (legacyKeys.slice(0, 2).every(key => resolved[key] === false)
|
|
@@ -65,6 +93,38 @@ export function resolveConfig(config = {}) {
|
|
|
65
93
|
return resolved
|
|
66
94
|
}
|
|
67
95
|
|
|
96
|
+
/** Whether a validated config carries live volatile field handles. */
|
|
97
|
+
export function hasVolatileFields(config) {
|
|
98
|
+
if (config === null || typeof config !== 'object') return false
|
|
99
|
+
return Object.values(config).some(value => value !== null && typeof value === 'object'
|
|
100
|
+
&& typeof value.get === 'function' && Object.keys(value).length === 1)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Follow the host's volatile-only commit into the running fiber.
|
|
105
|
+
*
|
|
106
|
+
* The loader updates the retained handles in place and emits
|
|
107
|
+
* `loader/volatile-update` on the owning fiber; listeners on a child scope are
|
|
108
|
+
* filtered out, so the listener belongs to the plugin's own context and is
|
|
109
|
+
* released with the runtime scope it serves.
|
|
110
|
+
*
|
|
111
|
+
* @param host - the plugin's own context, owning the loader event listener.
|
|
112
|
+
* @param scope - the runtime scope whose disposal removes the listener.
|
|
113
|
+
* @param config - the validated config carrying the volatile handles.
|
|
114
|
+
* @param listener - receives the newly resolved plain configuration.
|
|
115
|
+
* @returns whether a volatile listener was installed.
|
|
116
|
+
*/
|
|
117
|
+
export function watchVolatileConfig(host, scope, config, listener) {
|
|
118
|
+
if (!hasVolatileFields(config) || typeof host?.on !== 'function') return false
|
|
119
|
+
const update = () => listener(resolveConfig(config))
|
|
120
|
+
if (typeof scope?.effect === 'function') {
|
|
121
|
+
scope.effect(() => host.on('loader/volatile-update', update), 'ptc-plus volatile settings lifecycle')
|
|
122
|
+
} else {
|
|
123
|
+
host.on('loader/volatile-update', update)
|
|
124
|
+
}
|
|
125
|
+
return true
|
|
126
|
+
}
|
|
127
|
+
|
|
68
128
|
/**
|
|
69
129
|
* Validate one wall-clock ceiling against the platform timer limit.
|
|
70
130
|
* @param value - the configured maximum wall time in milliseconds.
|
|
@@ -27,6 +27,7 @@ export const runtimeIntrinsics = {
|
|
|
27
27
|
defineProperty: compilerDescriptors.reflectDefineProperty,
|
|
28
28
|
has: Reflect.has,
|
|
29
29
|
ownKeys: Reflect.ownKeys,
|
|
30
|
+
set: Reflect.set,
|
|
30
31
|
},
|
|
31
32
|
Error, TypeError, Array, Map, Set, WeakMap, WeakSet, RegExp, Proxy,
|
|
32
33
|
objectCreate: Object.create,
|
|
@@ -46,6 +47,8 @@ export const runtimeIntrinsics = {
|
|
|
46
47
|
toBigInt: BigInt,
|
|
47
48
|
toNumber: Number,
|
|
48
49
|
toString: String,
|
|
50
|
+
bigintToString: uncurry(BigInt.prototype.toString),
|
|
51
|
+
jsonStringify: JSON.stringify,
|
|
49
52
|
mapGet: uncurry(Map.prototype.get),
|
|
50
53
|
mapHas: uncurry(Map.prototype.has),
|
|
51
54
|
mapSet: uncurry(Map.prototype.set),
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { createUserMessage } from '@deepseek-ai/dsh-llm'
|
|
2
2
|
|
|
3
|
+
import { isPtcMessageSource, PTC_MESSAGE_SOURCE_KIND } from './message-sources.js'
|
|
4
|
+
|
|
3
5
|
export const PTC_DELIVERY_CONTEXT = 'tools:ptc-plus-message-delivery'
|
|
4
6
|
export const PTC_BINDING_CATALOG = 'tools:ptc-plus-user-binding-defaults'
|
|
5
7
|
export const PTC_STATE_NAMES = Object.freeze([
|
|
@@ -54,7 +56,7 @@ function stateText(sections, prefix = STATE_PREFIX) {
|
|
|
54
56
|
/** Recognize only bounded, formed PTC records, never authoring tasks or Skills. */
|
|
55
57
|
export function readRuntimeMessage(message) {
|
|
56
58
|
const source = message?.source
|
|
57
|
-
if (source
|
|
59
|
+
if (!isPtcMessageSource(source)
|
|
58
60
|
|| !Array.isArray(message.content) || message.content.length !== 1
|
|
59
61
|
|| message.content[0]?.type !== 'text') return undefined
|
|
60
62
|
const text = message.content[0].text
|
|
@@ -87,7 +89,7 @@ export function runtimeBindingCatalogMessage(context) {
|
|
|
87
89
|
throw new Error('invalid PTC binding catalog')
|
|
88
90
|
}
|
|
89
91
|
return createUserMessage({
|
|
90
|
-
source: { kind:
|
|
92
|
+
source: { kind: PTC_MESSAGE_SOURCE_KIND, form: 'catalog' },
|
|
91
93
|
content: [{ type: 'text', text: `${CATALOG_PREFIX}\n\n${context?.text ?? EMPTY_CATALOG}` }],
|
|
92
94
|
})
|
|
93
95
|
}
|
|
@@ -96,14 +98,14 @@ export function runtimeStateMessage(value) {
|
|
|
96
98
|
const sections = stateSections(value)
|
|
97
99
|
if (sections === undefined) throw new Error('invalid PTC runtime state sections')
|
|
98
100
|
return createUserMessage({
|
|
99
|
-
source: { kind:
|
|
101
|
+
source: { kind: PTC_MESSAGE_SOURCE_KIND, form: 'snapshot', sections },
|
|
100
102
|
content: [{ type: 'text', text: stateText(sections) }],
|
|
101
103
|
})
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
export function runtimeNoticeMessage(tip) {
|
|
105
107
|
const message = createUserMessage({
|
|
106
|
-
source: { kind:
|
|
108
|
+
source: { kind: PTC_MESSAGE_SOURCE_KIND, form: 'notice', summary: tip.name },
|
|
107
109
|
content: [{ type: 'text', text: tip.text }],
|
|
108
110
|
})
|
|
109
111
|
if (readRuntimeMessage(message) === undefined) throw new Error('invalid PTC recovery notice')
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { resolve } from 'node:path'
|
|
2
|
+
import { pathToFileURL } from 'node:url'
|
|
1
3
|
import {
|
|
2
4
|
decodeValue,
|
|
3
5
|
encodeValue,
|
|
@@ -415,6 +417,8 @@ export class SessionCellExecutor {
|
|
|
415
417
|
const priorBindingCatalog = userBindingPlan.catalog
|
|
416
418
|
const catalog = priorBindingCatalog.inputs()
|
|
417
419
|
const { bindingPolicy, rewritesEnabled, moduleSemantics, languageSemantics, moduleTransform } = executionPolicies(config, replayRecord)
|
|
420
|
+
const moduleDirectory = kernel.cwd ?? process.cwd()
|
|
421
|
+
const moduleFilename = resolve(moduleDirectory, 'repl')
|
|
418
422
|
const prepareCell = program => prepareProgram(program, {
|
|
419
423
|
knownBindings: catalog.knownBindings,
|
|
420
424
|
bindingPolicy,
|
|
@@ -430,6 +434,11 @@ export class SessionCellExecutor {
|
|
|
430
434
|
dynamicOrigins: catalog.dynamicOrigins,
|
|
431
435
|
establishedRoots: catalog.establishedRoots,
|
|
432
436
|
moduleSemantics,
|
|
437
|
+
importMeta: {
|
|
438
|
+
url: pathToFileURL(moduleFilename).href,
|
|
439
|
+
filename: moduleFilename,
|
|
440
|
+
dirname: moduleDirectory,
|
|
441
|
+
},
|
|
433
442
|
})
|
|
434
443
|
let prepared
|
|
435
444
|
try {
|
|
@@ -712,9 +721,10 @@ export class SessionCellExecutor {
|
|
|
712
721
|
volatile: this.handleVolatile,
|
|
713
722
|
call: this.handleCall,
|
|
714
723
|
'output-limit': this.handleOutputLimit,
|
|
724
|
+
'worker-output-error': this.handleWorkerOutputError,
|
|
715
725
|
done: this.handleDone,
|
|
716
726
|
}[message.type]
|
|
717
|
-
handler?.call(this, message)
|
|
727
|
+
return handler?.call(this, message)
|
|
718
728
|
}
|
|
719
729
|
|
|
720
730
|
handleVolatile(message) {
|
|
@@ -742,6 +752,19 @@ export class SessionCellExecutor {
|
|
|
742
752
|
}, true)
|
|
743
753
|
}
|
|
744
754
|
|
|
755
|
+
handleWorkerOutputError(message) {
|
|
756
|
+
const active = this.kernel.active
|
|
757
|
+
if (active?.id !== message.id) return false
|
|
758
|
+
const detail = typeof message.message === 'string'
|
|
759
|
+
? message.message
|
|
760
|
+
: 'the kernel returned an invalid output-attribution event'
|
|
761
|
+
active.resolve({
|
|
762
|
+
logs: [],
|
|
763
|
+
error: { kind: 'worker-exit', message: `worker output attribution failed: ${detail}` },
|
|
764
|
+
}, true)
|
|
765
|
+
return true
|
|
766
|
+
}
|
|
767
|
+
|
|
745
768
|
handleDone(message) {
|
|
746
769
|
const kernel = this.kernel
|
|
747
770
|
if (kernel.active?.id !== message.id) return
|