@ckanner/flywheel 0.33.1 → 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 +22 -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 +40 -5
- 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 +33 -11
- 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/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.1.md +78 -0
- package/docs/validation/README.md +3 -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/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 +149 -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,77 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
Export a pure ESM SDK composition API alongside the existing desktop CLI.
|
|
6
|
+
`createSdkPtcComposition` shares daily/heavy policy and returns an isolated PTC
|
|
7
|
+
overlay with canonical base/bundle provenance. `inspectPtcBundle` distinguishes
|
|
8
|
+
same-version source bundles without inferring runtime support, and
|
|
9
|
+
`validateSdkPtcAdmission` checks correlated executor/output/image observations.
|
|
10
|
+
The API neither writes the desktop profile nor mounts a named preset or grants
|
|
11
|
+
GUI/delegation access. Existing profile-preservation tests now use portable ASAR
|
|
12
|
+
fixtures and run in CI without the Mac application.
|
|
13
|
+
|
|
14
|
+
The CLI now creates an absent output file in an existing parent directory,
|
|
15
|
+
including a profile's first `cordis.patch.yml`. Existing files retain the same
|
|
16
|
+
merge behavior; only `ENOENT` is treated as an empty patch. Other read errors
|
|
17
|
+
still stop before writing, and missing parent directories are not created.
|
|
18
|
+
Exclusive first creation also preserves a patch written concurrently.
|
|
19
|
+
|
|
20
|
+
## 0.3.0
|
|
21
|
+
|
|
22
|
+
Both generated presets now use PTC.
|
|
23
|
+
|
|
24
|
+
**LCU moves to a PTC bridge.** The `heavy` preset no longer swaps in
|
|
25
|
+
`tool-executor-native`; LCU's Computer/Browser runtime is exposed as the PTC
|
|
26
|
+
`cua` object instead of a directly-callable `js` tool. A profile regenerated
|
|
27
|
+
against 0.3.0 therefore has `tool-executor-ptc` in both `daily` and `heavy`.
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
## 0.2.0
|
|
31
|
+
|
|
32
|
+
Track DSH Desktop 0.2.2 and stop generating the deprecated presentation selector.
|
|
33
|
+
|
|
34
|
+
**The generated presets mount the executor directly.** DSH 0.2.2 deprecates
|
|
35
|
+
`@deepseek-ai/dsh-agent-tool-presentation`; the base preset now carries
|
|
36
|
+
`@deepseek-ai/dsh-tool-executor-ptc`. The generator replaces that row with
|
|
37
|
+
`tool-executor-ptc` (daily) or `tool-executor-native` (heavy) instead of writing a
|
|
38
|
+
`mode:` field, and still accepts a pre-0.2.2 base that has the old selector.
|
|
39
|
+
|
|
40
|
+
**Re-run after upgrading DSH.** The 0.2.2 base adds `tool-office`, the
|
|
41
|
+
`tool-fs.applyPatch` option, and `tool-subagent-control.interrupt`; a re-run picks
|
|
42
|
+
them up. The `--dry-run` output now matches the current profile block except for
|
|
43
|
+
these base changes.
|
|
44
|
+
|
|
45
|
+
## 0.1.1
|
|
46
|
+
|
|
47
|
+
Track DSH Desktop 0.2.1.
|
|
48
|
+
|
|
49
|
+
**`heavy` uses `native` presentation.** DSH 0.2.1 removed the `both` tool-presentation mode; a preset declares
|
|
50
|
+
one presentation and only `native` and `ptc` exist. `heavy` now asks for `native`, which keeps computer-use tools
|
|
51
|
+
directly callable and gives up the PTC `run_code` surface for that preset. A profile regenerated against the new
|
|
52
|
+
app no longer fails the `heavy` mount.
|
|
53
|
+
|
|
54
|
+
**Re-run after upgrading DSH.** The shipped base changed again: `persona.suffix` with `{{cwd}}` is gone (the
|
|
55
|
+
variable no longer exists and prompt assembly rejects it), `time-context` and `tool-schedule` rows were added,
|
|
56
|
+
`backgroundMode` was removed from `tool-subagent`, and the per-preset `tool-subagent-codex` /
|
|
57
|
+
`tool-subagent-claude-code` / `tool-ralph` rows were dropped. The generator reads the installed base, so a re-run
|
|
58
|
+
picks all of it up; the tests now pin the current surface instead of the old Codex-row expectation.
|
|
59
|
+
|
|
60
|
+
## 0.1.0
|
|
61
|
+
|
|
62
|
+
First release, as a project of its own.
|
|
63
|
+
|
|
64
|
+
**Moved out of `dsh-plugin-lcu`.** It generates presets that serve more than one plugin, and the region it writes
|
|
65
|
+
is also written by DSH's settings UI, so it does not belong to any one plugin — least of all one whose script was
|
|
66
|
+
rewriting a profile region shared with settings it does not own. The move is behaviour-preserving: generating
|
|
67
|
+
from the same profile with both produced byte-identical output.
|
|
68
|
+
|
|
69
|
+
**The marker still names the old path.** `BEGIN` contains `scripts/gen-presets.mjs` on purpose: a profile
|
|
70
|
+
generated before the move already has that exact string, and a different one would make the script append a
|
|
71
|
+
second block instead of replacing the first.
|
|
72
|
+
|
|
73
|
+
**Replaces only its own entry.** The region between the markers is not exclusively this script's — the settings
|
|
74
|
+
UI appends `agent-preset-registry` and `subagent-model-selection-settings` there. Splicing the whole region
|
|
75
|
+
deleted them; re-emitting them after the block left another copy behind on every later settings write. A run now
|
|
76
|
+
replaces the single `- insert:` entry it wrote and leaves everything else byte for byte where it is, so there is
|
|
77
|
+
nothing to move and nothing to accumulate.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kanner
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# dsh-preset-generator
|
|
2
|
+
|
|
3
|
+
English | [中文](docs/README.zh.md)
|
|
4
|
+
|
|
5
|
+
Materialize [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) agent presets from the base preset
|
|
6
|
+
that ships inside the application.
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
node src/gen-presets.mjs # write into ~/.dsh/profiles/desktop
|
|
10
|
+
node src/gen-presets.mjs --dry-run # print the block, write nothing
|
|
11
|
+
node src/gen-presets.mjs --out FILE # write somewhere else
|
|
12
|
+
node src/gen-presets.mjs --daily-only # refuse if the file already defines heavy
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
An absent output file is created in its existing parent directory. This also
|
|
16
|
+
supports a profile that does not yet have `cordis.patch.yml`. Existing files are
|
|
17
|
+
updated in place, preserving other settings. The parent directory must already
|
|
18
|
+
exist; unreadable files, directory targets and other filesystem errors stop the
|
|
19
|
+
run without treating existing content as empty.
|
|
20
|
+
New-file creation is exclusive: a patch created by another writer during the
|
|
21
|
+
run is not overwritten.
|
|
22
|
+
|
|
23
|
+
## Why this exists
|
|
24
|
+
|
|
25
|
+
DSH agent presets have **no inheritance**. A preset's `plugins` list is its complete composition, and a patch
|
|
26
|
+
replaces a whole entry rather than merging into it. So a custom preset has to restate the base preset — and the
|
|
27
|
+
base ships inside the application and changes when DSH is upgraded, which makes hand-copying it a losing game.
|
|
28
|
+
|
|
29
|
+
This writes that restatement: it reads the base from the installed `app.asar`, applies a policy, and writes the
|
|
30
|
+
marked region of a profile's `cordis.patch.yml`.
|
|
31
|
+
|
|
32
|
+
It is deliberately **not part of a plugin**. The presets it generates serve more than one plugin, and the region
|
|
33
|
+
it writes is also written by DSH's own settings UI. A tool that belongs to one plugin ends up rewriting a file
|
|
34
|
+
that several parties own; this one belongs to nobody in particular, which is the honest description.
|
|
35
|
+
|
|
36
|
+
## The marker is load-bearing
|
|
37
|
+
|
|
38
|
+
The `BEGIN` marker contains the path this file lived at when the first profiles were generated. It has to keep
|
|
39
|
+
containing that path: a profile generated before the move already has the exact string, and a different one would
|
|
40
|
+
make this script **append a second block** instead of replacing the first.
|
|
41
|
+
|
|
42
|
+
## What it writes
|
|
43
|
+
|
|
44
|
+
| Preset | Base | Policy |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| `daily` (日常) | the shipped `ptc` preset | PTC executor; daily coding label |
|
|
47
|
+
| `heavy` (重活) | the same `ptc` preset | the same executor policy; heavy work label |
|
|
48
|
+
|
|
49
|
+
Both presets use PTC. LCU exposes its ChatGPT-app Computer/Browser runtime through the PTC `cua`
|
|
50
|
+
bridge: `cua.getApp`, `cua.getTab`, screenshots and handle methods all run through `run_code`, so
|
|
51
|
+
`heavy` no longer opts out of PTC for a directly-callable native `js` tool.
|
|
52
|
+
The generated plugin lists currently have the same policy. GUI availability and
|
|
53
|
+
Codex delegation depend on separately mounted providers and their allowlists;
|
|
54
|
+
the name `heavy` does not itself grant either capability.
|
|
55
|
+
|
|
56
|
+
The official Codex tool is no longer a per-preset row: as of DSH 0.2.1 the `@deepseek-ai/dsh-subagent-codex`
|
|
57
|
+
bundle contributes a **global** `subagent_codex` tool, and a separate provider (`dsh-subagent-codex-pro`)
|
|
58
|
+
registers its own same-named tool per Agent for these presets, shadowing the global one. Two registrations in
|
|
59
|
+
*one* scope still collide, so nothing may enable a same-name `tool-subagent` row beside that provider.
|
|
60
|
+
|
|
61
|
+
## What it must not touch
|
|
62
|
+
|
|
63
|
+
The region between the markers is **not exclusively this script's**. DSH's settings UI appends its own entries
|
|
64
|
+
there — `agent-preset-registry` and `subagent-model-selection-settings` have both landed between the markers —
|
|
65
|
+
and so may anything else that writes a profile patch.
|
|
66
|
+
|
|
67
|
+
So a run replaces **only its own entry**: the `- insert:` shell carrying the preset rows. Everything else in the
|
|
68
|
+
region is left exactly where it is, byte for byte.
|
|
69
|
+
|
|
70
|
+
That is a stronger property than handling them carefully. An earlier version replaced the whole region, then
|
|
71
|
+
re-emitted the entries it had not written after the block, then de-duplicated them against copies already
|
|
72
|
+
outside it — three mechanisms, each fixing a failure of the one before:
|
|
73
|
+
|
|
74
|
+
| Approach | What went wrong |
|
|
75
|
+
|---|---|
|
|
76
|
+
| Replace the region | Deleted the settings entries silently |
|
|
77
|
+
| Re-emit them after the block | The next settings write landed inside again, and the run left another copy each time |
|
|
78
|
+
| De-duplicate by id | Correct, but ~60 lines that exist only to undo a move that should not happen |
|
|
79
|
+
|
|
80
|
+
Replacing one entry has none of those failure modes, because nothing is ever moved: there is no copy to
|
|
81
|
+
de-duplicate and nothing to accumulate. Entries that are not ours are not read, not rewritten, and not
|
|
82
|
+
relocated — including values this script would not have chosen.
|
|
83
|
+
|
|
84
|
+
## Development
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
npm test # portable CLI/ASAR fixtures and SDK composition contracts
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The tests use a synthetic ASAR and profile patch, so preservation, idempotency,
|
|
91
|
+
legacy selectors and direct executor rows run on CI without a desktop app. They
|
|
92
|
+
do not certify a user's installed application or call a model.
|
|
93
|
+
|
|
94
|
+
## SDK composition API
|
|
95
|
+
|
|
96
|
+
The package also exports a pure ESM API. Importing it performs no file access,
|
|
97
|
+
runtime launch, model call, or desktop-profile write. Desktop generation and SDK
|
|
98
|
+
composition share `src/policy.mjs`; SDK callers apply a separate profile patch.
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
import {
|
|
102
|
+
inspectPtcBundle, createSdkPtcComposition, validateSdkPtcAdmission,
|
|
103
|
+
} from 'dsh-preset-generator'
|
|
104
|
+
|
|
105
|
+
// Values read by the caller from the exact installed bundle, not a version guess.
|
|
106
|
+
const evidence = inspectPtcBundle({ packageJson, files, sourceRevision })
|
|
107
|
+
const composition = createSdkPtcComposition({
|
|
108
|
+
runtimeProfile: 'flywheel-sdk', preset: 'heavy', baseRows, ptcEvidence: evidence,
|
|
109
|
+
})
|
|
110
|
+
// Apply composition.overlay via the SDK host's profile loader, then run a probe.
|
|
111
|
+
const admission = validateSdkPtcAdmission(composition, observed, { requireImages: true })
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`baseRows` are effective JSON plugin rows with unique ids, after the host has
|
|
115
|
+
resolved its configuration; nested patch `insert` directives are rejected. The
|
|
116
|
+
overlay disables the native executor, enables PTC, and sets
|
|
117
|
+
`tool-fs.applyPatch: false`, preserving supplied fs/PTC configuration. A known
|
|
118
|
+
legacy `tool-presentation` row is disabled too. `desktop` is refused as an SDK
|
|
119
|
+
profile. Absent `baseRows`, the result is the official minimal SDK PTC patch; it
|
|
120
|
+
does not claim to have inspected custom configuration.
|
|
121
|
+
|
|
122
|
+
The returned `digest` binds the profile, policy, base, overlay and bundle
|
|
123
|
+
evidence. `compositionDigest(value)` hashes canonical JSON; files are supplied as
|
|
124
|
+
relative paths mapped to their exact text. `inspectPtcBundle` returns per-file
|
|
125
|
+
hashes, actual manifest exports and descriptive source clues. It always leaves
|
|
126
|
+
`outputContract: 'unknown'`: release rc.3 and later master can share
|
|
127
|
+
`0.2.2-rc.3` while changing exports and image/output semantics. Source identity
|
|
128
|
+
must accompany the version. See the [release PTC manifest](https://github.com/deepseek-ai/deepseek-harness/blob/38a6a5c965aa99ab525161efb7cc5cf1fe62fced/packages/core/tool-executor-ptc/package.json)
|
|
129
|
+
and [later manifest](https://github.com/deepseek-ai/deepseek-harness/blob/39163dd30f703a79079e2d2446545cff3c453738/packages/core/tool-executor-ptc/package.json).
|
|
130
|
+
|
|
131
|
+
Admission requires observed `compositionDigest`, `runtimeProfile`,
|
|
132
|
+
`executor: 'ptc'`, `activeExecutors: ['ptc']`, `tools` containing `run_code`,
|
|
133
|
+
`probeStatus: 'passed'`, and a verified `outputContract` of
|
|
134
|
+
`deferred-context-v1` or `inspect-image-v1`. Supplied bundle evidence requires a
|
|
135
|
+
matching observed `bundleDigest`. `requireImages` additionally requires
|
|
136
|
+
`imageProbeStatus: 'passed'`; an absent image probe never advertises image
|
|
137
|
+
support. The worker obtains these facts from its actual runtime. This library
|
|
138
|
+
only checks the receipt, hashes all JSON observations, and rejects a composition
|
|
139
|
+
modified after generation. It does not authenticate the receipt transport.
|
|
140
|
+
|
|
141
|
+
`provenance.kind` is `sdk-global-composition` and `namedPresetMounted` is always
|
|
142
|
+
false: sharing daily/heavy policy is not a named-preset SDK RPC. GUI and
|
|
143
|
+
delegation remain separate grants. A consumer adding permission or provider
|
|
144
|
+
overlays must also bind the complete final configuration in its own digest.
|
|
145
|
+
Executor image probing and delivery through the provider are separate checks.
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
|
|
149
|
+
MIT.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# dsh-preset-generator
|
|
2
|
+
|
|
3
|
+
[English](../README.md) | 中文
|
|
4
|
+
|
|
5
|
+
从**应用内自带的基底 preset** 生成 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 的 agent preset。
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
node src/gen-presets.mjs # 写进 ~/.dsh/profiles/desktop
|
|
9
|
+
node src/gen-presets.mjs --dry-run # 只打印块,不写文件
|
|
10
|
+
node src/gen-presets.mjs --out FILE # 写到别处
|
|
11
|
+
node src/gen-presets.mjs --daily-only # 文件里已有 heavy 时拒绝
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
目标文件不存在时,会在已有的父目录中创建文件;首次使用、profile 里尚无
|
|
15
|
+
`cordis.patch.yml` 时也可以直接生成。已有文件则原地更新并保留其他设置。
|
|
16
|
+
父目录必须已经存在;无法读取的文件、目录类型的目标及其他文件系统错误会终止
|
|
17
|
+
操作,不会把已有内容当作空文件处理。
|
|
18
|
+
新文件以独占方式创建,运行期间其他程序抢先写入的补丁不会被覆盖。
|
|
19
|
+
|
|
20
|
+
## 为什么需要它
|
|
21
|
+
|
|
22
|
+
DSH 的 agent preset **没有继承**。一个 preset 的 `plugins` 列表就是它完整的组合,而 patch 是**整体替换**一个条目而不是合并进去。所以自定义 preset 必须把基底 preset 重述一遍 —— 而基底随应用一起发布、会随 DSH 升级变化,手抄注定跟不上。
|
|
23
|
+
|
|
24
|
+
本工具做的就是这份"重述":从已安装的 `app.asar` 里读出基底,套用一份策略,写进某个 profile 的 `cordis.patch.yml` 的**标记区间**。
|
|
25
|
+
|
|
26
|
+
它**刻意不属于任何插件**。它生成的 preset 服务于不止一个插件,而它写入的那个区间**DSH 自己的设置 UI 也会写**。一个属于某个插件的工具,最后就在改写一个多方共有的文件;这个工具不属于任何一方,这才是诚实的描述。
|
|
27
|
+
|
|
28
|
+
## 标记是承重的
|
|
29
|
+
|
|
30
|
+
`BEGIN` 标记里写着这个文件**曾经所在的路径**。它必须继续写着那个路径:在本次搬迁之前生成的 profile 里已经含有那串确切文本,换一串会让本脚本**追加第二个块**而不是替换第一个。
|
|
31
|
+
|
|
32
|
+
## 它写什么
|
|
33
|
+
|
|
34
|
+
| preset | 基底 | 策略 |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| `daily`(日常) | 自带的 `ptc` preset | PTC executor;标记为日常编码 |
|
|
37
|
+
| `heavy`(重活) | 同一个 `ptc` preset | 相同的 executor 策略;标记为重活 |
|
|
38
|
+
|
|
39
|
+
两个 preset 都使用 PTC。LCU 通过 PTC 的 `cua` 桥暴露 ChatGPT 应用的 Computer/Browser runtime;`cua.getApp`、`cua.getTab`、截图和 handle 方法都经 `run_code` 运行,重活不再切换到 native executor 的直接 `js` 工具。目前生成的插件列表使用相同策略;GUI 与 Codex 委派是否可用取决于另外挂载的 provider 和各自 allowlist,`heavy` 这个名字本身不授予这些能力。
|
|
40
|
+
|
|
41
|
+
官方的 Codex 工具不再是 per-preset 行:从 DSH 0.2.1 起,`@deepseek-ai/dsh-subagent-codex` bundle 提供的是**全局**的 `subagent_codex` 工具;独立 provider(`dsh-subagent-codex-pro`)在这些 preset 里按 Agent 注册同名工具,遮蔽全局那个。而**同一个** scope 内的两次注册仍会冲突,所以不能在这个 provider 之外再启用同名的 `tool-subagent` 行。
|
|
42
|
+
|
|
43
|
+
## 它绝不能碰的东西
|
|
44
|
+
|
|
45
|
+
标记之间的区间**不是本脚本独占的**。DSH 的设置 UI 会把它的条目追加在那里 —— `agent-preset-registry` 和 `subagent-model-selection-settings` 都曾落在标记之间 —— 别的往 profile patch 里写东西的组件也可能如此。
|
|
46
|
+
|
|
47
|
+
所以一次运行**只替换它自己那一个条目**:那个携带 preset 行的 `- insert:` 外壳。区间内其他一切**逐字节留在原位**。
|
|
48
|
+
|
|
49
|
+
这比"小心处理它们"是更强的性质。早先的实现替换整个区间,然后把不是自己写的条目重新发到块后面,再对区间外的副本做去重 —— 三个机制,每一个都在补前一个的失败:
|
|
50
|
+
|
|
51
|
+
| 做法 | 出了什么问题 |
|
|
52
|
+
|---|---|
|
|
53
|
+
| 替换整个区间 | 静默删掉了设置项 |
|
|
54
|
+
| 把它们重新发到块后 | 下一次设置写入又落进区间,每次运行多留一份 |
|
|
55
|
+
| 按 id 去重 | 对了,但那是约 60 行只为撤销一次**本不该发生的搬动** |
|
|
56
|
+
|
|
57
|
+
只替换一个条目就不存在这些失败模式,因为**什么都不搬动**:没有副本要去重,也没有东西会累积。不属于我们的条目**连读都不读、不重写、不搬移** —— 包括我们不会选的值。
|
|
58
|
+
|
|
59
|
+
## 开发
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
npm test # 跨平台 CLI/ASAR 夹具及 SDK composition 契约
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
测试使用合成 ASAR 与 profile patch,配置保留、幂等、旧 selector 和新 executor 行都会在没有桌面应用的 CI 上实际执行。它们不认证用户已安装的应用,也不调用模型。
|
|
66
|
+
|
|
67
|
+
## SDK composition API
|
|
68
|
+
|
|
69
|
+
本包还导出纯 ESM API。导入不会读取文件、启动 runtime、调用模型或改写桌面 profile。桌面生成器与 SDK composition 共用 `src/policy.mjs`,SDK 调用方将 patch 应用于独立 profile。
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
import {
|
|
73
|
+
inspectPtcBundle, createSdkPtcComposition, validateSdkPtcAdmission,
|
|
74
|
+
} from 'dsh-preset-generator'
|
|
75
|
+
|
|
76
|
+
// 调用方从确切的已安装 bundle 读取这些值,不能只猜版本号。
|
|
77
|
+
const evidence = inspectPtcBundle({ packageJson, files, sourceRevision })
|
|
78
|
+
const composition = createSdkPtcComposition({
|
|
79
|
+
runtimeProfile: 'flywheel-sdk', preset: 'heavy', baseRows, ptcEvidence: evidence,
|
|
80
|
+
})
|
|
81
|
+
// 经 SDK 宿主的 profile loader 应用 composition.overlay,然后执行探针。
|
|
82
|
+
const admission = validateSdkPtcAdmission(composition, observed, { requireImages: true })
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`baseRows` 是宿主解析完配置后的有效 JSON 插件行,id 必须唯一,不能传未展开的 `insert` 指令。overlay 禁用 native executor、启用 PTC、设置 `tool-fs.applyPatch: false`,同时保留已提供的 fs/PTC 配置。已知的旧 `tool-presentation` 行也会被禁用。SDK profile 拒绝 `desktop`;不提供 `baseRows` 时,只产生官方最小 SDK PTC patch,不声称检查过自定义配置。
|
|
86
|
+
|
|
87
|
+
返回的 `digest` 绑定 profile、策略、基底、overlay 和 bundle 证据。`compositionDigest(value)` 对规范化 JSON 求哈希;`files` 是相对路径到确切文本的映射。`inspectPtcBundle` 返回各文件哈希、实际 manifest exports 和描述性的源码线索,始终保留 `outputContract: 'unknown'`:rc.3 release 与后续 master 可能同为 `0.2.2-rc.3`,但 exports 与图片/输出语义不同,因此版本必须附带源码身份。参见 [release PTC manifest](https://github.com/deepseek-ai/deepseek-harness/blob/38a6a5c965aa99ab525161efb7cc5cf1fe62fced/packages/core/tool-executor-ptc/package.json) 与[后续 manifest](https://github.com/deepseek-ai/deepseek-harness/blob/39163dd30f703a79079e2d2446545cff3c453738/packages/core/tool-executor-ptc/package.json)。
|
|
88
|
+
|
|
89
|
+
准入要求实际观测到匹配的 `compositionDigest`、`runtimeProfile`、`executor: 'ptc'`、`activeExecutors: ['ptc']`、包含 `run_code` 的 `tools`、`probeStatus: 'passed'`,以及已验证的 `outputContract`(`deferred-context-v1` 或 `inspect-image-v1`)。若 composition 提供了 bundle 证据,观测的 `bundleDigest` 也必须匹配。`requireImages` 还要求 `imageProbeStatus: 'passed'`,缺少图片探针永远不能声明已支持图片。worker 应从真实 runtime 获取这些事实;本库只校验回执、哈希 JSON 观测,并拒绝生成后被修改的 composition,不认证回执的传输通道。
|
|
90
|
+
|
|
91
|
+
`provenance.kind` 为 `sdk-global-composition`,`namedPresetMounted` 恒为 false:共享日常/重活策略不代表存在 named-preset SDK RPC。GUI 与委派必须分别授权。消费方另外增加权限或 provider overlay 后,仍需在自己的总摘要中绑定完整最终配置。executor 图片探针与 provider 向模型传图是不同的验证。
|
|
92
|
+
|
|
93
|
+
## 许可
|
|
94
|
+
|
|
95
|
+
MIT。
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-preset-generator",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Materialize DeepSeek Harness agent presets from the base preset shipped inside the application.",
|
|
5
|
+
"private": true,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/composition.mjs",
|
|
9
|
+
"./composition": "./src/composition.mjs",
|
|
10
|
+
"./policy": "./src/policy.mjs",
|
|
11
|
+
"./package.json": "./package.json"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"bin": {
|
|
15
|
+
"dsh-preset-generator": "./src/gen-presets.mjs"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"src",
|
|
19
|
+
"README.md",
|
|
20
|
+
"docs/README.zh.md",
|
|
21
|
+
"CHANGELOG.md",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"presets": "node src/gen-presets.mjs",
|
|
26
|
+
"test": "node --test \"tests/*.spec.mjs\""
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=22.18.0"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://github.com/ckanner/dsh-preset-generator#readme",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/ckanner/dsh-preset-generator.git"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"deepseek-harness",
|
|
38
|
+
"dsh",
|
|
39
|
+
"cordis",
|
|
40
|
+
"presets",
|
|
41
|
+
"configuration"
|
|
42
|
+
]
|
|
43
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/** Pure SDK composition and observation checks; importing this module performs no I/O. */
|
|
2
|
+
import { createHash } from 'node:crypto'
|
|
3
|
+
import { presetPolicy } from './policy.mjs'
|
|
4
|
+
|
|
5
|
+
export { PRESETS, presetPolicy } from './policy.mjs'
|
|
6
|
+
export const COMPOSITION_PROTOCOL = 1
|
|
7
|
+
const CONTRACTS = new Set(['deferred-context-v1', 'inspect-image-v1'])
|
|
8
|
+
const record = value => typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
9
|
+
|
|
10
|
+
function canonical(value) {
|
|
11
|
+
if (value === null || typeof value === 'boolean' || typeof value === 'string') return value
|
|
12
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value
|
|
13
|
+
if (Array.isArray(value)) return value.map(canonical)
|
|
14
|
+
if (!record(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) {
|
|
15
|
+
throw new TypeError('Composition inputs must contain only JSON values')
|
|
16
|
+
}
|
|
17
|
+
return Object.fromEntries(Object.keys(value).sort().map(key => [key, canonical(value[key])]))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Stable content identity, independent of object property insertion order. */
|
|
21
|
+
export function compositionDigest(value) {
|
|
22
|
+
return createHash('sha256').update(JSON.stringify(canonical(value))).digest('hex')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function identityOf(composition) {
|
|
26
|
+
return { version: composition.version, runtimeProfile: composition.runtimeProfile,
|
|
27
|
+
presetPolicy: composition.presetPolicy, overlay: composition.overlay,
|
|
28
|
+
provenance: composition.provenance, requirements: composition.requirements }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Inspect caller-supplied, actually read bundle files. This is static provenance,
|
|
33
|
+
* never proof that an executor ran or that a model received an image.
|
|
34
|
+
* Versions alone deliberately cannot select the output protocol.
|
|
35
|
+
*/
|
|
36
|
+
export function inspectPtcBundle({ packageJson, files, sourceRevision } = {}) {
|
|
37
|
+
if (!record(packageJson) || packageJson.name !== '@deepseek-ai/dsh-tool-executor-ptc'
|
|
38
|
+
|| typeof packageJson.version !== 'string' || !packageJson.version) {
|
|
39
|
+
throw new TypeError('Expected the actual dsh-tool-executor-ptc package manifest')
|
|
40
|
+
}
|
|
41
|
+
if (!record(files) || !Object.keys(files).length || Object.entries(files).some(([name, value]) =>
|
|
42
|
+
!name || name.startsWith('/') || name.split('/').includes('..') || typeof value !== 'string')) {
|
|
43
|
+
throw new TypeError('Supply nonempty relative bundle filenames and their exact text')
|
|
44
|
+
}
|
|
45
|
+
if (sourceRevision !== undefined && (typeof sourceRevision !== 'string' || !sourceRevision)) {
|
|
46
|
+
throw new TypeError('sourceRevision must be a nonempty source identity')
|
|
47
|
+
}
|
|
48
|
+
const exports = record(packageJson.exports) ? structuredClone(packageJson.exports) : {}
|
|
49
|
+
const joined = Object.values(files).join('\n')
|
|
50
|
+
const inspectImage = /\binspect\b/.test(joined) && /\bPtcImage\b|\bImage\b/.test(joined)
|
|
51
|
+
const deferred = /deferredContext|deferred context/i.test(joined)
|
|
52
|
+
const clues = {
|
|
53
|
+
privateProgramExport: Object.hasOwn(exports, './run-code'),
|
|
54
|
+
inspectImage: inspectImage,
|
|
55
|
+
deferredContext: deferred,
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
version: COMPOSITION_PROTOCOL, kind: 'static-bundle',
|
|
59
|
+
package: packageJson.name, packageVersion: packageJson.version,
|
|
60
|
+
bundleDigest: compositionDigest({ packageJson, files }),
|
|
61
|
+
files: Object.fromEntries(Object.entries(files).sort(([a], [b]) => a.localeCompare(b))
|
|
62
|
+
.map(([name, content]) => [name, createHash('sha256').update(content).digest('hex')])),
|
|
63
|
+
exports, clues,
|
|
64
|
+
// Clues are descriptive: an unknown or misleading README cannot certify behavior.
|
|
65
|
+
outputContract: 'unknown',
|
|
66
|
+
...(sourceRevision === undefined ? {} : { sourceRevision }),
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Produce an SDK-only patch. baseRows, when supplied, are the effective rows
|
|
72
|
+
* obtained through the host loader, not raw nested insert directives. Cordis
|
|
73
|
+
* replaces config values, so preserve unrelated fs/executor config explicitly.
|
|
74
|
+
* This does not mount a named Agent Preset, enable children, or grant GUI access.
|
|
75
|
+
*/
|
|
76
|
+
export function createSdkPtcComposition({ runtimeProfile = 'sdk', preset = 'heavy', baseRows = [], ptcEvidence } = {}) {
|
|
77
|
+
if (typeof runtimeProfile !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(runtimeProfile)
|
|
78
|
+
|| runtimeProfile === 'desktop') throw new TypeError('A non-desktop SDK runtime profile is required')
|
|
79
|
+
const policy = presetPolicy(preset)
|
|
80
|
+
if (!Array.isArray(baseRows)) throw new TypeError('baseRows must be effective plugin rows')
|
|
81
|
+
const rows = new Map()
|
|
82
|
+
for (const row of baseRows) {
|
|
83
|
+
canonical(row)
|
|
84
|
+
if (!record(row) || typeof row.id !== 'string' || !row.id || rows.has(row.id)
|
|
85
|
+
|| Object.hasOwn(row, 'insert')) throw new TypeError('baseRows require unique plugin ids, not patch directives')
|
|
86
|
+
rows.set(row.id, row)
|
|
87
|
+
}
|
|
88
|
+
if (ptcEvidence !== undefined && (!record(ptcEvidence) || ptcEvidence.version !== COMPOSITION_PROTOCOL
|
|
89
|
+
|| ptcEvidence.kind !== 'static-bundle' || !/^[a-f0-9]{64}$/.test(ptcEvidence.bundleDigest))) {
|
|
90
|
+
throw new TypeError('ptcEvidence must be an inspectPtcBundle result')
|
|
91
|
+
}
|
|
92
|
+
const configFor = id => {
|
|
93
|
+
const config = rows.get(id)?.config
|
|
94
|
+
if (config !== undefined && !record(config)) throw new TypeError(`${id}.config must be an object`)
|
|
95
|
+
return structuredClone(config ?? {})
|
|
96
|
+
}
|
|
97
|
+
const overlay = [
|
|
98
|
+
...(rows.has('tool-presentation') ? [{ id: 'tool-presentation', disabled: true }] : []),
|
|
99
|
+
{ id: 'tool-executor-native', disabled: true },
|
|
100
|
+
{ id: 'tool-executor-ptc', disabled: false, ...(rows.has('tool-executor-ptc') ? { config: configFor('tool-executor-ptc') } : {}) },
|
|
101
|
+
{ id: 'tool-fs', config: { ...configFor('tool-fs'), applyPatch: false } },
|
|
102
|
+
]
|
|
103
|
+
const composition = {
|
|
104
|
+
version: COMPOSITION_PROTOCOL, runtimeProfile, presetPolicy: policy.id,
|
|
105
|
+
overlay,
|
|
106
|
+
provenance: {
|
|
107
|
+
kind: 'sdk-global-composition', baseDigest: compositionDigest(baseRows),
|
|
108
|
+
bundleDigest: ptcEvidence?.bundleDigest ?? null,
|
|
109
|
+
staticEvidence: ptcEvidence === undefined ? null : structuredClone(ptcEvidence),
|
|
110
|
+
namedPresetMounted: false,
|
|
111
|
+
},
|
|
112
|
+
requirements: {
|
|
113
|
+
executor: 'ptc', tools: ['run_code'], outputContract: 'observed-runtime',
|
|
114
|
+
guiGranted: false, delegationGranted: false,
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
return { ...composition, digest: compositionDigest(identityOf(composition)) }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Validate facts supplied by the owning worker after its real runtime probe.
|
|
122
|
+
* No subprocess, model, tool or GUI action is performed here. This validator is
|
|
123
|
+
* not an attestation transport: its caller must authenticate/correlate the probe.
|
|
124
|
+
*/
|
|
125
|
+
export function validateSdkPtcAdmission(composition, observed, { requireImages = false } = {}) {
|
|
126
|
+
const failures = []
|
|
127
|
+
if (!record(composition) || composition.version !== COMPOSITION_PROTOCOL
|
|
128
|
+
|| !/^[a-f0-9]{64}$/.test(composition.digest ?? '')) throw new TypeError('Invalid composition')
|
|
129
|
+
if (compositionDigest(identityOf(composition)) !== composition.digest) throw new TypeError('Composition changed after generation')
|
|
130
|
+
if (!record(observed)) observed = {}
|
|
131
|
+
if (observed.compositionDigest !== composition.digest) failures.push('composition-digest-mismatch')
|
|
132
|
+
if (observed.runtimeProfile !== composition.runtimeProfile) failures.push('runtime-profile-mismatch')
|
|
133
|
+
if (observed.executor !== 'ptc' || !Array.isArray(observed.activeExecutors)
|
|
134
|
+
|| observed.activeExecutors.length !== 1 || observed.activeExecutors[0] !== 'ptc') failures.push('executor-not-exclusive-ptc')
|
|
135
|
+
if (!Array.isArray(observed.tools) || !observed.tools.includes('run_code')) failures.push('run-code-not-registered')
|
|
136
|
+
if (!CONTRACTS.has(observed.outputContract)) failures.push('output-contract-unverified')
|
|
137
|
+
if (observed.probeStatus !== 'passed') failures.push('runtime-probe-not-passed')
|
|
138
|
+
if (composition.provenance.bundleDigest !== null && observed.bundleDigest !== composition.provenance.bundleDigest) {
|
|
139
|
+
failures.push('bundle-digest-mismatch')
|
|
140
|
+
}
|
|
141
|
+
if (requireImages && observed.imageProbeStatus !== 'passed') failures.push('image-contract-unverified')
|
|
142
|
+
return {
|
|
143
|
+
version: COMPOSITION_PROTOCOL, eligible: failures.length === 0, failures,
|
|
144
|
+
compositionDigest: composition.digest,
|
|
145
|
+
observedDigest: compositionDigest(observed),
|
|
146
|
+
// Absent optional image evidence never turns into a positive capability.
|
|
147
|
+
imagesVerified: failures.length === 0 && observed.imageProbeStatus === 'passed',
|
|
148
|
+
namedPresetMounted: false,
|
|
149
|
+
}
|
|
150
|
+
}
|