dsh-ptc-plus 0.5.0 → 0.5.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.
@@ -39,6 +39,15 @@ import {
39
39
 
40
40
  export const EDIT_RUN_CODE = 'edit_run_code'
41
41
 
42
+ const TARGET_FIRST_LINE_LIMIT = 120
43
+
44
+ /** Bounded identity of the cell an edit actually captured, for model-facing reasons. */
45
+ function targetSummary(target) {
46
+ const line = typeof target?.source === 'string' ? target.source.split(/\r?\n/, 1)[0].trim() : ''
47
+ const firstLine = line.length > TARGET_FIRST_LINE_LIMIT ? `${line.slice(0, TARGET_FIRST_LINE_LIMIT)}…` : line
48
+ return `run_code call ${target?.callSeq}${firstLine === '' ? '' : `, first line: ${firstLine}`}`
49
+ }
50
+
42
51
  function nestedEditStart(sessionLog, exec) {
43
52
  const events = sessionEvents(sessionLog)
44
53
  if (!Array.isArray(events) || typeof exec?.callId !== 'string'
@@ -188,13 +197,16 @@ export function createEditTransportOwner(ctx, {
188
197
  return { accepted: false, scheduled: false, reason: 'no run_code cell is currently eligible for safe editing' }
189
198
  }
190
199
  const edited = editRejectedCell(args, target.source)
191
- if (!edited.edited) return { accepted: false, scheduled: false, reason: edited.reason }
200
+ if (!edited.edited) {
201
+ return { accepted: false, scheduled: false, reason: `${edited.reason} (edit target: ${targetSummary(target)})` }
202
+ }
192
203
  const expectedTargetCallSeq = args[EXPECTED_TARGET_CALL_SEQ]
193
204
  if (expectedTargetCallSeq !== undefined && expectedTargetCallSeq !== target.callSeq) {
194
205
  return {
195
206
  accepted: false,
196
207
  scheduled: false,
197
- reason: `validated repair targets run_code call ${expectedTargetCallSeq}, but this edit captured call ${target.callSeq}`,
208
+ reason: `validated repair targets run_code call ${expectedTargetCallSeq}, but this edit captured ${targetSummary(target)}.`
209
+ + ' Only the most recent settled cell stays editable; if the intended cell is no longer editable, re-submit its corrected source with run_code instead.',
198
210
  }
199
211
  }
200
212
  if (typeof ctx.tools.execute !== 'function') {
@@ -207,7 +219,8 @@ export function createEditTransportOwner(ctx, {
207
219
  editClaims.set(id, claims)
208
220
  }
209
221
  if (claims.has(target.callSeq)) {
210
- return { accepted: false, scheduled: false, reason: 'the current edit target already has a derived execution' }
222
+ return { accepted: false, scheduled: false,
223
+ reason: `the current edit target already has a derived execution (edit target: ${targetSummary(target)})` }
211
224
  }
212
225
  claims.set(target.callSeq, 'scheduled')
213
226
  const receipt = { accepted: true, scheduled: true, target_call_seq: target.callSeq }
@@ -219,6 +232,7 @@ export function createEditTransportOwner(ctx, {
219
232
  rootCallSeq: start.rootCallSeq,
220
233
  subCallId: exec.callId,
221
234
  targetCallSeq: target.callSeq,
235
+ targetDescription: targetSummary(target),
222
236
  finalized: false,
223
237
  finalizeTentative: undefined,
224
238
  finish(confirmed) {
@@ -64,12 +64,32 @@ export function apply(ctx, config = {}) {
64
64
  }
65
65
  const publish = () => {
66
66
  const resolutions = new WeakMap()
67
+ const ownerOriginals = new WeakMap()
68
+ const isOwner = owner => owner !== null && (typeof owner === 'object' || typeof owner === 'function')
67
69
  const delegate = {
68
70
  get language() { return readOriginal().language },
69
71
  get isolation() { return readOriginal().isolation },
72
+ get programFormat() { return readOriginal().programFormat ?? 'function-body' },
70
73
  get executionInstructions() { return readOriginal().executionInstructions },
71
74
  get sandboxMode() { return readOriginal().sandboxMode },
72
75
  get timeout() { return readOriginal().timeout },
76
+ release(owner) {
77
+ const originals = isOwner(owner) ? ownerOriginals.get(owner) : undefined
78
+ if (originals !== undefined) {
79
+ return Promise.all([...originals].map(original => Promise.resolve()
80
+ .then(() => original.release(owner))
81
+ .then(() => originals.delete(original)))).then(() => undefined)
82
+ }
83
+ let original
84
+ try {
85
+ original = readOriginal()
86
+ } catch {
87
+ // No retained dispatch belongs to this owner and execution is unavailable.
88
+ return Promise.resolve()
89
+ }
90
+ if (typeof original.release !== 'function') return Promise.resolve()
91
+ return Promise.resolve(original.release.call(original, owner))
92
+ },
73
93
  resolve(request) {
74
94
  const original = readOriginal()
75
95
  const spec = serviceName === EXECUTION_SEAM_SERVICE ? original.resolve(request) : request
@@ -82,6 +102,11 @@ export function apply(ctx, config = {}) {
82
102
  const resolved = resolutions.get(spec)
83
103
  if (resolved?.generation?.active === false) throw new Error('ptc-plus: resolved spec belongs to an unavailable original generation')
84
104
  const original = resolved?.original ?? readOriginal()
105
+ if (isOwner(spec?.stateOwner) && typeof original.release === 'function') {
106
+ let originals = ownerOriginals.get(spec.stateOwner)
107
+ if (originals === undefined) ownerOriginals.set(spec.stateOwner, originals = new Set())
108
+ originals.add(original)
109
+ }
85
110
  return original.run(spec)
86
111
  },
87
112
  }
@@ -60,6 +60,10 @@ export function createExecutionSeam(service, serviceName) {
60
60
  const provider = Object.freeze({
61
61
  get language() { requireCompatible(); return service.language },
62
62
  get isolation() { requireCompatible(); return installed === undefined ? service.isolation : 'worker-thread' },
63
+ // Source evaluation form belongs to the provider that actually receives
64
+ // the program. Program-format consumers delegate to the original provider,
65
+ // so this descriptor always follows the original generation.
66
+ get programFormat() { requireCompatible(); return service.programFormat ?? 'function-body' },
63
67
  get executionInstructions() { requireCompatible(); return installed === undefined ? service.executionInstructions ?? '' : '' },
64
68
  get sandboxMode() { requireCompatible(); return installed === undefined ? service.sandboxMode : undefined },
65
69
  get timeout() { requireCompatible(); return installed === undefined ? service.timeout : undefined },
@@ -71,6 +75,20 @@ export function createExecutionSeam(service, serviceName) {
71
75
  requireCompatible()
72
76
  return installed === undefined ? upstreamRun.call(service, input) : installed.execute(input)
73
77
  },
78
+ /**
79
+ * Release one owner's retained environment. The resources live in the
80
+ * original generation (the session kernel releases its state through its
81
+ * own lifecycle). Adapter retirement only closes execution; the original
82
+ * still owns cleanup for references retained by its consumers.
83
+ */
84
+ release(owner) {
85
+ if (typeof service.release !== 'function') return Promise.resolve()
86
+ try {
87
+ return Promise.resolve(service.release.call(service, owner))
88
+ } catch (error) {
89
+ return Promise.reject(error)
90
+ }
91
+ },
74
92
  [PLUGIN_EXECUTION_SEAM]() { return seam },
75
93
  })
76
94
  const seam = Object.freeze({
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Official persistent-REPL priority.
3
+ *
4
+ * The official `persistent-in-memory` execution mode is a live provider
5
+ * capability, not a release fact: a provider reports `programFormat` `'repl'`
6
+ * exactly when it retains a REPL per state owner. While the user selects the
7
+ * official REPL priority and that provider is live, the plugin yields PTC
8
+ * session execution to the official implementation and installs no session
9
+ * runtime: `run_code`, its descriptors and its guidance stay official. The
10
+ * decision is re-evaluated on every configuration change and host source
11
+ * change, so either side can take over without restarting the host.
12
+ */
13
+
14
+ /**
15
+ * Decide whether this configuration yields PTC session execution to the
16
+ * official provider.
17
+ * @param {object} config - resolved plugin configuration.
18
+ * @param {object} provider - the live provider surface registered for the seam.
19
+ * @returns {boolean} true when the official persistent REPL owns execution.
20
+ */
21
+ export function defersToOfficialRepl(config, provider) {
22
+ if (config?.officialReplPriority !== true) return false
23
+ try {
24
+ return provider?.programFormat === 'repl'
25
+ } catch {
26
+ // An unreadable capability descriptor cannot prove official ownership;
27
+ // the plugin keeps its execution responsibility instead of guessing.
28
+ return false
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Runtime record for an attachment that yields execution to the official
34
+ * provider. It owns no execution resources: the official provider releases
35
+ * anything it retained through its own lifecycle, and the plugin's settings,
36
+ * storage and UI owners remain available so the user can switch back.
37
+ */
38
+ export function createDeferredRuntime() {
39
+ return Object.freeze({
40
+ cancelCordisActivation() {
41
+ // No Cordis companion is installed while execution is deferred.
42
+ },
43
+ contextsForRequest: () => [],
44
+ async reconfigure() {
45
+ // This attachment has no owners to reconfigure.
46
+ },
47
+ async dispose() {
48
+ // Nothing is owned here; disposal returns immediately.
49
+ },
50
+ })
51
+ }
@@ -587,9 +587,10 @@ export function createRuntimeBridgeOwner({
587
587
  valueText = '; value omitted from preview'
588
588
  }
589
589
  }
590
+ const targetDescription = pendingEdit.targetDescription ?? `run_code call ${pendingEdit.targetCallSeq}`
590
591
  const status = outcome.edited
591
- ? `edit_run_code settled${outcome.error === undefined ? '' : ' with execution error'}`
592
- : 'edit_run_code did not execute the accepted revision'
592
+ ? `edit_run_code settled${outcome.error === undefined ? '' : ' with execution error'} (target ${targetDescription})`
593
+ : `edit_run_code did not execute the accepted revision (target ${targetDescription})`
593
594
  const detail = [
594
595
  ...(hasRenderedContent || !Array.isArray(outcome.logs) ? [] : outcome.logs),
595
596
  ...(!hasRenderedContent && typeof outcome.error === 'string' ? [outcome.error] : []),
@@ -625,9 +626,9 @@ export function createRuntimeBridgeOwner({
625
626
  const inherited = scope.getStore()
626
627
  const ownsDerived = ownedDerivedScope(exec) !== undefined
627
628
  if ((exec.parent !== undefined && !ownsDerived) || id === undefined
628
- || inherited?.submission?.sessionId === id) return operation()
629
+ || (inherited?.exec === exec && inherited.submission?.sessionId === id)) return operation()
629
630
  const submission = sessions.beginSubmission({ id }, exec.signal)
630
- const composite = inherited?.deferredSettlement !== undefined && inherited.id === id
631
+ const composite = ownsDerived
631
632
  ? {
632
633
  persistedCallSeq: inherited.persistedCallSeq,
633
634
  sourceCallSeq: inherited.sourceCallSeq,
@@ -637,9 +638,11 @@ export function createRuntimeBridgeOwner({
637
638
  }
638
639
  : {}
639
640
  try {
640
- // Derived edits retain their proved source under a fresh admission for
641
- // the same session. Delegated agents own a fresh log and no child handoff.
642
- return await scope.run({ ...composite, submission }, () => childScope.run(undefined, operation))
641
+ // Only reentry for this exact execution shares admission. Background
642
+ // continuations can inherit a settled execution's asynchronous context.
643
+ // Only proved derived edits retain composite source and settlement under
644
+ // a fresh admission; other roots own a fresh log and no child handoff.
645
+ return await scope.run({ ...composite, exec, submission }, () => childScope.run(undefined, operation))
643
646
  } finally {
644
647
  sessions.releaseSubmission(submission)
645
648
  }
@@ -10,7 +10,7 @@ export function projectProgramSdk(nativeSdk, { languageSemantics = 'legacy-v1' }
10
10
  const fence = nativeSdk.indexOf(TYPESCRIPT_FENCE)
11
11
  if (fence < 0) return nativeSdk
12
12
  return nativeSdk.slice(0, fence).replace(
13
- '`code` — the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped)',
14
- '`code` — a PTC Plus TypeScript cell (runtime `enum` and namespaces are supported; type annotations are advisory)',
13
+ 'the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped)',
14
+ 'a PTC Plus TypeScript cell (runtime `enum` and namespaces are supported; type annotations are advisory)',
15
15
  ) + nativeSdk.slice(fence)
16
16
  }
@@ -1,5 +1,5 @@
1
1
  import { PTC_MESSAGE_SOURCE_KIND } from './message-sources.js'
2
- import { bindingToggleMessage, readBindingToggle } from './binding-toggle-receipt.js'
2
+ import { bindingToggleMessage } from './binding-toggle-receipt.js'
3
3
  import { RPC_CONTRACTS } from './rpc-contract.js'
4
4
  import { randomUUID } from 'node:crypto'
5
5
  import { dirname } from 'node:path'
@@ -186,7 +186,6 @@ export function createUserBindingsOwner(ctx, options = {}) {
186
186
  valueLimits: options.valueLimits,
187
187
  }
188
188
  let disposed = false
189
- const pendingConfigurationReceipts = new WeakMap()
190
189
  const codeConsole = new UserBindingConsole(currentOptions)
191
190
  const commandRegistrations = new Map()
192
191
  const sessionAgents = new Map()
@@ -387,15 +386,11 @@ export function createUserBindingsOwner(ctx, options = {}) {
387
386
  const sessions = new Set((ctx.agents?.list?.() ?? []).map(agent => agent.session).filter(Boolean))
388
387
  for (const session of sessions) {
389
388
  try {
389
+ // Before the first accepted step these are initial settings, not changes
390
+ // to an environment the model has received. The catalog owns first delivery.
391
+ if (session.deriveMessages()[0]?.role !== 'system') continue
390
392
  const message = createUserMessage(bindingToggleMessage(entry, next.revision))
391
- // The Host alone establishes the first system head. Before that accepted
392
- // boundary, keep UI configuration facts pending without waking the Agent.
393
- if (session.deriveMessages()[0]?.role === 'system') session.append('user/message', message, { surfaceOp: 'append' })
394
- else {
395
- const pending = pendingConfigurationReceipts.get(session) ?? []
396
- pending.push(message)
397
- pendingConfigurationReceipts.set(session, pending)
398
- }
393
+ session.append('user/message', message, { surfaceOp: 'append' })
399
394
  } catch (error) { reportContainedFailure('ptc-plus: binding configuration saved but its session receipt could not be appended', error) }
400
395
  }
401
396
  return next
@@ -916,23 +911,6 @@ export function createUserBindingsOwner(ctx, options = {}) {
916
911
  mountAuthoring()
917
912
 
918
913
  return Object.freeze({
919
- committedEvent(session, event) {
920
- if (event.type !== 'user/message' || !readBindingToggle(event.data)) return
921
- const pending = pendingConfigurationReceipts.get(session)
922
- if (pending) pendingConfigurationReceipts.set(session, pending.filter(message => message.id !== event.data.id))
923
- },
924
- async preStep(payload, next) {
925
- const decision = await next()
926
- if (disposed || decision?.kind !== 'enter' || payload.signal?.aborted || payload.agent?.signal?.aborted) return decision
927
- const session = payload.agent?.session
928
- const pending = pendingConfigurationReceipts.get(session)
929
- if (!pending?.length) return decision
930
- const visible = new Set(session.deriveMessages().map(message => message.id))
931
- const remaining = pending.filter(message => !visible.has(message.id))
932
- pendingConfigurationReceipts.set(session, remaining)
933
- const proposed = new Set((decision.messages ?? []).map(message => message.id))
934
- return { ...decision, messages: [...(decision.messages ?? []), ...remaining.filter(message => !proposed.has(message.id))] }
935
- },
936
914
  path: store.filename,
937
915
  cwd: bindingsCwd,
938
916
  async snapshot() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-ptc-plus",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "icon": "assets/ptc-plus-icon.svg",
5
5
  "description": "A session-bound agent-native REPL for DeepSeek Harness PTC mode.",
6
6
  "type": "module",
@@ -42,7 +42,8 @@
42
42
  "LICENSE"
43
43
  ],
44
44
  "engines": {
45
- "node": "^22.19.0 || >=24.0.0"
45
+ "node": "^22.19.0 || >=24.0.0",
46
+ "dsh": "*"
46
47
  },
47
48
  "allowScripts": {
48
49
  "esbuild": false
@@ -506,6 +506,7 @@ export function derivePtcToolView(block, toolName = undefined) {
506
506
  ? block.meta[GENERATED_RUN_CODE_DESCRIPTION_KEY]
507
507
  : undefined
508
508
  const description = (explicitDescription ?? generatedDescription)?.split(/\r?\n/, 1)[0] ?? ''
509
+ const descriptionGenerated = explicitDescription === undefined && generatedDescription !== undefined
509
510
  const output = settled ? resultText(block) : ''
510
511
  const state = !settled ? 'running'
511
512
  : block.error?.code === 'interrupted' ? 'stopped'
@@ -514,7 +515,7 @@ export function derivePtcToolView(block, toolName = undefined) {
514
515
  ? block.meta.dshPtcPlus
515
516
  : undefined
516
517
  if (journal === undefined) {
517
- return Object.freeze({ state, description, code, output, ptc: false, features: Object.freeze([]) })
518
+ return Object.freeze({ state, description, descriptionGenerated, code, output, ptc: false, features: Object.freeze([]) })
518
519
  }
519
520
  const rewrites = isValidRewrites(block.meta.dshPtcPlusRewrites, journal)
520
521
  ? block.meta.dshPtcPlusRewrites
@@ -542,5 +543,5 @@ export function derivePtcToolView(block, toolName = undefined) {
542
543
  : undefined,
543
544
  ...operationFeatures(journal.operations),
544
545
  ])
545
- return Object.freeze({ state, description, code, output, ptc: true, features: Object.freeze(features) })
546
+ return Object.freeze({ state, description, descriptionGenerated, code, output, ptc: true, features: Object.freeze(features) })
546
547
  }