dsh-ptc-plus 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +137 -0
  3. package/README.zh.md +137 -0
  4. package/assets/dsh-ptc-plus-banner.webp +0 -0
  5. package/assets/ptc-plus-repair.png +0 -0
  6. package/assets/ptc-plus-settings.png +0 -0
  7. package/client.js +366 -0
  8. package/cordis.patch.yml +5 -0
  9. package/docs/README.md +21 -0
  10. package/docs/adr/0001-delegate-governance-to-dsh.md +3 -0
  11. package/docs/adr/0002-limit-work-map-scope.md +3 -0
  12. package/docs/adr/0003-prefer-native-program-surfaces.md +3 -0
  13. package/docs/adr/0004-declare-program-bindings-at-the-host.md +5 -0
  14. package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +129 -0
  15. package/docs/adr/0006-rewrite-module-syntax-with-an-ast.md +45 -0
  16. package/docs/adr/0007-separate-worker-transport-from-session-semantics.md +39 -0
  17. package/docs/adr/0008-define-agent-operating-contract.md +25 -0
  18. package/docs/adr/0009-bound-expensive-acceptance-default.md +39 -0
  19. package/docs/adr/0010-session-log-derived-recovery-tips.md +26 -0
  20. package/docs/adr/0011-isolate-the-local-recovery-tip-provider.md +20 -0
  21. package/docs/adr/0012-preserve-mixed-destructuring-initialization.md +23 -0
  22. package/docs/adr/0013-isolate-repl-redeclaration-convenience.md +21 -0
  23. package/docs/adr/0014-persist-cell-rewrite-policy.md +25 -0
  24. package/docs/adr/0015-preserve-source-positions-through-rewrites.md +25 -0
  25. package/docs/adr/0016-archive-resolved-review-findings.md +43 -0
  26. package/docs/adr/0017-track-the-latest-dsh-public-surface.md +23 -0
  27. package/docs/adr/0018-use-versioned-fixture-for-ab-benchmark.md +25 -0
  28. package/docs/adr/0019-plugin-settings-and-kill-switch.md +32 -0
  29. package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +36 -0
  30. package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +68 -0
  31. package/docs/adr/README.md +18 -0
  32. package/docs/architecture.md +134 -0
  33. package/docs/capability-projection.md +48 -0
  34. package/docs/client-ui.md +47 -0
  35. package/docs/durability-design.md +265 -0
  36. package/docs/evaluation.md +62 -0
  37. package/docs/installation.md +82 -0
  38. package/docs/program-data-plane.md +61 -0
  39. package/docs/publishing.md +37 -0
  40. package/docs/runtime-reference.md +119 -0
  41. package/docs/value-wire.md +61 -0
  42. package/index.js +532 -0
  43. package/internal/ast-traversal.js +20 -0
  44. package/internal/binding-descriptors.js +95 -0
  45. package/internal/binding-pattern.js +54 -0
  46. package/internal/builtin-esm-sync.js +9 -0
  47. package/internal/cell-analysis.js +467 -0
  48. package/internal/cell-rewriter.js +520 -0
  49. package/internal/config-spec.js +179 -0
  50. package/internal/cordis-tools-owner.js +363 -0
  51. package/internal/diagnostic.js +133 -0
  52. package/internal/direct-surface-owner.js +326 -0
  53. package/internal/edit-transport-owner.js +287 -0
  54. package/internal/failure-reporting.js +139 -0
  55. package/internal/kernel-worker.js +484 -0
  56. package/internal/module-policy.js +46 -0
  57. package/internal/program-bindings.js +187 -0
  58. package/internal/record-utils.js +53 -0
  59. package/internal/recovery-tips.js +81 -0
  60. package/internal/rejected-cell-editor.js +328 -0
  61. package/internal/repl-convenience.js +201 -0
  62. package/internal/runtime-bridge-owner.js +417 -0
  63. package/internal/runtime-config.js +77 -0
  64. package/internal/runtime-contexts.js +31 -0
  65. package/internal/sdk-projection.js +88 -0
  66. package/internal/session-cell-executor.js +612 -0
  67. package/internal/session-journal.js +889 -0
  68. package/internal/session-log-view.js +80 -0
  69. package/internal/session-runtime.js +536 -0
  70. package/internal/session-state.js +45 -0
  71. package/internal/source-position-map.js +224 -0
  72. package/internal/tool-call-canonicalizer.js +339 -0
  73. package/internal/value-wire.js +421 -0
  74. package/internal/worker-client.js +218 -0
  75. package/internal/worker-cwd-virtualization.js +155 -0
  76. package/package.json +100 -0
  77. package/src/client.js +165 -0
@@ -0,0 +1,520 @@
1
+ /** Adapts TypeScript module declarations to the async function-body grammar used by PTC cells. */
2
+ import { parse } from '@babel/parser'
3
+ import traverseModule, { Hub, NodePath } from '@babel/traverse'
4
+ import { bindingNodes, createGeneratedNameAllocator } from './binding-pattern.js'
5
+ import { applySourceEdits, createMappedTextBuilder, identitySourceMap } from './source-position-map.js'
6
+
7
+ export const STRIP_PREFIX = 'async function __ptc_cell__(){\n'
8
+ export const STRIP_SUFFIX = '\n}'
9
+
10
+ export class ModuleRewriteError extends SyntaxError {
11
+ constructor(message, cellPosition) {
12
+ super(message)
13
+ if (cellPosition !== undefined) this.cellPosition = cellPosition
14
+ }
15
+ }
16
+
17
+ const PARSER_PLUGINS = ['typescript', 'importAttributes']
18
+ const TYPE_DECLARATIONS = new Set([
19
+ 'TSDeclareFunction',
20
+ 'TSEnumDeclaration',
21
+ 'TSInterfaceDeclaration',
22
+ 'TSModuleDeclaration',
23
+ 'TSTypeAliasDeclaration',
24
+ ])
25
+ const traverse = traverseModule.default ?? traverseModule
26
+
27
+ function declaredTypeNames(program) {
28
+ const names = new Set()
29
+ for (const node of program.body) {
30
+ const declaration = node.type === 'ExportNamedDeclaration' || node.type === 'ExportDefaultDeclaration'
31
+ ? node.declaration
32
+ : node
33
+ if (TYPE_DECLARATIONS.has(declaration?.type) && declaration.id?.type === 'Identifier') {
34
+ names.add(declaration.id.name)
35
+ }
36
+ }
37
+ return names
38
+ }
39
+
40
+ function createProgramScope(file) {
41
+ const hub = new Hub()
42
+ hub.buildError = (node, message, ErrorType = TypeError) => {
43
+ const error = new ErrorType(message)
44
+ error.loc = node.loc?.start
45
+ return error
46
+ }
47
+ const path = NodePath.get({ hub, parent: file, container: file, key: 'program' })
48
+ path.setContext()
49
+ path.scope = path.getScope()
50
+ path.scope.init()
51
+ return path.scope
52
+ }
53
+
54
+ function validateLocalExports(file, programScope) {
55
+ const localExports = file.program.body.filter(node => node.type === 'ExportNamedDeclaration'
56
+ && node.source === null && node.declaration === null)
57
+ const valueSpecifiers = localExports.flatMap(node => node.specifiers
58
+ .filter(specifier => specifier.exportKind !== 'type'))
59
+ if (valueSpecifiers.length === 0) return
60
+ const typeNames = declaredTypeNames(file.program)
61
+ const missing = valueSpecifiers.find(specifier => (
62
+ programScope.getBinding(specifier.local.name) === undefined && !typeNames.has(specifier.local.name)
63
+ ))
64
+ if (missing === undefined) return
65
+ throw new ModuleRewriteError(`Export '${missing.local.name}' is not defined.`, {
66
+ line: missing.local.loc.start.line,
67
+ column: missing.local.loc.start.column + 1,
68
+ })
69
+ }
70
+
71
+ function parseCell(code) {
72
+ try {
73
+ const file = parse(code, {
74
+ sourceType: 'script',
75
+ allowAwaitOutsideFunction: true,
76
+ allowImportExportEverywhere: true,
77
+ allowReturnOutsideFunction: true,
78
+ allowUndeclaredExports: true,
79
+ plugins: PARSER_PLUGINS,
80
+ })
81
+ validateLocalExports(file, createProgramScope(file))
82
+ return file.program
83
+ } catch (error) {
84
+ if (error instanceof ModuleRewriteError) throw error
85
+ const loc = error?.loc
86
+ throw new ModuleRewriteError(error instanceof Error ? error.message : String(error),
87
+ Number.isSafeInteger(loc?.line) && Number.isSafeInteger(loc?.column)
88
+ ? { line: loc.line, column: loc.column + 1 }
89
+ : undefined)
90
+ }
91
+ }
92
+
93
+ function parseSource(node) { return node.source.value }
94
+ function importOptions(node) {
95
+ if (!node.attributes || node.attributes.length === 0) return undefined
96
+ const attributes = Object.fromEntries(node.attributes.map((attribute) => {
97
+ const key = attribute.key.type === 'Identifier' ? attribute.key.name : attribute.key.value
98
+ return [key, attribute.value.value]
99
+ }))
100
+ const keyword = node.attributes[0].type === 'ImportAttribute' ? 'with' : 'assert'
101
+ return { [keyword]: attributes }
102
+ }
103
+ function moduleLoad(node, source, global = undefined, requiredExports = []) {
104
+ return {
105
+ source,
106
+ ...(global === undefined ? {} : { global }),
107
+ ...(requiredExports.length === 0 ? {} : { requiredExports }),
108
+ ...(importOptions(node) === undefined ? {} : { options: importOptions(node) }),
109
+ position: {
110
+ line: node.source.loc.start.line,
111
+ column: node.source.loc.start.column + 1,
112
+ end: {
113
+ line: node.source.loc.end.line,
114
+ column: node.source.loc.end.column + 1,
115
+ },
116
+ },
117
+ }
118
+ }
119
+ function record(kind, description, node, source) {
120
+ return { kind, description, ...(source === undefined ? {} : { source }), at: node.start }
121
+ }
122
+ function preserveLines(original, replacement = '') {
123
+ const missing = (original.match(/\n/g)?.length ?? 0) - (replacement.match(/\n/g)?.length ?? 0)
124
+ return missing > 0 ? replacement + '\n'.repeat(missing) : replacement
125
+ }
126
+ function editNode(edits, node, code, replacement = '') {
127
+ const mapped = typeof replacement === 'string' ? { text: replacement } : replacement
128
+ const separated = node.end < code.length && code[node.end] !== '\n' && code[node.end] !== '\r'
129
+ ? `${mapped.text}\n`
130
+ : mapped.text
131
+ edits.push({
132
+ start: node.start,
133
+ end: node.end,
134
+ text: preserveLines(code.slice(node.start, node.end), separated),
135
+ ...(mapped.mappings === undefined ? {} : { mappings: mapped.mappings }),
136
+ })
137
+ }
138
+ function edit(edits, start, end, text) { edits.push({ start, end, text }) }
139
+ function localName(specifier) { return specifier.local.name }
140
+ function importedName(specifier) {
141
+ if (specifier.type === 'ImportDefaultSpecifier') return 'default'
142
+ if (specifier.type === 'ImportNamespaceSpecifier') return undefined
143
+ return specifier.imported.type === 'Identifier' ? specifier.imported.name : specifier.imported.value
144
+ }
145
+
146
+ function importEdits(
147
+ node,
148
+ code,
149
+ edits,
150
+ rewrites,
151
+ imports,
152
+ importDeclarations,
153
+ moduleLoads,
154
+ namespaceCaptures,
155
+ allocateNamespace,
156
+ allocateGlobal,
157
+ ) {
158
+ const source = parseSource(node)
159
+ const rewrite = record('import', `adapted the static import of ${JSON.stringify(source)} for REPL execution`, node, source)
160
+ if (node.importKind === 'type') {
161
+ editNode(edits, node, code)
162
+ rewrites.push(record('import', `removed the type-only import of ${JSON.stringify(source)}`, node, source))
163
+ return
164
+ }
165
+ if (node.specifiers.length > 0 && node.specifiers.every(specifier => specifier.importKind === 'type')) {
166
+ editNode(edits, node, code)
167
+ rewrites.push(record('import', `removed the type-only import of ${JSON.stringify(source)}`, node, source))
168
+ return
169
+ }
170
+ const specifiers = node.specifiers.filter(specifier => specifier.importKind !== 'type')
171
+ if (specifiers.length === 0) {
172
+ moduleLoads.push(moduleLoad(node, source))
173
+ editNode(edits, node, code)
174
+ rewrites.push(rewrite)
175
+ return
176
+ }
177
+ const namespace = allocateNamespace('import_namespace')
178
+ const global = allocateGlobal('import_global')
179
+ const requiredExports = []
180
+ for (const specifier of specifiers) {
181
+ const imported = importedName(specifier)
182
+ imports.set(localName(specifier), { namespace, imported })
183
+ if (imported !== undefined && !requiredExports.includes(imported)) requiredExports.push(imported)
184
+ importDeclarations.push({
185
+ name: localName(specifier),
186
+ span: {
187
+ line: specifier.local.loc.start.line,
188
+ column: specifier.local.loc.start.column + 1,
189
+ end: {
190
+ line: specifier.local.loc.end.line,
191
+ column: specifier.local.loc.end.column + 1,
192
+ },
193
+ },
194
+ original: true,
195
+ })
196
+ }
197
+ moduleLoads.push(moduleLoad(node, source, global, requiredExports))
198
+ namespaceCaptures.push({ namespace, global, source: node.source })
199
+ editNode(edits, node, code)
200
+ rewrites.push(rewrite)
201
+ }
202
+
203
+ function importPrologueEdit(program, tree, namespaceCaptures) {
204
+ if (namespaceCaptures.length === 0) return undefined
205
+ const directive = tree.directives.at(-1)
206
+ let insertion = directive?.end ?? tree.interpreter?.end ?? 0
207
+ if (directive === undefined && tree.interpreter !== null && tree.interpreter !== undefined) {
208
+ if (program[insertion] === '\r' && program[insertion + 1] === '\n') insertion += 2
209
+ else if (program[insertion] === '\r' || program[insertion] === '\n') insertion += 1
210
+ }
211
+ const builder = createMappedTextBuilder(program)
212
+ if (directive !== undefined && program[insertion - 1] !== ';') builder.append(';')
213
+ for (const capture of namespaceCaptures) {
214
+ builder.append('const ')
215
+ builder.appendMapped(capture.namespace, capture.source.start, capture.source.end)
216
+ builder.append(' = ')
217
+ builder.appendMapped(capture.global, capture.source.start, capture.source.end)
218
+ builder.append(';')
219
+ }
220
+ return { start: insertion, end: insertion, ...builder.result() }
221
+ }
222
+
223
+ function importMember(namespace, imported) {
224
+ return imported === undefined ? namespace
225
+ : /^[A-Za-z_$][\w$]*$/.test(imported) ? `${namespace}.${imported}`
226
+ : `${namespace}[${JSON.stringify(imported)}]`
227
+ }
228
+
229
+ function appendReadonlyImportWrite(builder, appendValue) {
230
+ builder.append("((__dsh_ptc_import_value__) => { throw new TypeError('Assignment to constant variable.'); })(")
231
+ appendValue()
232
+ builder.append(')')
233
+ }
234
+
235
+ function importedBindingFor(path, identifier, imports) {
236
+ const binding = imports.get(identifier.name)
237
+ return binding !== undefined && path.scope.getBinding(identifier.name) === undefined ? binding : undefined
238
+ }
239
+
240
+ function importedWriteBinding(path, imports) {
241
+ if (path.isAssignmentExpression() && path.node.left.type === 'Identifier') {
242
+ return importedBindingFor(path, path.node.left, imports)
243
+ }
244
+ return undefined
245
+ }
246
+
247
+ function assertNoImportedWriteAncestor(path, imports) {
248
+ if (path.findParent(parent => importedWriteBinding(parent, imports) !== undefined) !== null) {
249
+ throw new ModuleRewriteError('nested assignment to an imported binding is not supported')
250
+ }
251
+ }
252
+
253
+ function preserveExpressionStatementBoundary(path, edits) {
254
+ const statement = path.findParent(parent => parent.isExpressionStatement())
255
+ if (statement?.node.expression.start === path.node.start) {
256
+ edits.push({ start: path.node.start, end: path.node.start, text: ';' })
257
+ }
258
+ }
259
+
260
+ function assertNoDynamicImportResolution(tree, imports) {
261
+ if (imports.size === 0) return
262
+ traverse(tree, {
263
+ WithStatement() {
264
+ throw new ModuleRewriteError('with statement is not supported while imported bindings are active')
265
+ },
266
+ CallExpression(path) {
267
+ if (path.node.callee.type === 'Identifier' && path.node.callee.name === 'eval'
268
+ && !imports.has('eval') && path.scope.getBinding('eval') === undefined) {
269
+ throw new ModuleRewriteError('direct eval is not supported while imported bindings are active')
270
+ }
271
+ },
272
+ })
273
+ }
274
+
275
+ function rewriteImportReferences(code, sourceMap, imports) {
276
+ if (imports.size === 0) return { code, sourceMap }
277
+ const parseRewritten = source => parse(source, {
278
+ sourceType: 'script',
279
+ allowAwaitOutsideFunction: true,
280
+ allowReturnOutsideFunction: true,
281
+ plugins: PARSER_PLUGINS,
282
+ })
283
+ let file = parseRewritten(code)
284
+ const writeEdits = []
285
+ traverse(file, {
286
+ AssignmentExpression(path) {
287
+ if (path.node.left.type !== 'Identifier') {
288
+ const names = bindingNodes(path.node.left).map(node => node.name)
289
+ if (names.some(name => imports.has(name) && path.scope.getBinding(name) === undefined)) {
290
+ throw new ModuleRewriteError('destructuring assignment to an imported binding is not supported')
291
+ }
292
+ return
293
+ }
294
+ const binding = importedBindingFor(path, path.node.left, imports)
295
+ if (binding === undefined) return
296
+ assertNoImportedWriteAncestor(path, imports)
297
+ const member = importMember(binding.namespace, binding.imported)
298
+ const builder = createMappedTextBuilder(code)
299
+ const appendMember = () => builder.appendMapped(member, path.node.left.start, path.node.left.end)
300
+ const appendRight = () => builder.appendSource(path.node.right.start, path.node.right.end)
301
+ if (path.node.operator === '=') appendReadonlyImportWrite(builder, appendRight)
302
+ else if (['&&=', '||=', '??='].includes(path.node.operator)) {
303
+ const operator = path.node.operator.slice(0, -1)
304
+ appendMember()
305
+ builder.append(` ${operator} `)
306
+ appendReadonlyImportWrite(builder, appendRight)
307
+ } else {
308
+ appendReadonlyImportWrite(builder, () => {
309
+ appendMember()
310
+ builder.append(` ${path.node.operator.slice(0, -1)} (`)
311
+ appendRight()
312
+ builder.append(')')
313
+ })
314
+ }
315
+ writeEdits.push({
316
+ start: path.node.start,
317
+ end: path.node.end,
318
+ ...builder.result(),
319
+ })
320
+ },
321
+ UpdateExpression(path) {
322
+ if (path.node.argument.type !== 'Identifier') return
323
+ const binding = importedBindingFor(path, path.node.argument, imports)
324
+ if (binding === undefined) return
325
+ assertNoImportedWriteAncestor(path, imports)
326
+ const member = importMember(binding.namespace, binding.imported)
327
+ const builder = createMappedTextBuilder(code)
328
+ builder.append('((__dsh_ptc_import_value__) => { ')
329
+ builder.append(path.node.operator === '++' ? '__dsh_ptc_import_value__++' : '__dsh_ptc_import_value__--')
330
+ builder.append("; throw new TypeError('Assignment to constant variable.'); })(")
331
+ builder.appendMapped(member, path.node.argument.start, path.node.argument.end)
332
+ builder.append(')')
333
+ writeEdits.push({
334
+ start: path.node.start,
335
+ end: path.node.end,
336
+ ...builder.result(),
337
+ })
338
+ },
339
+ UnaryExpression(path) {
340
+ if (path.node.operator !== 'delete' || path.node.argument.type !== 'Identifier') return
341
+ if (importedBindingFor(path, path.node.argument, imports) !== undefined) {
342
+ throw new ModuleRewriteError('delete of an imported binding is not supported')
343
+ }
344
+ },
345
+ ForInStatement(path) {
346
+ if (path.node.left.type === 'VariableDeclaration') return
347
+ const names = bindingNodes(path.node.left)
348
+ if (names.some(node => importedBindingFor(path, node, imports) !== undefined)) {
349
+ throw new ModuleRewriteError('for-in assignment to an imported binding is not supported')
350
+ }
351
+ },
352
+ ForOfStatement(path) {
353
+ if (path.node.left.type === 'VariableDeclaration') return
354
+ const names = bindingNodes(path.node.left)
355
+ if (names.some(node => importedBindingFor(path, node, imports) !== undefined)) {
356
+ throw new ModuleRewriteError('for-of assignment to an imported binding is not supported')
357
+ }
358
+ },
359
+ })
360
+ const written = applySourceEdits(code, sourceMap, writeEdits)
361
+ file = parseRewritten(written.code)
362
+ const readEdits = []
363
+ traverse(file, {
364
+ ReferencedIdentifier(path) {
365
+ const binding = imports.get(path.node.name)
366
+ if (binding === undefined || path.scope.getBinding(path.node.name) !== undefined) return
367
+ const member = importMember(binding.namespace, binding.imported)
368
+ const parent = path.parent
369
+ if (((parent.type === 'CallExpression' || parent.type === 'OptionalCallExpression') && parent.callee === path.node)
370
+ || (parent.type === 'TaggedTemplateExpression' && parent.tag === path.node)) {
371
+ preserveExpressionStatementBoundary(path, readEdits)
372
+ readEdits.push({ start: path.node.start, end: path.node.end, text: `(0, ${member})` })
373
+ return
374
+ }
375
+ if (parent.type === 'ObjectProperty' && parent.shorthand && parent.value === path.node) {
376
+ readEdits.push({ start: parent.start, end: parent.end, text: `${path.node.name}: ${member}` })
377
+ return
378
+ }
379
+ readEdits.push({ start: path.node.start, end: path.node.end, text: member })
380
+ },
381
+ })
382
+ return applySourceEdits(written.code, written.sourceMap, readEdits)
383
+ }
384
+
385
+ function exportNamedEdits(node, code, edits, rewrites, moduleLoads) {
386
+ const onlyTypeSpecifiers = node.specifiers.length > 0
387
+ && node.specifiers.every(specifier => specifier.exportKind === 'type')
388
+ if (node.exportKind === 'type' || onlyTypeSpecifiers
389
+ || (node.declaration && ['TSInterfaceDeclaration', 'TSTypeAliasDeclaration', 'TSDeclareFunction'].includes(node.declaration.type))) {
390
+ editNode(edits, node, code)
391
+ const source = node.exportKind === 'type' || onlyTypeSpecifiers ? 'type' : node.declaration.type
392
+ rewrites.push(record('export', 'removed a type-only export declaration', node, source))
393
+ return
394
+ }
395
+ if (node.source !== null && node.source !== undefined) {
396
+ const source = parseSource(node)
397
+ moduleLoads.push(moduleLoad(node, source))
398
+ editNode(edits, node, code)
399
+ rewrites.push(record('export', `converted the re-export of ${JSON.stringify(source)} into a side-effect import`, node, source))
400
+ return
401
+ }
402
+ if (node.declaration !== null && node.declaration !== undefined) {
403
+ edit(edits, node.start, node.declaration.start, preserveLines(code.slice(node.start, node.declaration.start)))
404
+ rewrites.push(record('export', 'stripped the export modifier from a top-level declaration', node))
405
+ return
406
+ }
407
+ editNode(edits, node, code)
408
+ rewrites.push(record('export', 'removed a local re-export declaration', node))
409
+ }
410
+
411
+ function exportAllEdits(node, code, edits, rewrites, moduleLoads) {
412
+ if (node.exportKind === 'type') {
413
+ editNode(edits, node, code)
414
+ rewrites.push(record('export', 'removed a type-only export declaration', node, 'type'))
415
+ return
416
+ }
417
+ const source = parseSource(node)
418
+ moduleLoads.push(moduleLoad(node, source))
419
+ editNode(edits, node, code)
420
+ rewrites.push(record('export', `converted the re-export of ${JSON.stringify(source)} into a side-effect import`, node, source))
421
+ }
422
+
423
+ function exportDefaultEdits(node, code, edits, rewrites, defaultNameAvailable) {
424
+ const declaration = node.declaration
425
+ if (declaration.type === 'TSInterfaceDeclaration') {
426
+ editNode(edits, node, code)
427
+ rewrites.push(record('export', 'removed a type-only export declaration', node, 'interface'))
428
+ return
429
+ }
430
+ if (declaration.id?.name === '__default') {
431
+ edit(edits, node.start, declaration.start, preserveLines(code.slice(node.start, declaration.start)))
432
+ rewrites.push(record('export', 'stripped the export modifier from the __default declaration', node))
433
+ return
434
+ }
435
+ if (!defaultNameAvailable) {
436
+ throw new ModuleRewriteError(
437
+ 'export default cannot be exposed as __default because that name is already declared',
438
+ { line: node.loc.start.line, column: node.loc.start.column + 1 },
439
+ )
440
+ }
441
+ const rewrite = record('export', 'converted the default export into a local __default binding', node)
442
+ if (declaration.type === 'FunctionDeclaration' && declaration.id !== null) {
443
+ const prefix = code.slice(node.start, declaration.start)
444
+ edit(edits, node.start, declaration.start, preserveLines(prefix, `const __default = ${declaration.id.name}; `))
445
+ } else if (declaration.type === 'ClassDeclaration' && declaration.id !== null) {
446
+ edit(edits, node.start, declaration.start, preserveLines(code.slice(node.start, declaration.start)))
447
+ edit(edits, node.end, node.end, `; const __default = ${declaration.id.name};`)
448
+ } else if (declaration.type === 'FunctionDeclaration' || declaration.type === 'ClassDeclaration') {
449
+ const prefix = code.slice(node.start, declaration.start)
450
+ edit(edits, node.start, declaration.start, preserveLines(prefix, 'const __default = '))
451
+ edit(edits, node.end, node.end, ';')
452
+ } else {
453
+ const prefix = code.slice(node.start, declaration.start)
454
+ edit(edits, node.start, declaration.start, preserveLines(prefix, 'const __default = '))
455
+ }
456
+ rewrites.push(rewrite)
457
+ }
458
+
459
+ function ascending(left, right) { return left.at - right.at }
460
+
461
+ export function rewriteModuleImportsExports(program, enabled, existingImports = new Map(), existingNamespaces = new Set(), unavailableNames = new Set()) {
462
+ const tree = parseCell(program)
463
+ const edits = []
464
+ const rewrites = []
465
+ const imports = new Map(existingImports)
466
+ const importDeclarations = []
467
+ const moduleLoads = []
468
+ const importNamespaces = new Set(existingNamespaces)
469
+ const allocateName = createGeneratedNameAllocator(tree, [...unavailableNames, ...importNamespaces])
470
+ const generatedNamespaces = new Set()
471
+ const allocateImportName = purpose => {
472
+ const name = allocateName(purpose)
473
+ importNamespaces.add(name)
474
+ generatedNamespaces.add(name)
475
+ return name
476
+ }
477
+ const namespaceCaptures = []
478
+ const defaultNameAvailable = !tree.body.some((node) => {
479
+ const declaration = node.type === 'ExportNamedDeclaration' ? node.declaration : node
480
+ if (declaration?.type === 'VariableDeclaration' && declaration.declare !== true) {
481
+ return declaration.declarations.some(entry => bindingNodes(entry.id).some(binding => binding.name === '__default'))
482
+ }
483
+ if (declaration?.type === 'FunctionDeclaration' || declaration?.type === 'ClassDeclaration') {
484
+ return declaration.id?.name === '__default'
485
+ }
486
+ return node.type === 'ImportDeclaration' && node.importKind !== 'type' && node.specifiers.some(specifier => (
487
+ specifier.importKind !== 'type' && specifier.local.name === '__default'
488
+ ))
489
+ })
490
+ for (const node of tree.body) {
491
+ if (node.type === 'ImportDeclaration' && enabled.autoRewriteImports) {
492
+ importEdits(node, program, edits, rewrites, imports, importDeclarations,
493
+ moduleLoads, namespaceCaptures, allocateImportName, allocateName)
494
+ }
495
+ else if (node.type === 'ExportNamedDeclaration' && enabled.autoStripExports) {
496
+ exportNamedEdits(node, program, edits, rewrites, moduleLoads)
497
+ }
498
+ else if (node.type === 'ExportAllDeclaration' && enabled.autoStripExports) {
499
+ exportAllEdits(node, program, edits, rewrites, moduleLoads)
500
+ }
501
+ else if (node.type === 'ExportDefaultDeclaration' && enabled.autoStripExports) {
502
+ exportDefaultEdits(node, program, edits, rewrites, defaultNameAvailable)
503
+ }
504
+ }
505
+ assertNoDynamicImportResolution(tree, imports)
506
+ const prologue = importPrologueEdit(program, tree, namespaceCaptures)
507
+ if (prologue !== undefined) edits.push(prologue)
508
+ const applied = applySourceEdits(program, identitySourceMap(program.length), edits)
509
+ const rewritten = rewriteImportReferences(applied.code, applied.sourceMap, imports)
510
+ rewrites.sort(ascending)
511
+ return {
512
+ ...rewritten,
513
+ imports,
514
+ importNamespaces,
515
+ generatedNamespaces,
516
+ importDeclarations,
517
+ moduleLoads,
518
+ rewrites: rewrites.map(({ at: _at, ...rest }) => rest),
519
+ }
520
+ }
@@ -0,0 +1,179 @@
1
+ /** Configuration fields shared by the Host schema and settings card. */
2
+ export const MAX_TIMER_DELAY_MS = 2_147_483_647
3
+
4
+ /** Stable settings namespace used by both Host and browser halves. */
5
+ export const SETTINGS_NAMESPACE = 'ptc-plus'
6
+
7
+ /** Ordered field definitions for the plugin configuration card. */
8
+ export const CONFIG_FIELDS = Object.freeze([
9
+ {
10
+ key: 'enabled',
11
+ type: 'boolean',
12
+ default: true,
13
+ label: '启用 PTC Plus',
14
+ description: '',
15
+ },
16
+ {
17
+ key: 'cordisToolsEnabled',
18
+ type: 'boolean',
19
+ default: false,
20
+ label: '在 PTC 模式中启用 Cordis 工具',
21
+ description: '即时为 PTC agent 加入或移除官方 Cordis 工具。',
22
+ },
23
+ {
24
+ key: 'computeMs',
25
+ type: 'integer',
26
+ default: 60_000,
27
+ min: 1,
28
+ max: Number.MAX_SAFE_INTEGER,
29
+ label: '单 cell 最大 CPU 时间 (ms)',
30
+ description: '同步计算超过该预算的 cell 会被中断。',
31
+ },
32
+ {
33
+ key: 'maxWallMs',
34
+ type: 'integer',
35
+ default: 600_000,
36
+ min: 1,
37
+ max: MAX_TIMER_DELAY_MS,
38
+ label: '单 cell 最大墙钟时间 (ms)',
39
+ description: '完整 cell 执行(含异步等待)的最长耗时。',
40
+ },
41
+ {
42
+ key: 'maxOutputBytes',
43
+ type: 'integer',
44
+ default: 64 * 1024 * 1024,
45
+ min: 1,
46
+ max: Number.MAX_SAFE_INTEGER,
47
+ label: '最大输出字节',
48
+ description: '限制单个 cell 的输出和结果数据总大小。',
49
+ },
50
+ {
51
+ key: 'maxOldGenerationSizeMb',
52
+ type: 'integer',
53
+ default: 512,
54
+ min: 1,
55
+ max: Number.MAX_SAFE_INTEGER,
56
+ label: 'worker 旧生代内存上限 (MiB)',
57
+ description: '每个 worker 的 V8 旧生代上限;活动 worker 存在时修改会被拒绝。',
58
+ },
59
+ {
60
+ key: 'maxValueNodes',
61
+ type: 'integer',
62
+ default: 100_000,
63
+ min: 1,
64
+ max: Number.MAX_SAFE_INTEGER,
65
+ label: 'Value Graph 最大节点数',
66
+ description: '限制单次返回值的节点数。',
67
+ },
68
+ {
69
+ key: 'maxValueEdges',
70
+ type: 'integer',
71
+ default: 1_000_000,
72
+ min: 1,
73
+ max: Number.MAX_SAFE_INTEGER,
74
+ label: 'Value Graph 最大边数',
75
+ description: '限制单次返回值的引用关系数。',
76
+ },
77
+ {
78
+ key: 'maxValueArrayLength',
79
+ type: 'integer',
80
+ default: 1_000_000,
81
+ min: 1,
82
+ max: Number.MAX_SAFE_INTEGER,
83
+ label: '数组最大声明长度',
84
+ description: '限制返回数组的最大长度。',
85
+ },
86
+ {
87
+ key: 'maxValueBigIntDigits',
88
+ type: 'integer',
89
+ default: 100_000,
90
+ min: 1,
91
+ max: Number.MAX_SAFE_INTEGER,
92
+ label: 'BigInt 最大十进制位数',
93
+ description: '限制返回值中 BigInt 的十进制位数。',
94
+ },
95
+ {
96
+ key: 'maxNestedRunCodeDepth',
97
+ type: 'integer',
98
+ default: 8,
99
+ min: 1,
100
+ max: Number.MAX_SAFE_INTEGER,
101
+ label: 'code.run 最大递归深度',
102
+ description: '限制 code.run 的嵌套层数。',
103
+ },
104
+ {
105
+ key: 'canonicalizeToolCalls',
106
+ type: 'boolean',
107
+ default: true,
108
+ label: '规范顶层 native 误调',
109
+ description: '修正可以明确识别的顶层 native 误调。',
110
+ },
111
+ {
112
+ key: 'looseTopLevelRedeclarations',
113
+ type: 'boolean',
114
+ default: true,
115
+ label: '宽松顶层重声明',
116
+ description: '允许顶层 const/let 重声明已有变量。',
117
+ },
118
+ {
119
+ key: 'durableReplay',
120
+ type: 'boolean',
121
+ default: true,
122
+ label: '持久重放',
123
+ description: 'worker 重启后恢复可以重建的 REPL 状态。',
124
+ },
125
+ {
126
+ key: 'autoRewriteImports',
127
+ type: 'boolean',
128
+ default: true,
129
+ label: '自动改写 import',
130
+ description: '允许在 run_code 中使用静态 import。',
131
+ },
132
+ {
133
+ key: 'autoStripExports',
134
+ type: 'boolean',
135
+ default: true,
136
+ label: '自动剥离 export',
137
+ description: '允许在 run_code 中使用顶层 export。',
138
+ },
139
+ {
140
+ key: 'autoSplitRedeclarations',
141
+ type: 'boolean',
142
+ default: true,
143
+ label: '自动拆分混合重声明',
144
+ description: '允许顶层解构声明同时包含新旧变量。',
145
+ },
146
+ {
147
+ key: 'tipsEnabled',
148
+ type: 'boolean',
149
+ default: true,
150
+ label: '启用恢复提示',
151
+ description: '在符合条件的失败后显示恢复提示。',
152
+ },
153
+ {
154
+ key: 'tipCooldownMessages',
155
+ type: 'integer',
156
+ default: 3,
157
+ min: 1,
158
+ max: Number.MAX_SAFE_INTEGER,
159
+ label: '恢复提示冷却步数',
160
+ description: '同类恢复提示之间的最小间隔。',
161
+ },
162
+ {
163
+ key: 'tipEscalationFailures',
164
+ type: 'integer',
165
+ default: 2,
166
+ min: 1,
167
+ max: Number.MAX_SAFE_INTEGER,
168
+ label: '恢复提示升级失败次数',
169
+ description: '连续失败达到此次数后显示更详细的恢复提示。',
170
+ },
171
+ ])
172
+
173
+ /** Resolved defaults, derived once from the field definitions. */
174
+ export const CONFIG_DEFAULTS = Object.freeze(
175
+ Object.fromEntries(CONFIG_FIELDS.map(field => [field.key, field.default])),
176
+ )
177
+
178
+ /** Key of the runtime kill switch; all settings are applied live by the Host. */
179
+ export const ENABLED_KEY = 'enabled'