dsh-ptc-plus 0.2.2 → 0.2.4

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.
@@ -11,6 +11,7 @@ const RUN_CODE_TOOL_DESCRIPTION = 'Evaluate the next TypeScript cell in this ses
11
11
  const RUN_CODE_CODE_DESCRIPTION = 'Code for the next REPL cell, parsed as the body of an async TypeScript function.'
12
12
  const RUN_CODE_DESCRIPTION_DESCRIPTION = 'Short active-voice summary of what this cell does, 5-10 words (shown in the UI).'
13
13
  const CODE_TRANSPORT_INSTRUCTION = '`run_code` and `edit_run_code` are the only tools callable directly. Call every native tool declared by the SDK from inside a program.'
14
+ const PTC_COLLAPSE_SECTION_NAMES = Object.freeze(['tools:ptc-only', 'tools:code-only'])
14
15
 
15
16
  function adaptRunCodeSchema(tool) {
16
17
  const parameters = tool.parameters
@@ -94,19 +95,21 @@ export function createCordisRecoveryPolicy(initiallyEnabled) {
94
95
  function presentationState(assembly) {
95
96
  const tools = Array.isArray(assembly?.tools) ? assembly.tools : []
96
97
  if (!tools.some(tool => tool?.name === RUN_CODE)) {
97
- return { presentation: 'native', ownerProven: false }
98
+ return { presentation: 'native', ownerProven: false, collapseSectionName: undefined }
98
99
  }
99
- const collapse = assembly?.sections?.find(section => section?.name === 'tools:code-only')
100
+ const collapse = PTC_COLLAPSE_SECTION_NAMES
101
+ .map(name => assembly?.sections?.find(section => section?.name === name))
102
+ .find(section => section !== undefined)
100
103
  if (collapse !== undefined) {
101
104
  const presentation = typeof collapse.text === 'string' && collapse.text.trim().length > 0
102
- ? 'code'
105
+ ? 'ptc'
103
106
  : 'both'
104
- return { presentation, ownerProven: true }
107
+ return { presentation, ownerProven: true, collapseSectionName: collapse.name }
105
108
  }
106
109
  const presentation = tools.every(tool => tool?.name === RUN_CODE || tool?.name === EDIT_RUN_CODE)
107
- ? 'code'
110
+ ? 'ptc'
108
111
  : 'both'
109
- return { presentation, ownerProven: false }
112
+ return { presentation, ownerProven: false, collapseSectionName: undefined }
110
113
  }
111
114
 
112
115
  async function* bindCallPolicies(source, policy, calls) {
@@ -219,10 +222,10 @@ export function createDirectSurfaceOwner({
219
222
  const initialState = presentationState(initialAssembly)
220
223
  let composition = compositions.get(agent)
221
224
  if (composition === undefined && id !== undefined
222
- && (initialState.ownerProven || initialState.presentation === 'code')) {
225
+ && (initialState.ownerProven || initialState.presentation === 'ptc')) {
223
226
  composition = captureComposition(agent, id, initialState.presentation)
224
227
  }
225
- if (composition?.presentation === 'code' && !editTransport.isInstalled(agent)) {
228
+ if (composition?.presentation === 'ptc' && !editTransport.isInstalled(agent)) {
226
229
  editTransport.ensureInstalled(agent)
227
230
  }
228
231
 
@@ -235,7 +238,7 @@ export function createDirectSurfaceOwner({
235
238
  if (composition === undefined && id !== undefined) {
236
239
  const state = initialState.ownerProven ? initialState : completedState
237
240
  composition = captureComposition(agent, id, state.presentation)
238
- if (composition.presentation === 'code' && !editTransport.isInstalled(agent)) {
241
+ if (composition.presentation === 'ptc' && !editTransport.isInstalled(agent)) {
239
242
  editTransport.ensureInstalled(agent)
240
243
  }
241
244
  }
@@ -257,10 +260,10 @@ export function createDirectSurfaceOwner({
257
260
  throw new Error('ptc-plus: native agent composition assembled with run_code')
258
261
  }
259
262
  const sessionPtc = presentation !== 'native' && id !== undefined
260
- const sessionCodeOnlyProjection = presentation === 'code' && id !== undefined
263
+ const sessionPtcProjection = presentation === 'ptc' && id !== undefined
261
264
  const runCode = tools.find(tool => tool?.name === RUN_CODE)
262
265
  let directTools = tools
263
- if (sessionCodeOnlyProjection) {
266
+ if (sessionPtcProjection) {
264
267
  editTransport.ensureInstalled(agent)
265
268
  directTools = [adaptRunCodeSchema(runCode), editRunCodeSchema()]
266
269
  }
@@ -277,13 +280,13 @@ export function createDirectSurfaceOwner({
277
280
  : assembly.contexts
278
281
  const projectsSections = presentation !== 'native' && Array.isArray(assembly.sections)
279
282
  && assembly.sections.some(section => section?.name === 'tools:sdk'
280
- || (sessionCodeOnlyProjection && section?.name === 'tools:code-only'))
283
+ || (sessionPtcProjection && section?.name === completedState.collapseSectionName))
281
284
  const sections = projectsSections
282
285
  ? assembly.sections.map(section => {
283
286
  if (section?.name === 'tools:sdk') {
284
287
  return { ...section, text: capabilitySdk(section.text) }
285
288
  }
286
- if (sessionCodeOnlyProjection && section?.name === 'tools:code-only') {
289
+ if (sessionPtcProjection && section?.name === completedState.collapseSectionName) {
287
290
  return { ...section, text: CODE_TRANSPORT_INSTRUCTION }
288
291
  }
289
292
  return section
@@ -291,7 +294,7 @@ export function createDirectSurfaceOwner({
291
294
  : assembly.sections
292
295
 
293
296
  if (id !== undefined) {
294
- const nativeSchemas = presentation === 'code'
297
+ const nativeSchemas = presentation === 'ptc'
295
298
  ? new Map(toolSchemasForAgent(agent)
296
299
  .filter(schema => typeof schema?.name === 'string'
297
300
  && schema.name !== RUN_CODE && schema.name !== EDIT_RUN_CODE)
@@ -302,7 +305,7 @@ export function createDirectSurfaceOwner({
302
305
 
303
306
  return {
304
307
  ...assembly,
305
- tools: sessionCodeOnlyProjection
308
+ tools: sessionPtcProjection
306
309
  ? directTools
307
310
  : directTools.map(tool => tool?.name === RUN_CODE ? adaptRunCodeSchema(tool) : tool),
308
311
  sections,
@@ -313,7 +316,7 @@ export function createDirectSurfaceOwner({
313
316
  const optionSessionId = options.sessionId === undefined ? undefined : String(options.sessionId)
314
317
  const policy = requestPolicy(options?.signal, optionSessionId)
315
318
  if (policy === undefined || optionSessionId === undefined) return next()
316
- const source = policy.presentation === 'code'
319
+ const source = policy.presentation === 'ptc'
317
320
  ? canonicalizeToolCallStream(next(), {
318
321
  tools: options.tools,
319
322
  nativeSchemas: currentCanonicalizeToolCalls ? policy.nativeSchemas : new Map(),
@@ -330,14 +333,14 @@ export function createDirectSurfaceOwner({
330
333
  const policy = executionPolicy(id, exec.callId)
331
334
  ?? requestPolicy(exec?.signal, id)
332
335
  if (exec.name === EDIT_RUN_CODE) {
333
- return policy?.presentation === 'code'
336
+ return policy?.presentation === 'ptc'
334
337
  ? undefined
335
338
  : rejection(`tool ${EDIT_RUN_CODE} is not declared for this request`)
336
339
  }
337
340
  if (exec.name === RUN_CODE || exec.parent !== undefined) {
338
341
  return undefined
339
342
  }
340
- if (policy?.presentation !== 'code') return undefined
343
+ if (policy?.presentation !== 'ptc') return undefined
341
344
  return rejection(`tool ${exec.name} is not a direct PTC tool; use run_code or edit_run_code directly, and call native tools from inside run_code`)
342
345
  },
343
346
  handleResult(exec) {
@@ -12,7 +12,11 @@ import {
12
12
  normalizeRewrites,
13
13
  validatedRewrites,
14
14
  } from './session-journal.js'
15
- import { editRejectedCell, editRunCodeSchema } from './rejected-cell-editor.js'
15
+ import {
16
+ EXPECTED_TARGET_CALL_SEQ,
17
+ editRejectedCell,
18
+ editRunCodeSchema,
19
+ } from './rejected-cell-editor.js'
16
20
  import { editTargetForCall, projectSessionLog } from './session-log-view.js'
17
21
  import { RUN_CODE } from './runtime-bridge-owner.js'
18
22
  import { isRecord } from './record-utils.js'
@@ -126,6 +130,13 @@ export function createEditTransportOwner(ctx, {
126
130
  }
127
131
  const edited = editRejectedCell(args, target.source)
128
132
  if (!edited.edited) return edited
133
+ const expectedTargetCallSeq = args[EXPECTED_TARGET_CALL_SEQ]
134
+ if (expectedTargetCallSeq !== undefined && expectedTargetCallSeq !== target.callSeq) {
135
+ return {
136
+ edited: false,
137
+ reason: `validated repair targets run_code call ${expectedTargetCallSeq}, but this edit captured call ${target.callSeq}`,
138
+ }
139
+ }
129
140
  if (typeof ctx.tools.execute !== 'function') {
130
141
  throw new Error('ptc-plus: DSH tools.execute is required for derived edit execution')
131
142
  }
@@ -2,6 +2,7 @@ import { diagnostic } from './diagnostic.js'
2
2
 
3
3
  export const MAX_ERROR_LOG_BYTES = 4 * 1024
4
4
  export const FAILURE_HINT_THRESHOLD = 3
5
+ export const LONG_CELL_CODE_UNITS = 2_000
5
6
  const BINDING_FAILURE = Symbol('binding failure')
6
7
 
7
8
  export function markBindingFailure(error) {
@@ -1,6 +1,5 @@
1
1
  const PLATFORM_CAUSE_CODES = new Set(['EACCES', 'EINVAL', 'ENOTDIR', 'ENOENT', 'UNKNOWN'])
2
2
  const TIP_CONTEXT_PREFIX = 'tools:ptc-plus-tip/'
3
- export const LONG_FAILED_CELL_TIP_CODE_UNITS = 2_000
4
3
  const TIP_PREFIXES = Object.freeze({
5
4
  'repeated-binding-failure': 'The same binding failure has recurred.',
6
5
  'platform-command-failure': 'An executable, shell, or path failed in the current execution world.',
@@ -14,6 +14,7 @@ export const EDIT_LIMITS = Object.freeze({
14
14
 
15
15
  const EDIT_RUN_CODE_EXECUTION_DESCRIPTION = 'Edit and run TypeScript cell'
16
16
  const REGEX_EDIT_RUN_CODE_EXECUTION_DESCRIPTION = 'Regex-edit and run TypeScript cell'
17
+ export const EXPECTED_TARGET_CALL_SEQ = 'expected_target_call_seq'
17
18
 
18
19
  const REGEX_MATCH_SCRIPT = `(() => {
19
20
  const replacements = []
@@ -254,7 +255,7 @@ function assemble(source, replacements) {
254
255
  export function editRunCodeSchema() {
255
256
  return {
256
257
  name: 'edit_run_code',
257
- description: 'Make a small exact or regular-expression change to the most recent eligible cell captured when this edit call is dispatched, then run the complete corrected cell. A successful edit becomes the next eligible cell. Use this only when replaying the whole cell is safe. If earlier code may already have caused an external effect, use a new run_code cell and its existing variables instead; this tool does not resume at the error location. Send exactly one atomic edits or regex_edits array. Choose edits for a few unique literal fragments; choose regex_edits when one counted pattern covers repeated fragments. Every resolved range must be non-overlapping in the original cell.',
258
+ description: 'Make a small exact or regular-expression change to the most recent eligible cell captured when this edit call is dispatched, then run the complete corrected cell. A successful edit becomes the next eligible cell. Use this only when replaying the whole cell is safe. If earlier code may already have caused an external effect, use a new run_code cell and its existing variables instead; this tool does not resume at the error location. Send exactly one atomic edits or regex_edits array, plus expected_target_call_seq only when a diagnostic supplies it to bind a validated repair to its rejected cell. Choose edits for a few unique literal fragments; choose regex_edits when one counted pattern covers repeated fragments. Every resolved range must be non-overlapping in the original cell.',
258
259
  parameters: {
259
260
  type: 'object',
260
261
  additionalProperties: false,
@@ -285,6 +286,10 @@ export function editRunCodeSchema() {
285
286
  required: ['pattern', 'flags', 'replacement', 'expected_matches'],
286
287
  },
287
288
  },
289
+ [EXPECTED_TARGET_CALL_SEQ]: {
290
+ type: 'integer', minimum: 0,
291
+ description: 'Optional target precondition copied from a validated diagnostic. The edit is rejected if the captured cell has another call sequence.',
292
+ },
288
293
  },
289
294
  oneOf: [{ required: ['edits'] }, { required: ['regex_edits'] }],
290
295
  },
@@ -296,32 +301,41 @@ export function editRejectedCell(value, source, timeoutMs = DEFAULT_REGEX_TIMEOU
296
301
  return rejection('edit_run_code expects an object with exactly one edits or regex_edits array')
297
302
  }
298
303
  const keys = Object.keys(value)
299
- if (keys.length !== 1 || !['edits', 'regex_edits'].includes(keys[0]) || !Array.isArray(value[keys[0]])) {
304
+ const operationKeys = keys.filter(key => key === 'edits' || key === 'regex_edits')
305
+ if (operationKeys.length !== 1 || keys.length > 2
306
+ || keys.some(key => !['edits', 'regex_edits', EXPECTED_TARGET_CALL_SEQ].includes(key))
307
+ || !Array.isArray(value[operationKeys[0]])) {
300
308
  return rejection('edit_run_code expects exactly one edits or regex_edits array')
301
309
  }
310
+ if (Object.hasOwn(value, EXPECTED_TARGET_CALL_SEQ)
311
+ && (!Number.isSafeInteger(value[EXPECTED_TARGET_CALL_SEQ])
312
+ || value[EXPECTED_TARGET_CALL_SEQ] < 0)) {
313
+ return rejection('edit_run_code expected_target_call_seq must be a non-negative safe integer')
314
+ }
302
315
  if (source === undefined) return rejection('no run_code cell is currently eligible for safe editing')
303
- const resolved = keys[0] === 'edits'
316
+ const operation = operationKeys[0]
317
+ const resolved = operation === 'edits'
304
318
  ? exactReplacements(value.edits, source)
305
319
  : regexReplacements(value.regex_edits, source, timeoutMs)
306
320
  if (resolved.error !== undefined) return rejection(resolved.error)
307
321
  const replacements = resolved.replacements.sort((left, right) => left.start - right.start)
308
322
  for (let index = 1; index < replacements.length; index += 1) {
309
323
  if (replacements[index].start < replacements[index - 1].end) {
310
- return rejection(`${keys[0]}[${replacements[index].index}] overlaps ${keys[0]}[${replacements[index - 1].index}] in the target cell`)
324
+ return rejection(`${operation}[${replacements[index].index}] overlaps ${operation}[${replacements[index - 1].index}] in the target cell`)
311
325
  }
312
326
  }
313
327
  const preflight = preflightAssembly(source, replacements)
314
328
  if (preflight.error !== undefined) {
315
- return rejection(keys[0] === 'regex_edits' ? preflight.error.replace(/^edits/, 'regex_edits') : preflight.error)
329
+ return rejection(operation === 'regex_edits' ? preflight.error.replace(/^edits/, 'regex_edits') : preflight.error)
316
330
  }
317
331
  const assembled = assemble(source, preflight.replacements)
318
332
  if (assembled.error !== undefined) {
319
- return rejection(keys[0] === 'regex_edits' ? assembled.error.replace(/^edits/, 'regex_edits') : assembled.error)
333
+ return rejection(operation === 'regex_edits' ? assembled.error.replace(/^edits/, 'regex_edits') : assembled.error)
320
334
  }
321
335
  return Object.freeze({
322
336
  edited: true,
323
337
  code: assembled.code,
324
- description: keys[0] === 'edits'
338
+ description: operation === 'edits'
325
339
  ? EDIT_RUN_CODE_EXECUTION_DESCRIPTION
326
340
  : REGEX_EDIT_RUN_CODE_EXECUTION_DESCRIPTION,
327
341
  })
@@ -9,6 +9,7 @@ import { diagnostic, renderDiagnostic } from './diagnostic.js'
9
9
  import {
10
10
  firstLine,
11
11
  limitLogs,
12
+ LONG_CELL_CODE_UNITS,
12
13
  markBindingFailure,
13
14
  messageOf,
14
15
  oneLineMessage,
@@ -19,7 +20,7 @@ import { PreflightError, prepareProgram } from './cell-analysis.js'
19
20
  import { ModuleRewriteError } from './cell-rewriter.js'
20
21
  import { mapSourcePosition } from './source-position-map.js'
21
22
  import { durabilityState, transitionDurability } from './session-state.js'
22
- import { LONG_FAILED_CELL_TIP_CODE_UNITS } from './recovery-tips.js'
23
+ import { validatedEofClosureRepair } from './validated-parse-repair.js'
23
24
 
24
25
  const OUTPUT_LIMIT_MESSAGE = bytes => `output exceeded ${bytes} bytes; reduce the returned value or keep it in a REPL binding`
25
26
 
@@ -49,7 +50,7 @@ function isBindingReferenceError(error) {
49
50
  || /Cannot access ['"][^'"]+['"] before initialization/.test(error.message))
50
51
  }
51
52
 
52
- function parseDiagnostic(error, source) {
53
+ function parseCellPosition(error) {
53
54
  const cellPosition = error instanceof ModuleRewriteError ? error.cellPosition : undefined
54
55
  const line = Number.isSafeInteger(cellPosition?.line)
55
56
  ? cellPosition.line
@@ -57,16 +58,29 @@ function parseDiagnostic(error, source) {
57
58
  const column = Number.isSafeInteger(cellPosition?.column)
58
59
  ? cellPosition.column
59
60
  : Number.isSafeInteger(error?.loc?.column) ? error.loc.column + 1 : undefined
61
+ return line !== undefined && line >= 1 && column !== undefined
62
+ ? { line, column }
63
+ : undefined
64
+ }
65
+
66
+ function parseDiagnostic(error, source, position, repair) {
60
67
  return diagnostic({
61
68
  code: 'PTC-C001',
62
69
  severity: 'error',
63
70
  phase: 'parse',
64
71
  message: `cell could not be parsed: ${oneLineMessage(error)}`,
65
72
  stateEffect: 'unchanged',
66
- ...(line !== undefined && line >= 1 && column !== undefined ? {
67
- source: { cell: 'current', start: { line, column } },
68
- } : {}),
69
- help: ['repair the reported syntax and retry only this cell'],
73
+ ...(position === undefined ? {} : {
74
+ source: { cell: 'current', start: position },
75
+ }),
76
+ help: repair === undefined
77
+ ? source.length >= LONG_CELL_CODE_UNITS
78
+ ? ['this cell was not executed; when edit_run_code is declared for the current request and the correction is small and localized, use it to avoid resending this long source; otherwise retry only this cell with corrected source in run_code']
79
+ : ['this cell was not executed; correct the reported syntax and retry only this cell with run_code']
80
+ : [
81
+ `this cell was not executed; validated syntax repair: append ${JSON.stringify(repair.delimiter)} at the end of this cell`,
82
+ `when edit_run_code is declared for the current request, call ${repair.invocation} to apply this correction and rerun the cell; otherwise retry only this cell with the corrected source in run_code`,
83
+ ],
70
84
  })
71
85
  }
72
86
 
@@ -196,29 +210,41 @@ export class SessionCellExecutor {
196
210
  return result
197
211
  }
198
212
 
199
- let prepared
200
213
  const catalog = kernel.bindingCatalog.inputs()
201
- let looseTopLevelRedeclarations
214
+ const looseTopLevelRedeclarations = replayRecord === undefined
215
+ ? config.looseTopLevelRedeclarations
216
+ : replayRecord.bindingMode === 'loose'
217
+ const rewritesEnabled = replayRecord === undefined ? {
218
+ autoRewriteImports: config.autoRewriteImports,
219
+ autoStripExports: config.autoStripExports,
220
+ autoSplitRedeclarations: config.autoSplitRedeclarations,
221
+ } : replayRecord.rewritePolicy
222
+ const prepareCell = program => prepareProgram(
223
+ program,
224
+ catalog.knownBindings,
225
+ looseTopLevelRedeclarations,
226
+ request.bindingDescriptors.reservedNames,
227
+ rewritesEnabled,
228
+ catalog.importBindings,
229
+ catalog.importNamespaces,
230
+ )
231
+ let prepared
202
232
  try {
203
- looseTopLevelRedeclarations = replayRecord === undefined
204
- ? config.looseTopLevelRedeclarations
205
- : replayRecord.bindingMode === 'loose'
206
- prepared = prepareProgram(
207
- request.program,
208
- catalog.knownBindings,
209
- looseTopLevelRedeclarations,
210
- request.bindingDescriptors.reservedNames,
211
- replayRecord === undefined ? {
212
- autoRewriteImports: config.autoRewriteImports,
213
- autoStripExports: config.autoStripExports,
214
- autoSplitRedeclarations: config.autoSplitRedeclarations,
215
- } : replayRecord.rewritePolicy,
216
- catalog.importBindings,
217
- catalog.importNamespaces,
218
- )
233
+ prepared = prepareCell(request.program)
219
234
  } catch (error) {
220
235
  const result = earlyResult('exception', messageOf(error))
221
- const failure = error instanceof PreflightError ? preflightDiagnostic(error) : parseDiagnostic(error, request.program)
236
+ const position = parseCellPosition(error)
237
+ const repair = error instanceof PreflightError || replayRecord !== undefined
238
+ ? undefined
239
+ : validatedEofClosureRepair({
240
+ source: request.program,
241
+ position,
242
+ prepare: prepareCell,
243
+ targetCallSeq: request.sourceCallSeq,
244
+ })
245
+ const failure = error instanceof PreflightError
246
+ ? preflightDiagnostic(error)
247
+ : parseDiagnostic(error, request.program, position, repair)
222
248
  result.error.message = renderDiagnostic(failure, request.program)
223
249
  kernel.completeJournal(request.journal, 'noop', result, undefined, [failure])
224
250
  return result
@@ -466,7 +492,7 @@ export class SessionCellExecutor {
466
492
  active.prepared.sourceMap,
467
493
  ),
468
494
  declared: message.moduleLoadFailed === true ? new Set() : active.prepared.declared,
469
- longCellFailure: active.request.program.length >= LONG_FAILED_CELL_TIP_CODE_UNITS,
495
+ longCellFailure: active.request.program.length >= LONG_CELL_CODE_UNITS,
470
496
  })
471
497
  active.appliedBindingCatalog = message.moduleLoadFailed === true
472
498
  ? active.priorBindingCatalog
@@ -432,14 +432,22 @@ export class SessionRuntime {
432
432
  request = { ...request, bindings: bindingDescriptors.namespaces, bindingDescriptors }
433
433
  const { id: sessionId, session, callId, persistedCallSeq, cwd } = sessionOf(sessionContext)
434
434
  let callSeq
435
+ let sourceCallSeq
435
436
  try {
436
437
  if (persistedCallSeq !== undefined
437
438
  && (!Number.isSafeInteger(persistedCallSeq) || persistedCallSeq < 0)) {
438
439
  throw new Error('persisted tool call sequence must be a non-negative safe integer')
439
440
  }
440
- callSeq = cellConfig.durableReplay
441
- ? persistedCallSeq ?? liveToolCallSeq(session, callId, 'run_code')
442
- : undefined
441
+ if (cellConfig.durableReplay) {
442
+ sourceCallSeq = liveToolCallSeq(session, callId, 'run_code')
443
+ callSeq = persistedCallSeq ?? sourceCallSeq
444
+ } else {
445
+ try {
446
+ sourceCallSeq = liveToolCallSeq(session, callId, 'run_code')
447
+ } catch {
448
+ sourceCallSeq = undefined
449
+ }
450
+ }
443
451
  } catch (error) {
444
452
  return completed({ logs: [], error: { kind: 'recovery', message: `cannot identify current run_code call in session log: ${messageOf(error)}` } })
445
453
  }
@@ -471,7 +479,7 @@ export class SessionRuntime {
471
479
  const workerReservation = kernel.reserveWorkerConfiguration(cellConfig)
472
480
  let result
473
481
  try {
474
- result = await kernel.run({ ...request, journal, callSeq }, cellConfig)
482
+ result = await kernel.run({ ...request, journal, callSeq, sourceCallSeq }, cellConfig)
475
483
  } finally {
476
484
  kernel.releaseWorkerConfiguration(workerReservation)
477
485
  }
@@ -0,0 +1,31 @@
1
+ /** Install an optional settings section across current and legacy DSH settings APIs. */
2
+ export function installSettingsSectionCompat({
3
+ ctx,
4
+ settingsModule,
5
+ namespace,
6
+ schema,
7
+ entry,
8
+ hooks,
9
+ onProvider,
10
+ }) {
11
+ ctx.inject(['settings'], settingsContext => {
12
+ const provider = settingsContext.settings
13
+ onProvider(provider)
14
+ if (typeof provider?.installSection === 'function') {
15
+ provider.installSection(ctx, namespace, schema, entry, hooks)
16
+ return
17
+ }
18
+ if (typeof settingsModule?.installSettingsSection !== 'function') {
19
+ throw new Error('ptc-plus: incompatible settings service; expected installSection or installSettingsSection')
20
+ }
21
+ const mountedOwner = new Proxy(ctx, {
22
+ get(target, property) {
23
+ if (property === 'inject') {
24
+ return (_services, attach) => attach(settingsContext)
25
+ }
26
+ return Reflect.get(target, property, target)
27
+ },
28
+ })
29
+ settingsModule.installSettingsSection(mountedOwner, namespace, schema, entry, hooks)
30
+ })
31
+ }
@@ -0,0 +1,82 @@
1
+ import { EXPECTED_TARGET_CALL_SEQ, editRejectedCell } from './rejected-cell-editor.js'
2
+
3
+ const CLOSING_DELIMITERS = Object.freeze(['}', ')', ']'])
4
+ const LINE_TERMINATORS = /\r\n|[\n\r\u2028\u2029]/gu
5
+ export const MAX_PARSE_REPAIR_ANCHOR_CODE_UNITS = 256
6
+
7
+ function sourceEndPosition(source) {
8
+ let line = 1
9
+ let lineStart = 0
10
+ for (const match of source.matchAll(LINE_TERMINATORS)) {
11
+ line += 1
12
+ lineStart = match.index + match[0].length
13
+ }
14
+ return { line, column: source.length - lineStart + 1 }
15
+ }
16
+
17
+ function isSourceEnd(source, position) {
18
+ if (!Number.isSafeInteger(position?.line) || !Number.isSafeInteger(position?.column)) return false
19
+ const end = sourceEndPosition(source)
20
+ return position.line === end.line && position.column === end.column
21
+ }
22
+
23
+ function startsInsideSurrogatePair(source, index) {
24
+ if (index <= 0) return false
25
+ const current = source.charCodeAt(index)
26
+ const previous = source.charCodeAt(index - 1)
27
+ return current >= 0xdc00 && current <= 0xdfff && previous >= 0xd800 && previous <= 0xdbff
28
+ }
29
+
30
+ function boundedUniqueSuffix(source) {
31
+ const first = Math.max(0, source.length - MAX_PARSE_REPAIR_ANCHOR_CODE_UNITS)
32
+ for (let start = source.length - 1; start >= first; start -= 1) {
33
+ if (startsInsideSurrogatePair(source, start)) continue
34
+ const suffix = source.slice(start)
35
+ if (source.indexOf(suffix) === source.lastIndexOf(suffix)) return suffix
36
+ }
37
+ return undefined
38
+ }
39
+
40
+ function repairArguments(anchor, delimiter, targetCallSeq) {
41
+ const edit = Object.freeze({ old_string: anchor, new_string: anchor + delimiter })
42
+ return Object.freeze({
43
+ edits: Object.freeze([edit]),
44
+ [EXPECTED_TARGET_CALL_SEQ]: targetCallSeq,
45
+ })
46
+ }
47
+
48
+ function renderInvocation(argumentsValue) {
49
+ const json = JSON.stringify(argumentsValue)
50
+ .replaceAll('\u2028', '\\u2028')
51
+ .replaceAll('\u2029', '\\u2029')
52
+ return `edit_run_code(${json})`
53
+ }
54
+
55
+ /** Returns a source-exact edit only when one bounded EOF-closing candidate is valid. */
56
+ export function validatedEofClosureRepair({ source, position, prepare, targetCallSeq }) {
57
+ if (!Number.isSafeInteger(targetCallSeq) || targetCallSeq < 0
58
+ || !isSourceEnd(source, position)) return undefined
59
+ const anchor = boundedUniqueSuffix(source)
60
+ if (anchor === undefined) return undefined
61
+ const candidates = []
62
+ for (const delimiter of CLOSING_DELIMITERS) {
63
+ const code = source + delimiter
64
+ let prepared
65
+ try {
66
+ prepared = prepare(code)
67
+ } catch {
68
+ continue
69
+ }
70
+ if (prepared.collisions.length > 0) continue
71
+ const argumentsValue = repairArguments(anchor, delimiter, targetCallSeq)
72
+ const edited = editRejectedCell(argumentsValue, source)
73
+ if (!edited.edited || edited.code !== code) continue
74
+ candidates.push({ delimiter, arguments: argumentsValue })
75
+ }
76
+ if (candidates.length !== 1) return undefined
77
+ const candidate = candidates[0]
78
+ return Object.freeze({
79
+ ...candidate,
80
+ invocation: renderInvocation(candidate.arguments),
81
+ })
82
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-ptc-plus",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "A session-bound agent-native REPL for DeepSeek Harness PTC mode.",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -15,9 +15,11 @@
15
15
  "src",
16
16
  "internal",
17
17
  "docs",
18
- "assets/dsh-ptc-plus-banner.webp",
18
+ "assets/dsh-ptc-plus-banner-en.webp",
19
+ "assets/dsh-ptc-plus-banner-zh.webp",
19
20
  "assets/ptc-plus-repair.png",
20
- "assets/ptc-plus-settings.png",
21
+ "assets/ptc-plus-settings-en.png",
22
+ "assets/ptc-plus-settings-zh.png",
21
23
  "cordis.patch.yml",
22
24
  "README.md",
23
25
  "README.zh.md",
@@ -26,6 +28,9 @@
26
28
  "engines": {
27
29
  "node": "^22.19.0 || >=24.0.0"
28
30
  },
31
+ "allowScripts": {
32
+ "esbuild": false
33
+ },
29
34
  "dependencies": {
30
35
  "@babel/code-frame": "^7.29.7",
31
36
  "@babel/parser": "^7.29.0",
@@ -34,10 +39,10 @@
34
39
  "acorn": "^8.15.0"
35
40
  },
36
41
  "peerDependencies": {
37
- "@deepseek-ai/dsh-settings": "next",
38
- "@deepseek-ai/dsh-skill-filesystem": "next",
39
- "@deepseek-ai/dsh-tool-cordis": "next",
40
- "@deepseek-ai/dsh-tools": "next"
42
+ "@deepseek-ai/dsh-settings": "*",
43
+ "@deepseek-ai/dsh-skill-filesystem": "*",
44
+ "@deepseek-ai/dsh-tool-cordis": "*",
45
+ "@deepseek-ai/dsh-tools": "*"
41
46
  },
42
47
  "scripts": {
43
48
  "build": "node scripts/client-bundle.mjs build",
@@ -82,6 +87,7 @@
82
87
  ],
83
88
  "inject": [
84
89
  "@deepseek-ai/dsh-client-runtime",
90
+ "@deepseek-ai/dsh-client-locale",
85
91
  "@deepseek-ai/dsh-client-ui-settings",
86
92
  "@deepseek-ai/dsh-client-ui-settings-plugins",
87
93
  "@deepseek-ai/dsh-client-connection",
@@ -90,10 +96,10 @@
90
96
  }
91
97
  },
92
98
  "devDependencies": {
93
- "@deepseek-ai/dsh-settings": "next",
94
- "@deepseek-ai/dsh-skill-filesystem": "next",
95
- "@deepseek-ai/dsh-tool-cordis": "next",
96
- "@deepseek-ai/dsh-tools": "next",
99
+ "@deepseek-ai/dsh-settings": "alpha",
100
+ "@deepseek-ai/dsh-skill-filesystem": "alpha",
101
+ "@deepseek-ai/dsh-tool-cordis": "alpha",
102
+ "@deepseek-ai/dsh-tools": "alpha",
97
103
  "esbuild": "0.28.2",
98
104
  "yaml": "^2.9.0"
99
105
  }