@ckanner/flywheel 0.33.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/README.md +1 -0
- package/README.zh-CN.md +2 -0
- package/THIRD_PARTY_NOTICES.md +5 -1
- package/bin/flywheel-kimi-native-worker.js +5 -0
- package/bin/flywheel.js +8 -1
- package/bin/runtime.js +5 -1
- package/dist/bridge.mjs +89 -7
- package/dist/claude-code-pro-bridge.mjs +55 -6
- package/dist/client.js +107 -10
- package/dist/codex-pro-bridge.mjs +12 -5
- package/dist/control.mjs +1 -1
- package/dist/kimi-native-bridge.mjs +429 -0
- package/dist/kimi-native-runtime.mjs +233 -0
- package/dist/kimi-native-worker.mjs +77 -0
- package/dist/kimi-tower-candidate.mjs +227 -0
- package/dist/machine-worker.mjs +39 -5
- package/dist/managed-lcu.mjs +223 -0
- package/dist/native-doctor.mjs +36 -0
- package/dist/native-jobs.mjs +72 -0
- package/dist/native-policy.mjs +371 -0
- package/dist/native-resources.mjs +144 -0
- package/dist/native.mjs +251 -0
- package/dist/policy.mjs +13 -3
- package/dist/prewarm.mjs +4 -1
- package/dist/protocol.mjs +8 -1
- package/dist/worker.mjs +16 -9
- package/docs/README.md +9 -0
- package/docs/README.zh-CN.md +3 -0
- package/docs/design.md +34 -12
- package/docs/en/claude-code-pro.md +2 -2
- package/docs/en/configuration.md +17 -5
- package/docs/en/contributing.md +5 -3
- package/docs/en/flow-console.md +1 -1
- package/docs/en/getting-started.md +2 -2
- package/docs/en/model-policy.md +2 -2
- package/docs/en/native-capabilities.md +185 -0
- package/docs/en/releasing.md +2 -2
- package/docs/en/session-recovery.md +2 -2
- package/docs/plans/2026-11-13-native-harness-capabilities.en.md +258 -0
- package/docs/plans/2026-11-13-native-harness-capabilities.md +276 -0
- package/docs/plans/README.md +1 -0
- package/docs/validation/2026-11-14-desktop-native-configuration.md +128 -0
- package/docs/validation/2026-11-14-native-harness-implementation.md +215 -0
- package/docs/validation/2026-11-14-published-companions.md +15 -0
- package/docs/validation/2026-11-14-release-0.33.0.md +49 -0
- package/docs/validation/2026-11-14-release-0.33.1.md +78 -0
- package/docs/validation/README.md +4 -0
- package/docs/validation/evidence/2026-11-14-native-harness-candidate.json +255 -0
- package/docs/validation/evidence/2026-11-14-native-harness-sessions.json +6009 -0
- package/docs/validation/evidence/2026-11-14-native-harness-tower-candidate.json +192 -0
- package/docs/zh-CN/claude-code-pro.md +2 -2
- package/docs/zh-CN/configuration.md +21 -8
- package/docs/zh-CN/contributing.md +5 -3
- package/docs/zh-CN/flow-console.md +1 -1
- package/docs/zh-CN/getting-started.md +2 -2
- package/docs/zh-CN/model-policy.md +2 -2
- package/docs/zh-CN/native-capabilities.md +184 -0
- package/docs/zh-CN/releasing.md +2 -2
- package/docs/zh-CN/session-recovery.md +1 -1
- package/native/darwin-amd64/flywheel +0 -0
- package/native/darwin-arm64/flywheel +0 -0
- package/native/linux-amd64/flywheel +0 -0
- package/native/linux-arm64/flywheel +0 -0
- package/native/manifest.json +9 -9
- package/node_modules/dsh-preset-generator/CHANGELOG.md +77 -0
- package/node_modules/dsh-preset-generator/LICENSE +21 -0
- package/node_modules/dsh-preset-generator/README.md +149 -0
- package/node_modules/dsh-preset-generator/docs/README.zh.md +95 -0
- package/node_modules/dsh-preset-generator/package.json +43 -0
- package/node_modules/dsh-preset-generator/src/composition.mjs +150 -0
- package/node_modules/dsh-preset-generator/src/gen-presets.mjs +367 -0
- package/node_modules/dsh-preset-generator/src/policy.mjs +17 -0
- package/node_modules/dsh-subagent-codex-pro/CHANGELOG.md +16 -1
- package/node_modules/dsh-subagent-codex-pro/README.md +57 -4
- package/node_modules/dsh-subagent-codex-pro/assets/native-client.js +118 -0
- package/node_modules/dsh-subagent-codex-pro/cordis.patch.yml +6 -0
- package/node_modules/dsh-subagent-codex-pro/docs/README.zh.md +49 -2
- package/node_modules/dsh-subagent-codex-pro/docs/machine-lifecycle-validation.md +1 -1
- package/node_modules/dsh-subagent-codex-pro/docs/machine-lifecycle.md +1 -1
- package/node_modules/dsh-subagent-codex-pro/docs/machine-lifecycle.zh.md +1 -1
- package/node_modules/dsh-subagent-codex-pro/docs/machine.md +2 -2
- package/node_modules/dsh-subagent-codex-pro/docs/native-capabilities.md +254 -0
- package/node_modules/dsh-subagent-codex-pro/docs/native-capabilities.zh.md +180 -0
- package/node_modules/dsh-subagent-codex-pro/lib/config.js +56 -0
- package/node_modules/dsh-subagent-codex-pro/lib/config.js.map +1 -0
- package/node_modules/dsh-subagent-codex-pro/lib/desktop-native.js +109 -0
- package/node_modules/dsh-subagent-codex-pro/lib/desktop-native.js.map +1 -0
- package/node_modules/dsh-subagent-codex-pro/lib/dsh-types.js +3 -4
- package/node_modules/dsh-subagent-codex-pro/lib/dsh-types.js.map +1 -1
- package/node_modules/dsh-subagent-codex-pro/lib/index.js +64 -15
- package/node_modules/dsh-subagent-codex-pro/lib/index.js.map +1 -1
- package/node_modules/dsh-subagent-codex-pro/lib/jobs.js +6 -2
- package/node_modules/dsh-subagent-codex-pro/lib/jobs.js.map +1 -1
- package/node_modules/dsh-subagent-codex-pro/lib/machine-cli.js +2 -2
- package/node_modules/dsh-subagent-codex-pro/lib/machine-cli.js.map +1 -1
- package/node_modules/dsh-subagent-codex-pro/lib/machine-local.js +5 -2
- package/node_modules/dsh-subagent-codex-pro/lib/machine-local.js.map +1 -1
- package/node_modules/dsh-subagent-codex-pro/lib/machine-supervision.js +11 -0
- package/node_modules/dsh-subagent-codex-pro/lib/machine-supervision.js.map +1 -1
- package/node_modules/dsh-subagent-codex-pro/lib/machine-supervisor.js +1 -0
- package/node_modules/dsh-subagent-codex-pro/lib/machine-supervisor.js.map +1 -1
- package/node_modules/dsh-subagent-codex-pro/lib/machine.js +20 -3
- package/node_modules/dsh-subagent-codex-pro/lib/machine.js.map +1 -1
- package/node_modules/dsh-subagent-codex-pro/lib/native.js +297 -0
- package/node_modules/dsh-subagent-codex-pro/lib/native.js.map +1 -0
- package/node_modules/dsh-subagent-codex-pro/lib/process-guardian.js +11 -1
- package/node_modules/dsh-subagent-codex-pro/lib/process-guardian.js.map +1 -1
- package/node_modules/dsh-subagent-codex-pro/lib/run.js +19 -1
- package/node_modules/dsh-subagent-codex-pro/lib/run.js.map +1 -1
- package/node_modules/dsh-subagent-codex-pro/lib/tool.js +123 -57
- package/node_modules/dsh-subagent-codex-pro/lib/tool.js.map +1 -1
- package/node_modules/dsh-subagent-codex-pro/lib/types/config.d.ts +31 -0
- package/node_modules/dsh-subagent-codex-pro/lib/types/config.d.ts.map +1 -0
- package/node_modules/dsh-subagent-codex-pro/lib/types/desktop-native.d.ts +30 -0
- package/node_modules/dsh-subagent-codex-pro/lib/types/desktop-native.d.ts.map +1 -0
- package/node_modules/dsh-subagent-codex-pro/lib/types/dsh-types.d.ts +7 -5
- package/node_modules/dsh-subagent-codex-pro/lib/types/dsh-types.d.ts.map +1 -1
- package/node_modules/dsh-subagent-codex-pro/lib/types/index.d.ts +31 -0
- package/node_modules/dsh-subagent-codex-pro/lib/types/index.d.ts.map +1 -1
- package/node_modules/dsh-subagent-codex-pro/lib/types/jobs.d.ts +2 -0
- package/node_modules/dsh-subagent-codex-pro/lib/types/jobs.d.ts.map +1 -1
- package/node_modules/dsh-subagent-codex-pro/lib/types/machine-local.d.ts.map +1 -1
- package/node_modules/dsh-subagent-codex-pro/lib/types/machine-supervision.d.ts.map +1 -1
- package/node_modules/dsh-subagent-codex-pro/lib/types/machine.d.ts +7 -0
- package/node_modules/dsh-subagent-codex-pro/lib/types/machine.d.ts.map +1 -1
- package/node_modules/dsh-subagent-codex-pro/lib/types/native.d.ts +120 -0
- package/node_modules/dsh-subagent-codex-pro/lib/types/native.d.ts.map +1 -0
- package/node_modules/dsh-subagent-codex-pro/lib/types/run.d.ts +11 -0
- package/node_modules/dsh-subagent-codex-pro/lib/types/run.d.ts.map +1 -1
- package/node_modules/dsh-subagent-codex-pro/lib/types/tool.d.ts +11 -0
- package/node_modules/dsh-subagent-codex-pro/lib/types/tool.d.ts.map +1 -1
- package/node_modules/dsh-subagent-codex-pro/lib/types/wire.d.ts +7 -1
- package/node_modules/dsh-subagent-codex-pro/lib/types/wire.d.ts.map +1 -1
- package/node_modules/dsh-subagent-codex-pro/lib/wire.js +34 -6
- package/node_modules/dsh-subagent-codex-pro/lib/wire.js.map +1 -1
- package/node_modules/dsh-subagent-codex-pro/package.json +28 -4
- package/npm/install.js +2 -2
- package/package.json +25 -10
- package/plugins/dsh/KIMI_NATIVE_PROTOCOL.en.md +76 -0
- package/plugins/dsh/KIMI_NATIVE_PROTOCOL.md +76 -0
- package/plugins/dsh/PROTOCOL.md +151 -17
- package/plugins/dsh/README.md +15 -12
- package/plugins/dsh/README.zh-CN.md +8 -8
- package/plugins/dsh/THIRD_PARTY_NOTICES.md +5 -1
- package/plugins/dsh/vendor/README.md +40 -11
- package/plugins/dsh/vendor/chatgpt.json +21 -0
- package/plugins/dsh/vendor/claude-code-pro.json +21 -0
- package/plugins/dsh/vendor/codex-pro.json +13 -8
- package/plugins/dsh/vendor/lcu.json +21 -0
- package/plugins/dsh/vendor/preset-generator.json +16 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* gen-presets.mjs — materialize DSH agent presets from the installed base.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this is not part of a plugin
|
|
6
|
+
*
|
|
7
|
+
* DSH agent presets have no inheritance: a preset's `plugins` list is its
|
|
8
|
+
* complete composition, and a patch replaces a whole entry rather than merging
|
|
9
|
+
* into it. So any custom preset has to restate the base preset, and the base
|
|
10
|
+
* ships inside the application and changes when DSH is upgraded.
|
|
11
|
+
*
|
|
12
|
+
* That job belongs to no single plugin. It started inside one, which meant a
|
|
13
|
+
* plugin's script was rewriting a profile region that also serves other plugins
|
|
14
|
+
* and the settings UI. Here it is a tool of its own: it reads the base, applies a
|
|
15
|
+
* policy, and writes the marked region of a profile patch.
|
|
16
|
+
*
|
|
17
|
+
* ## The marker is load-bearing
|
|
18
|
+
*
|
|
19
|
+
* `BEGIN` names this file's original path, and it has to keep naming it: a
|
|
20
|
+
* profile generated before this move already contains that exact string, and a
|
|
21
|
+
* different one would make this script append a second block instead of
|
|
22
|
+
* replacing the first.
|
|
23
|
+
*
|
|
24
|
+
* ## Usage
|
|
25
|
+
*
|
|
26
|
+
* node src/gen-presets.mjs # write into the desktop profile
|
|
27
|
+
* node src/gen-presets.mjs --dry-run # print, write nothing
|
|
28
|
+
* node src/gen-presets.mjs --out FILE # write to FILE instead
|
|
29
|
+
* node src/gen-presets.mjs --daily-only # refuse if the file already has heavy
|
|
30
|
+
* node src/gen-presets.mjs --asar PATH # override the app.asar path
|
|
31
|
+
* node src/gen-presets.mjs --profile DIR # override the profile directory
|
|
32
|
+
*
|
|
33
|
+
* @module dsh-preset-generator
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { closeSync, openSync, readFileSync, readSync, writeFileSync } from 'node:fs'
|
|
37
|
+
import { homedir } from 'node:os'
|
|
38
|
+
import { join } from 'node:path'
|
|
39
|
+
import { PRESETS } from './policy.mjs'
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Defaults
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
const DEFAULT_ASAR =
|
|
46
|
+
'/Applications/DeepSeek Harness.app/Contents/Resources/app.asar'
|
|
47
|
+
const BASE_PRESET = 'dsh/node_modules/@deepseek-ai/dsh-web-app/presets/ptc.patch.yml'
|
|
48
|
+
const DEFAULT_PROFILE = join(homedir(), '.dsh', 'profiles', 'desktop')
|
|
49
|
+
|
|
50
|
+
const BEGIN = '# >>> BEGIN generated presets (scripts/gen-presets.mjs) >>>'
|
|
51
|
+
const END = '# <<< END generated presets <<<'
|
|
52
|
+
|
|
53
|
+
/** The two presets this script owns. Both are PTC: LCU now exposes its
|
|
54
|
+
* Computer/Browser runtime through the PTC `cua` bridge, not a native `js` tool.
|
|
55
|
+
* DSH 0.2.2 deprecates the presentation selector in favour of mounting
|
|
56
|
+
* `dsh-tool-executor-ptc` directly. */
|
|
57
|
+
// The pure SDK API consumes the same policies without running this CLI or
|
|
58
|
+
// writing the user's desktop profile. LCU attachment remains a separate grant.
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Minimal asar reader (Electron's asar is a simple header + flat blob)
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Read one file out of an asar archive without Electron.
|
|
66
|
+
* @param asarPath - absolute path to the `.asar` file
|
|
67
|
+
* @param innerPath - slash-separated path inside the archive
|
|
68
|
+
* @returns the file contents
|
|
69
|
+
*/
|
|
70
|
+
function readAsarFile(asarPath, innerPath) {
|
|
71
|
+
const fd = openSync(asarPath, 'r')
|
|
72
|
+
try {
|
|
73
|
+
const head = Buffer.alloc(16)
|
|
74
|
+
readSync(fd, head, 0, 16, 0)
|
|
75
|
+
const baseOffset = 8 + head.readUInt32LE(4)
|
|
76
|
+
const jsonSize = head.readUInt32LE(12)
|
|
77
|
+
const json = Buffer.alloc(jsonSize)
|
|
78
|
+
readSync(fd, json, 0, jsonSize, 16)
|
|
79
|
+
let node = JSON.parse(json.toString('utf8'))
|
|
80
|
+
for (const segment of innerPath.split('/').filter(Boolean)) {
|
|
81
|
+
node = node.files?.[segment]
|
|
82
|
+
if (node === undefined) throw new Error(`not in archive: ${innerPath}`)
|
|
83
|
+
}
|
|
84
|
+
if (node.files !== undefined) throw new Error(`is a directory: ${innerPath}`)
|
|
85
|
+
const buf = Buffer.alloc(node.size)
|
|
86
|
+
readSync(fd, buf, 0, node.size, baseOffset + Number(node.offset))
|
|
87
|
+
return buf
|
|
88
|
+
} finally {
|
|
89
|
+
closeSync(fd)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// Line surgery
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
/** Indentation (0-based column of the first non-space char); -1 for blank lines. */
|
|
98
|
+
const indentOf = (line) => (line.trim() === '' ? -1 : line.search(/\S/))
|
|
99
|
+
|
|
100
|
+
/** Index of the child row `- id: <rowId>` inside a plugin list. */
|
|
101
|
+
function findRow(lines, rowId) {
|
|
102
|
+
const index = lines.findIndex((line) => line.trim().startsWith(`- id: ${rowId}`))
|
|
103
|
+
if (index < 0) throw new Error(`plugin row not found: ${rowId}`)
|
|
104
|
+
return index
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Iterate the direct/indirect body lines of the row starting at `start`,
|
|
109
|
+
* stopping at the next row or any line dedented to the row's own level.
|
|
110
|
+
*/
|
|
111
|
+
function* body(lines, start) {
|
|
112
|
+
const base = indentOf(lines[start])
|
|
113
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
114
|
+
const ind = indentOf(lines[i])
|
|
115
|
+
if (ind === -1) continue
|
|
116
|
+
if (ind <= base) return
|
|
117
|
+
yield i
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Remove `disabled: true` from one row, turning it on.
|
|
123
|
+
* @returns a new line array
|
|
124
|
+
*/
|
|
125
|
+
function enableRow(lines, rowId) {
|
|
126
|
+
const start = findRow(lines, rowId)
|
|
127
|
+
for (const i of body(lines, start)) {
|
|
128
|
+
if (lines[i].trim().startsWith('disabled:')) {
|
|
129
|
+
return lines.filter((_, k) => k !== i)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return lines // already enabled — idempotent
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Replace the base's tool-presentation selector with the concrete executor.
|
|
136
|
+
*
|
|
137
|
+
* DSH 0.2.2 carries `@deepseek-ai/dsh-tool-executor-ptc` in the base preset and
|
|
138
|
+
* deprecates `@deepseek-ai/dsh-agent-tool-presentation`. Older bases still have
|
|
139
|
+
* the selector with a `mode:` field. Both shapes are accepted here: the row is
|
|
140
|
+
* replaced in place, so the custom preset asks for the executor directly and
|
|
141
|
+
* never mounts the deprecated selector.
|
|
142
|
+
*/
|
|
143
|
+
function setPresentation(lines, presentation) {
|
|
144
|
+
const rowId = `tool-executor-${presentation}`
|
|
145
|
+
const rowName = `@deepseek-ai/dsh-tool-executor-${presentation}`
|
|
146
|
+
const start = lines.findIndex((line) => {
|
|
147
|
+
const text = line.trim()
|
|
148
|
+
return /^- id: tool-executor-(native|ptc)$/.test(text) || text === '- id: tool-presentation'
|
|
149
|
+
})
|
|
150
|
+
if (start < 0) throw new Error('base preset has no tool-presentation or tool-executor row')
|
|
151
|
+
|
|
152
|
+
const base = indentOf(lines[start])
|
|
153
|
+
const indent = lines[start].slice(0, base)
|
|
154
|
+
let end = start + 1
|
|
155
|
+
while (end < lines.length) {
|
|
156
|
+
const ind = indentOf(lines[end])
|
|
157
|
+
if (ind !== -1 && ind <= base) break
|
|
158
|
+
end += 1
|
|
159
|
+
}
|
|
160
|
+
const trailing = []
|
|
161
|
+
while (end > start + 1 && lines[end - 1].trim() === '') {
|
|
162
|
+
trailing.unshift(lines[end - 1])
|
|
163
|
+
end -= 1
|
|
164
|
+
}
|
|
165
|
+
const replacement = [`${indent}- id: ${rowId}`, `${indent} name: '${rowName}'`]
|
|
166
|
+
return [...lines.slice(0, start), ...replacement, ...trailing, ...lines.slice(end)]
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Extract the `plugins:` sub-tree (the row list) from a preset patch file. */
|
|
170
|
+
function extractPlugins(lines) {
|
|
171
|
+
const index = lines.findIndex((line) => /^\s*plugins:\s*$/.test(line))
|
|
172
|
+
if (index < 0) throw new Error('base preset has no `plugins:` key')
|
|
173
|
+
const base = indentOf(lines[index])
|
|
174
|
+
const out = []
|
|
175
|
+
for (let i = index + 1; i < lines.length; i += 1) {
|
|
176
|
+
const ind = indentOf(lines[i])
|
|
177
|
+
if (ind !== -1 && ind <= base) break
|
|
178
|
+
out.push(lines[i])
|
|
179
|
+
}
|
|
180
|
+
if (out.every((line) => line.trim() === '')) throw new Error('base `plugins:` is empty')
|
|
181
|
+
while (out.length > 0 && out[out.length - 1].trim() === '') out.pop()
|
|
182
|
+
return out
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
// Preset rendering
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
function renderPreset(spec, basePlugins) {
|
|
190
|
+
const plugins = spec.enable.reduce(enableRow, [...basePlugins])
|
|
191
|
+
const withMode = spec.presentation === null
|
|
192
|
+
? plugins
|
|
193
|
+
: setPresentation(plugins, spec.presentation)
|
|
194
|
+
const out = [
|
|
195
|
+
` - id: preset-${spec.id}`,
|
|
196
|
+
" name: '@deepseek-ai/dsh-agent-preset'",
|
|
197
|
+
' config:',
|
|
198
|
+
` id: ${spec.id}`,
|
|
199
|
+
` name: '${spec.name}'`,
|
|
200
|
+
` description: '${spec.description}'`,
|
|
201
|
+
` order: ${spec.order}`,
|
|
202
|
+
' plugins:',
|
|
203
|
+
...withMode,
|
|
204
|
+
...spec.append,
|
|
205
|
+
]
|
|
206
|
+
return out
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
// Main
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
function parseArgs(argv) {
|
|
214
|
+
const opts = {
|
|
215
|
+
dryRun: false,
|
|
216
|
+
// Preserve both managed choices on routine upgrades. An existing heavy
|
|
217
|
+
// preset may be selected by the user or referenced by a plugin allowlist.
|
|
218
|
+
withHeavy: true,
|
|
219
|
+
out: undefined,
|
|
220
|
+
asar: DEFAULT_ASAR,
|
|
221
|
+
profile: DEFAULT_PROFILE,
|
|
222
|
+
}
|
|
223
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
224
|
+
const arg = argv[i]
|
|
225
|
+
if (arg === '--dry-run') opts.dryRun = true
|
|
226
|
+
else if (arg === '--with-heavy') opts.withHeavy = true
|
|
227
|
+
else if (arg === '--daily-only') opts.withHeavy = false
|
|
228
|
+
else if (arg === '--out') opts.out = argv[++i]
|
|
229
|
+
else if (arg === '--asar') opts.asar = argv[++i]
|
|
230
|
+
else if (arg === '--profile') opts.profile = argv[++i]
|
|
231
|
+
else throw new Error(`unknown argument: ${arg}`)
|
|
232
|
+
}
|
|
233
|
+
return opts
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function buildBlock(baseLines, { withHeavy }) {
|
|
237
|
+
const basePlugins = extractPlugins(baseLines)
|
|
238
|
+
const specs = withHeavy ? [PRESETS.daily, PRESETS.heavy] : [PRESETS.daily]
|
|
239
|
+
const lines = ['- insert:']
|
|
240
|
+
for (const spec of specs) lines.push(...renderPreset(spec, basePlugins))
|
|
241
|
+
return { body: lines.join('\n'), basePlugins, specs }
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Replace the marked block in place, or append one. */
|
|
245
|
+
/**
|
|
246
|
+
* Whether a top-level entry is the one this script wrote.
|
|
247
|
+
*
|
|
248
|
+
* The markers only say where the block lives, and the region is not exclusively
|
|
249
|
+
* ours: DSH's settings UI appends its own entries there. So the entry to replace
|
|
250
|
+
* is identified by what it contains — the `- insert:` shell carrying preset rows
|
|
251
|
+
* — rather than by being the only thing between the markers.
|
|
252
|
+
*
|
|
253
|
+
* @param entry - the lines of one top-level entry.
|
|
254
|
+
* @returns whether it is this script's.
|
|
255
|
+
*/
|
|
256
|
+
function isOwnEntry(entry) {
|
|
257
|
+
return /^-\s+insert:\s*$/.test(entry[0]) && entry.some((line) => /^\s+- id: preset-/.test(line))
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Replace this script's own entry inside the region, and nothing else.
|
|
262
|
+
*
|
|
263
|
+
* Everything else in the region is left exactly where it is. That is the whole
|
|
264
|
+
* point: entries the settings UI put there are neither moved nor re-emitted, so
|
|
265
|
+
* there is nothing to collect, nothing to de-duplicate, and no way for a
|
|
266
|
+
* repeated settings write to accumulate a copy per run.
|
|
267
|
+
*
|
|
268
|
+
* @param region - the text between the markers.
|
|
269
|
+
* @param body - the entry to write, without the markers.
|
|
270
|
+
* @returns the region with our entry replaced.
|
|
271
|
+
*/
|
|
272
|
+
function replaceOwnEntry(region, body) {
|
|
273
|
+
const lines = region.split('\n')
|
|
274
|
+
const out = []
|
|
275
|
+
let replaced = false
|
|
276
|
+
let i = 0
|
|
277
|
+
while (i < lines.length) {
|
|
278
|
+
if (!/^-\s+\S/.test(lines[i])) {
|
|
279
|
+
out.push(lines[i])
|
|
280
|
+
i += 1
|
|
281
|
+
continue
|
|
282
|
+
}
|
|
283
|
+
const start = i
|
|
284
|
+
i += 1
|
|
285
|
+
while (i < lines.length && !/^-\s+\S/.test(lines[i])) i += 1
|
|
286
|
+
const entry = lines.slice(start, i)
|
|
287
|
+
if (!replaced && isOwnEntry(entry)) {
|
|
288
|
+
// The entry as consumed includes the blank lines that followed it, since
|
|
289
|
+
// they are not the start of another entry. They separate it from whatever
|
|
290
|
+
// comes next, so they are restored — otherwise the marker that follows ends
|
|
291
|
+
// up glued to the last line of the body.
|
|
292
|
+
const trailing = []
|
|
293
|
+
while (entry.length > 0 && entry[entry.length - 1].trim() === '') trailing.unshift(entry.pop())
|
|
294
|
+
out.push(...body.split('\n'), ...trailing)
|
|
295
|
+
replaced = true
|
|
296
|
+
continue
|
|
297
|
+
}
|
|
298
|
+
out.push(...entry)
|
|
299
|
+
}
|
|
300
|
+
// No entry of ours inside — a hand-edited file, or a region whose block was
|
|
301
|
+
// removed. Writing one is the same thing the first run does.
|
|
302
|
+
if (!replaced) out.unshift(body)
|
|
303
|
+
return out.join('\n')
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function spliceBlock(text, body) {
|
|
307
|
+
const begin = text.indexOf(BEGIN)
|
|
308
|
+
const end = text.indexOf(END)
|
|
309
|
+
if (begin >= 0 && end > begin) {
|
|
310
|
+
// `region` runs from just after the BEGIN marker to just before END, so it
|
|
311
|
+
// already carries the newline that ends the marker line and the one before
|
|
312
|
+
// END. Adding either again would leave a blank line behind on every run.
|
|
313
|
+
const region = text.slice(begin + BEGIN.length, end)
|
|
314
|
+
return text.slice(0, begin)
|
|
315
|
+
+ BEGIN + replaceOwnEntry(region, body) + END
|
|
316
|
+
+ text.slice(end + END.length)
|
|
317
|
+
}
|
|
318
|
+
const separator = text === '' ? '' : text.endsWith('\n') ? '\n' : '\n\n'
|
|
319
|
+
return `${text}${separator}${BEGIN}\n${body}\n${END}\n`
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function main() {
|
|
323
|
+
const opts = parseArgs(process.argv.slice(2))
|
|
324
|
+
const baseLines = readAsarFile(opts.asar, BASE_PRESET).toString('utf8').split('\n')
|
|
325
|
+
const { body, basePlugins, specs } = buildBlock(baseLines, opts)
|
|
326
|
+
const verb = opts.dryRun ? 'would write' : 'wrote'
|
|
327
|
+
const rowCount = basePlugins.filter((line) => /^\s*- id: /.test(line)).length
|
|
328
|
+
|
|
329
|
+
if (opts.dryRun) {
|
|
330
|
+
process.stdout.write(`${BEGIN}\n${body}\n${END}\n`)
|
|
331
|
+
} else {
|
|
332
|
+
const target = opts.out ?? join(opts.profile, 'cordis.patch.yml')
|
|
333
|
+
let before
|
|
334
|
+
let missing = false
|
|
335
|
+
try {
|
|
336
|
+
before = readFileSync(target, 'utf8')
|
|
337
|
+
} catch (error) {
|
|
338
|
+
// A new profile may not have a patch yet. Only absence is an empty
|
|
339
|
+
// document; an unreadable or invalid existing target must still fail.
|
|
340
|
+
if (error.code !== 'ENOENT') throw error
|
|
341
|
+
before = ''
|
|
342
|
+
missing = true
|
|
343
|
+
}
|
|
344
|
+
// Never delete a preset this file already had. A re-run is the documented
|
|
345
|
+
// response to a DSH upgrade, and it must not be able to take away the mode a
|
|
346
|
+
// user relies on as a side effect of an argument they did not pass.
|
|
347
|
+
for (const preset of ['daily', 'heavy']) {
|
|
348
|
+
const had = before.includes(`- id: preset-${preset}`)
|
|
349
|
+
const keeps = body.includes(`- id: preset-${preset}`)
|
|
350
|
+
if (had && !keeps) {
|
|
351
|
+
throw new Error(
|
|
352
|
+
`${target} already defines preset-${preset} and this run would remove it. `
|
|
353
|
+
+ 'Re-run without --daily-only to keep every preset the file has.',
|
|
354
|
+
)
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// Do not overwrite a patch another writer created after the missing read.
|
|
358
|
+
writeFileSync(target, spliceBlock(before, body), { mode: 0o600, flag: missing ? 'wx' : 'w' })
|
|
359
|
+
process.stderr.write(`${verb} ${target}\n`)
|
|
360
|
+
}
|
|
361
|
+
process.stderr.write(
|
|
362
|
+
`base: ${BASE_PRESET} (${rowCount} top-level rows)\n`
|
|
363
|
+
+ `presets: ${specs.map((s) => s.id).join(', ')}\n`,
|
|
364
|
+
)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
main()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Shared desktop and SDK intent. Names describe use, not separate GUI grants. */
|
|
2
|
+
export const PRESETS = Object.freeze({
|
|
3
|
+
daily: Object.freeze({
|
|
4
|
+
id: 'daily', name: '日常', description: '日常编码:PTC 程序化工具调用 + Codex 委派',
|
|
5
|
+
order: 5, presentation: 'ptc', enable: Object.freeze([]), append: Object.freeze([]),
|
|
6
|
+
}),
|
|
7
|
+
heavy: Object.freeze({
|
|
8
|
+
id: 'heavy', name: '重活', description: '重活:PTC Computer/Browser CUA + Codex 委派',
|
|
9
|
+
order: 6, presentation: 'ptc', enable: Object.freeze([]), append: Object.freeze([]),
|
|
10
|
+
}),
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
/** Return a detached policy; callers cannot alter future generated presets. */
|
|
14
|
+
export function presetPolicy(id) {
|
|
15
|
+
if (!Object.hasOwn(PRESETS, id)) throw new TypeError(`Unknown managed preset: ${String(id)}`)
|
|
16
|
+
return structuredClone(PRESETS[id])
|
|
17
|
+
}
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
3
|
+
## 0.10.0 (source candidate)
|
|
4
|
+
|
|
5
|
+
- Add a real DSH native settings page, a live Config schema, whole-object per-call overrides and explicit null/off semantics. Keep default inheritance distinct from off.
|
|
6
|
+
- Wire desktop/PTC delegation and the Host provider through native validation, app-server configuration, child lifecycle and receipts; invalidate implicit session reuse when the effective binding changes.
|
|
7
|
+
- Add a dedicated fresh, read-only subagent_codex_review tool. Review cannot become an ordinary delegation default or silently ignore prompt/schema/resume arguments.
|
|
8
|
+
- Preserve native receipts in direct tools and background results, report bounded Host evidence, reject unfinished children, and retain image attachment blocks in PTC rendering.
|
|
9
|
+
- Validate schemas with the actual DSH closed subset and PTC SDK generator, validate serialized settings before save, and exercise real React revisions/off/reset plus plugin-to-process cancellation and cleanup.
|
|
10
|
+
|
|
11
|
+
- Add optional native capability v1 controls, verified scoped Codex configuration, V2 backend selection and explicit off across both backends.
|
|
12
|
+
- Preserve full native agent tools/permissions; collect owned child states, model evidence and deduplicated baseline-aware usage without mixing observations into the model answer.
|
|
13
|
+
- Add independent fresh-thread native review, correct review model/effort configuration and explicit native combination constraints.
|
|
14
|
+
- Propagate child events and terminal native evidence through the existing guardian, machine supervisor and read-only recovery lifecycle.
|
|
15
|
+
- Keep the current child turn authoritative under replay and late completion, including failed terminal turns.
|
|
16
|
+
- Keep protocol 1, authentication and absent-native behavior compatible. See [native capabilities](docs/native-capabilities.md) for exact evidence and validation limits.
|
|
17
|
+
|
|
18
|
+
## 0.9.0 (2026-11-12)
|
|
4
19
|
|
|
5
20
|
- Add lifecycle v1 with awaited configuration-checked thread-ready, dispatch-intent,
|
|
6
21
|
dispatch-possible and turn-accepted checkpoints. A failed ACK cannot silently
|
|
@@ -14,12 +14,65 @@ tools.subagent_codex({
|
|
|
14
14
|
})
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
##
|
|
17
|
+
## Native capabilities in DSH and the machine API
|
|
18
|
+
|
|
19
|
+
The 0.10.0 source candidate exposes native agents in the **plugin itself**:
|
|
20
|
+
open the installed bundle in DSH's Plugins page and configure its **codex-pro**
|
|
21
|
+
row. The page offers inheritance, explicit off, concurrency, child model/effort,
|
|
22
|
+
and the V2 backend. Saving applies to the next invocation without remounting the
|
|
23
|
+
plugin; an active run keeps its original snapshot. A conflicting save retains
|
|
24
|
+
your draft until you explicitly reload.
|
|
25
|
+
|
|
26
|
+
`subagent_codex` accepts a per-call `native` value. DSH's existing PTC executor
|
|
27
|
+
automatically generates this tool's SDK; native Codex agents still use Codex's
|
|
28
|
+
own harness and tools. There is no separate PTC executor to install.
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
await tools.subagent_codex({
|
|
32
|
+
description: "Implement independent changes",
|
|
33
|
+
prompt: "Implement the task; delegate independent work when useful.",
|
|
34
|
+
native: { version: 1, agents: { enabled: true, maxConcurrentThreads: 3, backend: "v2" } },
|
|
35
|
+
})
|
|
36
|
+
await tools.subagent_codex_review({
|
|
37
|
+
description: "Review the branch",
|
|
38
|
+
target: { type: "baseBranch", branch: "main" },
|
|
39
|
+
})
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
| Desktop native value | Meaning |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| Omitted | Read the current plugin default; if none is configured, leave the choice to the CLI. |
|
|
45
|
+
| `null` | Explicitly turn agents off across both backends. |
|
|
46
|
+
| A version-1 object | Replace the entire native default, without merging nested fields. |
|
|
47
|
+
| `{version:1, agents:{enabled:false}}` | Explicit off; active controls beside `enabled:false` are rejected. |
|
|
48
|
+
|
|
49
|
+
A present object without `agents` also turns agents off. An enabled override
|
|
50
|
+
under `modelSelection:false` must preserve exactly the configured child model
|
|
51
|
+
and effort, including whether they are omitted. Inherited settings are allowed;
|
|
52
|
+
changing or clearing those model choices requires editing the plugin settings.
|
|
53
|
+
Native controls never expand the configured permission policy.
|
|
54
|
+
|
|
55
|
+
Native review is a **separate, explicitly requested operation**:
|
|
56
|
+
`subagent_codex_review` always creates a fresh ephemeral read-only thread with
|
|
57
|
+
agents off. It accepts an explicit target and never accepts an ignored prompt,
|
|
58
|
+
resume id, image list or output schema. Normal delegation defaults cannot select
|
|
59
|
+
review.
|
|
60
|
+
|
|
61
|
+
Direct tool results keep full evidence in `native` alongside clean `content`.
|
|
62
|
+
Background jobs with native evidence return a JSON string envelope containing
|
|
63
|
+
`text`, `threadId` and `native`; jobs without native controls keep their
|
|
64
|
+
existing text result. The Host provider reads the same live defaults and reports
|
|
65
|
+
bounded native diagnostics through the official Host contract. The standalone
|
|
66
|
+
machine API retains its full receipt and recovery journal. See the
|
|
67
|
+
[complete contract](docs/native-capabilities.md) / [中文契约](docs/native-capabilities.zh.md)
|
|
68
|
+
for settings, per-call policy, provider boundaries, evidence and verification.
|
|
69
|
+
|
|
70
|
+
## 0.9.0: durable lifecycle and recovery
|
|
18
71
|
|
|
19
72
|
The public machine API now exposes awaitable typed checkpoints, a private
|
|
20
73
|
per-invocation journal, and read-only recovery. An independent machine supervisor
|
|
21
|
-
preserves the original terminal receipt after caller death.
|
|
22
|
-
|
|
74
|
+
preserves the original terminal receipt after caller death. Version 0.10.0 remains
|
|
75
|
+
an unpublished source candidate. See the [lifecycle contract](docs/machine-lifecycle.md)
|
|
23
76
|
and [中文说明](docs/machine-lifecycle.zh.md); existing DSH tool behavior is unchanged.
|
|
24
77
|
|
|
25
78
|
## 0.8.0 integration and migration
|
|
@@ -544,7 +597,7 @@ completed commands and file changes, plus Codex's `commentary` messages — so
|
|
|
544
597
|
| continuable / thread reuse | ✅ `session` reuses one; `thread_id` continues any |
|
|
545
598
|
| `maxDepth` / persona / `toolFilter` | ❌ not applicable — see below |
|
|
546
599
|
|
|
547
|
-
The three refusals above are not gaps to be filled. A Codex delegation is a **leaf**: it has no DSH child
|
|
600
|
+
The three refusals above are not gaps to be filled. A Codex delegation is a **DSH leaf**: it has no DSH child
|
|
548
601
|
agent, so a delegation-depth cap has nothing to cap. Its tools are Codex's own — the shell, the patch tool, its
|
|
549
602
|
own MCP servers — not this harness's, so a **tool filter**, which names DSH tools, cannot describe them; restrict
|
|
550
603
|
Codex through its own configuration instead. And a **persona** is a system instruction for a child agent, where
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/* DSH browser closure format. React is supplied by the Host module table. */
|
|
2
|
+
window.__ModuleLoader__.load({
|
|
3
|
+
id: 'dsh-subagent-codex-pro',
|
|
4
|
+
factory(require) {
|
|
5
|
+
const React = require('react')
|
|
6
|
+
const h = React.createElement
|
|
7
|
+
const zh = typeof navigator !== 'undefined' && /^zh\b/i.test(navigator.language)
|
|
8
|
+
const copy = zh ? {
|
|
9
|
+
title: 'Codex 原生协作', mode: '默认能力', inherit: '继承配置', off: '全部关闭', agents: '启用原生 Agent',
|
|
10
|
+
limit: '最多并行子线程(留空使用 CLI 默认)', model: '子 Agent 默认模型(可选)', effort: '子 Agent 默认思考等级(可选)',
|
|
11
|
+
backend: '使用 v2 Agent 后端', save: '保存', reload: '重新载入', saving: '保存中…', saved: '已保存;下一次调用使用新设置。',
|
|
12
|
+
conflict: '未保存:配置可能已更新或不可写。草稿已保留;重新载入后再编辑。', unavailable: '当前配置不可用或不可写。',
|
|
13
|
+
invalidLimit: '并行数必须是大于零且小于 Number.MAX_SAFE_INTEGER 的整数。', invalidText: '子模型和思考等级不能包含 NUL,且 UTF-8 长度不能超过 65536 字节。', effective: '当前生效的 native 配置',
|
|
14
|
+
note: '每次调用可整体覆盖默认值。关闭或继承不会保存下面的 Agent 参数;独立 review 使用 subagent_codex_review 工具。模型选择关闭时,调用者不得改变配置中的子模型与思考等级。',
|
|
15
|
+
summary: '原生 Agent 默认配置与并行能力。',
|
|
16
|
+
} : {
|
|
17
|
+
title: 'Codex native collaboration', mode: 'Default capabilities', inherit: 'Inherit configuration', off: 'All off', agents: 'Enable native Agents',
|
|
18
|
+
limit: 'Maximum concurrent child threads (blank: CLI default)', model: 'Default child model (optional)', effort: 'Default child effort (optional)',
|
|
19
|
+
backend: 'Use the v2 Agent backend', save: 'Save', reload: 'Reload', saving: 'Saving…', saved: 'Saved. New settings apply to the next invocation.',
|
|
20
|
+
conflict: 'Not saved: configuration changed or is not writable. Your draft is retained; reload before editing again.', unavailable: 'Configuration is unavailable or read-only.',
|
|
21
|
+
invalidLimit: 'Concurrency must be a positive integer below Number.MAX_SAFE_INTEGER.', invalidText: 'Child model and effort cannot contain NUL or exceed 65536 UTF-8 bytes.', effective: 'Currently effective native configuration',
|
|
22
|
+
note: 'An invocation can replace these defaults as a whole. Off and inherit do not save Agent fields. Independent review uses subagent_codex_review. With model selection disabled, callers cannot change the configured child model or effort.',
|
|
23
|
+
summary: 'Native Agent defaults and concurrency.',
|
|
24
|
+
}
|
|
25
|
+
const own = (value, key) => value !== null && typeof value === 'object' && Object.prototype.hasOwnProperty.call(value, key)
|
|
26
|
+
const draftOf = state => {
|
|
27
|
+
const native = state?.value?.native
|
|
28
|
+
const agents = native && typeof native === 'object' ? native.agents : undefined
|
|
29
|
+
return {
|
|
30
|
+
mode: !own(state?.user, 'native') ? 'inherit' : native === null || agents?.enabled !== true ? 'off' : 'agents',
|
|
31
|
+
limit: agents?.maxConcurrentThreads === undefined ? '' : String(agents.maxConcurrentThreads),
|
|
32
|
+
model: agents?.defaultModel ?? '', effort: agents?.defaultEffort ?? '', backend: agents?.backend === 'v2',
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const mutationOf = draft => {
|
|
36
|
+
if (draft.mode === 'inherit') return [{ op: 'unset', path: ['native'] }]
|
|
37
|
+
if (draft.mode === 'off') return [{ op: 'set', path: ['native'], value: null }]
|
|
38
|
+
const agents = { enabled: true }
|
|
39
|
+
if (draft.limit.trim() !== '') {
|
|
40
|
+
const n = Number(draft.limit)
|
|
41
|
+
if (!Number.isSafeInteger(n) || n < 1 || n >= Number.MAX_SAFE_INTEGER) throw new Error(copy.invalidLimit)
|
|
42
|
+
agents.maxConcurrentThreads = n
|
|
43
|
+
}
|
|
44
|
+
for (const [field, value] of [['defaultModel', draft.model.trim()], ['defaultEffort', draft.effort.trim()]]) {
|
|
45
|
+
if (value.includes('\0') || new TextEncoder().encode(value).byteLength > 65536) throw new Error(copy.invalidText)
|
|
46
|
+
if (value !== '') agents[field] = value
|
|
47
|
+
}
|
|
48
|
+
if (draft.backend) agents.backend = 'v2'
|
|
49
|
+
return [{ op: 'set', path: ['native'], value: { version: 1, agents } }]
|
|
50
|
+
}
|
|
51
|
+
function Editor({ form }) {
|
|
52
|
+
const state = form?.state
|
|
53
|
+
const [draft, setDraft] = React.useState(() => draftOf(state))
|
|
54
|
+
const [revision, setRevision] = React.useState(state?.revision)
|
|
55
|
+
const [dirty, setDirty] = React.useState(false)
|
|
56
|
+
const [busy, setBusy] = React.useState(false)
|
|
57
|
+
const [notice, setNotice] = React.useState('')
|
|
58
|
+
const mounted = React.useRef(true)
|
|
59
|
+
React.useEffect(() => { mounted.current = true; return () => { mounted.current = false } }, [])
|
|
60
|
+
React.useEffect(() => {
|
|
61
|
+
if (!dirty && !busy) { setDraft(draftOf(state)); setRevision(state?.revision) }
|
|
62
|
+
}, [state?.revision, state?.status, dirty, busy])
|
|
63
|
+
const ready = state?.status === 'ready' && state.writable && Number.isSafeInteger(state.revision)
|
|
64
|
+
const change = (field, value) => {
|
|
65
|
+
if (!ready || busy) return
|
|
66
|
+
if (!dirty) setRevision(state.revision)
|
|
67
|
+
setDraft(previous => ({ ...previous, [field]: value }))
|
|
68
|
+
setDirty(true); setNotice('')
|
|
69
|
+
}
|
|
70
|
+
const reload = () => {
|
|
71
|
+
setDraft(draftOf(state)); setRevision(state?.revision); setDirty(false); setNotice('')
|
|
72
|
+
}
|
|
73
|
+
const save = async event => {
|
|
74
|
+
event.preventDefault()
|
|
75
|
+
if (!ready || busy || !dirty) return
|
|
76
|
+
let ops
|
|
77
|
+
try { ops = mutationOf(draft) } catch (error) { setNotice(error.message); return }
|
|
78
|
+
setBusy(true); setNotice('')
|
|
79
|
+
try {
|
|
80
|
+
const accepted = await form.mutate(ops, revision)
|
|
81
|
+
if (!mounted.current) return
|
|
82
|
+
if (accepted) { setDirty(false); setNotice(copy.saved) }
|
|
83
|
+
else setNotice(copy.conflict)
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (mounted.current) setNotice(String(error?.message ?? error))
|
|
86
|
+
} finally { if (mounted.current) setBusy(false) }
|
|
87
|
+
}
|
|
88
|
+
const textField = (key, title, type = 'text') => h('label', { style: { display: 'grid', gap: 4 } }, title,
|
|
89
|
+
h('input', { type, value: draft[key], onChange: event => change(key, event.target.value), ...(type === 'number' ? { min: 1, step: 1 } : {}) }))
|
|
90
|
+
return h('form', { onSubmit: save, style: { display: 'grid', gap: 12 } },
|
|
91
|
+
h('h3', null, copy.title), h('p', null, copy.note),
|
|
92
|
+
h('fieldset', { disabled: !ready || busy, style: { display: 'grid', gap: 12, border: 0, padding: 0, margin: 0 } },
|
|
93
|
+
h('label', null, copy.mode, h('select', { value: draft.mode, onChange: event => change('mode', event.target.value) },
|
|
94
|
+
...['inherit', 'off', 'agents'].map(value => h('option', { key: value, value }, copy[value])))),
|
|
95
|
+
draft.mode === 'agents' ? h('div', { style: { display: 'grid', gap: 10 } },
|
|
96
|
+
textField('limit', copy.limit, 'number'), textField('model', copy.model), textField('effort', copy.effort),
|
|
97
|
+
h('label', null, h('input', { type: 'checkbox', checked: draft.backend, onChange: event => change('backend', event.target.checked) }), ' ', copy.backend)) : null,
|
|
98
|
+
h('div', { style: { display: 'flex', gap: 8 } },
|
|
99
|
+
h('button', { type: 'submit', disabled: !dirty }, busy ? copy.saving : copy.save),
|
|
100
|
+
h('button', { type: 'button', onClick: reload, disabled: !dirty }, copy.reload))),
|
|
101
|
+
!ready ? h('p', { role: 'status' }, copy.unavailable) : null,
|
|
102
|
+
notice ? h('p', { role: 'status', 'aria-live': 'polite' }, notice) : null,
|
|
103
|
+
h('details', null, h('summary', null, copy.effective),
|
|
104
|
+
h('pre', { style: { whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' } }, state?.value?.native === undefined ? '(CLI / inherited defaults)' : JSON.stringify(state.value.native, null, 2))))
|
|
105
|
+
}
|
|
106
|
+
function NativeSettings(props) {
|
|
107
|
+
return props.view === 'summary' ? h('span', null, copy.summary) : h(Editor, { form: props.form })
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
inject: ['slots'],
|
|
111
|
+
apply(ctx) {
|
|
112
|
+
ctx.effect(() => ctx.slots.inject('plugins.row.config', () => ctx.slots.register({
|
|
113
|
+
name: 'plugins.row.config', key: 'dsh-subagent-codex-pro#codex-pro',
|
|
114
|
+
}, NativeSettings)), 'codex-pro native settings')
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
})
|
|
@@ -28,3 +28,9 @@
|
|
|
28
28
|
#
|
|
29
29
|
# `modelSelection: false` hides `model` and `reasoning_effort` from the
|
|
30
30
|
# tool schema entirely.
|
|
31
|
+
|
|
32
|
+
#
|
|
33
|
+
# Native defaults are intentionally unset. Configure the codex-pro row
|
|
34
|
+
# in the Plugins page or add version: 1 / agents here. native: null
|
|
35
|
+
# explicitly disables agents; omission inherits CLI defaults.
|
|
36
|
+
# Review is always an explicit subagent_codex_review operation.
|