dsh-ptc-plus 0.4.10 → 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.
- package/README.en.md +65 -127
- package/README.md +65 -127
- 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 +42702 -0
- package/cordis.patch.yml +1 -0
- package/docs/README.md +6 -2
- package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +31 -198
- 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 +4 -4
- package/docs/adr/0019-plugin-settings-and-kill-switch.md +1 -1
- package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +1 -1
- package/docs/adr/0022-stage-npm-releases-with-oidc.md +4 -2
- package/docs/adr/0023-global-user-bindings.md +2 -0
- 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 +25 -23
- package/docs/adr/0031-close-semantic-obligations-by-owner.md +1 -1
- 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/0044-coexist-with-the-official-persistent-repl.md +35 -0
- package/docs/adr/README.md +8 -0
- package/docs/architecture.md +23 -25
- package/docs/capability-planning-components.md +93 -0
- package/docs/capability-projection.md +7 -0
- package/docs/client-ui.md +70 -23
- package/docs/client-visual-contract.md +26 -9
- 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 +20 -15
- 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 +33 -10
- package/docs/program-data-plane.md +5 -3
- package/docs/publishing.md +7 -7
- 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 +53 -16
- package/docs/semantic-validation.md +14 -2
- package/docs/stateful-binding-updates-proposal.md +2 -2
- package/docs/user-bindings.md +1 -1
- package/docs/value-wire.md +11 -1
- package/docs/verification-optimization-work.md +26 -15
- package/docs/verification.md +30 -88
- package/index.js +146 -20
- package/internal/binding-toggle-receipt.js +24 -0
- package/internal/capability-command-receipt.js +31 -0
- package/internal/capability-environment.js +1253 -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 +214 -0
- package/internal/config-spec.js +89 -10
- package/internal/direct-surface-owner.js +27 -29
- package/internal/dynamic-environment-runtime.js +9 -9
- package/internal/edit-transport-owner.js +195 -140
- package/internal/execution-provider-owner.js +40 -12
- package/internal/execution-seam-compat.js +26 -8
- package/internal/kernel-worker.js +104 -32
- package/internal/native-root-dynamic.js +12 -13
- package/internal/official-repl-priority.js +51 -0
- package/internal/program-bindings.js +190 -0
- package/internal/recovery-tips.js +3 -2
- 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 +275 -34
- package/internal/runtime-config.js +58 -0
- package/internal/runtime-contexts.js +74 -5
- 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 +34 -0
- package/internal/session-journal-recovery.js +234 -11
- package/internal/session-journal-schema.js +6 -3
- package/internal/session-journal.js +31 -2
- package/internal/session-log-view.js +1 -1
- package/internal/session-runtime.js +119 -15
- package/internal/settings-compat.js +12 -5
- 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 +19 -2
- 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/package.json +49 -30
- package/src/client-activity.js +3 -2
- package/src/client-authoring-view.js +67 -30
- package/src/client-binding-review.js +2 -1
- package/src/client-binding-toggle.js +18 -0
- package/src/client-capabilities-view.js +1555 -0
- package/src/client-capability-sort.js +29 -0
- package/src/client-copy.js +923 -8
- package/src/client-feature-gates.js +3 -0
- package/src/client-indicator-view.js +4 -6
- package/src/client-primitives.js +33 -2
- package/src/client-repl-view.js +20 -13
- package/src/client-settings-view.js +411 -41
- package/src/client-styles.js +112 -15
- package/src/client-tool-hover.js +185 -0
- package/src/client-tool-view.js +45 -9
- package/src/client-transport.js +44 -9
- package/src/client-workbench.js +6 -7
- package/src/client.js +76 -22
- package/client.js +0 -32479
package/src/client-tool-view.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { derivePtcToolView } from './client-activity.js'
|
|
2
2
|
import { isHostComponent } from './client-host-compat.js'
|
|
3
|
+
import { createToolHover, toolHoverExcerpt } from './client-tool-hover.js'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* run_code / edit_run_code tool rows. The row is a pure projection of the
|
|
@@ -7,14 +8,16 @@ import { isHostComponent } from './client-host-compat.js'
|
|
|
7
8
|
* DisclosureRow when the host provides it and an accessible fallback otherwise.
|
|
8
9
|
*/
|
|
9
10
|
export function createPtcToolView(React, deps) {
|
|
10
|
-
const { CodeBlock, DisclosureRow, icons } = deps
|
|
11
|
+
const { CodeBlock, DisclosureRow, createPortal, icons } = deps
|
|
11
12
|
const h = React.createElement
|
|
12
13
|
const hasDisclosureRow = isHostComponent(DisclosureRow)
|
|
14
|
+
const { ToolHoverAnchor } = createToolHover(React, createPortal)
|
|
13
15
|
|
|
14
|
-
function PTCPlusToolRow({ toolName, block, inspect, t }) {
|
|
16
|
+
function PTCPlusToolRow({ toolName, block, inspect, t, hoverEnabled = true }) {
|
|
15
17
|
const [open, setOpen] = React.useState(false)
|
|
16
18
|
const view = derivePtcToolView(block, toolName)
|
|
17
|
-
const
|
|
19
|
+
const previewable = view.code !== '' || view.output !== ''
|
|
20
|
+
const expandable = previewable || typeof inspect === 'function'
|
|
18
21
|
const stateKey = {
|
|
19
22
|
running: 'tool.running',
|
|
20
23
|
ok: 'tool.completed',
|
|
@@ -30,12 +33,17 @@ export function createPtcToolView(React, deps) {
|
|
|
30
33
|
if (expandable) setOpen(current => !current)
|
|
31
34
|
}
|
|
32
35
|
const summaryText = summary === '' ? null : summary
|
|
36
|
+
// An auto-filled summary stays visually identical but declares its provenance in the DOM
|
|
37
|
+
// and on hover, so readers can tell who wrote it without changing the model-facing result.
|
|
38
|
+
const generatedSummary = view.descriptionGenerated && outputSummary === ''
|
|
33
39
|
const summaryLine = stateText === null && summaryText === null ? null
|
|
34
40
|
: h('div', { className: 'ptcPlusToolSummaryLine', 'data-state': view.state, role: 'status' },
|
|
35
41
|
stateText === null ? null : h('span', { className: 'ptcPlusToolState' }, stateText),
|
|
36
42
|
summaryText === null ? null : h('span', { className: 'ptcPlusToolSep', 'aria-hidden': true }),
|
|
37
|
-
summaryText === null ? null : h('span', { className: 'ptcPlusToolDescription'
|
|
38
|
-
|
|
43
|
+
summaryText === null ? null : h('span', { className: 'ptcPlusToolDescription',
|
|
44
|
+
'data-generated': generatedSummary ? 'true' : undefined,
|
|
45
|
+
title: generatedSummary ? t('tool.descriptionGenerated') : undefined }, summaryText))
|
|
46
|
+
const renderBody = (withInspect) => h('div', { className: 'ptcPlusToolBody' },
|
|
39
47
|
view.code === '' ? null : h('div', { className: 'ptcPlusToolSection' },
|
|
40
48
|
h('span', { className: 'ptcPlusToolSectionLabel' }, t('tool.source')),
|
|
41
49
|
isHostComponent(CodeBlock)
|
|
@@ -51,7 +59,7 @@ export function createPtcToolView(React, deps) {
|
|
|
51
59
|
className: 'ptcPlusIoText',
|
|
52
60
|
'data-error': view.state === 'error' || undefined,
|
|
53
61
|
}, view.output))),
|
|
54
|
-
typeof inspect !== 'function' ? null : h('button', {
|
|
62
|
+
!withInspect || typeof inspect !== 'function' ? null : h('button', {
|
|
55
63
|
type: 'button', className: 'ptcPlusInspect', onClick: inspect,
|
|
56
64
|
}, h(icons.inspect, { 'aria-hidden': true }), t('tool.inspect')))
|
|
57
65
|
const features = view.features.length === 0 ? null : h('div', { className: 'ptcPlusFeatures' },
|
|
@@ -62,8 +70,36 @@ export function createPtcToolView(React, deps) {
|
|
|
62
70
|
feature.detail === '' ? null
|
|
63
71
|
: h('span', { className: 'ptcPlusFeatureDetail', title: feature.detail }, feature.detail))))
|
|
64
72
|
const collapsedContent = h('div', { className: 'ptcPlusToolPreview' }, summaryLine, features)
|
|
73
|
+
const body = open ? renderBody(true) : null
|
|
74
|
+
const sourceExcerpt = toolHoverExcerpt(view.code, 6)
|
|
75
|
+
const outputExcerpt = toolHoverExcerpt(view.output, 3)
|
|
76
|
+
const preview = h(React.Fragment, null,
|
|
77
|
+
h('div', { className: 'ptcPlusToolHoverHeader', 'data-state': view.state },
|
|
78
|
+
h('span', { className: 'ptcPlusToolHoverHeading' },
|
|
79
|
+
t(toolName === 'edit_run_code' ? 'tool.editPreview' : 'tool.preview')),
|
|
80
|
+
h('span', { className: 'ptcPlusToolHoverState' }, t(stateKey))),
|
|
81
|
+
h('div', { className: 'ptcPlusToolHoverContent' },
|
|
82
|
+
view.description === '' ? null : h('div', { className: 'ptcPlusToolHoverDescription',
|
|
83
|
+
'data-generated': view.descriptionGenerated ? 'true' : undefined,
|
|
84
|
+
title: view.descriptionGenerated ? t('tool.descriptionGenerated') : undefined }, view.description),
|
|
85
|
+
view.code === '' ? null : h('div', { className: 'ptcPlusToolHoverSection' },
|
|
86
|
+
h('span', { className: 'ptcPlusToolSectionLabel' }, `${t('tool.source')} · TypeScript`),
|
|
87
|
+
isHostComponent(CodeBlock) ? h(CodeBlock, {
|
|
88
|
+
code: sourceExcerpt.text, lang: 'typescript', showHeader: false, lineNumbers: true,
|
|
89
|
+
className: 'ptcPlusToolCode ptcPlusToolHoverCode',
|
|
90
|
+
}) : h('pre', { className: 'ptcPlusToolCode ptcPlusToolHoverCode' }, sourceExcerpt.text)),
|
|
91
|
+
view.output === '' ? null : h('div', { className: 'ptcPlusToolHoverSection' },
|
|
92
|
+
h('span', { className: 'ptcPlusToolSectionLabel' }, t('tool.result')),
|
|
93
|
+
h('pre', { className: 'ptcPlusIoText', 'data-error': view.state === 'error' || undefined }, outputExcerpt.text)),
|
|
94
|
+
features),
|
|
95
|
+
h('div', { className: 'ptcPlusToolHoverFooter' },
|
|
96
|
+
h('span', null, t('tool.previewHint')),
|
|
97
|
+
h('span', { className: 'ptcPlusToolHoverBounded',
|
|
98
|
+
'data-truncated': sourceExcerpt.truncated || outputExcerpt.truncated || undefined,
|
|
99
|
+
}, t('tool.previewBounded'))))
|
|
100
|
+
const wrapRow = anchor => h(ToolHoverAnchor, { disabled: !hoverEnabled || open || !previewable, content: preview }, anchor)
|
|
65
101
|
if (hasDisclosureRow) {
|
|
66
|
-
return
|
|
102
|
+
return wrapRow(
|
|
67
103
|
h(DisclosureRow, {
|
|
68
104
|
icon: expandable
|
|
69
105
|
? h(icons.chevron, { size: 14 })
|
|
@@ -84,7 +120,7 @@ export function createPtcToolView(React, deps) {
|
|
|
84
120
|
children: body,
|
|
85
121
|
}))
|
|
86
122
|
}
|
|
87
|
-
return h(
|
|
123
|
+
return wrapRow(h(React.Fragment, null,
|
|
88
124
|
h('div', {
|
|
89
125
|
className: 'ptcPlusToolSummary', 'data-state': view.state,
|
|
90
126
|
'data-expandable': expandable || undefined,
|
|
@@ -108,7 +144,7 @@ export function createPtcToolView(React, deps) {
|
|
|
108
144
|
h('span', { className: 'ptcPlusToolSep', 'aria-hidden': true }),
|
|
109
145
|
h('span', { className: 'ptcPlusToolDescription' }, summary)),
|
|
110
146
|
features,
|
|
111
|
-
body)
|
|
147
|
+
body))
|
|
112
148
|
}
|
|
113
149
|
|
|
114
150
|
return { PTCPlusToolRow }
|
package/src/client-transport.js
CHANGED
|
@@ -7,9 +7,9 @@ import { normalizeReplMemorySnapshot } from '../internal/repl-memory-projection.
|
|
|
7
7
|
* - observeRepl watches one visible REPL region, retries the watch with bounded
|
|
8
8
|
* backoff and only reports an observation that still matches the requested
|
|
9
9
|
* inventory. Everything it starts is aborted by its disposer.
|
|
10
|
-
* -
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* - createCommandAccess publishes command availability per session, keyed by
|
|
11
|
+
* epoch so late directory replies cannot revive an ended subscription.
|
|
12
|
+
* Execution receipts must also match the connection and preset generations.
|
|
13
13
|
*/
|
|
14
14
|
export function createReplObserver({ rpc, ctx }) {
|
|
15
15
|
function observeRepl(sessionId, element, memory, onObservation) {
|
|
@@ -28,25 +28,33 @@ export function createReplObserver({ rpc, ctx }) {
|
|
|
28
28
|
previous?.abort()
|
|
29
29
|
}
|
|
30
30
|
const watch = async current => {
|
|
31
|
+
let receipt
|
|
31
32
|
try {
|
|
32
|
-
await rpc.call(RPC_CONTRACTS.repl, 'watch', { sessionId }, current.signal)
|
|
33
|
+
receipt = await rpc.call(RPC_CONTRACTS.repl, 'watch', { sessionId }, current.signal)
|
|
33
34
|
} catch {}
|
|
34
35
|
if (disposed || controller !== current) return
|
|
35
36
|
controller = undefined
|
|
36
37
|
current.abort()
|
|
37
38
|
if (!visible || document.visibilityState === 'hidden') { stop(); return }
|
|
39
|
+
if (receipt?.ok === true && receipt.value?.changed === true) {
|
|
40
|
+
retries = 0
|
|
41
|
+
sync()
|
|
42
|
+
return
|
|
43
|
+
}
|
|
38
44
|
const delay = retryDelays[retries++]
|
|
39
45
|
if (delay === undefined) return
|
|
40
46
|
retryTimer = setTimeout(() => { retryTimer = undefined; sync() }, delay)
|
|
41
47
|
}
|
|
42
48
|
const read = async current => {
|
|
43
|
-
if (!memory.available || memory.entries.length === 0
|
|
49
|
+
if (!memory.available || memory.entries.length === 0) return
|
|
44
50
|
try {
|
|
45
51
|
const result = await rpc.call(RPC_CONTRACTS.repl, 'observe', { sessionId, memory }, current.signal)
|
|
46
52
|
if (disposed || controller !== current || current.signal.aborted || result?.ok !== true || result.value === null) return
|
|
47
53
|
const observed = normalizeReplMemorySnapshot(result.value)
|
|
48
54
|
const { observation, ...inventory } = observed
|
|
49
|
-
|
|
55
|
+
const { observation: _previousObservation, ...expected } = memory
|
|
56
|
+
if (!observed.available || (observation !== undefined
|
|
57
|
+
&& JSON.stringify(inventory) === JSON.stringify(expected))) onObservation(observed)
|
|
50
58
|
} catch {}
|
|
51
59
|
}
|
|
52
60
|
const sync = () => {
|
|
@@ -78,14 +86,15 @@ export function createReplObserver({ rpc, ctx }) {
|
|
|
78
86
|
return { observeRepl }
|
|
79
87
|
}
|
|
80
88
|
|
|
81
|
-
export function
|
|
89
|
+
export function createCommandAccess(scope, name) {
|
|
82
90
|
const entries = new Map()
|
|
83
91
|
let commandRemote
|
|
92
|
+
let connectionGeneration = 0
|
|
84
93
|
const entryFor = (sessionId) => {
|
|
85
94
|
const id = String(sessionId)
|
|
86
95
|
let entry = entries.get(id)
|
|
87
96
|
if (entry === undefined) {
|
|
88
|
-
entry = { available: false, epoch: 0, listeners: new Set() }
|
|
97
|
+
entry = { available: false, epoch: 0, commandGeneration: 0, listeners: new Set() }
|
|
89
98
|
entry.source = {
|
|
90
99
|
getSnapshot: () => entry.available,
|
|
91
100
|
subscribe(listener) {
|
|
@@ -119,7 +128,7 @@ export function createBindingCommandAvailability(scope) {
|
|
|
119
128
|
const result = await commandRemote?.list(String(sessionId))
|
|
120
129
|
available = result?.ok === true
|
|
121
130
|
&& Array.isArray(result.value)
|
|
122
|
-
&& result.value.some(command => command?.name ===
|
|
131
|
+
&& result.value.some(command => command?.name === name)
|
|
123
132
|
} catch {}
|
|
124
133
|
if (entry.epoch === epoch) publish(entry, available)
|
|
125
134
|
}
|
|
@@ -128,10 +137,12 @@ export function createBindingCommandAvailability(scope) {
|
|
|
128
137
|
const entry = entries.get(String(sessionId))
|
|
129
138
|
if (entry === undefined || entry.listeners.size === 0) return
|
|
130
139
|
entry.epoch += 1
|
|
140
|
+
entry.commandGeneration += 1
|
|
131
141
|
publish(entry, false)
|
|
132
142
|
void refresh(sessionId)
|
|
133
143
|
}
|
|
134
144
|
scope.inject(['remote', 'remote.commands'], commandScope => {
|
|
145
|
+
connectionGeneration++
|
|
135
146
|
commandRemote = commandScope.get('remote.commands')
|
|
136
147
|
commandScope.effect(() => commandScope.remote.$on('commands/change', () => {
|
|
137
148
|
for (const sessionId of entries.keys()) void refresh(sessionId)
|
|
@@ -139,6 +150,7 @@ export function createBindingCommandAvailability(scope) {
|
|
|
139
150
|
commandScope.effect(() => commandScope.remote.$on('agent-preset/selected', reset))
|
|
140
151
|
for (const sessionId of entries.keys()) void refresh(sessionId)
|
|
141
152
|
commandScope.effect(() => () => {
|
|
153
|
+
connectionGeneration++
|
|
142
154
|
commandRemote = undefined
|
|
143
155
|
for (const entry of entries.values()) {
|
|
144
156
|
entry.epoch++
|
|
@@ -147,13 +159,36 @@ export function createBindingCommandAvailability(scope) {
|
|
|
147
159
|
})
|
|
148
160
|
})
|
|
149
161
|
scope.on('connection/reset', () => {
|
|
162
|
+
connectionGeneration++
|
|
150
163
|
for (const sessionId of entries.keys()) reset(sessionId)
|
|
151
164
|
})
|
|
152
165
|
scope.effect(() => () => {
|
|
166
|
+
connectionGeneration++
|
|
153
167
|
for (const entry of entries.values()) entry.epoch++
|
|
154
168
|
entries.clear()
|
|
155
169
|
})
|
|
156
170
|
return Object.freeze({
|
|
157
171
|
source: sessionId => entryFor(sessionId).source,
|
|
172
|
+
async run(sessionId, input, signal) {
|
|
173
|
+
signal?.throwIfAborted()
|
|
174
|
+
const remote = commandRemote
|
|
175
|
+
const generation = connectionGeneration
|
|
176
|
+
const entry = entries.get(String(sessionId))
|
|
177
|
+
const commandGeneration = entry?.commandGeneration
|
|
178
|
+
if (!entries.get(String(sessionId))?.available || typeof remote?.execute !== 'function') {
|
|
179
|
+
throw new Error(`/${name} is unavailable in this session`)
|
|
180
|
+
}
|
|
181
|
+
const result = await remote.execute(String(sessionId), `/${name}${input ? ` ${input}` : ''}`, [], signal)
|
|
182
|
+
signal?.throwIfAborted()
|
|
183
|
+
if (remote !== commandRemote || generation !== connectionGeneration
|
|
184
|
+
|| commandGeneration !== entry?.commandGeneration) throw new Error('Command connection changed')
|
|
185
|
+
if (result?.ok !== true || !result.value) throw new Error(result?.error?.message ?? 'Command was not accepted')
|
|
186
|
+
if (result.value.result?.kind !== 'success') throw new Error(result.value.result?.text ?? 'Command failed')
|
|
187
|
+
return result.value.result
|
|
188
|
+
},
|
|
158
189
|
})
|
|
159
190
|
}
|
|
191
|
+
|
|
192
|
+
export function createBindingCommandAvailability(scope) {
|
|
193
|
+
return createCommandAccess(scope, 'binding')
|
|
194
|
+
}
|
package/src/client-workbench.js
CHANGED
|
@@ -12,7 +12,7 @@ import { isHostComponent, isHostIconComponent } from './client-host-compat.js'
|
|
|
12
12
|
* discard an unsaved draft.
|
|
13
13
|
*/
|
|
14
14
|
export function createUserBindingsWorkbench(React, deps) {
|
|
15
|
-
const { TypeScriptEditor, BindingConsole, IconButton, ActionButton, CodeBlock, Modal, icons, catalogOwner } = deps
|
|
15
|
+
const { TypeScriptEditor, BindingConsole, IconButton, ActionButton, CodeBlock, Modal, Select, icons, catalogOwner } = deps
|
|
16
16
|
const h = React.createElement
|
|
17
17
|
|
|
18
18
|
const initialState = Object.freeze({
|
|
@@ -120,7 +120,7 @@ export function createUserBindingsWorkbench(React, deps) {
|
|
|
120
120
|
}
|
|
121
121
|
if (action.imported === true) next.importPath = ''
|
|
122
122
|
next.message = action.removed === true ? { key: 'bindings.removed' }
|
|
123
|
-
: action.imported === true ? { key: 'bindings.imported' } : state.message
|
|
123
|
+
: action.imported === true ? { key: 'bindings.imported' } : stored ? { key: stored.enabled ? 'bindings.toggleEnabled' : 'bindings.toggleDisabled', params: { name: stored.name } } : state.message
|
|
124
124
|
return next
|
|
125
125
|
}
|
|
126
126
|
case 'reloadResult': {
|
|
@@ -450,10 +450,9 @@ export function createUserBindingsWorkbench(React, deps) {
|
|
|
450
450
|
})),
|
|
451
451
|
h('label', { className: 'ptcPlusBindingField' },
|
|
452
452
|
h('span', { className: 'ptcPlusBindingFieldLabel' }, t('bindings.scope')),
|
|
453
|
-
h(
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
}, h('option', { value: 'namespace' }, 'namespace'), h('option', { value: 'top-level' }, 'top-level'))),
|
|
453
|
+
h(Select, { value: state.draft.scope, disabled: state.busy || !state.editing,
|
|
454
|
+
label: t('bindings.scope'), onChange: next => edit('scope', next),
|
|
455
|
+
options: [{ value: 'namespace', label: 'namespace' }, { value: 'top-level', label: 'top-level' }] })),
|
|
457
456
|
h('label', { className: 'ptcPlusBindingField' },
|
|
458
457
|
h('span', { className: 'ptcPlusBindingFieldLabel' }, t('bindings.symbols')),
|
|
459
458
|
h('input', {
|
|
@@ -502,7 +501,7 @@ export function createUserBindingsWorkbench(React, deps) {
|
|
|
502
501
|
const trapFocus = event => {
|
|
503
502
|
if (event.key !== 'Tab') return
|
|
504
503
|
const controls = [...content.current.querySelectorAll('button:not(:disabled),input:not(:disabled),textarea:not(:disabled),select:not(:disabled),[contenteditable=true],summary')]
|
|
505
|
-
.filter(element => element.getClientRects().length > 0)
|
|
504
|
+
.filter(element => element.getClientRects().length > 0 && !element.closest('[aria-hidden=true],[hidden]'))
|
|
506
505
|
const first = controls[0]
|
|
507
506
|
const last = controls.at(-1)
|
|
508
507
|
if ((event.shiftKey && document.activeElement === first) || (!event.shiftKey && document.activeElement === last)) {
|
package/src/client.js
CHANGED
|
@@ -3,8 +3,11 @@ import { RPC_CONTRACTS } from '../internal/rpc-contract.js'
|
|
|
3
3
|
import { CONFIG_DEFAULTS, SETTINGS_NAMESPACE } from '../internal/config-spec.js'
|
|
4
4
|
import { createPtcToolView } from './client-tool-view.js'
|
|
5
5
|
import { createPtcSettingsView } from './client-settings-view.js'
|
|
6
|
+
import { createCapabilityCommandCard, createCapabilityRefreshView, createSessionCapabilityControlsView } from './client-capabilities-view.js'
|
|
7
|
+
import { capabilityProfileConfiguration } from '../internal/runtime-config.js'
|
|
6
8
|
import { createReplView } from './client-repl-view.js'
|
|
7
9
|
import { createAuthoringView } from './client-authoring-view.js'
|
|
10
|
+
import { bindingToggleConversationDefinition } from './client-binding-toggle.js'
|
|
8
11
|
import { createIndicatorView } from './client-indicator-view.js'
|
|
9
12
|
import { createTypeScriptEditor } from './client-code-editor.js'
|
|
10
13
|
import { createBindingConsole } from './client-console.js'
|
|
@@ -29,7 +32,7 @@ import { installStyles } from './client-styles.js'
|
|
|
29
32
|
import { featureEnabled, registerGated } from './client-feature-gates.js'
|
|
30
33
|
import { createCatalogOwner } from './client-catalog.js'
|
|
31
34
|
import { createUserBindingsWorkbench } from './client-workbench.js'
|
|
32
|
-
import { createBindingCommandAvailability, createReplObserver } from './client-transport.js'
|
|
35
|
+
import { createBindingCommandAvailability, createCommandAccess, createReplObserver } from './client-transport.js'
|
|
33
36
|
import { createActionButton, resolvePrimitives } from './client-primitives.js'
|
|
34
37
|
|
|
35
38
|
/** Settings snapshot used until the installed generation serves a transport. */
|
|
@@ -87,10 +90,7 @@ function createPreferenceScope(ctx) {
|
|
|
87
90
|
})
|
|
88
91
|
}
|
|
89
92
|
|
|
90
|
-
|
|
91
|
-
// Replaced by the bundle entry with the package name from package.json.
|
|
92
|
-
id: __PTC_PLUS_CLIENT_MODULE_ID__,
|
|
93
|
-
factory: (require) => {
|
|
93
|
+
export function createClientPlugin(require) {
|
|
94
94
|
const React = require('react')
|
|
95
95
|
const { createPortal } = require('react-dom')
|
|
96
96
|
let primitives
|
|
@@ -106,6 +106,7 @@ window.__ModuleLoader__.load({
|
|
|
106
106
|
Menu,
|
|
107
107
|
MenuFallback,
|
|
108
108
|
Modal,
|
|
109
|
+
Select,
|
|
109
110
|
Toast,
|
|
110
111
|
Tooltip,
|
|
111
112
|
} = resolvePrimitives(primitives, React, createPortal)
|
|
@@ -123,7 +124,6 @@ window.__ModuleLoader__.load({
|
|
|
123
124
|
play: IconPlayOutline16,
|
|
124
125
|
stop: IconStopFill16,
|
|
125
126
|
} = hostIconComponents(primitives)
|
|
126
|
-
const module = { exports: {} }
|
|
127
127
|
const h = React.createElement
|
|
128
128
|
const TypeScriptEditor = createTypeScriptEditor(React)
|
|
129
129
|
|
|
@@ -138,7 +138,7 @@ window.__ModuleLoader__.load({
|
|
|
138
138
|
const BindingConsole = createBindingConsole(React, { TypeScriptEditor, IconButton, ActionButton,
|
|
139
139
|
icons: { play: IconPlayOutline16, stop: IconStopFill16, reset: IconRefreshOutline16, clear: IconTrashOutline16 } })
|
|
140
140
|
const { PTCPlusToolRow } = createPtcToolView(React, {
|
|
141
|
-
CodeBlock, DisclosureRow,
|
|
141
|
+
CodeBlock, DisclosureRow, createPortal,
|
|
142
142
|
icons: { chevron: IconChevronDownOutline14, check: IconCheckOutline14, inspect: IconInspectOutline12 },
|
|
143
143
|
})
|
|
144
144
|
|
|
@@ -167,7 +167,7 @@ window.__ModuleLoader__.load({
|
|
|
167
167
|
const catalogOwner = createCatalogOwner({ callUserBindings })
|
|
168
168
|
ctx.effect(() => () => catalogOwner.dispose(), 'ptc-plus: binding catalog sources')
|
|
169
169
|
const { useWorkbenchController, UserBindingsWorkbench, BindingsDialog } = createUserBindingsWorkbench(React, {
|
|
170
|
-
TypeScriptEditor, BindingConsole, IconButton, ActionButton, CodeBlock, Modal,
|
|
170
|
+
TypeScriptEditor, BindingConsole, IconButton, ActionButton, CodeBlock, Modal, Select,
|
|
171
171
|
catalogOwner,
|
|
172
172
|
icons: {
|
|
173
173
|
refresh: IconRefreshOutline16, plus: IconPlusOutline16, check: IconCheckOutline14,
|
|
@@ -176,12 +176,43 @@ window.__ModuleLoader__.load({
|
|
|
176
176
|
},
|
|
177
177
|
})
|
|
178
178
|
const { observeRepl } = createReplObserver({ rpc, ctx })
|
|
179
|
+
const capabilityCommands = createCommandAccess(ctx, 'capabilities')
|
|
180
|
+
const CapabilityCommandCard = createCapabilityCommandCard(React)
|
|
181
|
+
const CapabilityRefreshButton = createCapabilityRefreshView(React, capabilityCommands)
|
|
182
|
+
const SessionCapabilityControls = createSessionCapabilityControlsView(React, {
|
|
183
|
+
commands: capabilityCommands, Modal, Select,
|
|
184
|
+
async inspectSessionCatalog(sessionId, signal) {
|
|
185
|
+
const result = await rpc.call(RPC_CONTRACTS.capabilities, 'session-catalog', { sessionId }, signal)
|
|
186
|
+
if (result?.ok !== true) throw new Error(result?.error?.message ?? 'Session capability catalog unavailable')
|
|
187
|
+
return result.value
|
|
188
|
+
},
|
|
189
|
+
})
|
|
179
190
|
const { PTCPlusSettingsCard, PTCPlusSettingsDialog } = createPtcSettingsView(React, {
|
|
180
|
-
ActionButton, BindingsDialog, Modal, useWorkbenchController,
|
|
181
|
-
|
|
191
|
+
ActionButton, BindingsDialog, Modal, Select, useWorkbenchController, CapabilityRefreshButton,
|
|
192
|
+
async inspectCapabilityTargets(signal) {
|
|
193
|
+
const result = await rpc.call(RPC_CONTRACTS.capabilities, 'targets', {}, signal)
|
|
194
|
+
if (result?.ok !== true) throw new Error(result?.error?.message ?? 'Capability refresh targets unavailable')
|
|
195
|
+
return result.value
|
|
196
|
+
},
|
|
197
|
+
async inspectCapabilityPlans(signal) {
|
|
198
|
+
const result = await rpc.call(RPC_CONTRACTS.capabilities, 'inspect', {}, signal)
|
|
199
|
+
if (result?.ok !== true) throw new Error(result?.error?.message ?? 'Capability plans unavailable')
|
|
200
|
+
return result.value
|
|
201
|
+
},
|
|
202
|
+
async inspectCapabilityCatalog(signal) {
|
|
203
|
+
const result = await rpc.call(RPC_CONTRACTS.capabilities, 'catalog', {}, signal)
|
|
204
|
+
if (result?.ok !== true) throw new Error(result?.error?.message ?? 'Capability catalog unavailable')
|
|
205
|
+
return result.value
|
|
206
|
+
},
|
|
207
|
+
async inspectCapabilityAllocation(payload, signal) {
|
|
208
|
+
const result = await rpc.call(RPC_CONTRACTS.capabilities, 'allocation', payload, signal)
|
|
209
|
+
if (result?.ok !== true) throw new Error(result?.error?.message ?? 'Capability allocation unavailable')
|
|
210
|
+
return result.value
|
|
211
|
+
},
|
|
212
|
+
icons: { search: IconSearchOutline16, chevron: IconChevronDownOutline14 },
|
|
182
213
|
})
|
|
183
|
-
const { ReplComposer, ReplConsole, ReplMemoryCard, replPopoverIsOpen, placeReplPopover } = createReplView(React, {
|
|
184
|
-
CodeBlock, featureEnabled, normalizeReplMemorySnapshot, unavailableReplMemorySnapshot,
|
|
214
|
+
const { ReplComposer, ReplConsole, ReplMemoryCard, replPopoverIsOpen, placeReplPopover, useObservedReplMemory } = createReplView(React, {
|
|
215
|
+
CodeBlock, Select, featureEnabled, normalizeReplMemorySnapshot, unavailableReplMemorySnapshot,
|
|
185
216
|
useSessionPreset, sessionUsesPtcPreset, useWorkbenchController, UserBindingsWorkbench,
|
|
186
217
|
icons: { search: IconSearchOutline16, chevron: IconChevronDownOutline14 },
|
|
187
218
|
})
|
|
@@ -204,6 +235,15 @@ window.__ModuleLoader__.load({
|
|
|
204
235
|
&& (key !== 'bindingUpdates' || after.value.legacyBindingSettings !== true)
|
|
205
236
|
? 'status.applied' : 'status.conflict'
|
|
206
237
|
}
|
|
238
|
+
const updateCapabilityConfiguration = async configuration => {
|
|
239
|
+
const before = preferenceScope.getSnapshot()
|
|
240
|
+
if (before.status !== 'ready' || before.writable !== true || !featureEnabled(before, 'plugin')) throw new Error('Capability settings are not writable')
|
|
241
|
+
const validated = capabilityProfileConfiguration(configuration)
|
|
242
|
+
await preferenceScope.mutate(Object.entries(validated).map(([key, value]) => ({ op: 'set', path: [key], value })))
|
|
243
|
+
const after = preferenceScope.getSnapshot()
|
|
244
|
+
if (after.status !== 'ready' || Object.entries(validated).some(([key, value]) => after.value?.[key] !== value)) throw new Error('Capability configuration was not applied')
|
|
245
|
+
return 'status.applied'
|
|
246
|
+
}
|
|
207
247
|
// One gated-registration shape for every settings-driven contribution.
|
|
208
248
|
const settingsGate = (feature, register) => ({
|
|
209
249
|
subscribe: listener => preferenceScope.subscribe(listener),
|
|
@@ -217,20 +257,25 @@ window.__ModuleLoader__.load({
|
|
|
217
257
|
const settingsCard = publishSettingsCard(ctx, {
|
|
218
258
|
bundleName: __PTC_PLUS_CLIENT_MODULE_ID__,
|
|
219
259
|
locale: LOCALE_NS,
|
|
220
|
-
injectProps: () => ({ ...settingsProps(), updateSetting }),
|
|
260
|
+
injectProps: () => ({ ...settingsProps(), updateSetting, updateCapabilityConfiguration }),
|
|
221
261
|
component: PTCPlusSettingsCard,
|
|
222
262
|
})
|
|
223
263
|
|
|
264
|
+
function ConfiguredToolRow(props) {
|
|
265
|
+
const snapshot = React.useSyncExternalStore(preferenceScope.subscribe, preferenceScope.getSnapshot)
|
|
266
|
+
return h(PTCPlusToolRow, { ...props, hoverEnabled: featureEnabled(snapshot, 'toolHover') })
|
|
267
|
+
}
|
|
268
|
+
|
|
224
269
|
ctx.slots.inject('tool.call.toolview', () => registerGated(ctx, settingsGate('toolView', () => {
|
|
225
270
|
// Two rows share one eligibility decision; roll both back if either fails.
|
|
226
271
|
const disposers = []
|
|
227
272
|
try {
|
|
228
273
|
disposers.push(ctx.slots.register({
|
|
229
274
|
name: 'tool.call.toolview', key: 'run_code', locale: LOCALE_NS,
|
|
230
|
-
},
|
|
275
|
+
}, ConfiguredToolRow))
|
|
231
276
|
disposers.push(ctx.slots.register({
|
|
232
277
|
name: 'tool.call.toolview', key: 'edit_run_code', locale: LOCALE_NS,
|
|
233
|
-
},
|
|
278
|
+
}, ConfiguredToolRow))
|
|
234
279
|
} catch (error) {
|
|
235
280
|
disposers.reverse().forEach(dispose => dispose())
|
|
236
281
|
throw error
|
|
@@ -283,7 +328,7 @@ window.__ModuleLoader__.load({
|
|
|
283
328
|
const { BindingAuthorButton, BindingReviewDock, BindingCommandCard } = createAuthoringView(React, {
|
|
284
329
|
ActionButton, IconButton, Menu, MenuFallback, Toast, Tooltip, CodeBlock, Modal, createPortal, BindingsDialog, PTCPlusSettingsDialog,
|
|
285
330
|
useWorkbenchController, useBindingReview, catalogOwner, callUserBindings, subscribeReset,
|
|
286
|
-
settingsCardSeat: settingsCard.seat, updateSetting, menuChildren,
|
|
331
|
+
settingsCardSeat: settingsCard.seat, updateSetting, updateCapabilityConfiguration, menuChildren, SessionCapabilityControls,
|
|
287
332
|
icons: {
|
|
288
333
|
sparkle: IconSparkle16, chevron: IconChevronDownOutline14,
|
|
289
334
|
close: IconCloseOutline16, check: IconCheckOutline14,
|
|
@@ -294,6 +339,7 @@ window.__ModuleLoader__.load({
|
|
|
294
339
|
normalizeReplMemorySnapshot, unavailableReplMemorySnapshot,
|
|
295
340
|
ReplMemoryCard, replPopoverIsOpen, placeReplPopover,
|
|
296
341
|
catalogOwner, subscribeReset,
|
|
342
|
+
useObservedReplMemory, observeRepl,
|
|
297
343
|
})
|
|
298
344
|
|
|
299
345
|
ctx.slots.inject('conversation.session.header.actions', () => registerGated(ctx, settingsGate('plugin', () => ctx.slots.register({
|
|
@@ -304,6 +350,15 @@ window.__ModuleLoader__.load({
|
|
|
304
350
|
name: 'conversation.chat.commandview', key: 'binding', locale: LOCALE_NS,
|
|
305
351
|
inject: settingsProps,
|
|
306
352
|
}, props => h(BindingCommandCard, { ...props, key: props.node.commandId })))))
|
|
353
|
+
ctx.slots.inject('conversation.chat.commandview', () => registerGated(ctx, settingsGate('plugin', () => ctx.slots.register({
|
|
354
|
+
name: 'conversation.chat.commandview', key: 'capabilities', locale: LOCALE_NS,
|
|
355
|
+
}, props => h(CapabilityCommandCard, props)))))
|
|
356
|
+
ctx.inject(['uiConversation'], scope => scope.effect(() => scope.uiConversation.events.register(bindingToggleConversationDefinition),
|
|
357
|
+
'ptc-plus binding configuration receipts'))
|
|
358
|
+
ctx.slots.inject('conversation.chat.node', () => ctx.effect(() => ctx.slots.register({
|
|
359
|
+
name: 'conversation.chat.node', key: 'ptc-plus-binding-toggle', locale: LOCALE_NS,
|
|
360
|
+
}, ({ node, t }) => h('p', { className: 'ptcPlusCapabilityReceipt', role: 'status' },
|
|
361
|
+
t(node.data.enabled ? 'bindings.toggleEnabled' : 'bindings.toggleDisabled', { name: node.data.name }))), 'ptc-plus binding configuration receipt renderer'))
|
|
307
362
|
ctx.slots.inject('conversation.input.dock', () => ctx.slots.inject('conversation.input.left', () =>
|
|
308
363
|
registerGated(ctx, settingsGate('bindings', () => ctx.slots.register({
|
|
309
364
|
name: 'conversation.input.dock', id: 'ptc-plus-binding-review', order: -30, locale: LOCALE_NS,
|
|
@@ -326,21 +381,20 @@ window.__ModuleLoader__.load({
|
|
|
326
381
|
return () => { unsubscribePreset(); unsubscribeSettings() }
|
|
327
382
|
},
|
|
328
383
|
isEnabled: () => sessionUsesPtcPreset(preset)
|
|
329
|
-
&& featureEnabled(preferenceScope.getSnapshot(), '
|
|
384
|
+
&& featureEnabled(preferenceScope.getSnapshot(), 'plugin'),
|
|
330
385
|
register: () => ctx.slots.register({
|
|
331
386
|
name: 'conversation.input.left', id: 'ptc-plus-binding-author', order: 20, locale: LOCALE_NS,
|
|
332
387
|
inject: sessionId => ({
|
|
333
|
-
hooks: { ptcSettings: preferenceScope, bindingCommand: availability.source(sessionId)
|
|
388
|
+
hooks: { ptcSettings: preferenceScope, bindingCommand: availability.source(sessionId),
|
|
389
|
+
capabilityCommand: capabilityCommands.source(sessionId) },
|
|
334
390
|
}),
|
|
335
391
|
}, props => props.sessionId === undefined ? null : h(BindingAuthorButton, { ...props, key: props.sessionId })),
|
|
336
392
|
}))
|
|
337
393
|
})
|
|
338
394
|
}
|
|
339
395
|
|
|
340
|
-
|
|
396
|
+
return {
|
|
341
397
|
apply,
|
|
342
398
|
inject: ['slots', 'locale', 'connection', 'remote'],
|
|
343
399
|
}
|
|
344
|
-
|
|
345
|
-
},
|
|
346
|
-
})
|
|
400
|
+
}
|