dsh-ptc-plus 0.2.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/LICENSE +21 -0
- package/README.md +137 -0
- package/README.zh.md +137 -0
- package/assets/dsh-ptc-plus-banner.webp +0 -0
- package/assets/ptc-plus-repair.png +0 -0
- package/assets/ptc-plus-settings.png +0 -0
- package/client.js +366 -0
- package/cordis.patch.yml +5 -0
- package/docs/README.md +21 -0
- package/docs/adr/0001-delegate-governance-to-dsh.md +3 -0
- package/docs/adr/0002-limit-work-map-scope.md +3 -0
- package/docs/adr/0003-prefer-native-program-surfaces.md +3 -0
- package/docs/adr/0004-declare-program-bindings-at-the-host.md +5 -0
- package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +129 -0
- package/docs/adr/0006-rewrite-module-syntax-with-an-ast.md +45 -0
- package/docs/adr/0007-separate-worker-transport-from-session-semantics.md +39 -0
- package/docs/adr/0008-define-agent-operating-contract.md +25 -0
- package/docs/adr/0009-bound-expensive-acceptance-default.md +39 -0
- package/docs/adr/0010-session-log-derived-recovery-tips.md +26 -0
- package/docs/adr/0011-isolate-the-local-recovery-tip-provider.md +20 -0
- package/docs/adr/0012-preserve-mixed-destructuring-initialization.md +23 -0
- package/docs/adr/0013-isolate-repl-redeclaration-convenience.md +21 -0
- package/docs/adr/0014-persist-cell-rewrite-policy.md +25 -0
- package/docs/adr/0015-preserve-source-positions-through-rewrites.md +25 -0
- package/docs/adr/0016-archive-resolved-review-findings.md +43 -0
- package/docs/adr/0017-track-the-latest-dsh-public-surface.md +23 -0
- package/docs/adr/0018-use-versioned-fixture-for-ab-benchmark.md +25 -0
- package/docs/adr/0019-plugin-settings-and-kill-switch.md +32 -0
- package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +36 -0
- package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +68 -0
- package/docs/adr/README.md +18 -0
- package/docs/architecture.md +134 -0
- package/docs/capability-projection.md +48 -0
- package/docs/client-ui.md +47 -0
- package/docs/durability-design.md +265 -0
- package/docs/evaluation.md +62 -0
- package/docs/installation.md +82 -0
- package/docs/program-data-plane.md +61 -0
- package/docs/publishing.md +37 -0
- package/docs/runtime-reference.md +119 -0
- package/docs/value-wire.md +61 -0
- package/index.js +532 -0
- package/internal/ast-traversal.js +20 -0
- package/internal/binding-descriptors.js +95 -0
- package/internal/binding-pattern.js +54 -0
- package/internal/builtin-esm-sync.js +9 -0
- package/internal/cell-analysis.js +467 -0
- package/internal/cell-rewriter.js +520 -0
- package/internal/config-spec.js +179 -0
- package/internal/cordis-tools-owner.js +363 -0
- package/internal/diagnostic.js +133 -0
- package/internal/direct-surface-owner.js +326 -0
- package/internal/edit-transport-owner.js +287 -0
- package/internal/failure-reporting.js +139 -0
- package/internal/kernel-worker.js +484 -0
- package/internal/module-policy.js +46 -0
- package/internal/program-bindings.js +187 -0
- package/internal/record-utils.js +53 -0
- package/internal/recovery-tips.js +81 -0
- package/internal/rejected-cell-editor.js +328 -0
- package/internal/repl-convenience.js +201 -0
- package/internal/runtime-bridge-owner.js +417 -0
- package/internal/runtime-config.js +77 -0
- package/internal/runtime-contexts.js +31 -0
- package/internal/sdk-projection.js +88 -0
- package/internal/session-cell-executor.js +612 -0
- package/internal/session-journal.js +889 -0
- package/internal/session-log-view.js +80 -0
- package/internal/session-runtime.js +536 -0
- package/internal/session-state.js +45 -0
- package/internal/source-position-map.js +224 -0
- package/internal/tool-call-canonicalizer.js +339 -0
- package/internal/value-wire.js +421 -0
- package/internal/worker-client.js +218 -0
- package/internal/worker-cwd-virtualization.js +155 -0
- package/package.json +100 -0
- package/src/client.js +165 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { isAbsolute, parse, resolve, sep } from 'node:path'
|
|
2
|
+
import { promisify } from 'node:util'
|
|
3
|
+
|
|
4
|
+
const FILE_SYSTEM_PATH_ARGUMENTS = Object.freeze({
|
|
5
|
+
access: [0], accessSync: [0], appendFile: [0], appendFileSync: [0],
|
|
6
|
+
chmod: [0], chmodSync: [0], chown: [0], chownSync: [0],
|
|
7
|
+
copyFile: [0, 1], copyFileSync: [0, 1], cp: [0, 1], cpSync: [0, 1],
|
|
8
|
+
createReadStream: [0], createWriteStream: [0], exists: [0], existsSync: [0],
|
|
9
|
+
lchmod: [0], lchmodSync: [0], lchown: [0], lchownSync: [0],
|
|
10
|
+
link: [0, 1], linkSync: [0, 1], lstat: [0], lstatSync: [0],
|
|
11
|
+
lutimes: [0], lutimesSync: [0], mkdir: [0], mkdirSync: [0],
|
|
12
|
+
open: [0], openAsBlob: [0], openSync: [0], opendir: [0], opendirSync: [0],
|
|
13
|
+
readFile: [0], readFileSync: [0], readdir: [0], readdirSync: [0],
|
|
14
|
+
readlink: [0], readlinkSync: [0], realpath: [0], realpathSync: [0],
|
|
15
|
+
rename: [0, 1], renameSync: [0, 1], rm: [0], rmSync: [0],
|
|
16
|
+
rmdir: [0], rmdirSync: [0], stat: [0], statSync: [0], statfs: [0], statfsSync: [0],
|
|
17
|
+
symlink: [1], symlinkSync: [1], truncate: [0], truncateSync: [0],
|
|
18
|
+
unlink: [0], unlinkSync: [0], utimes: [0], utimesSync: [0],
|
|
19
|
+
watch: [0], watchFile: [0], writeFile: [0], writeFileSync: [0],
|
|
20
|
+
})
|
|
21
|
+
const FILE_SYSTEM_PREFIX_ARGUMENTS = Object.freeze([
|
|
22
|
+
'mkdtemp', 'mkdtempSync', 'mkdtempDisposable', 'mkdtempDisposableSync',
|
|
23
|
+
])
|
|
24
|
+
const FILE_SYSTEM_PROJECTED_PROPERTIES = Object.freeze({
|
|
25
|
+
exists: [promisify.custom],
|
|
26
|
+
realpath: ['native'],
|
|
27
|
+
realpathSync: ['native'],
|
|
28
|
+
})
|
|
29
|
+
const CHILD_PROCESS_PROJECTED_PROPERTIES = Object.freeze({
|
|
30
|
+
exec: [promisify.custom],
|
|
31
|
+
execFile: [promisify.custom],
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
function wrapBuiltinCallable(original, projectArguments, projectedProperties = []) {
|
|
35
|
+
const wrapped = {
|
|
36
|
+
invoke(...args) {
|
|
37
|
+
return Reflect.apply(original, this, projectArguments(args))
|
|
38
|
+
},
|
|
39
|
+
}.invoke
|
|
40
|
+
const descriptors = Object.getOwnPropertyDescriptors(original)
|
|
41
|
+
const projected = new Set(projectedProperties)
|
|
42
|
+
for (const property of Reflect.ownKeys(descriptors)) {
|
|
43
|
+
const descriptor = descriptors[property]
|
|
44
|
+
if (descriptor.value === original) {
|
|
45
|
+
descriptors[property] = { ...descriptor, value: wrapped }
|
|
46
|
+
} else if (projected.has(property)) {
|
|
47
|
+
descriptors[property] = {
|
|
48
|
+
...descriptor,
|
|
49
|
+
value: wrapBuiltinCallable(descriptor.value, projectArguments),
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return Object.defineProperties(wrapped, descriptors)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function wrapBuiltin(owner, name, projectArguments, projectedProperties) {
|
|
57
|
+
const original = owner?.[name]
|
|
58
|
+
if (typeof original !== 'function') return
|
|
59
|
+
owner[name] = wrapBuiltinCallable(original, projectArguments, projectedProperties)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Install one session's cwd projection into Node builtins loaded by the worker. */
|
|
63
|
+
export function installWorkerCwdVirtualization(sessionCwd, originalRequire) {
|
|
64
|
+
if (sessionCwd === undefined) return
|
|
65
|
+
const nativeResolve = resolve
|
|
66
|
+
const bufferPrefix = Buffer.from(sessionCwd + (/[\\/]$/.test(sessionCwd) ? '' : sep))
|
|
67
|
+
const sessionPath = (value) => {
|
|
68
|
+
if (typeof value === 'string') return isAbsolute(value) ? value : nativeResolve(sessionCwd, value)
|
|
69
|
+
if (Buffer.isBuffer(value)) {
|
|
70
|
+
const absolute = isAbsolute(String.fromCharCode(...value.subarray(0, 3)))
|
|
71
|
+
return absolute ? value : Buffer.concat([bufferPrefix, value])
|
|
72
|
+
}
|
|
73
|
+
return value
|
|
74
|
+
}
|
|
75
|
+
const sessionPathPrefix = (value) => {
|
|
76
|
+
if (typeof value !== 'string' || isAbsolute(value) || parse(value).root !== '') return sessionPath(value)
|
|
77
|
+
return sessionCwd + (/[\\/]$/.test(sessionCwd) ? '' : sep) + value
|
|
78
|
+
}
|
|
79
|
+
const projectPaths = (args, indices) => {
|
|
80
|
+
const next = [...args]
|
|
81
|
+
for (const index of indices) next[index] = sessionPath(next[index])
|
|
82
|
+
return next
|
|
83
|
+
}
|
|
84
|
+
const projectPrefix = (args) => {
|
|
85
|
+
const next = [...args]
|
|
86
|
+
next[0] = sessionPathPrefix(next[0])
|
|
87
|
+
return next
|
|
88
|
+
}
|
|
89
|
+
const sessionChildOptions = options => (
|
|
90
|
+
options === null || typeof options !== 'object' || options.cwd !== undefined
|
|
91
|
+
? options
|
|
92
|
+
: { ...options, cwd: sessionCwd }
|
|
93
|
+
)
|
|
94
|
+
const withDefaultOptions = (args, index) => {
|
|
95
|
+
const next = [...args]
|
|
96
|
+
next[index] = sessionChildOptions(next[index]) ?? { cwd: sessionCwd }
|
|
97
|
+
return next
|
|
98
|
+
}
|
|
99
|
+
const childProcessOptions = (name, args) => {
|
|
100
|
+
if (name === 'exec' || name === 'execSync') {
|
|
101
|
+
const next = [...args]
|
|
102
|
+
if (typeof next[1] === 'function') next.splice(1, 0, { cwd: sessionCwd })
|
|
103
|
+
else next[1] = sessionChildOptions(next[1]) ?? { cwd: sessionCwd }
|
|
104
|
+
return next
|
|
105
|
+
}
|
|
106
|
+
if (name === 'execFile' || name === 'execFileSync') {
|
|
107
|
+
if (args.length === 1 || typeof args[1] === 'function') {
|
|
108
|
+
const next = [...args]
|
|
109
|
+
next.splice(1, 0, { cwd: sessionCwd })
|
|
110
|
+
return next
|
|
111
|
+
}
|
|
112
|
+
if (typeof args[2] === 'function') {
|
|
113
|
+
if (args[1] !== undefined && !Array.isArray(args[1])) return withDefaultOptions(args, 1)
|
|
114
|
+
const next = [...args]
|
|
115
|
+
next.splice(2, 0, { cwd: sessionCwd })
|
|
116
|
+
return next
|
|
117
|
+
}
|
|
118
|
+
if (args[1] !== undefined && !Array.isArray(args[1])) return withDefaultOptions(args, 1)
|
|
119
|
+
return withDefaultOptions(args, 2)
|
|
120
|
+
}
|
|
121
|
+
return args.length === 1 || (args[1] !== undefined && !Array.isArray(args[1]))
|
|
122
|
+
? withDefaultOptions(args, 1)
|
|
123
|
+
: withDefaultOptions(args, 2)
|
|
124
|
+
}
|
|
125
|
+
const sessionGlobOptions = (args) => {
|
|
126
|
+
const next = [...args]
|
|
127
|
+
if (typeof next[1] === 'function') next.splice(1, 0, { cwd: sessionCwd })
|
|
128
|
+
else if (next[1] === undefined) next[1] = { cwd: sessionCwd }
|
|
129
|
+
else if (next[1] !== null && typeof next[1] === 'object' && next[1].cwd === undefined) {
|
|
130
|
+
next[1] = { ...next[1], cwd: sessionCwd }
|
|
131
|
+
}
|
|
132
|
+
return next
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
process.cwd = () => sessionCwd
|
|
136
|
+
const fs = originalRequire('node:fs')
|
|
137
|
+
const path = originalRequire('node:path')
|
|
138
|
+
for (const [name, indices] of Object.entries(FILE_SYSTEM_PATH_ARGUMENTS)) {
|
|
139
|
+
wrapBuiltin(fs, name, args => projectPaths(args, indices), FILE_SYSTEM_PROJECTED_PROPERTIES[name])
|
|
140
|
+
wrapBuiltin(fs.promises, name, args => projectPaths(args, indices))
|
|
141
|
+
}
|
|
142
|
+
for (const name of FILE_SYSTEM_PREFIX_ARGUMENTS) {
|
|
143
|
+
wrapBuiltin(fs, name, projectPrefix)
|
|
144
|
+
wrapBuiltin(fs.promises, name, projectPrefix)
|
|
145
|
+
}
|
|
146
|
+
for (const name of ['glob', 'globSync']) {
|
|
147
|
+
wrapBuiltin(fs, name, sessionGlobOptions)
|
|
148
|
+
wrapBuiltin(fs.promises, name, sessionGlobOptions)
|
|
149
|
+
}
|
|
150
|
+
wrapBuiltin(path, 'resolve', args => [sessionCwd, ...args])
|
|
151
|
+
const childProcess = originalRequire('node:child_process')
|
|
152
|
+
for (const name of ['exec', 'execFile', 'execFileSync', 'execSync', 'fork', 'spawn', 'spawnSync']) {
|
|
153
|
+
wrapBuiltin(childProcess, name, args => childProcessOptions(name, args), CHILD_PROCESS_PROJECTED_PROPERTIES[name])
|
|
154
|
+
}
|
|
155
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-ptc-plus",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "A session-bound agent-native REPL for DeepSeek Harness PTC mode.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.js",
|
|
9
|
+
"./client": "./client.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"index.js",
|
|
14
|
+
"client.js",
|
|
15
|
+
"src",
|
|
16
|
+
"internal",
|
|
17
|
+
"docs",
|
|
18
|
+
"assets/dsh-ptc-plus-banner.webp",
|
|
19
|
+
"assets/ptc-plus-repair.png",
|
|
20
|
+
"assets/ptc-plus-settings.png",
|
|
21
|
+
"cordis.patch.yml",
|
|
22
|
+
"README.md",
|
|
23
|
+
"README.zh.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": "^22.19.0 || >=24.0.0"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@babel/code-frame": "^7.29.7",
|
|
31
|
+
"@babel/parser": "^7.29.0",
|
|
32
|
+
"@babel/traverse": "^7.29.0",
|
|
33
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
34
|
+
"acorn": "^8.15.0"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@deepseek-ai/dsh-settings": "next",
|
|
38
|
+
"@deepseek-ai/dsh-skill-filesystem": "next",
|
|
39
|
+
"@deepseek-ai/dsh-tool-cordis": "next",
|
|
40
|
+
"@deepseek-ai/dsh-tools": "next"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "node scripts/client-bundle.mjs build",
|
|
44
|
+
"build:check": "node scripts/client-bundle.mjs check",
|
|
45
|
+
"prepack": "npm run build:check",
|
|
46
|
+
"hooks:install": "node scripts/review-findings.mjs install-hook",
|
|
47
|
+
"review:new": "node scripts/review-findings.mjs create",
|
|
48
|
+
"review:validate": "node scripts/review-findings.mjs validate",
|
|
49
|
+
"review:gate": "node scripts/review-findings.mjs gate",
|
|
50
|
+
"session:migrate": "node scripts/migrate-session-log.mjs",
|
|
51
|
+
"coverage": "node --test --experimental-test-module-mocks --experimental-test-coverage --test-concurrency=1 --test-coverage-include=\"index.js\" --test-coverage-include=\"internal/*.js\" --test-coverage-lines=100 --test-coverage-branches=95 --test-coverage-functions=100 \"test/*.test.js\"",
|
|
52
|
+
"verify": "npm run review:validate && 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 && npm run coverage",
|
|
53
|
+
"check": "node scripts/review-findings.mjs check",
|
|
54
|
+
"test:expensive": "node scripts/expensive-headless-acceptance.mjs",
|
|
55
|
+
"test:ab": "node scripts/ab-headless-trajectories.mjs"
|
|
56
|
+
},
|
|
57
|
+
"keywords": [
|
|
58
|
+
"deepseek-harness",
|
|
59
|
+
"dsh",
|
|
60
|
+
"dsh-plugin",
|
|
61
|
+
"ptc",
|
|
62
|
+
"ptc-mode",
|
|
63
|
+
"repl"
|
|
64
|
+
],
|
|
65
|
+
"license": "MIT",
|
|
66
|
+
"repository": {
|
|
67
|
+
"type": "git",
|
|
68
|
+
"url": "git+https://github.com/muyuanjin/dsh-ptc-plus.git"
|
|
69
|
+
},
|
|
70
|
+
"homepage": "https://github.com/muyuanjin/dsh-ptc-plus#readme",
|
|
71
|
+
"bugs": {
|
|
72
|
+
"url": "https://github.com/muyuanjin/dsh-ptc-plus/issues"
|
|
73
|
+
},
|
|
74
|
+
"dsh": {
|
|
75
|
+
"bundle": {
|
|
76
|
+
"patch": "./cordis.patch.yml"
|
|
77
|
+
},
|
|
78
|
+
"client": {
|
|
79
|
+
"platform": "web",
|
|
80
|
+
"external": [
|
|
81
|
+
"react"
|
|
82
|
+
],
|
|
83
|
+
"inject": [
|
|
84
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
85
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
86
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins",
|
|
87
|
+
"@deepseek-ai/dsh-client-connection",
|
|
88
|
+
"@deepseek-ai/dsh-client-ui-primitives"
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"devDependencies": {
|
|
93
|
+
"@deepseek-ai/dsh-settings": "next",
|
|
94
|
+
"@deepseek-ai/dsh-skill-filesystem": "next",
|
|
95
|
+
"@deepseek-ai/dsh-tool-cordis": "next",
|
|
96
|
+
"@deepseek-ai/dsh-tools": "next",
|
|
97
|
+
"esbuild": "0.28.2",
|
|
98
|
+
"yaml": "^2.9.0"
|
|
99
|
+
}
|
|
100
|
+
}
|
package/src/client.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { CONFIG_FIELDS, SETTINGS_NAMESPACE } from '../internal/config-spec.js'
|
|
2
|
+
|
|
3
|
+
const CLIENT_STYLE_ID = 'ptc-plus-client-style'
|
|
4
|
+
const CLIENT_CSS = `
|
|
5
|
+
.ptcPlusCard{border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.1));background:var(--dsw-alias-bg-layer-3,#fff);border-radius:8px;list-style:none;overflow:hidden}
|
|
6
|
+
.ptcPlusHeader{appearance:none;width:100%;display:flex;align-items:center;gap:12px;padding:14px 16px;border:0;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer;transition:background-color .16s ease}
|
|
7
|
+
.ptcPlusHeader:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.06))}.ptcPlusHeader:focus-visible,.ptcPlusButton:focus-visible,.ptcPlusInput:focus-visible{outline:2px solid var(--dsw-alias-interactive-primary,#4d6bfe);outline-offset:-2px}
|
|
8
|
+
.ptcPlusHeadText{display:flex;flex:1;min-width:0;flex-direction:column;align-items:flex-start;gap:1px}.ptcPlusName{font-size:14px;font-weight:600;line-height:20px}.ptcPlusDescription{color:var(--dsw-alias-label-tertiary,#74777d);font-size:12px;line-height:18px;overflow-wrap:anywhere}.ptcPlusStatus{display:inline-flex;align-items:center;flex:none;padding:2px 8px;border-radius:999px;font-size:11px;font-weight:500;line-height:16px}.ptcPlusStatus[data-enabled=true]{color:var(--dsw-alias-state-success-primary,#16794f);background:var(--dsw-alias-state-success-tertiary,#e7f7ef)}.ptcPlusStatus[data-enabled=false]{color:var(--dsw-alias-label-tertiary,#74777d);background:var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.06))}
|
|
9
|
+
.ptcPlusChevron{display:flex;color:var(--dsw-alias-label-tertiary,#74777d);transition:transform .18s ease}.ptcPlusChevron[data-open=true]{transform:rotate(180deg)}.ptcPlusBody{display:grid;grid-template-rows:0fr;transition:grid-template-rows .2s ease}.ptcPlusBody[data-open=true]{grid-template-rows:1fr}.ptcPlusBodyInner{min-height:0;overflow:hidden}.ptcPlusFields{margin:0 16px;padding:8px 0 12px;border-top:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.1))}
|
|
10
|
+
.ptcPlusRow{display:flex;align-items:center;gap:12px;min-height:48px;border-top:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.1))}.ptcPlusRow:first-child{border-top:0}.ptcPlusMain{flex:1;min-width:0}.ptcPlusLabel{font-size:14px;font-weight:500;line-height:20px}.ptcPlusDetail,.ptcPlusMessage{color:var(--dsw-alias-label-tertiary,#74777d);font-size:12px;line-height:18px;overflow-wrap:anywhere}.ptcPlusInput{box-sizing:border-box;min-width:72px;width:140px;padding:5px 8px;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.1));border-radius:6px;background:var(--dsw-alias-bg-layer-1,#fff);color:inherit;font:12px/18px ui-monospace,SFMono-Regular,Consolas,monospace}.ptcPlusCheck{width:18px;height:18px;accent-color:var(--dsw-alias-interactive-primary,#4d6bfe)}
|
|
11
|
+
.ptcPlusFooter{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:8px}.ptcPlusButton{min-height:32px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.1));border-radius:6px;background:transparent;color:inherit;cursor:pointer;font:500 13px/20px inherit;transition:background-color .16s ease,border-color .16s ease}.ptcPlusButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.06))}.ptcPlusButton:disabled,.ptcPlusInput:disabled,.ptcPlusCheck:disabled{cursor:not-allowed;opacity:.55}.ptcPlusActive{display:inline-flex;align-items:center;gap:5px;color:var(--dsw-alias-state-success-primary,#16794f);font-size:12px;line-height:18px;white-space:nowrap}
|
|
12
|
+
@media(max-width:560px){.ptcPlusHeader{padding:12px}.ptcPlusFields{margin:0 12px}.ptcPlusRow{align-items:flex-start;flex-direction:column;gap:6px;padding:10px 0}.ptcPlusInput{width:100%}.ptcPlusFooter{align-items:stretch;flex-direction:column}.ptcPlusButton{width:100%}}
|
|
13
|
+
@media(prefers-reduced-motion:reduce){.ptcPlusHeader,.ptcPlusChevron,.ptcPlusBody,.ptcPlusButton{transition:none}}
|
|
14
|
+
`
|
|
15
|
+
|
|
16
|
+
window.__ModuleLoader__.load({
|
|
17
|
+
// Replaced by the bundle entry with the package name from package.json.
|
|
18
|
+
id: __PTC_PLUS_CLIENT_MODULE_ID__,
|
|
19
|
+
factory: (require) => {
|
|
20
|
+
const React = require('react')
|
|
21
|
+
const {
|
|
22
|
+
IconCheckOutline14,
|
|
23
|
+
IconChevronDownOutline14,
|
|
24
|
+
IconSettingsOutline16,
|
|
25
|
+
} = require('@deepseek-ai/dsh-client-ui-primitives')
|
|
26
|
+
const module = { exports: {} }
|
|
27
|
+
const h = React.createElement
|
|
28
|
+
|
|
29
|
+
function installStyles() {
|
|
30
|
+
if (document.getElementById(CLIENT_STYLE_ID) !== null) return () => {}
|
|
31
|
+
const style = document.createElement('style')
|
|
32
|
+
style.id = CLIENT_STYLE_ID
|
|
33
|
+
style.textContent = CLIENT_CSS
|
|
34
|
+
document.head.append(style)
|
|
35
|
+
return () => style.remove()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function fieldInput(field, value, disabled, onChange) {
|
|
39
|
+
if (field.type === 'boolean') {
|
|
40
|
+
return h('input', {
|
|
41
|
+
type: 'checkbox', role: 'switch', className: 'ptcPlusCheck', checked: value === true,
|
|
42
|
+
disabled, 'aria-label': field.label,
|
|
43
|
+
onChange: event => onChange(field, event.target.checked),
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
return h('input', {
|
|
47
|
+
type: 'number', className: 'ptcPlusInput',
|
|
48
|
+
value: Number.isSafeInteger(value) ? String(value) : '',
|
|
49
|
+
min: field.min, max: field.max, step: 1, disabled, 'aria-label': field.label,
|
|
50
|
+
onChange: event => {
|
|
51
|
+
const input = event.target.value
|
|
52
|
+
const parsed = input === '' ? '' : Number(input)
|
|
53
|
+
onChange(field, Number.isSafeInteger(parsed) ? parsed : input)
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function apply(ctx) {
|
|
59
|
+
const preferenceScope = ctx.settingsScope.bind({ namespace: SETTINGS_NAMESPACE })
|
|
60
|
+
ctx.effect(installStyles, 'ptc-plus: client styles')
|
|
61
|
+
|
|
62
|
+
function PTCPlusSettingsCard() {
|
|
63
|
+
const [open, setOpen] = React.useState(false)
|
|
64
|
+
const [status, setStatus] = React.useState('')
|
|
65
|
+
const [pending, setPending] = React.useState(() => new Set())
|
|
66
|
+
const writeTail = React.useRef(Promise.resolve())
|
|
67
|
+
const subscribe = React.useCallback(listener => preferenceScope.subscribe(listener), [])
|
|
68
|
+
const getSnapshot = React.useCallback(() => preferenceScope.getSnapshot(), [])
|
|
69
|
+
const snapshot = React.useSyncExternalStore(subscribe, getSnapshot, getSnapshot)
|
|
70
|
+
const value = snapshot.status === 'ready' ? (snapshot.value ?? {}) : {}
|
|
71
|
+
const enabled = value.enabled === true
|
|
72
|
+
const unavailable = snapshot.status !== 'ready' || snapshot.writable !== true
|
|
73
|
+
const persist = (field, nextValue) => {
|
|
74
|
+
if (unavailable || pending.has(field.key)) return
|
|
75
|
+
const operation = writeTail.current.then(async () => {
|
|
76
|
+
const before = preferenceScope.getSnapshot()
|
|
77
|
+
if (before.status !== 'ready' || before.writable !== true) return
|
|
78
|
+
if (field.key !== 'enabled' && before.value?.enabled !== true) return
|
|
79
|
+
if (before.value?.[field.key] === nextValue) return
|
|
80
|
+
setPending(current => new Set(current).add(field.key))
|
|
81
|
+
setStatus('')
|
|
82
|
+
try {
|
|
83
|
+
await preferenceScope.set(field.key, nextValue)
|
|
84
|
+
const after = preferenceScope.getSnapshot()
|
|
85
|
+
if (after.status !== 'ready' || after.value?.[field.key] !== nextValue) {
|
|
86
|
+
setStatus('设置未生效,请检查设置冲突')
|
|
87
|
+
} else {
|
|
88
|
+
setStatus('设置已立即生效')
|
|
89
|
+
}
|
|
90
|
+
} catch (error) {
|
|
91
|
+
setStatus('设置失败:' + (error instanceof Error ? error.message : String(error)))
|
|
92
|
+
} finally {
|
|
93
|
+
setPending(current => {
|
|
94
|
+
const next = new Set(current)
|
|
95
|
+
next.delete(field.key)
|
|
96
|
+
return next
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
writeTail.current = operation.catch(() => {})
|
|
101
|
+
}
|
|
102
|
+
const fieldDisabled = field => unavailable
|
|
103
|
+
|| pending.has(field.key)
|
|
104
|
+
|| (field.key !== 'enabled' && !enabled)
|
|
105
|
+
return h('li', { className: 'ptcPlusCard' },
|
|
106
|
+
h('button', {
|
|
107
|
+
type: 'button', className: 'ptcPlusHeader', 'aria-expanded': open,
|
|
108
|
+
'aria-label': open ? '收起 PTC Plus 设置' : '展开 PTC Plus 设置',
|
|
109
|
+
'aria-controls': 'ptc-plus-settings-body', onClick: () => setOpen(current => !current),
|
|
110
|
+
},
|
|
111
|
+
h(IconSettingsOutline16, { size: 16 }),
|
|
112
|
+
h('span', { className: 'ptcPlusHeadText' },
|
|
113
|
+
h('span', { className: 'ptcPlusName' }, 'PTC Plus'),
|
|
114
|
+
h('span', { className: 'ptcPlusDescription' }, 'PTC 模式的会话级 TypeScript REPL。')),
|
|
115
|
+
h('span', { className: 'ptcPlusStatus', 'data-enabled': enabled }, enabled ? '已启用' : '已停用'),
|
|
116
|
+
h('span', { className: 'ptcPlusChevron', 'data-open': open, 'aria-hidden': true }, h(IconChevronDownOutline14, { size: 14 }))),
|
|
117
|
+
h('div', { id: 'ptc-plus-settings-body', className: 'ptcPlusBody', 'data-open': open, 'aria-hidden': !open },
|
|
118
|
+
h('div', { className: 'ptcPlusBodyInner' }, h('div', { className: 'ptcPlusFields' },
|
|
119
|
+
snapshot.status === 'loading'
|
|
120
|
+
? h('p', { className: 'ptcPlusMessage' }, '正在同步设置...')
|
|
121
|
+
: snapshot.status === 'unavailable'
|
|
122
|
+
? h('p', { className: 'ptcPlusMessage' }, '当前 DSH 实例未提供设置服务')
|
|
123
|
+
: [
|
|
124
|
+
...CONFIG_FIELDS.map(field => h('div', { key: field.key, className: 'ptcPlusRow' },
|
|
125
|
+
h('div', { className: 'ptcPlusMain' },
|
|
126
|
+
h('div', { className: 'ptcPlusLabel' }, field.label),
|
|
127
|
+
field.description === '' ? null : h('div', { className: 'ptcPlusDetail' }, field.description)),
|
|
128
|
+
fieldInput(field, value[field.key], fieldDisabled(field), persist))),
|
|
129
|
+
h('div', { key: 'footer', className: 'ptcPlusFooter' },
|
|
130
|
+
h('span', { className: 'ptcPlusMessage', role: 'status' }, status || (snapshot.writable ? '设置会在修改后立即生效' : '当前设置为只读'))),
|
|
131
|
+
]))),
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
ctx.slots.inject('settings.plugin.item', () => ctx.slots.register({
|
|
136
|
+
name: 'settings.plugin.item', key: SETTINGS_NAMESPACE,
|
|
137
|
+
}, PTCPlusSettingsCard))
|
|
138
|
+
|
|
139
|
+
ctx.inject(['slots', 'sessions'], (scope) => {
|
|
140
|
+
function PTCPlusSessionIndicator({ sessionId }) {
|
|
141
|
+
const sessions = React.useSyncExternalStore(
|
|
142
|
+
listener => scope.sessions.list.subscribe(listener),
|
|
143
|
+
() => scope.sessions.list.getSnapshot(),
|
|
144
|
+
() => scope.sessions.list.getSnapshot(),
|
|
145
|
+
)
|
|
146
|
+
const settings = React.useSyncExternalStore(
|
|
147
|
+
listener => preferenceScope.subscribe(listener),
|
|
148
|
+
() => preferenceScope.getSnapshot(),
|
|
149
|
+
() => preferenceScope.getSnapshot(),
|
|
150
|
+
)
|
|
151
|
+
if (sessions.byId?.[sessionId]?.agentPreset !== 'code'
|
|
152
|
+
|| settings.status !== 'ready' || settings.value?.enabled !== true) return null
|
|
153
|
+
return h('span', { className: 'ptcPlusActive', title: 'PTC Plus 已启用' },
|
|
154
|
+
h(IconCheckOutline14, { size: 14 }), 'PTC Plus')
|
|
155
|
+
}
|
|
156
|
+
scope.slots.inject('conversation.session.header.actions', () => scope.slots.register({
|
|
157
|
+
name: 'conversation.session.header.actions', id: 'ptc-plus-active', order: -9,
|
|
158
|
+
}, PTCPlusSessionIndicator))
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
module.exports = { apply, inject: ['settingsScope', 'slots', 'sessions'] }
|
|
163
|
+
return module.exports
|
|
164
|
+
},
|
|
165
|
+
})
|