dsh-ptc-plus 0.5.0 → 0.5.2
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.cjs +1983 -234
- package/compiler-core.cjs +132 -132
- package/compiler-core.cjs.map +3 -3
- package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +2 -0
- package/docs/adr/0017-track-the-latest-dsh-public-surface.md +2 -2
- package/docs/adr/0023-global-user-bindings.md +1 -1
- package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +11 -1
- package/docs/adr/0032-own-session-log-facts-in-a-host-only-projection.md +31 -1
- package/docs/adr/0038-freeze-session-capability-plans.md +1 -1
- package/docs/adr/0044-coexist-with-the-official-persistent-repl.md +35 -0
- package/docs/adr/0045-customize-model-visible-prompt-text.md +38 -0
- package/docs/adr/0046-gate-the-commit-with-the-semantic-evidence-tier.md +35 -0
- package/docs/adr/0047-store-journal-argument-digests-in-the-projection.md +88 -0
- package/docs/adr/README.md +2 -0
- package/docs/architecture.md +2 -2
- package/docs/capability-planning-components.md +2 -2
- package/docs/client-ui.md +13 -10
- package/docs/client-visual-contract.md +1 -1
- package/docs/durability-design.md +7 -1
- package/docs/installation.md +5 -3
- package/docs/publishing.md +2 -2
- package/docs/releases/v0.5.2.md +79 -0
- package/docs/runtime-reference.md +42 -3
- package/docs/unmeasured-cell-recovery-repair-proposal.md +387 -0
- package/docs/verification.md +6 -2
- package/index.js +63 -19
- package/internal/bounded-literal.js +85 -0
- package/internal/capability-command-receipt.js +3 -1
- package/internal/capability-environment.js +171 -13
- package/internal/capability-plan.js +11 -8
- package/internal/capability-statistics.js +6 -2
- package/internal/cell-error.js +13 -0
- package/internal/config-spec.js +122 -2
- package/internal/direct-surface-owner.js +14 -10
- package/internal/edit-transport-owner.js +96 -17
- package/internal/execution-provider-owner.js +25 -0
- package/internal/execution-seam-compat.js +18 -0
- package/internal/isolated-worker.js +134 -17
- package/internal/kernel-worker.js +167 -3
- package/internal/legacy-cell-analysis.js +14 -2
- package/internal/legacy-repl-convenience.js +16 -3
- package/internal/official-repl-priority.js +51 -0
- package/internal/projection-retention.js +238 -0
- package/internal/prompt-text.js +858 -0
- package/internal/recovery-tips.js +50 -11
- package/internal/rejected-cell-editor.js +3 -2
- package/internal/runtime-bridge-owner.js +11 -8
- package/internal/runtime-config.js +2 -0
- package/internal/runtime-contexts.js +14 -5
- package/internal/runtime-messages.js +43 -15
- package/internal/sdk-projection.js +2 -2
- package/internal/session-cell-executor.js +132 -11
- package/internal/session-journal-recovery.js +1009 -99
- package/internal/session-journal-schema.js +7 -5
- package/internal/session-journal.js +177 -14
- package/internal/session-log-view.js +111 -24
- package/internal/session-runtime.js +674 -33
- package/internal/session-state.js +27 -0
- package/internal/stateful-root-compiler.js +11 -0
- package/internal/stateful-root-runtime.js +17 -0
- package/internal/user-binding-console.js +7 -0
- package/internal/user-bindings-owner.js +8 -27
- package/internal/user-bindings.js +2 -2
- package/internal/validated-parse-repair.js +6 -1
- package/internal/worker-client.js +27 -4
- package/package.json +5 -2
- package/src/client-activity.js +27 -6
- package/src/client-capabilities-view.js +774 -125
- package/src/client-copy.js +231 -18
- package/src/client-repl-view.js +6 -1
- package/src/client-styles.js +68 -2
- package/src/client-tool-view.js +9 -2
|
@@ -94,6 +94,33 @@ export class BindingCatalog {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Catalog after installing recorded values.
|
|
99
|
+
*
|
|
100
|
+
* A restored node is not compiled: its source may be dead (it can reference a
|
|
101
|
+
* binding a recorded rewind ended), so there is no preparation to take
|
|
102
|
+
* declarations from. The journal records the names and values but not their
|
|
103
|
+
* declaration kind, so a restored binding is catalogued as reassignable. That is
|
|
104
|
+
* deliberately permissive: refusing a later `let`-style redeclaration would break
|
|
105
|
+
* continuation, while accepting one only widens what the session may do with a
|
|
106
|
+
* binding whose original declaration the log cannot describe.
|
|
107
|
+
*/
|
|
108
|
+
restore(names) {
|
|
109
|
+
if (!Array.isArray(names) || names.length === 0) return this
|
|
110
|
+
const entries = new Map(this.#entries)
|
|
111
|
+
for (const name of names) {
|
|
112
|
+
if (typeof name !== 'string' || entries.has(name)) continue
|
|
113
|
+
entries.set(name, { kind: 'let', writable: true })
|
|
114
|
+
}
|
|
115
|
+
return new BindingCatalog({
|
|
116
|
+
entries,
|
|
117
|
+
namespaces: this.#namespaces,
|
|
118
|
+
userBindingAncestry: this.#userBindingAncestry,
|
|
119
|
+
rootCandidates: this.#rootCandidates,
|
|
120
|
+
dynamicOrigins: this.#dynamicOrigins,
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
97
124
|
advance(prepared, source = undefined, committedRedeclarations = undefined, rootBindingFacts = undefined) {
|
|
98
125
|
const semantics = normalizeLanguageSemantics(prepared.languageSemantics ?? LEGACY_LANGUAGE_SEMANTICS)
|
|
99
126
|
if (semantics === LEGACY_LANGUAGE_SEMANTICS) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** Compile each cell into a private native frame backed by session identities. */
|
|
2
|
+
import { boundedLiteralEvidence } from './bounded-literal.js'
|
|
2
3
|
import { adaptRootModuleImports } from './managed-module-operations.js'
|
|
3
4
|
import { parse } from '@babel/parser'
|
|
4
5
|
import { createHash } from 'node:crypto'
|
|
@@ -321,11 +322,21 @@ export function compileStatefulRoot(program, {
|
|
|
321
322
|
return remember(text, [{ generatedStart: 6, generatedEnd: 6 + source.length,
|
|
322
323
|
originalStart: original.start, originalEnd: original.end }])
|
|
323
324
|
}
|
|
325
|
+
const firstDeclaration = declarations.length
|
|
324
326
|
const target = record(names[0], 'variable', node, bindings[0], `:${declarator.start}`)
|
|
325
327
|
for (const binding of bindings.slice(1)) {
|
|
326
328
|
record(binding.name, 'variable', node, binding, `:${declarator.start}`)
|
|
327
329
|
declarations.at(-1).commitDependency = target
|
|
328
330
|
}
|
|
331
|
+
// A single `const name = { … }` initializer proves its own size from the
|
|
332
|
+
// source, so settlement may record that value without walking an object the
|
|
333
|
+
// codec cannot size. A destructuring pattern has no one initializer to prove.
|
|
334
|
+
if (declarator.id.type === 'Identifier' && supplied === undefined && declarator.init !== null) {
|
|
335
|
+
const evidence = boundedLiteralEvidence(declarator.init)
|
|
336
|
+
if (evidence !== undefined) {
|
|
337
|
+
for (const declaration of declarations.slice(firstDeclaration)) declaration.boundedLiteral = evidence
|
|
338
|
+
}
|
|
339
|
+
}
|
|
329
340
|
if (declarator.init === null && supplied === undefined) {
|
|
330
341
|
return emptyCompletion(`${runtime}.declare(${JSON.stringify(names[0])}, ${JSON.stringify(target)})`)
|
|
331
342
|
}
|
|
@@ -333,6 +333,23 @@ export function createStatefulRootRuntime({ readAmbient, typeofAmbient, writeAmb
|
|
|
333
333
|
const entry = mapGet(entries, name)
|
|
334
334
|
return entry?.source === 'local' && entry.object !== true && entry.read === undefined ? { value: entry.value } : undefined
|
|
335
335
|
},
|
|
336
|
+
/**
|
|
337
|
+
* Install a recorded binding without running the cell that produced it.
|
|
338
|
+
*
|
|
339
|
+
* A volatile cell cannot be replayed: re-executing its source would
|
|
340
|
+
* redispatch the ambient effects that made it volatile. Recovery therefore
|
|
341
|
+
* installs the value the journal recorded, as the committed local
|
|
342
|
+
* declaration the original cell's own commit produced. Returns false when
|
|
343
|
+
* the store cannot publish a session identity for the name, so the caller
|
|
344
|
+
* reports that binding instead of claiming it was restored.
|
|
345
|
+
*/
|
|
346
|
+
restoreValue(name, value) {
|
|
347
|
+
const entry = storeValue(name, value, { declaration: true })
|
|
348
|
+
if (entry === undefined) return false
|
|
349
|
+
publish(name, () => read(name), next => write(name, next))
|
|
350
|
+
changed(name, 'local')
|
|
351
|
+
return true
|
|
352
|
+
},
|
|
336
353
|
has: name => mapHas(entries, name) && mapGet(entries, name).object !== true && mapGet(entries, name).deleted !== true,
|
|
337
354
|
facts() {
|
|
338
355
|
const facts = []
|
|
@@ -55,6 +55,12 @@ export class UserBindingConsole {
|
|
|
55
55
|
async reconfigure(options) {
|
|
56
56
|
// Reconfiguration releases the current environments; the owner stays usable so
|
|
57
57
|
// a later run can start again.
|
|
58
|
+
if (this.options.workerReclaimMs !== options.workerReclaimMs && Number.isSafeInteger(options.workerReclaimMs)) {
|
|
59
|
+
// A bound change needs no new worker: the close path reads the transport.
|
|
60
|
+
for (const environment of this.environments.values()) {
|
|
61
|
+
environment.worker.reclaimBoundMs = options.workerReclaimMs
|
|
62
|
+
}
|
|
63
|
+
}
|
|
58
64
|
if (['maxWallMs', 'maxOutputBytes', 'maxOldGenerationSizeMb'].some(key => this.options[key] !== options[key])) {
|
|
59
65
|
for (const capability of [...this.environments.keys()]) this.release(capability, 'reconfigured')
|
|
60
66
|
const failures = await this.owner.releaseAll()
|
|
@@ -88,6 +94,7 @@ export class UserBindingConsole {
|
|
|
88
94
|
workerData: { source, cwd: this.options.cwd, maxOutputBytes: this.options.maxOutputBytes },
|
|
89
95
|
env: normalizeWorkerEnvironment(process.env),
|
|
90
96
|
resourceLimits: { maxOldGenerationSizeMb: this.options.maxOldGenerationSizeMb },
|
|
97
|
+
reclaimBoundMs: this.options.workerReclaimMs,
|
|
91
98
|
protocol: 'parent-port',
|
|
92
99
|
})
|
|
93
100
|
const worker = started.transport
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PTC_MESSAGE_SOURCE_KIND } from './message-sources.js'
|
|
2
|
-
import { bindingToggleMessage
|
|
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'
|
|
@@ -111,6 +111,7 @@ function runCandidate(source, invocation, options, signal, owner) {
|
|
|
111
111
|
workerData: { source, invocation, valueLimits: options.valueLimits, cwd: options.cwd },
|
|
112
112
|
env: normalizeWorkerEnvironment(process.env),
|
|
113
113
|
resourceLimits: { maxOldGenerationSizeMb: options.maxOldGenerationSizeMb },
|
|
114
|
+
reclaimBoundMs: options.workerReclaimMs,
|
|
114
115
|
})
|
|
115
116
|
const worker = started.transport
|
|
116
117
|
const outputLimitError = () => Object.assign(
|
|
@@ -183,10 +184,10 @@ export function createUserBindingsOwner(ctx, options = {}) {
|
|
|
183
184
|
maxWallMs: options.maxWallMs,
|
|
184
185
|
maxOutputBytes: options.maxOutputBytes,
|
|
185
186
|
maxOldGenerationSizeMb: options.maxOldGenerationSizeMb,
|
|
187
|
+
workerReclaimMs: options.workerReclaimMs,
|
|
186
188
|
valueLimits: options.valueLimits,
|
|
187
189
|
}
|
|
188
190
|
let disposed = false
|
|
189
|
-
const pendingConfigurationReceipts = new WeakMap()
|
|
190
191
|
const codeConsole = new UserBindingConsole(currentOptions)
|
|
191
192
|
const commandRegistrations = new Map()
|
|
192
193
|
const sessionAgents = new Map()
|
|
@@ -387,15 +388,11 @@ export function createUserBindingsOwner(ctx, options = {}) {
|
|
|
387
388
|
const sessions = new Set((ctx.agents?.list?.() ?? []).map(agent => agent.session).filter(Boolean))
|
|
388
389
|
for (const session of sessions) {
|
|
389
390
|
try {
|
|
391
|
+
// Before the first accepted step these are initial settings, not changes
|
|
392
|
+
// to an environment the model has received. The catalog owns first delivery.
|
|
393
|
+
if (session.deriveMessages()[0]?.role !== 'system') continue
|
|
390
394
|
const message = createUserMessage(bindingToggleMessage(entry, next.revision))
|
|
391
|
-
|
|
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
|
-
}
|
|
395
|
+
session.append('user/message', message, { surfaceOp: 'append' })
|
|
399
396
|
} catch (error) { reportContainedFailure('ptc-plus: binding configuration saved but its session receipt could not be appended', error) }
|
|
400
397
|
}
|
|
401
398
|
return next
|
|
@@ -916,23 +913,6 @@ export function createUserBindingsOwner(ctx, options = {}) {
|
|
|
916
913
|
mountAuthoring()
|
|
917
914
|
|
|
918
915
|
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
916
|
path: store.filename,
|
|
937
917
|
cwd: bindingsCwd,
|
|
938
918
|
async snapshot() {
|
|
@@ -997,6 +977,7 @@ export function createUserBindingsOwner(ctx, options = {}) {
|
|
|
997
977
|
maxWallMs: nextConfig.maxWallMs,
|
|
998
978
|
maxOutputBytes: nextConfig.maxOutputBytes,
|
|
999
979
|
maxOldGenerationSizeMb: nextConfig.maxOldGenerationSizeMb,
|
|
980
|
+
workerReclaimMs: nextConfig.workerReclaimMs,
|
|
1000
981
|
valueLimits: valueLimitsFromConfig(nextConfig),
|
|
1001
982
|
}
|
|
1002
983
|
if (nextEnabled === previousEnabled) {
|
|
@@ -268,7 +268,7 @@ export function userBindingsDeclaration(snapshot) {
|
|
|
268
268
|
return normalized.entries.map(entry => entry.declaration).join('\n\n')
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
-
export function userBindingsConfiguredContext(snapshot) {
|
|
271
|
+
export function userBindingsConfiguredContext(snapshot, { entryLabel = 'Binding' } = {}) {
|
|
272
272
|
const entries = normalizeUserBindingsSnapshot(snapshot).entries
|
|
273
273
|
.filter(entry => {
|
|
274
274
|
const { includeDeclaration, instructions } = bindingModelPreferences(entry.modelContext)
|
|
@@ -279,7 +279,7 @@ export function userBindingsConfiguredContext(snapshot) {
|
|
|
279
279
|
const content = entries.map(entry => {
|
|
280
280
|
const { includeDeclaration, instructions } = bindingModelPreferences(entry.modelContext)
|
|
281
281
|
return [
|
|
282
|
-
|
|
282
|
+
`${entryLabel}: ${entry.name}`,
|
|
283
283
|
instructions,
|
|
284
284
|
includeDeclaration ? `\`\`\`ts\n${entry.declaration}\n\`\`\`` : '',
|
|
285
285
|
].filter(Boolean).join('\n\n')
|
|
@@ -31,7 +31,7 @@ function startsInsideSurrogatePair(source, index) {
|
|
|
31
31
|
return current >= 0xdc00 && current <= 0xdfff && previous >= 0xd800 && previous <= 0xdbff
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
function boundedUniqueSuffix(source) {
|
|
34
|
+
export function boundedUniqueSuffix(source) {
|
|
35
35
|
const first = Math.max(0, source.length - MAX_PARSE_REPAIR_ANCHOR_CODE_UNITS)
|
|
36
36
|
for (let start = source.length - 1; start >= first; start -= 1) {
|
|
37
37
|
if (startsInsideSurrogatePair(source, start)) continue
|
|
@@ -81,6 +81,11 @@ export function validatedEofClosureRepair({ source, position, prepare, targetCal
|
|
|
81
81
|
const candidate = candidates[0]
|
|
82
82
|
return Object.freeze({
|
|
83
83
|
...candidate,
|
|
84
|
+
// The diagnostic names the target cell instead of embedding the whole
|
|
85
|
+
// invocation: the JSON form is hundreds of characters the model has to copy
|
|
86
|
+
// before it can act, while the delimiter and the target are the two facts it
|
|
87
|
+
// needs.
|
|
88
|
+
targetCallSeq,
|
|
84
89
|
invocation: renderInvocation(candidate.arguments),
|
|
85
90
|
})
|
|
86
91
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdtemp, rm } from 'node:fs/promises'
|
|
1
|
+
import { mkdir, mkdtemp, rm } from 'node:fs/promises'
|
|
2
2
|
import { randomBytes } from 'node:crypto'
|
|
3
3
|
import { tmpdir } from 'node:os'
|
|
4
4
|
import { isAbsolute, join } from 'node:path'
|
|
@@ -46,7 +46,15 @@ export class WorkerClient {
|
|
|
46
46
|
return lastLine === undefined ? undefined : lastLine.slice(0, 300)
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Start the session worker once and reuse it.
|
|
51
|
+
*
|
|
52
|
+
* `bounds` carries the startup bound for a worker that does not exist yet and
|
|
53
|
+
* the close bound this worker starts with; a later close reads the bound the
|
|
54
|
+
* owning kernel sets from its current configuration. It may be a getter, which
|
|
55
|
+
* is read after the scratch wait because a settings change can land there.
|
|
56
|
+
*/
|
|
57
|
+
async ensure(maxOldGenerationSizeMb, bounds = {}) {
|
|
50
58
|
if (this.pendingOutputFailure !== undefined) {
|
|
51
59
|
const message = this.pendingOutputFailure
|
|
52
60
|
this.pendingOutputFailure = undefined
|
|
@@ -61,7 +69,14 @@ export class WorkerClient {
|
|
|
61
69
|
if (!isAbsolute(scratchRoot)) {
|
|
62
70
|
throw new Error(`ptc-plus: host temporary directory must be absolute, got ${JSON.stringify(scratchRoot)}`)
|
|
63
71
|
}
|
|
64
|
-
this.scratchReady = mkdtemp(join(scratchRoot, 'dsh-ptc-plus-'))
|
|
72
|
+
this.scratchReady = mkdtemp(join(scratchRoot, 'dsh-ptc-plus-')).catch(async error => {
|
|
73
|
+
// A host that points TMPDIR at a directory it has not created yet (macOS CI)
|
|
74
|
+
// fails mkdtemp with ENOENT; create the root once and retry instead of
|
|
75
|
+
// failing the cell with a worker-exit error.
|
|
76
|
+
if (error?.code !== 'ENOENT') throw error
|
|
77
|
+
await mkdir(scratchRoot, { recursive: true })
|
|
78
|
+
return mkdtemp(join(scratchRoot, 'dsh-ptc-plus-'))
|
|
79
|
+
})
|
|
65
80
|
}
|
|
66
81
|
this.workerLimit = maxOldGenerationSizeMb
|
|
67
82
|
let scratchDirectory
|
|
@@ -79,13 +94,21 @@ export class WorkerClient {
|
|
|
79
94
|
/* c8 ignore next */
|
|
80
95
|
if (this.worker !== undefined) return this.workerReady
|
|
81
96
|
const environment = normalizeWorkerEnvironment(process.env)
|
|
97
|
+
// A settings change can land while the scratch directory is being created,
|
|
98
|
+
// so the bounds are read here rather than at the call.
|
|
99
|
+
const resolvedBounds = typeof bounds === 'function' ? bounds() : bounds
|
|
82
100
|
let worker
|
|
83
101
|
try {
|
|
84
102
|
const started = this.owner.start({
|
|
85
103
|
helper: KERNEL_HELPER,
|
|
86
104
|
entry: this.workerUrl,
|
|
87
|
-
workerData: {
|
|
105
|
+
workerData: {
|
|
106
|
+
cwd: this.cwd,
|
|
107
|
+
compilerCache: this.compilerCache?.(),
|
|
108
|
+
startupProbeMs: resolvedBounds.startupProbeMs,
|
|
109
|
+
},
|
|
88
110
|
resourceLimits: { maxOldGenerationSizeMb: this.workerLimit },
|
|
111
|
+
reclaimBoundMs: resolvedBounds.reclaimBoundMs,
|
|
89
112
|
env: {
|
|
90
113
|
...environment,
|
|
91
114
|
TEMP: scratchDirectory,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-ptc-plus",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
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
|
|
@@ -106,8 +107,10 @@
|
|
|
106
107
|
"coverage:focus": "node scripts/coverage.mjs --focus",
|
|
107
108
|
"test:backend:focus": "node scripts/test-suite-files.mjs --focus",
|
|
108
109
|
"test:backend": "node scripts/test-suite-files.mjs",
|
|
110
|
+
"test:backend:fast": "node scripts/test-suite-files.mjs --fast",
|
|
109
111
|
"test:ci:runtime": "node --test --test-concurrency=1 test/package-contract.test.js test/compiler-platform.test.js test/direct-surface-policy.test.js test/isolated-entrance.test.js test/plugin-native-call-transport.test.js",
|
|
110
112
|
"verify": "npm run review:validate && npm run host:baseline:verify && npm run semantic:check && npm run build:check && node --check index.js && node --check internal/kernel-worker.js && node --check scripts/migrate-session-log.mjs && node --check scripts/expensive-headless-acceptance.mjs && node --check scripts/ab-headless-trajectories.mjs && node scripts/deterministic-test-groups.mjs",
|
|
113
|
+
"verify:check": "npm run review:validate && npm run host:baseline:verify && npm run semantic:check && npm run build:check && node --check index.js && node --check internal/kernel-worker.js && node scripts/deterministic-test-groups.mjs --check",
|
|
111
114
|
"verify:ci": "npm run review:validate && npm run host:baseline:verify && npm run semantic:check && npm run build:check && node --check index.js && node --check internal/kernel-worker.js && node --check scripts/migrate-session-log.mjs && node scripts/deterministic-test-groups.mjs --ci",
|
|
112
115
|
"check": "node scripts/review-findings.mjs check",
|
|
113
116
|
"verify:docs": "node scripts/review-findings.mjs docs",
|
package/src/client-activity.js
CHANGED
|
@@ -249,12 +249,30 @@ function isValidModuleSemantics(value, version) {
|
|
|
249
249
|
&& (legacy || IMPORT_EXPRESSION_BOUNDARIES.has(value.importExpressionBoundary))
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
+
/**
|
|
253
|
+
* The retention window keeps everything recovery reads and replaces one payload
|
|
254
|
+
* with the digest of exactly the bytes it used to carry, so a recorded call keeps
|
|
255
|
+
* the Value V1 wire of its arguments or the digest of that wire, and a derived run
|
|
256
|
+
* keeps its source or the digest of that source. `normalizeCalls` and
|
|
257
|
+
* `normalizeDerivedRelation` accept exactly one of the two shapes and reject a
|
|
258
|
+
* record that claims both, so this boundary reports which shape it read.
|
|
259
|
+
*/
|
|
260
|
+
function payloadShape(value, textField, digestField, bytesField) {
|
|
261
|
+
if (Object.hasOwn(value, textField)) {
|
|
262
|
+
return Object.hasOwn(value, digestField) || Object.hasOwn(value, bytesField) ? undefined : 'text'
|
|
263
|
+
}
|
|
264
|
+
return typeof value[digestField] === 'string' && /^[a-f0-9]{64}$/u.test(value[digestField])
|
|
265
|
+
&& isSafeSequence(value[bytesField]) ? 'digest' : undefined
|
|
266
|
+
}
|
|
267
|
+
|
|
252
268
|
function isValidCall(value) {
|
|
253
269
|
if (!isRecord(value) || (value.ok !== true && value.ok !== false)) return false
|
|
254
270
|
const fields = value.ok ? CALL_SUCCESS_FIELDS : CALL_ERROR_FIELDS
|
|
255
|
-
|
|
256
|
-
|
|
271
|
+
const argsShape = payloadShape(value, 'args', 'argsDigest', 'argsBytes')
|
|
272
|
+
if (argsShape === undefined || !hasClosedFields(value, fields)
|
|
273
|
+
|| typeof value.global !== 'string' || typeof value.member !== 'string'
|
|
257
274
|
|| !isSafeSequence(value.settle)) return false
|
|
275
|
+
if (argsShape === 'text' && !isValidValueWire(value.args)) return false
|
|
258
276
|
return value.ok ? isValidValueWire(value.value) : typeof value.error === 'string'
|
|
259
277
|
}
|
|
260
278
|
|
|
@@ -389,8 +407,10 @@ function isValidEditTarget(value) {
|
|
|
389
407
|
|
|
390
408
|
function isValidDerivedRun(value) {
|
|
391
409
|
try {
|
|
392
|
-
|
|
393
|
-
|
|
410
|
+
if (!isRecord(value) || !hasClosedFields(value, DERIVED_RUN_FIELDS)
|
|
411
|
+
|| typeof value.description !== 'string') return false
|
|
412
|
+
const codeShape = payloadShape(value, 'code', 'codeDigest', 'codeBytes')
|
|
413
|
+
return codeShape === 'digest' || (codeShape === 'text' && typeof value.code === 'string')
|
|
394
414
|
} catch {
|
|
395
415
|
return false
|
|
396
416
|
}
|
|
@@ -506,6 +526,7 @@ export function derivePtcToolView(block, toolName = undefined) {
|
|
|
506
526
|
? block.meta[GENERATED_RUN_CODE_DESCRIPTION_KEY]
|
|
507
527
|
: undefined
|
|
508
528
|
const description = (explicitDescription ?? generatedDescription)?.split(/\r?\n/, 1)[0] ?? ''
|
|
529
|
+
const descriptionGenerated = explicitDescription === undefined && generatedDescription !== undefined
|
|
509
530
|
const output = settled ? resultText(block) : ''
|
|
510
531
|
const state = !settled ? 'running'
|
|
511
532
|
: block.error?.code === 'interrupted' ? 'stopped'
|
|
@@ -514,7 +535,7 @@ export function derivePtcToolView(block, toolName = undefined) {
|
|
|
514
535
|
? block.meta.dshPtcPlus
|
|
515
536
|
: undefined
|
|
516
537
|
if (journal === undefined) {
|
|
517
|
-
return Object.freeze({ state, description, code, output, ptc: false, features: Object.freeze([]) })
|
|
538
|
+
return Object.freeze({ state, description, descriptionGenerated, code, output, ptc: false, features: Object.freeze([]) })
|
|
518
539
|
}
|
|
519
540
|
const rewrites = isValidRewrites(block.meta.dshPtcPlusRewrites, journal)
|
|
520
541
|
? block.meta.dshPtcPlusRewrites
|
|
@@ -542,5 +563,5 @@ export function derivePtcToolView(block, toolName = undefined) {
|
|
|
542
563
|
: undefined,
|
|
543
564
|
...operationFeatures(journal.operations),
|
|
544
565
|
])
|
|
545
|
-
return Object.freeze({ state, description, code, output, ptc: true, features: Object.freeze(features) })
|
|
566
|
+
return Object.freeze({ state, description, descriptionGenerated, code, output, ptc: true, features: Object.freeze(features) })
|
|
546
567
|
}
|