dsh-ptc-plus 0.4.3 → 0.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +15 -3
- package/README.md +15 -3
- package/client.js +1157 -533
- package/cordis.patch.yml +12 -2
- package/docs/README.md +2 -0
- package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +60 -4
- package/docs/adr/0017-track-the-latest-dsh-public-surface.md +5 -1
- package/docs/adr/0019-plugin-settings-and-kill-switch.md +14 -3
- package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -2
- package/docs/adr/0023-global-user-bindings.md +1 -1
- package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +82 -18
- package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +3 -3
- package/docs/adr/0032-own-session-log-facts-in-a-host-only-projection.md +27 -0
- package/docs/adr/0033-gate-client-visual-usability-in-a-browser.md +23 -0
- package/docs/architecture.md +6 -4
- package/docs/binding-review-placement-design.md +29 -21
- package/docs/client-ui.md +20 -8
- package/docs/client-visual-contract.md +48 -0
- package/docs/durability-design.md +12 -0
- package/docs/installation.md +48 -9
- package/docs/publishing.md +4 -4
- package/docs/release-notes.md +109 -0
- package/docs/runtime-reference.md +8 -2
- package/docs/user-bindings.en.md +2 -2
- package/docs/user-bindings.md +3 -3
- package/docs/verification-optimization-work.md +26 -0
- package/docs/verification.md +7 -1
- package/index.js +28 -7
- package/internal/compiler-platform.js +5 -3
- package/internal/compiler-service.js +2 -9
- package/internal/cordis-tools-owner.js +75 -16
- package/internal/direct-surface-owner.js +333 -7
- package/internal/edit-transport-owner.js +8 -5
- package/internal/execution-provider-owner.js +157 -0
- package/internal/execution-seam-compat.js +69 -113
- package/internal/isolated-worker.js +12 -8
- package/internal/recovery-tips.js +50 -21
- package/internal/rejected-cell-editor.js +11 -21
- package/internal/runtime-bridge-owner.js +88 -57
- package/internal/runtime-contexts.js +13 -13
- package/internal/session-events.js +40 -4
- package/internal/session-journal-recovery.js +678 -181
- package/internal/session-log-view.js +189 -43
- package/internal/session-runtime.js +58 -10
- package/internal/settings-compat.js +12 -2
- package/internal/user-bindings-owner.js +6 -5
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +18 -13
- package/src/client-authoring-view.js +222 -141
- package/src/client-binding-review.js +2 -0
- package/src/client-code-editor.js +6 -0
- package/src/client-copy.js +10 -0
- package/src/client-feature-gates.js +12 -5
- package/src/client-host-compat.js +23 -2
- package/src/client-primitives.js +249 -0
- package/src/client-repl-view.js +3 -3
- package/src/client-settings-view.js +2 -29
- package/src/client-styles.js +92 -33
- package/src/client-tool-view.js +8 -3
- package/src/client-workbench.js +9 -7
- package/src/client.js +72 -46
- package/assets/ptc-plus-bindings-en.png +0 -0
- package/assets/ptc-plus-bindings-zh.png +0 -0
- package/assets/ptc-plus-repair-en.png +0 -0
- package/assets/ptc-plus-repair-zh.png +0 -0
|
@@ -1,39 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Own how PTC Plus attaches to the host's program-execution seam.
|
|
3
|
-
*
|
|
4
|
-
* DSH registers one execution seam per generation. The current release
|
|
5
|
-
* registers `ctx.ptcRuntime` (`@deepseek-ai/dsh-ptc-runtime`); the preceding one
|
|
6
|
-
* registered `ctx.codeRuntime` (`@deepseek-ai/dsh-code-runtime`). Both run one
|
|
7
|
-
* model-written program against host async bindings. They differ in the service
|
|
8
|
-
* name, in the call shape (`resolve(request)` then `run(spec)` against a single
|
|
9
|
-
* `run(request)`), and in the capability descriptors a provider publishes.
|
|
10
|
-
* Which seam is live is decided by the registered service, never by a version
|
|
11
|
-
* string, so a deployment on either generation obtains the same behavior.
|
|
12
|
-
*
|
|
13
|
-
* PTC Plus replaces the execution of session cells with its own persistent
|
|
14
|
-
* REPL. The object DSH describes to the model is therefore the plugin's
|
|
15
|
-
* execution, not the wrapped provider's, so `takeOver()` answers the
|
|
16
|
-
* descriptors the plugin can honor and withholds the ones it cannot. The REPL
|
|
17
|
-
* runs every cell in one long-lived worker rather than a fresh process per
|
|
18
|
-
* call, applies the session's configured budgets rather than a per-call host
|
|
19
|
-
* deadline, and performs no file confinement. DSH gates `timeoutMs` on
|
|
20
|
-
* `timeout` and resolves or escalates file policy on `sandboxMode`, so
|
|
21
|
-
* withholding those descriptors keeps DSH's authority decisions with DSH
|
|
22
|
-
* instead of having this plugin accept an input it would then ignore.
|
|
23
|
-
*
|
|
24
|
-
* A seam-neutral request carries exactly `program`, `bindings`, and `signal`:
|
|
25
|
-
* the fields PTC Plus consumes. A resolved spec's directory, deadline, and
|
|
26
|
-
* authority describe the provider whose execution was replaced, and are neither
|
|
27
|
-
* forwarded to the session kernel nor re-derived here.
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
1
|
/** Cordis service name registered by the current DSH generation. */
|
|
31
2
|
export const EXECUTION_SEAM_SERVICE = 'ptcRuntime'
|
|
32
3
|
|
|
33
|
-
/**
|
|
4
|
+
/** Historical request-only service name, retained with fixture evidence. */
|
|
34
5
|
export const LEGACY_EXECUTION_SEAM_SERVICE = 'codeRuntime'
|
|
35
6
|
|
|
36
7
|
const SEAM_SERVICES = Object.freeze([EXECUTION_SEAM_SERVICE, LEGACY_EXECUTION_SEAM_SERVICE])
|
|
8
|
+
const PLUGIN_EXECUTION_SEAM = Symbol.for('dsh-ptc-plus.execution-seam')
|
|
37
9
|
|
|
38
10
|
/**
|
|
39
11
|
* How long the plugin waits for the host's execution seam before it reports the
|
|
@@ -48,34 +20,12 @@ const SEAM_SERVICES = Object.freeze([EXECUTION_SEAM_SERVICE, LEGACY_EXECUTION_SE
|
|
|
48
20
|
*/
|
|
49
21
|
const SEAM_ATTACHMENT_TIMEOUT_MS = 30_000
|
|
50
22
|
|
|
51
|
-
/**
|
|
52
|
-
* Descriptors the plugin replaces with the capabilities of its own execution.
|
|
53
|
-
* `executionInstructions` describes the wrapped provider's program model, which
|
|
54
|
-
* no longer applies; `timeout` and `sandboxMode` gate host inputs the plugin
|
|
55
|
-
* cannot honor. Withdrawal is unconditional because the rule is a property of
|
|
56
|
-
* the plugin's execution, not of a generation: the preceding generation's
|
|
57
|
-
* contract defines none of these members and nothing in its install closure
|
|
58
|
-
* reads them, so withdrawing there is inert, while a provider that published
|
|
59
|
-
* them stays unable to outlive the takeover.
|
|
60
|
-
*/
|
|
61
|
-
const WITHHELD_DESCRIPTORS = Object.freeze(['executionInstructions', 'sandboxMode', 'timeout'])
|
|
62
|
-
|
|
63
23
|
/** Read a registered service without declaring an injection requirement. */
|
|
64
24
|
function readService(ctx, name) {
|
|
65
25
|
if (typeof ctx?.get === 'function') return ctx.get(name)
|
|
66
26
|
return ctx?.[name]
|
|
67
27
|
}
|
|
68
28
|
|
|
69
|
-
function descriptorsMatch(left, right) {
|
|
70
|
-
if (left === undefined || right === undefined) return left === right
|
|
71
|
-
return left.value === right.value
|
|
72
|
-
&& left.get === right.get
|
|
73
|
-
&& left.set === right.set
|
|
74
|
-
&& left.writable === right.writable
|
|
75
|
-
&& left.enumerable === right.enumerable
|
|
76
|
-
&& left.configurable === right.configurable
|
|
77
|
-
}
|
|
78
|
-
|
|
79
29
|
/**
|
|
80
30
|
* Build the seam handle for one live execution service.
|
|
81
31
|
* @param {object} service - the registered `ctx.ptcRuntime` or `ctx.codeRuntime`.
|
|
@@ -85,6 +35,7 @@ export function createExecutionSeam(service, serviceName) {
|
|
|
85
35
|
if (service === null || typeof service !== 'object') {
|
|
86
36
|
throw new TypeError(`ptc-plus: ${serviceName} must be an execution service object`)
|
|
87
37
|
}
|
|
38
|
+
if (typeof service[PLUGIN_EXECUTION_SEAM] === 'function') return service[PLUGIN_EXECUTION_SEAM]()
|
|
88
39
|
const current = serviceName === EXECUTION_SEAM_SERVICE
|
|
89
40
|
const upstreamRun = service.run
|
|
90
41
|
if (typeof upstreamRun !== 'function') {
|
|
@@ -95,82 +46,87 @@ export function createExecutionSeam(service, serviceName) {
|
|
|
95
46
|
throw new TypeError('ptc-plus: ptcRuntime.resolve must be a function')
|
|
96
47
|
}
|
|
97
48
|
let installed
|
|
98
|
-
|
|
49
|
+
const sourceListeners = new Set()
|
|
50
|
+
let available = true
|
|
51
|
+
const requireAvailable = () => {
|
|
52
|
+
if (!available) throw new Error(`ptc-plus: original ${serviceName} generation is unavailable`)
|
|
53
|
+
}
|
|
54
|
+
const requireCompatible = () => {
|
|
55
|
+
requireAvailable()
|
|
56
|
+
if (installed !== undefined && service.language !== installed.language) {
|
|
57
|
+
throw new Error(`ptc-plus: current original language ${JSON.stringify(service.language)} is incompatible with the active ${JSON.stringify(installed.language)} runtime`)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const provider = Object.freeze({
|
|
61
|
+
get language() { requireCompatible(); return service.language },
|
|
62
|
+
get isolation() { requireCompatible(); return installed === undefined ? service.isolation : 'worker-thread' },
|
|
63
|
+
get executionInstructions() { requireCompatible(); return installed === undefined ? service.executionInstructions ?? '' : '' },
|
|
64
|
+
get sandboxMode() { requireCompatible(); return installed === undefined ? service.sandboxMode : undefined },
|
|
65
|
+
get timeout() { requireCompatible(); return installed === undefined ? service.timeout : undefined },
|
|
66
|
+
resolve(request) {
|
|
67
|
+
requireCompatible()
|
|
68
|
+
return current ? upstreamResolve.call(service, request) : request
|
|
69
|
+
},
|
|
70
|
+
run(input) {
|
|
71
|
+
requireCompatible()
|
|
72
|
+
return installed === undefined ? upstreamRun.call(service, input) : installed.execute(input)
|
|
73
|
+
},
|
|
74
|
+
[PLUGIN_EXECUTION_SEAM]() { return seam },
|
|
75
|
+
})
|
|
76
|
+
const seam = Object.freeze({
|
|
99
77
|
serviceName,
|
|
100
|
-
language
|
|
78
|
+
get language() { requireAvailable(); return service.language },
|
|
79
|
+
provider,
|
|
80
|
+
get active() { return installed !== undefined && service.language === installed.language },
|
|
81
|
+
onSourceChange(listener) {
|
|
82
|
+
sourceListeners.add(listener)
|
|
83
|
+
return () => sourceListeners.delete(listener)
|
|
84
|
+
},
|
|
85
|
+
sourceChanged() {
|
|
86
|
+
return Promise.all([...sourceListeners].map(listener => listener())).then(() => undefined)
|
|
87
|
+
},
|
|
101
88
|
/**
|
|
102
89
|
* Run one program through the provider this plugin wraps. The current
|
|
103
90
|
* generation resolves the request before running it, exactly as its own
|
|
104
91
|
* consumer does; the preceding generation accepts the request directly.
|
|
105
92
|
*/
|
|
106
93
|
invokeUpstream(request) {
|
|
94
|
+
requireCompatible()
|
|
107
95
|
return current
|
|
108
96
|
? upstreamRun.call(service, upstreamResolve.call(service, request))
|
|
109
97
|
: upstreamRun.call(service, request)
|
|
110
98
|
},
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (installed !== undefined) throw new Error('ptc-plus: execution seam already
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
const requested = new Map()
|
|
122
|
-
requested.set('run', {
|
|
123
|
-
configurable: true,
|
|
124
|
-
writable: true,
|
|
125
|
-
value: current
|
|
126
|
-
? spec => execute({
|
|
127
|
-
program: spec.program,
|
|
128
|
-
bindings: spec.bindings,
|
|
129
|
-
signal: spec.signal,
|
|
130
|
-
})
|
|
131
|
-
: request => execute(request),
|
|
132
|
-
})
|
|
133
|
-
for (const name of WITHHELD_DESCRIPTORS) {
|
|
134
|
-
requested.set(name, { configurable: true, value: name === 'executionInstructions' ? '' : undefined })
|
|
135
|
-
}
|
|
136
|
-
const extensible = Object.isExtensible(service)
|
|
137
|
-
for (const [name, descriptor] of requested) {
|
|
138
|
-
const previous = Object.getOwnPropertyDescriptor(service, name)
|
|
139
|
-
saved.set(name, previous)
|
|
140
|
-
const next = { enumerable: previous?.enumerable ?? false, ...descriptor }
|
|
141
|
-
requested.set(name, next)
|
|
142
|
-
const probe = {}
|
|
143
|
-
if (previous !== undefined) Object.defineProperty(probe, name, previous)
|
|
144
|
-
if (!extensible) Object.preventExtensions(probe)
|
|
145
|
-
Object.defineProperty(probe, name, next)
|
|
146
|
-
}
|
|
147
|
-
try {
|
|
148
|
-
for (const [name, descriptor] of requested) {
|
|
149
|
-
Object.defineProperty(service, name, descriptor)
|
|
150
|
-
written.set(name, Object.getOwnPropertyDescriptor(service, name))
|
|
151
|
-
}
|
|
152
|
-
} catch (error) {
|
|
153
|
-
for (const name of [...written.keys()].reverse()) {
|
|
154
|
-
if (!descriptorsMatch(Object.getOwnPropertyDescriptor(service, name), written.get(name))) continue
|
|
155
|
-
const descriptor = saved.get(name)
|
|
156
|
-
if (descriptor === undefined) delete service[name]
|
|
157
|
-
else Object.defineProperty(service, name, descriptor)
|
|
158
|
-
}
|
|
159
|
-
throw error
|
|
160
|
-
}
|
|
99
|
+
runUpstream(spec) {
|
|
100
|
+
requireCompatible()
|
|
101
|
+
return upstreamRun.call(service, spec)
|
|
102
|
+
},
|
|
103
|
+
installExecute(execute) {
|
|
104
|
+
requireAvailable()
|
|
105
|
+
if (typeof execute !== 'function') throw new TypeError('ptc-plus: execution entry must be a function')
|
|
106
|
+
if (installed !== undefined) throw new Error('ptc-plus: execution seam already installed')
|
|
107
|
+
const entry = { execute, language: service.language }
|
|
108
|
+
installed = entry
|
|
161
109
|
const restore = () => {
|
|
162
|
-
if (installed !==
|
|
110
|
+
if (installed !== entry) return
|
|
163
111
|
installed = undefined
|
|
164
|
-
for (const [name, descriptor] of saved) {
|
|
165
|
-
if (!descriptorsMatch(Object.getOwnPropertyDescriptor(service, name), written.get(name))) continue
|
|
166
|
-
if (descriptor === undefined) delete service[name]
|
|
167
|
-
else Object.defineProperty(service, name, descriptor)
|
|
168
|
-
}
|
|
169
112
|
}
|
|
170
|
-
installed = restore
|
|
171
113
|
return restore
|
|
172
114
|
},
|
|
115
|
+
retire() {
|
|
116
|
+
available = false
|
|
117
|
+
installed = undefined
|
|
118
|
+
sourceListeners.clear()
|
|
119
|
+
},
|
|
173
120
|
})
|
|
121
|
+
return seam
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function registeredExecutionSeam(service, serviceName) {
|
|
125
|
+
const seam = createExecutionSeam(service, serviceName)
|
|
126
|
+
if (typeof service[PLUGIN_EXECUTION_SEAM] !== 'function') {
|
|
127
|
+
throw new Error(`ptc-plus: ${serviceName} requires the public execution-provider bundle composition`)
|
|
128
|
+
}
|
|
129
|
+
return seam
|
|
174
130
|
}
|
|
175
131
|
|
|
176
132
|
/**
|
|
@@ -259,7 +215,7 @@ export function installExecutionSeam(ctx, {
|
|
|
259
215
|
entry = { name, scope }
|
|
260
216
|
live = entry
|
|
261
217
|
registerLifecycle(entry, name, scope)
|
|
262
|
-
const attached = attach(scope,
|
|
218
|
+
const attached = attach(scope, registeredExecutionSeam(scope?.[name] ?? readService(ctx, name), name))
|
|
263
219
|
entry.attached = attached
|
|
264
220
|
return attached
|
|
265
221
|
})
|
|
@@ -292,7 +248,7 @@ export function installExecutionSeam(ctx, {
|
|
|
292
248
|
const entry = { name, scope }
|
|
293
249
|
live = entry
|
|
294
250
|
try {
|
|
295
|
-
entry.attached = attach(scope,
|
|
251
|
+
entry.attached = attach(scope, registeredExecutionSeam(scope?.[name] ?? readService(ctx, name), name))
|
|
296
252
|
} catch (error) {
|
|
297
253
|
live = undefined
|
|
298
254
|
throw error
|
|
@@ -15,6 +15,17 @@ const ISOLATED_STOP_MS = 5000
|
|
|
15
15
|
/** Bound on the helper's output pipes really ending after its process exited. */
|
|
16
16
|
const ISOLATED_OUTPUT_MS = 5000
|
|
17
17
|
|
|
18
|
+
export function isolatedWorkerEntry(entry) {
|
|
19
|
+
if (!(entry instanceof URL) && (typeof entry !== 'string' || entry.length === 0)) {
|
|
20
|
+
throw new TypeError('isolated worker entry must be a URL or a non-empty string filename')
|
|
21
|
+
}
|
|
22
|
+
return entry instanceof URL
|
|
23
|
+
? entry.href
|
|
24
|
+
: entry.slice(0, 5).toLowerCase() === 'file:'
|
|
25
|
+
? new URL(entry).href
|
|
26
|
+
: pathToFileURL(resolve(entry)).href
|
|
27
|
+
}
|
|
28
|
+
|
|
18
29
|
/**
|
|
19
30
|
* A worker-shaped transport whose worker lives in a killable helper process.
|
|
20
31
|
*
|
|
@@ -29,17 +40,10 @@ export class IsolatedWorker extends EventEmitter {
|
|
|
29
40
|
super()
|
|
30
41
|
// The entry follows the worker constructor's contract: a URL or a string
|
|
31
42
|
// filename. Anything else fails here, before a helper process exists.
|
|
32
|
-
if (!(entry instanceof URL) && (typeof entry !== 'string' || entry.length === 0)) {
|
|
33
|
-
throw new TypeError('isolated worker entry must be a URL or a non-empty string filename')
|
|
34
|
-
}
|
|
35
43
|
// The init message crosses advanced IPC, which cannot rebuild a URL object, so
|
|
36
44
|
// the boundary normalizes it to one absolute file URL string. A path follows
|
|
37
45
|
// the worker constructor's rule: resolved from the current working directory.
|
|
38
|
-
const entrySpec = entry
|
|
39
|
-
? entry.href
|
|
40
|
-
: entry.slice(0, 5).toLowerCase() === 'file:'
|
|
41
|
-
? new URL(entry).href
|
|
42
|
-
: pathToFileURL(resolve(entry)).href
|
|
46
|
+
const entrySpec = isolatedWorkerEntry(entry)
|
|
43
47
|
this.stdout = new PassThrough()
|
|
44
48
|
this.stderr = new PassThrough()
|
|
45
49
|
this.protocol = protocol
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { recoveryTipIdentity } from './runtime-messages.js'
|
|
1
|
+
import { PTC_STATE_NAMES, recoveryTipIdentity } from './runtime-messages.js'
|
|
2
2
|
|
|
3
3
|
const PLATFORM_CAUSE_CODES = new Set(['EACCES', 'EINVAL', 'ENOTDIR', 'ENOENT', 'UNKNOWN'])
|
|
4
4
|
const TIP_CONTEXT_PREFIX = 'tools:ptc-plus-tip/'
|
|
@@ -8,27 +8,58 @@ const TIP_PREFIXES = Object.freeze({
|
|
|
8
8
|
})
|
|
9
9
|
|
|
10
10
|
// TODO(dsh-tips-api): replace this local provider with an adapter after dsh-tips publishes a stable facts and decision interface.
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
export function createRuntimeHistory() {
|
|
12
|
+
return { seenNames: {}, tips: {}, hadState: false, hadCatalog: false }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function advanceRuntimeHistory(previous, { sections = [], notice, snapshot = false, catalog = false,
|
|
16
|
+
index, contextStep, lastSuccessfulRunIndex, resetSuccess = false }) {
|
|
17
|
+
let next = previous
|
|
18
|
+
const revise = () => {
|
|
19
|
+
if (next === previous) next = { ...previous, tips: { ...previous.tips }, seenNames: { ...previous.seenNames } }
|
|
20
|
+
}
|
|
21
|
+
if (resetSuccess) {
|
|
22
|
+
revise()
|
|
23
|
+
for (const [id, tip] of Object.entries(next.tips)) next.tips[id] = { ...tip, unresolved: 0 }
|
|
24
|
+
for (const [name, deliveredIndex] of Object.entries(next.seenNames)) {
|
|
25
|
+
const identity = recoveryTipIdentity(name)
|
|
26
|
+
if (deliveredIndex > (lastSuccessfulRunIndex ?? -1)) next.tips[identity.id].unresolved += 1
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const hadState = snapshot || sections.some(section => PTC_STATE_NAMES.includes(section.name))
|
|
30
|
+
if ((hadState && !next.hadState) || (catalog && !next.hadCatalog)) {
|
|
31
|
+
revise()
|
|
32
|
+
next.hadState ||= hadState
|
|
33
|
+
next.hadCatalog ||= catalog
|
|
34
|
+
}
|
|
35
|
+
for (const name of [...sections.map(section => section.name), ...(notice === undefined ? [] : [notice])]) {
|
|
36
|
+
const identity = recoveryTipIdentity(name)
|
|
37
|
+
if (identity === undefined || Object.hasOwn(next.seenNames, name)) continue
|
|
38
|
+
revise()
|
|
39
|
+
next.seenNames[name] = index
|
|
40
|
+
const earlier = next.tips[identity.id]
|
|
41
|
+
next.tips[identity.id] = {
|
|
42
|
+
highestOrdinal: Math.max(earlier?.highestOrdinal ?? 0, identity.ordinal),
|
|
43
|
+
contextStep,
|
|
44
|
+
unresolved: (earlier?.unresolved ?? 0) + (index > (lastSuccessfulRunIndex ?? -1) ? 1 : 0),
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return next
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function runtimeHistoryForView(view) {
|
|
51
|
+
if (view.runtimeHistory !== undefined) return view.runtimeHistory
|
|
52
|
+
let history = createRuntimeHistory()
|
|
53
|
+
history.hadState = view.ptcMessages?.some(record => record.form === 'snapshot') === true
|
|
54
|
+
history.hadCatalog = view.ptcMessages?.some(record => record.form === 'catalog') === true
|
|
15
55
|
const snapshots = [
|
|
16
|
-
...view.systemPromptSnapshots,
|
|
56
|
+
...(view.systemPromptSnapshots ?? []),
|
|
17
57
|
...(view.ptcMessages ?? []).filter(record => record.form === 'notice').map(record => ({
|
|
18
58
|
...record, sections: [{ name: record.name, text: record.text }],
|
|
19
59
|
})),
|
|
20
60
|
].sort((left, right) => left.index - right.index)
|
|
21
61
|
for (const snapshot of snapshots) {
|
|
22
|
-
|
|
23
|
-
for (const [name] of next) {
|
|
24
|
-
if (active.has(name) || seen.has(name)) continue
|
|
25
|
-
const identity = recoveryTipIdentity(name)
|
|
26
|
-
if (identity === undefined) continue
|
|
27
|
-
seen.add(name)
|
|
28
|
-
history.push({ ...identity, index: snapshot.index, contextStep: snapshot.contextStep })
|
|
29
|
-
}
|
|
30
|
-
active.clear()
|
|
31
|
-
for (const entry of next) active.set(...entry)
|
|
62
|
+
history = advanceRuntimeHistory(history, { ...snapshot, lastSuccessfulRunIndex: view.lastSuccessfulRunIndex })
|
|
32
63
|
}
|
|
33
64
|
return history
|
|
34
65
|
}
|
|
@@ -79,14 +110,12 @@ export function latestRecoveryTip(view, config) {
|
|
|
79
110
|
if (candidate === undefined) return undefined
|
|
80
111
|
// Cooldown, escalation, and ordinal are per trigger kind: one kind's tip must
|
|
81
112
|
// not suppress a different kind.
|
|
82
|
-
const
|
|
83
|
-
const lastTip = matching.at(-1)
|
|
113
|
+
const lastTip = runtimeHistoryForView(view).tips[candidate.id]
|
|
84
114
|
if (lastTip !== undefined && view.contextStep - lastTip.contextStep < config.cooldownMessages) return undefined
|
|
85
|
-
const
|
|
86
|
-
const ordinal = matching.reduce((highest, item) => Math.max(highest, item.ordinal), 0) + 1
|
|
115
|
+
const ordinal = (lastTip?.highestOrdinal ?? 0) + 1
|
|
87
116
|
if (!Number.isSafeInteger(ordinal)) return undefined
|
|
88
117
|
return {
|
|
89
118
|
name: `${TIP_CONTEXT_PREFIX}${candidate.id}/${ordinal}`,
|
|
90
|
-
text: renderTip(candidate.id, unresolved
|
|
119
|
+
text: renderTip(candidate.id, (lastTip?.unresolved ?? 0) >= config.escalationFailures, candidate),
|
|
91
120
|
}
|
|
92
121
|
}
|
|
@@ -252,29 +252,15 @@ function assemble(source, replacements) {
|
|
|
252
252
|
return changed ? { code: chunks.join('') } : { error: 'edits must change at least one matched fragment' }
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
-
function editRunCodeParameterBranch(operation, operationSchema) {
|
|
256
|
-
return {
|
|
257
|
-
type: 'object',
|
|
258
|
-
additionalProperties: false,
|
|
259
|
-
properties: {
|
|
260
|
-
[operation]: operationSchema,
|
|
261
|
-
[EXPECTED_TARGET_CALL_SEQ]: {
|
|
262
|
-
type: 'integer', minimum: 0,
|
|
263
|
-
description: 'Optional target precondition copied from a validated diagnostic. The edit is rejected if the captured cell has another call sequence.',
|
|
264
|
-
},
|
|
265
|
-
},
|
|
266
|
-
required: [operation],
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
255
|
export function editRunCodeSchema() {
|
|
271
256
|
return {
|
|
272
257
|
name: 'edit_run_code',
|
|
273
258
|
description: 'Edit the most recent eligible cell captured at dispatch and run the complete corrected cell. A successful edit becomes the next target. This reruns the entire cell, including prior effects; use it after pre-execution rejection or when repeating those effects is safe under the called API\'s retry rules. Otherwise continue in a new run_code cell using retained values. A diagnostic may supply a localized correction; use it when it matches your intent and preserve its expected_target_call_seq. Send exactly one edits or regex_edits array: edits replaces unique literal fragments; regex_edits replaces a counted set of pattern matches. All ranges must be non-overlapping in the original cell. Regex replacements use JavaScript $1/$<name> captures, not \\1.',
|
|
274
259
|
parameters: {
|
|
275
260
|
type: 'object',
|
|
276
|
-
|
|
277
|
-
|
|
261
|
+
additionalProperties: false,
|
|
262
|
+
properties: {
|
|
263
|
+
edits: {
|
|
278
264
|
type: 'array', minItems: 1, maxItems: EDIT_LIMITS.exactEdits,
|
|
279
265
|
description: 'Atomic exact replacements, all resolved against the original target cell.',
|
|
280
266
|
items: {
|
|
@@ -285,8 +271,8 @@ export function editRunCodeSchema() {
|
|
|
285
271
|
},
|
|
286
272
|
required: ['old_string', 'new_string'],
|
|
287
273
|
},
|
|
288
|
-
}
|
|
289
|
-
|
|
274
|
+
},
|
|
275
|
+
regex_edits: {
|
|
290
276
|
type: 'array', minItems: 1, maxItems: EDIT_LIMITS.regexEdits,
|
|
291
277
|
description: 'Atomic regular-expression replacements, all matched against the original target cell with JavaScript pattern and replacement semantics; each replacement is a JavaScript template (see replacement).',
|
|
292
278
|
items: {
|
|
@@ -299,8 +285,12 @@ export function editRunCodeSchema() {
|
|
|
299
285
|
},
|
|
300
286
|
required: ['pattern', 'flags', 'replacement', 'expected_matches'],
|
|
301
287
|
},
|
|
302
|
-
}
|
|
303
|
-
|
|
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
|
+
},
|
|
293
|
+
},
|
|
304
294
|
},
|
|
305
295
|
}
|
|
306
296
|
}
|