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.
- package/README.md +30 -11
- package/README.zh.md +30 -11
- package/assets/dsh-ptc-plus-banner-en.webp +0 -0
- package/assets/ptc-plus-settings-en.png +0 -0
- package/assets/ptc-plus-settings-zh.png +0 -0
- package/client.js +139 -46
- package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +25 -3
- package/docs/adr/0019-plugin-settings-and-kill-switch.md +2 -1
- package/docs/adr/0022-stage-npm-releases-with-oidc.md +31 -0
- package/docs/architecture.md +6 -4
- package/docs/client-ui.md +2 -2
- package/docs/installation.md +5 -5
- package/docs/publishing.md +25 -1
- package/docs/runtime-reference.md +4 -2
- package/index.js +13 -11
- package/internal/config-spec.js +46 -1
- package/internal/direct-surface-owner.js +21 -18
- package/internal/edit-transport-owner.js +12 -1
- package/internal/failure-reporting.js +1 -0
- package/internal/recovery-tips.js +0 -1
- package/internal/rejected-cell-editor.js +21 -7
- package/internal/session-cell-executor.js +52 -26
- package/internal/session-runtime.js +12 -4
- package/internal/settings-compat.js +31 -0
- package/internal/validated-parse-repair.js +82 -0
- package/package.json +17 -11
- package/src/client.js +88 -26
- package/assets/ptc-plus-settings.png +0 -0
- /package/assets/{dsh-ptc-plus-banner.webp → dsh-ptc-plus-banner-zh.webp} +0 -0
|
@@ -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 =
|
|
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
|
-
? '
|
|
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
|
-
? '
|
|
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 === '
|
|
225
|
+
&& (initialState.ownerProven || initialState.presentation === 'ptc')) {
|
|
223
226
|
composition = captureComposition(agent, id, initialState.presentation)
|
|
224
227
|
}
|
|
225
|
-
if (composition?.presentation === '
|
|
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 === '
|
|
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
|
|
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 (
|
|
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
|
-
|| (
|
|
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 (
|
|
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 === '
|
|
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:
|
|
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 === '
|
|
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 === '
|
|
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 !== '
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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(`${
|
|
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(
|
|
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(
|
|
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:
|
|
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 {
|
|
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
|
|
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
|
-
...(
|
|
67
|
-
source: { cell: 'current', start:
|
|
68
|
-
}
|
|
69
|
-
help:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 >=
|
|
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
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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.
|
|
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": "
|
|
38
|
-
"@deepseek-ai/dsh-skill-filesystem": "
|
|
39
|
-
"@deepseek-ai/dsh-tool-cordis": "
|
|
40
|
-
"@deepseek-ai/dsh-tools": "
|
|
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": "
|
|
94
|
-
"@deepseek-ai/dsh-skill-filesystem": "
|
|
95
|
-
"@deepseek-ai/dsh-tool-cordis": "
|
|
96
|
-
"@deepseek-ai/dsh-tools": "
|
|
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
|
}
|