dsh-ptc-plus 0.4.8 → 0.5.0
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 +61 -123
- package/README.md +61 -123
- package/assets/dsh-ptc-plus-banner-en.webp +0 -0
- package/assets/dsh-ptc-plus-banner-zh.webp +0 -0
- package/assets/ptc-plus-repl-workspace-en.png +0 -0
- package/assets/ptc-plus-repl-workspace-zh.png +0 -0
- package/assets/ptc-plus-settings-en.png +0 -0
- package/assets/ptc-plus-settings-zh.png +0 -0
- package/client.cjs +42336 -0
- package/cordis.patch.yml +13 -2
- package/docs/README.md +7 -2
- package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +31 -179
- package/docs/adr/0008-define-agent-operating-contract.md +5 -1
- package/docs/adr/0009-bound-expensive-acceptance-default.md +2 -2
- package/docs/adr/0016-archive-resolved-review-findings.md +1 -1
- package/docs/adr/0017-track-the-latest-dsh-public-surface.md +2 -2
- 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/0022-stage-npm-releases-with-oidc.md +4 -2
- package/docs/adr/0023-global-user-bindings.md +3 -1
- package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +2 -0
- package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +101 -35
- package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +3 -3
- package/docs/adr/0031-close-semantic-obligations-by-owner.md +1 -1
- 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/adr/0034-keep-program-values-separate-from-output-projections.md +29 -0
- package/docs/adr/0035-run-coverage-only-before-a-new-release.md +27 -0
- package/docs/adr/0036-separate-focused-development-from-delivery-verification.md +29 -0
- package/docs/adr/0037-keep-tool-hover-previews-passive.md +52 -0
- package/docs/adr/0038-freeze-session-capability-plans.md +71 -0
- package/docs/adr/0039-verify-documentation-without-runtime-suites.md +29 -0
- package/docs/adr/0040-separate-capability-configurations-from-session-observations.md +39 -0
- package/docs/adr/0041-record-cell-execution-directories.md +65 -0
- package/docs/adr/0042-persist-provider-availability-and-handback.md +41 -0
- package/docs/adr/0043-compose-capability-and-context-policies.md +37 -0
- package/docs/adr/README.md +6 -0
- package/docs/architecture.md +25 -27
- package/docs/binding-review-placement-design.md +29 -21
- package/docs/capability-planning-components.md +93 -0
- package/docs/capability-projection.md +7 -0
- package/docs/client-ui.md +88 -29
- package/docs/client-visual-contract.md +65 -0
- package/docs/context-management.md +22 -0
- package/docs/context-optimization-plan.md +353 -0
- package/docs/context-public-interface-coverage.md +25 -0
- package/docs/durability-design.md +31 -14
- package/docs/evaluation.md +41 -6
- package/docs/evaluations/command-goat-deepseek-v4.1-flash-20261006.md +126 -0
- package/docs/evaluations/command-goat-deepseek-v4.1-flash.md +281 -0
- package/docs/installation.md +50 -8
- package/docs/program-data-plane.md +5 -3
- package/docs/publishing.md +5 -5
- package/docs/release-coverage.md +73 -0
- package/docs/release-notes.md +1 -1
- package/docs/review-procedure.md +56 -0
- package/docs/runtime-reference.md +48 -16
- package/docs/semantic-validation.md +14 -2
- package/docs/stateful-binding-updates-proposal.md +2 -2
- package/docs/user-bindings.en.md +2 -2
- package/docs/user-bindings.md +3 -3
- package/docs/value-wire.md +11 -1
- package/docs/verification-optimization-work.md +52 -15
- package/docs/verification.md +30 -86
- package/index.js +147 -23
- package/internal/binding-toggle-receipt.js +24 -0
- package/internal/capability-command-receipt.js +29 -0
- package/internal/capability-environment.js +1247 -0
- package/internal/capability-plan.js +285 -0
- package/internal/capability-policy.js +96 -0
- package/internal/capability-statistics-store.js +180 -0
- package/internal/capability-statistics.js +210 -0
- package/internal/compiler-platform.js +5 -3
- package/internal/compiler-service.js +2 -9
- package/internal/config-spec.js +79 -9
- package/internal/cordis-tools-owner.js +22 -19
- package/internal/direct-surface-owner.js +29 -29
- package/internal/dynamic-environment-runtime.js +9 -9
- package/internal/edit-transport-owner.js +183 -139
- package/internal/execution-provider-owner.js +160 -0
- package/internal/execution-seam-compat.js +76 -120
- package/internal/isolated-worker.js +12 -8
- package/internal/kernel-worker.js +104 -32
- package/internal/native-root-dynamic.js +12 -13
- package/internal/program-bindings.js +190 -0
- package/internal/recovery-tips.js +53 -23
- package/internal/rejected-cell-editor.js +8 -8
- package/internal/repl-observation-interest.js +11 -3
- package/internal/rpc-contract.js +1 -0
- package/internal/runtime-bridge-owner.js +351 -82
- package/internal/runtime-config.js +58 -0
- package/internal/runtime-contexts.js +85 -16
- package/internal/runtime-intrinsics.js +1 -0
- package/internal/runtime-messages.js +41 -0
- package/internal/sdk-projection.js +10 -82
- package/internal/session-cell-executor.js +42 -19
- package/internal/session-events.js +74 -4
- package/internal/session-journal-recovery.js +904 -184
- package/internal/session-journal-schema.js +6 -3
- package/internal/session-journal.js +31 -2
- package/internal/session-log-view.js +189 -43
- package/internal/session-runtime.js +175 -23
- package/internal/settings-compat.js +23 -6
- package/internal/stateful-module-compiler.js +3 -2
- package/internal/stateful-module-runtime.js +7 -3
- package/internal/stateful-root-runtime.js +9 -4
- package/internal/user-binding-authoring.js +2 -7
- package/internal/user-bindings-owner.js +47 -7
- package/internal/validated-parse-repair.js +1 -1
- package/internal/value-wire.js +68 -21
- package/internal/worker-cwd-virtualization.js +20 -18
- package/internal/worker-realm-surfaces.js +3 -3
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +55 -38
- package/src/client-authoring-view.js +286 -168
- package/src/client-binding-review.js +4 -1
- package/src/client-binding-toggle.js +18 -0
- package/src/client-capabilities-view.js +1341 -0
- package/src/client-capability-sort.js +29 -0
- package/src/client-code-editor.js +6 -0
- package/src/client-copy.js +881 -8
- package/src/client-feature-gates.js +15 -5
- package/src/client-host-compat.js +23 -2
- package/src/client-indicator-view.js +4 -6
- package/src/client-primitives.js +280 -0
- package/src/client-repl-view.js +23 -16
- package/src/client-settings-view.js +412 -69
- package/src/client-styles.js +193 -41
- package/src/client-tool-hover.js +185 -0
- package/src/client-tool-view.js +45 -11
- package/src/client-transport.js +44 -9
- package/src/client-workbench.js +9 -10
- package/src/client.js +148 -68
- 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
- package/client.js +0 -31857
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
/** Deterministic capability metadata derived from the current DSH tool view. */
|
|
2
2
|
import { record, text } from './record-utils.js'
|
|
3
|
+
import { createHash } from 'node:crypto'
|
|
4
|
+
import { snapshotJsonValue } from '@deepseek-ai/dsh-util-values'
|
|
5
|
+
|
|
6
|
+
/** Derive SDK contracts from the public, scope-qualified tool registry. */
|
|
7
|
+
export function toolSdkSchemas(tools, scope) {
|
|
8
|
+
return tools.schemas(scope).filter(schema => schema.name !== 'run_code').map(schema => {
|
|
9
|
+
const definition = tools.get(schema.name, scope)
|
|
10
|
+
const output = snapshotJsonValue(definition?.output?.schema)
|
|
11
|
+
if (output === undefined) {
|
|
12
|
+
throw new TypeError(`ptc-plus: tool "${schema.name}" output schema must be lossless JSON before SDK projection`)
|
|
13
|
+
}
|
|
14
|
+
return { ...schema, output }
|
|
15
|
+
})
|
|
16
|
+
}
|
|
3
17
|
|
|
4
18
|
const COMPLETENESS = new Set(['complete', 'bounded', 'incremental', 'open-world', 'unknown'])
|
|
5
19
|
const SOURCE_KINDS = new Set(['authored', 'runtime', 'tests', 'docs'])
|
|
@@ -185,3 +199,179 @@ export function capabilityInspect(metadata, symbols = undefined, budget = 50) {
|
|
|
185
199
|
budget,
|
|
186
200
|
}
|
|
187
201
|
}
|
|
202
|
+
|
|
203
|
+
const SEARCH_DEFAULT_BYTES = 16_384
|
|
204
|
+
const SEARCH_MIN_BYTES = 512
|
|
205
|
+
const SEARCH_STOP_WORDS = new Set('a an and are as at be by can could do for from how i in is it me of on or please that the this to use using with would you 我 帮 帮我 请 的 了 和 与 在 如何 怎么 可以'.split(' '))
|
|
206
|
+
const SEARCH_SEGMENTER = new Intl.Segmenter('zh', { granularity: 'word' })
|
|
207
|
+
|
|
208
|
+
function taskTerms(value) {
|
|
209
|
+
const separated = value.normalize('NFKC')
|
|
210
|
+
.replace(/(\p{Lu}+)(\p{Lu}\p{Ll})/gu, '$1 $2')
|
|
211
|
+
.replace(/([\p{Ll}\p{N}])(\p{Lu})/gu, '$1 $2')
|
|
212
|
+
.toLowerCase()
|
|
213
|
+
.replace(/[^\p{L}\p{N}]+/gu, ' ')
|
|
214
|
+
return [...new Set([...SEARCH_SEGMENTER.segment(separated)]
|
|
215
|
+
.filter(part => part.isWordLike && !SEARCH_STOP_WORDS.has(part.segment))
|
|
216
|
+
.map(part => part.segment))]
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function searchKeywords(value) {
|
|
220
|
+
if (value === undefined) return ''
|
|
221
|
+
if (typeof value === 'string') return value
|
|
222
|
+
if (Array.isArray(value) && value.every(item => typeof item === 'string')) return value.join(' ')
|
|
223
|
+
throw new TypeError('ptc-plus: capability search keywords must be a string or string array')
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Search budgets measure JSON, so non-JSON metadata cannot be silently converted or dropped.
|
|
227
|
+
function searchJson(value) {
|
|
228
|
+
const ancestors = new Set()
|
|
229
|
+
const visit = (item) => {
|
|
230
|
+
if (item === null || typeof item === 'string' || typeof item === 'boolean') return
|
|
231
|
+
if (typeof item === 'number' && Number.isFinite(item)) return
|
|
232
|
+
if (typeof item !== 'object') throw new TypeError('ptc-plus: capability search metadata must be lossless JSON data')
|
|
233
|
+
if (ancestors.has(item)) throw new TypeError('ptc-plus: capability search metadata must be lossless JSON data')
|
|
234
|
+
const prototype = Object.getPrototypeOf(item)
|
|
235
|
+
if (prototype !== (Array.isArray(item) ? Array.prototype : Object.prototype) && prototype !== null) {
|
|
236
|
+
throw new TypeError('ptc-plus: capability search metadata must be lossless JSON data')
|
|
237
|
+
}
|
|
238
|
+
ancestors.add(item)
|
|
239
|
+
for (const key of Reflect.ownKeys(item)) {
|
|
240
|
+
if (Array.isArray(item) && key === 'length') continue
|
|
241
|
+
const descriptor = Object.getOwnPropertyDescriptor(item, key)
|
|
242
|
+
if (typeof key !== 'string' || !descriptor.enumerable || !Object.hasOwn(descriptor, 'value')) {
|
|
243
|
+
throw new TypeError('ptc-plus: capability search metadata must be lossless JSON data')
|
|
244
|
+
}
|
|
245
|
+
visit(descriptor.value)
|
|
246
|
+
}
|
|
247
|
+
if (Array.isArray(item) && (Object.keys(item).length !== item.length
|
|
248
|
+
|| Object.keys(item).some(key => !/^(0|[1-9][0-9]*)$/.test(key) || Number(key) >= item.length))) {
|
|
249
|
+
throw new TypeError('ptc-plus: capability search metadata must be lossless JSON data')
|
|
250
|
+
}
|
|
251
|
+
ancestors.delete(item)
|
|
252
|
+
}
|
|
253
|
+
visit(value)
|
|
254
|
+
return JSON.stringify(value, (_key, item) => item !== null && typeof item === 'object' && !Array.isArray(item)
|
|
255
|
+
? Object.fromEntries(Object.keys(item).sort().map(key => [key, item[key]])) : item)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function searchDigest(value) {
|
|
259
|
+
return createHash('sha256').update(value).digest('base64url')
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function measuredSearchPage(value) {
|
|
263
|
+
value.usedBytes = 0
|
|
264
|
+
let bytes = Buffer.byteLength(JSON.stringify(value), 'utf8')
|
|
265
|
+
while (value.usedBytes !== bytes) {
|
|
266
|
+
value.usedBytes = bytes
|
|
267
|
+
bytes = Buffer.byteLength(JSON.stringify(value), 'utf8')
|
|
268
|
+
}
|
|
269
|
+
return value
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Derive one request-local lexical index from owner metadata; never owns invocation. */
|
|
273
|
+
export function createCapabilitySearcher(metadata) {
|
|
274
|
+
const seen = new Set()
|
|
275
|
+
const snapshot = JSON.parse(searchJson(metadata))
|
|
276
|
+
const catalog = metadataEntries(snapshot).flatMap((entry) => {
|
|
277
|
+
const { members, ...owner } = entry
|
|
278
|
+
return members.map((member) => {
|
|
279
|
+
const symbol = `${entry.namespace}.${member.name}`
|
|
280
|
+
if (seen.has(symbol)) throw new TypeError(`ptc-plus: capability search has ambiguous symbol ${symbol}`)
|
|
281
|
+
seen.add(symbol)
|
|
282
|
+
const value = metadataSnapshot(JSON.parse(searchJson({ symbol, owner, contract: member })), 'capability search metadata')
|
|
283
|
+
const names = new Set(taskTerms(symbol))
|
|
284
|
+
const terms = new Set(taskTerms([
|
|
285
|
+
symbol, member.description ?? '', owner.description ?? '',
|
|
286
|
+
searchKeywords(member.keywords), searchKeywords(owner.keywords),
|
|
287
|
+
].join(' ')))
|
|
288
|
+
return { value, names, terms }
|
|
289
|
+
})
|
|
290
|
+
}).sort((left, right) => left.value.symbol < right.value.symbol ? -1 : left.value.symbol > right.value.symbol ? 1 : 0)
|
|
291
|
+
const catalogId = searchDigest(JSON.stringify(catalog.map(item => item.value)))
|
|
292
|
+
|
|
293
|
+
return (args) => {
|
|
294
|
+
const params = record(args, 'capability search arguments')
|
|
295
|
+
const allowed = new Set(['query', 'budgetBytes', 'limit', 'cursor'])
|
|
296
|
+
if (Reflect.ownKeys(params).some(key => typeof key !== 'string' || !allowed.has(key)
|
|
297
|
+
|| !Object.prototype.propertyIsEnumerable.call(params, key))) {
|
|
298
|
+
throw new TypeError('ptc-plus: capability search has an unknown argument')
|
|
299
|
+
}
|
|
300
|
+
const query = text(params.query, 'capability search query').normalize('NFKC').trim().toLowerCase()
|
|
301
|
+
const queryTerms = taskTerms(params.query)
|
|
302
|
+
const budgetBytes = params.budgetBytes === undefined ? SEARCH_DEFAULT_BYTES : params.budgetBytes
|
|
303
|
+
const limit = params.limit === undefined ? 10 : params.limit
|
|
304
|
+
if (!Number.isSafeInteger(budgetBytes) || budgetBytes < SEARCH_MIN_BYTES) {
|
|
305
|
+
throw new TypeError(`ptc-plus: capability search budgetBytes must be an integer of at least ${SEARCH_MIN_BYTES}`)
|
|
306
|
+
}
|
|
307
|
+
if (!Number.isSafeInteger(limit) || limit < 1) throw new TypeError('ptc-plus: capability search limit must be a positive integer')
|
|
308
|
+
const queryId = searchDigest(JSON.stringify({ query, terms: queryTerms }))
|
|
309
|
+
const ranked = catalog.flatMap((candidate) => {
|
|
310
|
+
const { symbol, owner, contract } = candidate.value
|
|
311
|
+
const coverage = queryTerms.filter(term => candidate.terms.has(term)).length
|
|
312
|
+
const nameCoverage = queryTerms.filter(term => candidate.names.has(term)).length
|
|
313
|
+
const rank = symbol.normalize('NFKC').toLowerCase() === query ? 0
|
|
314
|
+
: owner.namespace.normalize('NFKC').toLowerCase() === query ? 1
|
|
315
|
+
: contract.name.normalize('NFKC').toLowerCase() === query ? 2
|
|
316
|
+
: coverage === 0 ? undefined
|
|
317
|
+
: nameCoverage === queryTerms.length ? 3
|
|
318
|
+
: coverage === queryTerms.length ? 4 : 5
|
|
319
|
+
return rank === undefined ? [] : [{ ...candidate, rank, coverage, nameCoverage }]
|
|
320
|
+
}).sort((left, right) => left.rank - right.rank || right.coverage - left.coverage
|
|
321
|
+
|| right.nameCoverage - left.nameCoverage
|
|
322
|
+
|| (left.value.symbol < right.value.symbol ? -1 : left.value.symbol > right.value.symbol ? 1 : 0))
|
|
323
|
+
|
|
324
|
+
let offset = 0
|
|
325
|
+
if (params.cursor !== undefined) {
|
|
326
|
+
if (typeof params.cursor !== 'string' || !/^[A-Za-z0-9_-]+$/.test(params.cursor)) {
|
|
327
|
+
throw new TypeError('ptc-plus: capability search cursor is invalid')
|
|
328
|
+
}
|
|
329
|
+
let cursor
|
|
330
|
+
try { cursor = JSON.parse(Buffer.from(params.cursor, 'base64url').toString('utf8')) } catch {
|
|
331
|
+
throw new TypeError('ptc-plus: capability search cursor is invalid')
|
|
332
|
+
}
|
|
333
|
+
if (cursor?.version !== 1 || cursor.catalog !== catalogId || cursor.query !== queryId
|
|
334
|
+
|| !Number.isSafeInteger(cursor.offset) || cursor.offset < 0 || cursor.offset >= ranked.length
|
|
335
|
+
|| Buffer.from(JSON.stringify(cursor)).toString('base64url') !== params.cursor) {
|
|
336
|
+
throw new TypeError('ptc-plus: capability search cursor is invalid or catalog/query changed')
|
|
337
|
+
}
|
|
338
|
+
offset = cursor.offset
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const page = (matches, oversized, next) => measuredSearchPage({
|
|
342
|
+
matches, oversized, total: ranked.length, remaining: ranked.length - next,
|
|
343
|
+
cursor: next < ranked.length ? Buffer.from(JSON.stringify({
|
|
344
|
+
version: 1, catalog: catalogId, query: queryId, offset: next,
|
|
345
|
+
})).toString('base64url') : null,
|
|
346
|
+
budgetBytes, unit: 'utf8-json-bytes', usedBytes: 0,
|
|
347
|
+
})
|
|
348
|
+
let result = page([], [], offset)
|
|
349
|
+
for (let next = offset; next < ranked.length && next - offset < limit; next += 1) {
|
|
350
|
+
const candidate = ranked[next].value
|
|
351
|
+
const expanded = page([...result.matches, candidate], result.oversized, next + 1)
|
|
352
|
+
if (expanded.usedBytes <= budgetBytes) {
|
|
353
|
+
result = expanded
|
|
354
|
+
continue
|
|
355
|
+
}
|
|
356
|
+
const alone = page([candidate], [], next + 1)
|
|
357
|
+
if (alone.usedBytes <= budgetBytes) break
|
|
358
|
+
const omitted = {
|
|
359
|
+
symbol: candidate.symbol, requiredBytes: alone.usedBytes,
|
|
360
|
+
inspect: { symbols: [candidate.symbol], budget: 1 },
|
|
361
|
+
}
|
|
362
|
+
const bounded = page(result.matches, [...result.oversized, omitted], next + 1)
|
|
363
|
+
if (bounded.usedBytes > budgetBytes) {
|
|
364
|
+
if (next === offset) throw new RangeError(`ptc-plus: capability search needs at least ${bounded.usedBytes} budgetBytes for its continuation and inspect reference`)
|
|
365
|
+
break
|
|
366
|
+
}
|
|
367
|
+
result = bounded
|
|
368
|
+
}
|
|
369
|
+
if (result.usedBytes > budgetBytes) throw new RangeError(`ptc-plus: capability search needs at least ${result.usedBytes} budgetBytes for its continuation`)
|
|
370
|
+
return result
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** Return whole owner contracts within a UTF-8 JSON byte budget, including the envelope. */
|
|
375
|
+
export function capabilitySearch(metadata, args) {
|
|
376
|
+
return createCapabilitySearcher(metadata)(args)
|
|
377
|
+
}
|
|
@@ -1,34 +1,66 @@
|
|
|
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/'
|
|
5
|
+
const PLATFORM_FACTS = '`return { platform: process.platform, arch: process.arch, node: process.version, cwd: process.cwd(), execPath: process.execPath }`'
|
|
5
6
|
const TIP_PREFIXES = Object.freeze({
|
|
6
7
|
'repeated-binding-failure': 'The same binding failure has recurred.',
|
|
7
8
|
'platform-command-failure': 'An executable, shell, or path failed in the current execution world.',
|
|
8
9
|
})
|
|
9
10
|
|
|
10
11
|
// 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
|
-
|
|
12
|
+
export function createRuntimeHistory() {
|
|
13
|
+
return { seenNames: {}, tips: {}, hadState: false, hadCatalog: false }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function advanceRuntimeHistory(previous, { sections = [], notice, snapshot = false, catalog = false,
|
|
17
|
+
index, contextStep, lastSuccessfulRunIndex, resetSuccess = false }) {
|
|
18
|
+
let next = previous
|
|
19
|
+
const revise = () => {
|
|
20
|
+
if (next === previous) next = { ...previous, tips: { ...previous.tips }, seenNames: { ...previous.seenNames } }
|
|
21
|
+
}
|
|
22
|
+
if (resetSuccess) {
|
|
23
|
+
revise()
|
|
24
|
+
for (const [id, tip] of Object.entries(next.tips)) next.tips[id] = { ...tip, unresolved: 0 }
|
|
25
|
+
for (const [name, deliveredIndex] of Object.entries(next.seenNames)) {
|
|
26
|
+
const identity = recoveryTipIdentity(name)
|
|
27
|
+
if (deliveredIndex > (lastSuccessfulRunIndex ?? -1)) next.tips[identity.id].unresolved += 1
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const hadState = snapshot || sections.some(section => PTC_STATE_NAMES.includes(section.name))
|
|
31
|
+
if ((hadState && !next.hadState) || (catalog && !next.hadCatalog)) {
|
|
32
|
+
revise()
|
|
33
|
+
next.hadState ||= hadState
|
|
34
|
+
next.hadCatalog ||= catalog
|
|
35
|
+
}
|
|
36
|
+
for (const name of [...sections.map(section => section.name), ...(notice === undefined ? [] : [notice])]) {
|
|
37
|
+
const identity = recoveryTipIdentity(name)
|
|
38
|
+
if (identity === undefined || Object.hasOwn(next.seenNames, name)) continue
|
|
39
|
+
revise()
|
|
40
|
+
next.seenNames[name] = index
|
|
41
|
+
const earlier = next.tips[identity.id]
|
|
42
|
+
next.tips[identity.id] = {
|
|
43
|
+
highestOrdinal: Math.max(earlier?.highestOrdinal ?? 0, identity.ordinal),
|
|
44
|
+
contextStep,
|
|
45
|
+
unresolved: (earlier?.unresolved ?? 0) + (index > (lastSuccessfulRunIndex ?? -1) ? 1 : 0),
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return next
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function runtimeHistoryForView(view) {
|
|
52
|
+
if (view.runtimeHistory !== undefined) return view.runtimeHistory
|
|
53
|
+
let history = createRuntimeHistory()
|
|
54
|
+
history.hadState = view.ptcMessages?.some(record => record.form === 'snapshot') === true
|
|
55
|
+
history.hadCatalog = view.ptcMessages?.some(record => record.form === 'catalog') === true
|
|
15
56
|
const snapshots = [
|
|
16
|
-
...view.systemPromptSnapshots,
|
|
57
|
+
...(view.systemPromptSnapshots ?? []),
|
|
17
58
|
...(view.ptcMessages ?? []).filter(record => record.form === 'notice').map(record => ({
|
|
18
59
|
...record, sections: [{ name: record.name, text: record.text }],
|
|
19
60
|
})),
|
|
20
61
|
].sort((left, right) => left.index - right.index)
|
|
21
62
|
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)
|
|
63
|
+
history = advanceRuntimeHistory(history, { ...snapshot, lastSuccessfulRunIndex: view.lastSuccessfulRunIndex })
|
|
32
64
|
}
|
|
33
65
|
return history
|
|
34
66
|
}
|
|
@@ -62,8 +94,8 @@ function tipCandidate(view) {
|
|
|
62
94
|
function renderTip(id, detailed, candidate = {}) {
|
|
63
95
|
if (id === 'platform-command-failure') {
|
|
64
96
|
return detailed
|
|
65
|
-
? `${TIP_PREFIXES[id]} Re-check the active execution world and the actual executable before retrying. Use direct argv for a normal executable; use a shell only when its syntax or resolution is required. Windows, WSL, POSIX, and package shims have different paths and launch rules.`
|
|
66
|
-
: `${TIP_PREFIXES[id]} Inspect the executable
|
|
97
|
+
? `${TIP_PREFIXES[id]} Re-check the active execution world and the actual executable before retrying. First inspect bounded runtime facts with ${PLATFORM_FACTS}; use the result to choose the executable and path. Use direct argv for a normal executable; use a shell only when its syntax or resolution is required. Windows, WSL, POSIX, and package shims have different paths and launch rules. Do not dump process.env.`
|
|
98
|
+
: `${TIP_PREFIXES[id]} Inspect bounded runtime facts with ${PLATFORM_FACTS}, then choose the executable and path in the current execution world. Use direct argv or a shell only when required; do not assume Windows, WSL, POSIX, or one shell. Do not dump process.env.`
|
|
67
99
|
}
|
|
68
100
|
if (!candidate.capability) {
|
|
69
101
|
return `${TIP_PREFIXES[id]} Inspect the visible cell source and results for the local name, scope, initialization, or declaration conflict. Correct that expression before continuing; capability discovery does not list session-local variables.${detailed ? ' Earlier statements may have run; use current expression evidence without assuming a failed declaration initialized its binding.' : ''}`
|
|
@@ -79,14 +111,12 @@ export function latestRecoveryTip(view, config) {
|
|
|
79
111
|
if (candidate === undefined) return undefined
|
|
80
112
|
// Cooldown, escalation, and ordinal are per trigger kind: one kind's tip must
|
|
81
113
|
// not suppress a different kind.
|
|
82
|
-
const
|
|
83
|
-
const lastTip = matching.at(-1)
|
|
114
|
+
const lastTip = runtimeHistoryForView(view).tips[candidate.id]
|
|
84
115
|
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
|
|
116
|
+
const ordinal = (lastTip?.highestOrdinal ?? 0) + 1
|
|
87
117
|
if (!Number.isSafeInteger(ordinal)) return undefined
|
|
88
118
|
return {
|
|
89
119
|
name: `${TIP_CONTEXT_PREFIX}${candidate.id}/${ordinal}`,
|
|
90
|
-
text: renderTip(candidate.id, unresolved
|
|
120
|
+
text: renderTip(candidate.id, (lastTip?.unresolved ?? 0) >= config.escalationFailures, candidate),
|
|
91
121
|
}
|
|
92
122
|
}
|
|
@@ -255,39 +255,39 @@ function assemble(source, replacements) {
|
|
|
255
255
|
export function editRunCodeSchema() {
|
|
256
256
|
return {
|
|
257
257
|
name: 'edit_run_code',
|
|
258
|
-
description: '
|
|
258
|
+
description: 'Call this from within a run_code cell. It accepts an edit of the most recent eligible cell captured at nested dispatch and returns { accepted, scheduled, target_call_seq } when scheduled. The corrected cell runs after the current run_code cell releases its worker; its result is reported with that cell, and a successful edit becomes the next target. This reruns the entire target 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.',
|
|
259
259
|
parameters: {
|
|
260
260
|
type: 'object',
|
|
261
261
|
additionalProperties: false,
|
|
262
262
|
properties: {
|
|
263
263
|
edits: {
|
|
264
|
-
type: 'array',
|
|
264
|
+
type: 'array',
|
|
265
265
|
description: 'Atomic exact replacements, all resolved against the original target cell.',
|
|
266
266
|
items: {
|
|
267
267
|
type: 'object', additionalProperties: false,
|
|
268
268
|
properties: {
|
|
269
|
-
old_string: { type: 'string',
|
|
269
|
+
old_string: { type: 'string', description: 'Nonempty exact text in the target cell. It must occur exactly once.' },
|
|
270
270
|
new_string: { type: 'string', description: 'Literal replacement text. Use an empty string to delete the match.' },
|
|
271
271
|
},
|
|
272
272
|
required: ['old_string', 'new_string'],
|
|
273
273
|
},
|
|
274
274
|
},
|
|
275
275
|
regex_edits: {
|
|
276
|
-
type: 'array',
|
|
276
|
+
type: 'array',
|
|
277
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).',
|
|
278
278
|
items: {
|
|
279
279
|
type: 'object', additionalProperties: false,
|
|
280
280
|
properties: {
|
|
281
|
-
pattern: { type: 'string',
|
|
282
|
-
flags: { type: 'string',
|
|
281
|
+
pattern: { type: 'string', description: 'Nonempty JavaScript regular-expression source without delimiters or flags (for example \\d+px, not /\\d+px/). Zero-length matches are rejected.' },
|
|
282
|
+
flags: { type: 'string', description: 'Unique JavaScript g, i, m, s, or u flags. Global matching is always applied.' },
|
|
283
283
|
replacement: { type: 'string', description: 'JavaScript replacement template. Reference captures as $1, $2, ... or $<name> (for example "<$1>" inserts capture 1); backslash escapes such as \\1 are NOT capture references and stay literal text; write a literal dollar sign as $$.' },
|
|
284
|
-
expected_matches: { type: 'integer',
|
|
284
|
+
expected_matches: { type: 'integer', description: 'Positive exact match count required before any replacement is applied.' },
|
|
285
285
|
},
|
|
286
286
|
required: ['pattern', 'flags', 'replacement', 'expected_matches'],
|
|
287
287
|
},
|
|
288
288
|
},
|
|
289
289
|
[EXPECTED_TARGET_CALL_SEQ]: {
|
|
290
|
-
type: 'integer',
|
|
290
|
+
type: 'integer',
|
|
291
291
|
description: 'Optional target precondition copied from a validated diagnostic. The edit is rejected if the captured cell has another call sequence.',
|
|
292
292
|
},
|
|
293
293
|
},
|
|
@@ -12,6 +12,11 @@ export function createReplObservationInterest(ctx, initiallyEnabled, observe = a
|
|
|
12
12
|
for (const watcher of [...watchers]) watcher.release()
|
|
13
13
|
for (const controller of inspections) controller.abort()
|
|
14
14
|
}
|
|
15
|
+
const releaseEvents = typeof ctx.on === 'function' ? ctx.effect(() => ctx.on('session/event', session => {
|
|
16
|
+
for (const watcher of [...watchers]) {
|
|
17
|
+
if (watcher.sessionId === session.id) watcher.changed()
|
|
18
|
+
}
|
|
19
|
+
}), 'ptc-plus: model source observation changes') : undefined
|
|
15
20
|
const handler = async (endpoint, payload, signal) => {
|
|
16
21
|
if (!enabled || !['watch', 'observe'].includes(endpoint) || typeof payload?.sessionId !== 'string'
|
|
17
22
|
|| payload.sessionId.length === 0 || payload.sessionId.length > 256
|
|
@@ -32,11 +37,13 @@ export function createReplObservationInterest(ctx, initiallyEnabled, observe = a
|
|
|
32
37
|
}
|
|
33
38
|
}
|
|
34
39
|
return new Promise(resolve => {
|
|
35
|
-
const
|
|
40
|
+
const finish = value => {
|
|
36
41
|
signal.removeEventListener('abort', watcher.release)
|
|
37
42
|
watchers.delete(watcher)
|
|
38
|
-
resolve({ ok: true, value
|
|
39
|
-
}
|
|
43
|
+
resolve({ ok: true, value })
|
|
44
|
+
}
|
|
45
|
+
const watcher = { sessionId: payload.sessionId,
|
|
46
|
+
release: () => finish(null), changed: () => finish({ changed: true }) }
|
|
40
47
|
watchers.add(watcher)
|
|
41
48
|
signal.addEventListener('abort', watcher.release, { once: true })
|
|
42
49
|
if (signal.aborted) watcher.release()
|
|
@@ -62,6 +69,7 @@ export function createReplObservationInterest(ctx, initiallyEnabled, observe = a
|
|
|
62
69
|
async dispose() {
|
|
63
70
|
enabled = false
|
|
64
71
|
clear()
|
|
72
|
+
await releaseEvents?.()
|
|
65
73
|
for (const release of [...registrations]) await release()
|
|
66
74
|
if (typeof injection === 'function') await injection()
|
|
67
75
|
else await injection?.dispose?.()
|
package/internal/rpc-contract.js
CHANGED
|
@@ -17,6 +17,7 @@ const resultSchema = Schema.union([
|
|
|
17
17
|
export const RPC_CONTRACTS = Object.freeze({
|
|
18
18
|
bindings: Object.freeze({ service: 'ptcPlusBindings' }),
|
|
19
19
|
repl: Object.freeze({ service: 'ptcPlusRepl' }),
|
|
20
|
+
capabilities: Object.freeze({ service: 'ptcPlusCapabilities' }),
|
|
20
21
|
})
|
|
21
22
|
|
|
22
23
|
export function rpcDescriptor({ service }) {
|