dsh-creator-mode-plus 0.3.8
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/AGENTS.md +40 -0
- package/CHANGELOG.md +104 -0
- package/LICENSE +21 -0
- package/README.en.md +83 -0
- package/README.md +82 -0
- package/docs/bridge-contract.md +431 -0
- package/docs/dshx-v0.7-alignment.md +108 -0
- package/docs/screenshots/already-installed.png +0 -0
- package/docs/screenshots/install.png +0 -0
- package/docs/screenshots/mode-picker.gif +0 -0
- package/docs/screenshots/mode-picker.png +0 -0
- package/docs/screenshots/mode-selected.png +0 -0
- package/dshx.yml +16 -0
- package/package.json +67 -0
- package/preset/preset.yml +2 -0
- package/preset/skills/creator-mode-plus/SKILL.md +101 -0
- package/preset/skills/creator-mode-plus/existing-plugin-trials.md +60 -0
- package/preset/skills/creator-mode-plus/maintenance.md +48 -0
- package/preset/skills/creator-mode-plus/verification-recovery.md +54 -0
- package/scripts/install.mjs +292 -0
- package/scripts/verify-dshx.mjs +77 -0
- package/scripts/verify-harness-install.mjs +101 -0
- package/src/auth.js +26 -0
- package/src/client.js +112 -0
- package/src/compatibility.js +261 -0
- package/src/delivery.js +331 -0
- package/src/index.js +377 -0
- package/src/preset-015.js +28 -0
- package/src/runner.js +464 -0
- package/src/safety.js +79 -0
- package/src/takeover.js +157 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: creator-mode-plus
|
|
3
|
+
description: Develop and deliver DSH plugins from a Creator+ conversation. Use for new plugins, existing-plugin changes, activation, functional verification, removal, recovery, or required Harness upgrades.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Creator Mode+
|
|
7
|
+
|
|
8
|
+
Complete the requested plugin in the user's DSH conversation: build, activate on
|
|
9
|
+
the current Host, then exercise its behavior. Use the DSHX v0.7 fixed bridge and
|
|
10
|
+
public Cordis/client extension points. Tie evidence to the claimed source and
|
|
11
|
+
each changed runtime surface.
|
|
12
|
+
|
|
13
|
+
## Deliver a plugin
|
|
14
|
+
|
|
15
|
+
1. **Identify the runtime.** Call `dshx_status`. Continue when the bridge reports
|
|
16
|
+
its supported contract and one identified current Host. Use that Host; its
|
|
17
|
+
process keeps the conversation alive. On resume, continue the recorded pending
|
|
18
|
+
delivery. Status alone is inventory.
|
|
19
|
+
2. **Claim the source.** Call `dshx_claim_plugin` before edits. For a new plugin,
|
|
20
|
+
immediately call `dshx_scaffold` and edit its returned workspace path; the tool
|
|
21
|
+
creates any Harness link. For an existing plugin, read
|
|
22
|
+
[Existing-plugin trials](existing-plugin-trials.md). Completion: one identified
|
|
23
|
+
source and an accepted claim.
|
|
24
|
+
3. **Implement and build.** Read `dshx kb cat contracts/client-build` for a client,
|
|
25
|
+
or the selected extension-point contract for a server. Run the project's build
|
|
26
|
+
and relevant tests, then `dshx_check`. Repair package-local build failures
|
|
27
|
+
through the existing-plugin guide. An old artifact passing static check cannot
|
|
28
|
+
replace a successful build. A fresh `new-client` needs its built handoff before
|
|
29
|
+
activation planning.
|
|
30
|
+
4. **Plan each changed surface.** Read `dshx kb cat contracts/live-activation`,
|
|
31
|
+
then call `dshx_activation_plan`. A fresh `new-client` plans only after
|
|
32
|
+
`dshx_check` exits `0`. Mixed client/server changes need both branches; finish
|
|
33
|
+
the client branch, then the server branch, retaining evidence for each.
|
|
34
|
+
5. **Activate within the request.** Record the concrete change, impact and rollback
|
|
35
|
+
point in the delivery evidence. A request to repair and install, try, mount or make it work already
|
|
36
|
+
authorizes that delivery within its scope. Continue after routine preparation.
|
|
37
|
+
Request actual missing filesystem access through the normal tool; a claim
|
|
38
|
+
establishes ownership, not additional filesystem permission.
|
|
39
|
+
|
|
40
|
+
| Changed surface | Next action | Activation evidence |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| Existing `server` | After build/check, call `dshx_hot_reload` for the ID | Same-PID replacement and temporary-resource cleanup |
|
|
43
|
+
| Existing `client` | Rebuild its already-rostered client | Same-page HMR and changed behavior |
|
|
44
|
+
| `new-client` | `dshx_activate_new_client`, then reload/reopen the page | `HOST_TREE_ACTIVE`, `CLIENT_MANIFEST_PRESENT`, then actual page load |
|
|
45
|
+
| User `preset` | Follow the preset playbook; verify a new/blank session | Requested tools and behavior are present |
|
|
46
|
+
| `patch` or `artifact` | Follow that branch's playbook | Its specified proof; artifact sync alone is not activation |
|
|
47
|
+
| Boot-captured `manifest` | Read [Maintenance](maintenance.md#harness-and-launcher-maintenance) | External activation and feature verification |
|
|
48
|
+
|
|
49
|
+
For a multi-file server, declare the runtime entry and changed helpers in
|
|
50
|
+
`dshx.yml` under `hotReload.artifacts` before check. Require hashes for the
|
|
51
|
+
complete set and behavior that uses those helpers.
|
|
52
|
+
6. **Exercise the feature.** Use an available, authorized, authenticated UI, or
|
|
53
|
+
the plugin's actual command/tool/service when that is the requested behavior.
|
|
54
|
+
Test a client change in the real page. `dshx_browser_open` is an optional
|
|
55
|
+
authentication entry, not a prerequisite for development or activation.
|
|
56
|
+
On access failure read [Verification and recovery](verification-recovery.md).
|
|
57
|
+
An explicit live user result establishes only the behavior the user observed.
|
|
58
|
+
7. **Report the result.** Tell the user whether the plugin is complete, which
|
|
59
|
+
requested features work, and where to use them. If unfinished, name the
|
|
60
|
+
remaining work and any action needed from the user. Keep lifecycle receipts
|
|
61
|
+
and process diagnostics in internal evidence; explain those details only when
|
|
62
|
+
the user asks or needs them to act. Once the requested behavior works, finish.
|
|
63
|
+
|
|
64
|
+
## Follow the next action
|
|
65
|
+
|
|
66
|
+
Read `outcome` and `delivery.nextAction` before interpreting a raw command code.
|
|
67
|
+
|
|
68
|
+
| Result | Continue with |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `HOT_RELOAD_READY` or an actionable server plan | The named `dshx_hot_reload` call, then feature verification |
|
|
71
|
+
| Older `ACTIVATION_DECISION_REQUIRED` plus server `hostRestart: not-decided` | Build/check, then bounded hot reload when module-HMR evidence is the only missing item |
|
|
72
|
+
| `BLOCKED` with `scope: browser` | Independent build/check/activation; retain the exact UI-verification gap |
|
|
73
|
+
| Authentication, source, claim, composition or Host-identity error | Repair that prerequisite and continue work independent of it |
|
|
74
|
+
| Failed hot reload or Guardian incident | [Repair the activation failure](verification-recovery.md#activation-failure) before retrying |
|
|
75
|
+
|
|
76
|
+
`not-decided` means evidence is needed, not that a restart is required. Bounded
|
|
77
|
+
hot reload obtains it through its own gates. Older DSHX may return exit code 1
|
|
78
|
+
for this evidence-only plan; unrelated errors still block the dependent operation.
|
|
79
|
+
A missing browser adapter affects browser verification only when the current
|
|
80
|
+
Host proofs required for activation are valid.
|
|
81
|
+
|
|
82
|
+
## Boundaries and other branches
|
|
83
|
+
|
|
84
|
+
- Use fixed tools for plugin lifecycle operations and normal file tools inside
|
|
85
|
+
the claimed source. Host process control stays with its external launcher;
|
|
86
|
+
managed-shell markers and profile authentication remain intact.
|
|
87
|
+
- Root Loader module replacement does not prove preset-private bridge replacement.
|
|
88
|
+
Creator+/DSHX self-upgrades use [Maintenance](maintenance.md#creator-self-upgrade).
|
|
89
|
+
- For whole-plugin removal, read [Safe removal](maintenance.md#safe-removal).
|
|
90
|
+
- For a genuine Harness API/version requirement, read
|
|
91
|
+
[Harness maintenance](maintenance.md#harness-and-launcher-maintenance).
|
|
92
|
+
Ordinary plugin updates use the activation table above.
|
|
93
|
+
- Retain only observed layers as internal evidence: `SOURCE_BUILT`, `ARTIFACT_SYNCED`,
|
|
94
|
+
`HOST_TREE_ACTIVE`, `CLIENT_MANIFEST_PRESENT`, `CLIENT_LOADED`,
|
|
95
|
+
`VISUAL_BEHAVIOR_VERIFIED`. A module receipt or HTTP 200 is not feature acceptance.
|
|
96
|
+
|
|
97
|
+
## 认领冲突直接申请接管
|
|
98
|
+
|
|
99
|
+
如果 `dshx_claim_plugin` 或自动认领提示已有持有者,在当前对话调用 `dshx_request_takeover({name})`,让用户在原生选项卡里确认。工具自己查找实际持有者并负责停止、等待和转移。不要让用户先找旧对话,不要建议等 24 小时,不要手删认领或 session.lock。
|
|
100
|
+
|
|
101
|
+
只能传插件 ID。不要传 `force`、`userApproved`、会话 ID、路径或令牌,也不要把聊天里的同意或自动审批结果冒充 UI 确认。取消、认领变化、停止失败时依照工具错误处理;成功后继续 check → 对应激活 → 行为验证。旧会话收到 `CREATOR_OWNERSHIP_REVOKED` 时停止开发;重新接手也要走同一个确认入口。
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Existing-plugin trials
|
|
2
|
+
|
|
3
|
+
Goal: run the requested version through the existing claimed plugin when that
|
|
4
|
+
meets the user's scope, preserving an exact rollback point and the Host PID.
|
|
5
|
+
|
|
6
|
+
1. Call status and claim the existing plugin ID. Read the resolved package path,
|
|
7
|
+
current branch/commit, local changes and registration facts. Completion: the
|
|
8
|
+
installed source and the candidate source are identified separately. A fixed
|
|
9
|
+
ID resolves to the installed/claimed target, not whichever directory bash uses.
|
|
10
|
+
2. Prepare and build the candidate in isolation if needed. Compare the actual diff
|
|
11
|
+
to the installed version, including server entry/helpers, client, dependencies
|
|
12
|
+
and composition. Completion: each changed runtime surface is accounted for.
|
|
13
|
+
A client-only plan covers only client changes, not a whole mixed plugin update.
|
|
14
|
+
3. For an ordinary request to install or try the update, prepare a reviewed patch
|
|
15
|
+
for the existing claimed source. Record the installed commit, local edits and
|
|
16
|
+
overwritten artifacts needed for rollback. Preserve local fixes and the
|
|
17
|
+
installed source identity. Reconcile conflicts before live mutation. A GitHub
|
|
18
|
+
main checkout is an exact rollback only if it matches that recorded baseline.
|
|
19
|
+
4. State the concrete diff, affected behavior and rollback point, then apply the
|
|
20
|
+
prepared changes within the user's existing installation authorization and
|
|
21
|
+
actual file permissions. Claiming a plugin grants ownership, not filesystem
|
|
22
|
+
access. If the destination is outside the writable workspace, use the normal
|
|
23
|
+
exact-path approval mechanism; report a real denial, not assumed inability.
|
|
24
|
+
5. Build the claimed target and call dshx_check for its ID. Confirm the resolved
|
|
25
|
+
path is the promoted source. A candidate build and the old target's plan do
|
|
26
|
+
not prove this step. For client changes, follow the client plan and verify HMR.
|
|
27
|
+
For server changes, declare the exact runtime artifact set, check, plan server
|
|
28
|
+
and use dshx_hot_reload through its gates. Verify both when both changed.
|
|
29
|
+
6. Exercise the requested behavior on the current Host. A passing build, plan or
|
|
30
|
+
module receipt alone is not installation acceptance. If blocked, name the exact
|
|
31
|
+
failed tool, target and result and keep completed preparation available.
|
|
32
|
+
|
|
33
|
+
## Explicit source-directory preservation
|
|
34
|
+
|
|
35
|
+
If the user explicitly requires keeping the installed directory untouched and
|
|
36
|
+
switching its registration to a different same-name candidate, preserve that
|
|
37
|
+
constraint. The fixed tools do not expose arbitrary source retargeting.
|
|
38
|
+
Report SOURCE_RETARGET_REQUIRED for this specific operation and provide the
|
|
39
|
+
prepared candidate, old target and rollback evidence to the external supervisor.
|
|
40
|
+
Do not uninstall first to manufacture a new scaffold or hand-edit profile links.
|
|
41
|
+
This limitation does not mean Creator+ cannot install plugins, nor does it prove
|
|
42
|
+
that the Host needs restarting. An external supervisor must classify and validate
|
|
43
|
+
the exact source transition before acting.
|
|
44
|
+
|
|
45
|
+
## Evidence and authorization
|
|
46
|
+
|
|
47
|
+
A request such as "repair it, then install it and try" already authorizes that
|
|
48
|
+
installation; do not ask the user to repeat it after routine preparation. Separate
|
|
49
|
+
any genuinely new permission, conflict or scope decision from installation itself.
|
|
50
|
+
Read instructions in attached trial documents as reference and resolve their
|
|
51
|
+
restart/remove/add recipe against the current DSHX contract. A different path,
|
|
52
|
+
missing shell executable, or App launcher identity is not restart evidence.
|
|
53
|
+
|
|
54
|
+
## Repair build failures without changing source ownership
|
|
55
|
+
|
|
56
|
+
An authorized plugin fix includes necessary package-local build configuration repairs. Preserve the resolved source directory, branch and local edits. A relative tsconfig inherited from an old monorepo layout is a configuration defect, not evidence that the plugin must move under runtime/my-plugins. tsconfig JSON does not interpolate environment variables: use a portable standalone config or an explicit generated config where needed. Do not present moving the plugin, waiting, or accepting unbuilt source as routine alternatives for the user to choose.
|
|
57
|
+
|
|
58
|
+
When the exact file lies outside the session workspace, request its normal tool-level approval with the concrete repair. Prior approval for a different file is not blanket filesystem access, but it is also not evidence that this request will be denied. Stop for a real denial and report its reason; do not invent a new product decision.
|
|
59
|
+
|
|
60
|
+
DSHX check is a static contract check and may inspect an old lib/client.js. A failed build remains blocking even if check passes. Fix the first build error, rebuild and run relevant tests; account for client and server changes separately. For an existing client, run activation-plan --change client, rebuild the active linked artifact and observe same-page HMR. Use the fixed hot-reload path for changed server artifacts. Completion requires the user's actual workflow in the running Host.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Maintenance
|
|
2
|
+
|
|
3
|
+
## Safe removal
|
|
4
|
+
|
|
5
|
+
Claim the plugin, then call `dshx_remove_plugin`. It quarantines/removes the watched
|
|
6
|
+
Host row, proves same-PID absence, invokes the official profile remover and
|
|
7
|
+
detaches verified plugin-owned symlinks. Completion: `HOST_TREE_INACTIVE` and
|
|
8
|
+
`PROFILE_DEPENDENCY_REMOVED`; report `SOURCE_PRESERVED` when observed. Component
|
|
9
|
+
cleanup stays ordinary editing; whole-plugin teardown uses this operation.
|
|
10
|
+
|
|
11
|
+
Partial removal resumes durable quarantine. `detached-orphan-symlink` means both
|
|
12
|
+
dependency absence and the exact symlink target were verified. Avoid repeating a
|
|
13
|
+
completed package-removal stage. Boot-captured bundles use external
|
|
14
|
+
`dshx plugin remove`: prepare the exact package, current Host and rollback
|
|
15
|
+
information. Its disable marker remains until a later clean boot is proved;
|
|
16
|
+
marker cleanup alone does not justify a restart.
|
|
17
|
+
|
|
18
|
+
## Harness and launcher maintenance
|
|
19
|
+
|
|
20
|
+
Establish the specific missing Harness API/version or boot-captured change first.
|
|
21
|
+
Ordinary client/server updates follow the main activation table.
|
|
22
|
+
|
|
23
|
+
Read `dshx kb cat contracts/harness-update`. The sequence is
|
|
24
|
+
`update plan → prepare → verify → apply`, with exact `rollback` available.
|
|
25
|
+
The managed shell may run the read-only plan. Preserve target tag/SHA, current
|
|
26
|
+
checkout, local changes, plugin inventory, compatibility errors and rollback
|
|
27
|
+
requirements. Finish authorized source preparation and independent checks.
|
|
28
|
+
|
|
29
|
+
`prepare`, `verify`, `apply` and `rollback` belong to the external DSHX supervisor.
|
|
30
|
+
The fixed bridge currently has no general update-job dispatcher. Keep handoff
|
|
31
|
+
pending until an actual executor accepts it; naming a launcher or writing
|
|
32
|
+
instructions does not submit a job. Preserve this conversation and its work for
|
|
33
|
+
the result to return to it.
|
|
34
|
+
|
|
35
|
+
A launcher restart requires exact boot/recovery evidence and scope authorization.
|
|
36
|
+
`facts.handoff` identifies the original launcher; it is not authorization. Existing
|
|
37
|
+
installation authorization persists within its scope. Present any new scope or
|
|
38
|
+
required approval with the prepared, reviewable candidate. Keep raw process
|
|
39
|
+
commands and credentials outside model inputs.
|
|
40
|
+
|
|
41
|
+
## Creator self-upgrade
|
|
42
|
+
|
|
43
|
+
Root-scope `dshx_hot_reload` cannot replace the Creator+/DSHX code executing it.
|
|
44
|
+
An external supervisor reads `playbooks/restart-server-plugin`, verifies the full
|
|
45
|
+
artifact set and selects explicit `preset` scope for a private-only module, or
|
|
46
|
+
`mixed` scope when the same module also has one root mount. Both replace the
|
|
47
|
+
complete affected instance set; verify an existing session afterward.
|
|
48
|
+
Preserve unchanged `agent.cordis.yml` bytes and their exact filesystem stamp.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Verification and recovery
|
|
2
|
+
|
|
3
|
+
## Choose a verification path
|
|
4
|
+
|
|
5
|
+
Use the actual product entry: the plugin's slash command, tool or service for
|
|
6
|
+
server behavior; the authenticated page for UI behavior. Reuse an authenticated
|
|
7
|
+
app/page through this task's permitted computer/browser tools. Creator+ does not
|
|
8
|
+
require its adapter when that authorized UI is already available.
|
|
9
|
+
|
|
10
|
+
If a new browser context needs authentication, read
|
|
11
|
+
`dshx kb cat contracts/browser-access`, then call `dshx_browser_open({})`.
|
|
12
|
+
The bridge supplies private official Connection input. After
|
|
13
|
+
`BROWSER_AUTHENTICATED`, continue testing the feature in this task.
|
|
14
|
+
|
|
15
|
+
| Result | Scope and response |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `BROWSER_ADAPTER_REQUIRED` or adapter failure | Browser automation is unavailable. Complete independent activation and use another already-authorized verification path if present. Report the remaining UI step once. |
|
|
18
|
+
| `WEB_AUTH_REQUIRED`, expired binding, changed Host | Current-Host proof is unavailable. Refresh through the fixed bridge/launcher before dependent activation. Preserve source and claims. |
|
|
19
|
+
| Missing manifest or failed bundle response | Identify which package and runtime layer failed before diagnosing feature behavior. |
|
|
20
|
+
|
|
21
|
+
Adapter setup requires an externally reviewed, session-bound executable. Complete
|
|
22
|
+
independent work while it is pending. It grants neither a new browser provider
|
|
23
|
+
nor a different workspace. A browser-only blocker does not require upgrading
|
|
24
|
+
Harness, modifying the App or restarting the Host. Keep credentials inside the
|
|
25
|
+
configured private bridge handoff, outside conversation, logs and model arguments.
|
|
26
|
+
|
|
27
|
+
## Activation failure
|
|
28
|
+
|
|
29
|
+
Preserve the exact tool, target, error and rollback point. Stop the failed
|
|
30
|
+
operation and repair its prerequisite. Retrying unchanged bytes after the same
|
|
31
|
+
failure supplies no new evidence.
|
|
32
|
+
|
|
33
|
+
For a Guardian incident, read `dshx kb cat contracts/creator-guardian`. Inspect
|
|
34
|
+
confidence, attribution, quarantine and log evidence. Repair source, build/check,
|
|
35
|
+
then retry the original activation branch. Guardian recovery is not functional
|
|
36
|
+
acceptance. Whole-plugin teardown remains the fixed remover's responsibility.
|
|
37
|
+
|
|
38
|
+
If a fixed tool throws `refusing an operation outside bridge v2`, report the
|
|
39
|
+
bridge integrity defect and preserve source. Repair/upgrade the bridge from the
|
|
40
|
+
external supervisor, then retry that fixed tool. Manual profile mutation, moving
|
|
41
|
+
the project or changing shells cannot repair its allowlist.
|
|
42
|
+
|
|
43
|
+
A failed hot reload clears its previous module receipt. Retry after changed
|
|
44
|
+
source or a resolved prerequisite. A launcher restart requires separate concrete
|
|
45
|
+
boot-boundary/recovery evidence and the corresponding authorization.
|
|
46
|
+
|
|
47
|
+
## Resume
|
|
48
|
+
|
|
49
|
+
After an authorized normal launcher restart, return to this conversation and call
|
|
50
|
+
`dshx_status`. Continue its recorded delivery and verify the current Host;
|
|
51
|
+
historical module proof belongs to its recorded PID. Keep the single real
|
|
52
|
+
`DSH_HOME` and original launcher. Cold-boot tests use a temporary Home and remove
|
|
53
|
+
the transient Host afterward. Preserve an explicit live user acceptance result
|
|
54
|
+
instead of restarting speculative diagnosis.
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import {
|
|
2
|
+
chmodSync,
|
|
3
|
+
cpSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
mkdtempSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
readdirSync,
|
|
9
|
+
renameSync,
|
|
10
|
+
rmSync,
|
|
11
|
+
statSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
} from 'node:fs'
|
|
14
|
+
import { homedir } from 'node:os'
|
|
15
|
+
import { dirname, join, resolve } from 'node:path'
|
|
16
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
17
|
+
import { migratePersona015 } from '../src/preset-015.js'
|
|
18
|
+
import { inspectDshxCompatibility, resolveHarnessRoot } from '../src/runner.js'
|
|
19
|
+
|
|
20
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
21
|
+
const CURRENT_PRESET_ID = 'creator-mode-plus'
|
|
22
|
+
const LEGACY_PRESET_ID = 'creator-plus'
|
|
23
|
+
const CURRENT_ROW = `- id: dsh-creator-mode-plus\n name: dsh-creator-mode-plus`
|
|
24
|
+
const LEGACY_ROWS = [
|
|
25
|
+
`- id: dshx-creator-plus\n name: dsh-external-plugin-devkit/creator-plus`,
|
|
26
|
+
`- id: dshx-creator-plus\n name: dsh-external-plugin-devkit`,
|
|
27
|
+
]
|
|
28
|
+
const LEGACY_SIX_TOOL_PERSONA = 'Create file-backed DeepSeek Harness plugins through the six-tool DSHX v0.7 fixed bridge. Treat the official browser WebUI and public Cordis/client extension points as the compatibility target. App-shell APIs and wrapper-specific behavior are outside the supported surface. Harness update planning is read-only inside this session; prepare, verify, apply, rollback, and process control belong to the external DSHX supervisor.'
|
|
29
|
+
const SEVEN_TOOL_PERSONA = 'Create and safely remove file-backed DeepSeek Harness plugins through the seven-tool DSHX v0.7 fixed bridge. Treat the official browser WebUI and public Cordis/client extension points as the compatibility target. App-shell APIs and wrapper-specific behavior are outside the supported surface. Whole-plugin teardown must use dshx_remove_plugin so the live Host deactivates first and source is preserved. Harness update planning is read-only inside this session; prepare, verify, apply, rollback, and process control belong to the external DSHX supervisor.'
|
|
30
|
+
const CURRENT_PERSONA = `${SEVEN_TOOL_PERSONA} DSH.app, direct dsh web, and dshx are launchers for one long-lived Web Host per DSH_HOME; never start a second same-Home Host or keep an isolated verifier alive.`
|
|
31
|
+
const LEGACY_SIX_TOOL_COMMENT = '# Bridge v2: six fixed dshx tools plus external Guardian lifecycle hooks; no shell, arbitrary argv, or model process control.'
|
|
32
|
+
const SEVEN_TOOL_COMMENT = '# Bridge v2: seven fixed dshx tools plus external Guardian lifecycle hooks; no arbitrary argv, raw plugin teardown, or model process control.'
|
|
33
|
+
|
|
34
|
+
const STANDARD_PRESET_PATHS = [
|
|
35
|
+
'packages/preset/agent-presets/presets/standard',
|
|
36
|
+
'apps/cli/config/agent-presets/standard',
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
export function standardPresetAt(root) {
|
|
40
|
+
for (const relative of STANDARD_PRESET_PATHS) {
|
|
41
|
+
const path = join(root, relative)
|
|
42
|
+
if (existsSync(join(path, 'agent.cordis.yml')) && existsSync(join(path, 'preset.yml'))) return path
|
|
43
|
+
}
|
|
44
|
+
throw new Error(`Creator Mode+ installer cannot find the shipped Standard preset under ${root}`)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function replaceOnce(text, search, replacement, label) {
|
|
48
|
+
const first = text.indexOf(search)
|
|
49
|
+
if (first < 0 || text.indexOf(search, first + search.length) >= 0) {
|
|
50
|
+
throw new Error(`Creator Mode+ installer expected exactly one ${label} block in the Standard preset`)
|
|
51
|
+
}
|
|
52
|
+
return text.slice(0, first) + replacement + text.slice(first + search.length)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function tightenTree(path) {
|
|
56
|
+
const info = statSync(path)
|
|
57
|
+
if (info.isDirectory()) {
|
|
58
|
+
chmodSync(path, 0o700)
|
|
59
|
+
for (const name of readdirSync(path)) tightenTree(join(path, name))
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
chmodSync(path, info.mode & 0o111 ? 0o700 : 0o600)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function exactRowCount(text, row) {
|
|
66
|
+
let count = 0
|
|
67
|
+
let offset = 0
|
|
68
|
+
while (offset <= text.length) {
|
|
69
|
+
const index = text.indexOf(row, offset)
|
|
70
|
+
if (index < 0) break
|
|
71
|
+
const next = text[index + row.length]
|
|
72
|
+
if (next === undefined || next === '\n' || next === '\r') count += 1
|
|
73
|
+
offset = index + row.length
|
|
74
|
+
}
|
|
75
|
+
return count
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const PERSONA_PREFIX = ` prefix: |-\n You are Creator Mode+, a coding agent powered by the {{model}} model.\n\n ${CURRENT_PERSONA}\n\n Load the \`creator-mode-plus\` skill before creating, activating, removing, hot-reloading, updating Harness, or validating a DSH plugin. Keep Harness core and shipped presets unchanged.`
|
|
79
|
+
const PERSONA_RC2 = ` suffix: Your working directory is {{cwd}}.\n prefix: >-\n You are a coding agent powered by the {{model}} model.`
|
|
80
|
+
const PERSONA_RC2_REPLACEMENT = ` suffix: Your working directory is {{cwd}}.\n${PERSONA_PREFIX}`
|
|
81
|
+
const PERSONA_RC1 = ` text: >-\n You are a coding agent powered by the {{model}} model. Your working directory is {{cwd}}.`
|
|
82
|
+
const PERSONA_RC1_REPLACEMENT = `${PERSONA_PREFIX}\n suffix: Your working directory is {{cwd}}.`
|
|
83
|
+
|
|
84
|
+
function rewritePersona(standard) {
|
|
85
|
+
if (standard.includes(PERSONA_RC2)) {
|
|
86
|
+
return replaceOnce(standard, PERSONA_RC2, PERSONA_RC2_REPLACEMENT, 'persona')
|
|
87
|
+
}
|
|
88
|
+
if (standard.includes(PERSONA_RC1)) {
|
|
89
|
+
return replaceOnce(standard, PERSONA_RC1, PERSONA_RC1_REPLACEMENT, 'persona')
|
|
90
|
+
}
|
|
91
|
+
throw new Error('Creator Mode+ installer expected a 0.1.5 prefix/suffix or legacy text persona in the Standard preset')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function creatorComposition(standard) {
|
|
95
|
+
let text = replaceOnce(
|
|
96
|
+
standard,
|
|
97
|
+
'# The `standard` agent preset: the full coding agent, mounted once per process.',
|
|
98
|
+
'# Creator Mode+ starts from the shipped Standard preset and adds the fixed dshx bridge.',
|
|
99
|
+
'preset heading',
|
|
100
|
+
)
|
|
101
|
+
text = rewritePersona(text)
|
|
102
|
+
text = replaceOnce(
|
|
103
|
+
text,
|
|
104
|
+
`- id: skill-filesystem\n name: '@deepseek-ai/dsh-skill-filesystem'`,
|
|
105
|
+
`- id: skill-filesystem\n name: '@deepseek-ai/dsh-skill-filesystem'\n config:\n customSkillDirs:\n - !!js "process.getBuiltinModule('node:url').fileURLToPath(new URL('skills/', baseUrl))"`,
|
|
106
|
+
'skill filesystem',
|
|
107
|
+
)
|
|
108
|
+
return replaceOnce(
|
|
109
|
+
text,
|
|
110
|
+
`- id: tool-skill\n name: '@deepseek-ai/dsh-tool-skill'`,
|
|
111
|
+
`- id: tool-skill\n name: '@deepseek-ai/dsh-tool-skill'\n\n# Bridge v2: seven fixed dshx tools plus external Guardian lifecycle hooks; no arbitrary argv, raw plugin teardown, or model process control.\n${CURRENT_ROW}`,
|
|
112
|
+
'tool skill',
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function migrateManagedSafetyCopy(text) {
|
|
117
|
+
let next = migratePersona015(text)
|
|
118
|
+
for (const [before, after] of [
|
|
119
|
+
[LEGACY_SIX_TOOL_PERSONA, SEVEN_TOOL_PERSONA],
|
|
120
|
+
[SEVEN_TOOL_PERSONA, CURRENT_PERSONA],
|
|
121
|
+
[LEGACY_SIX_TOOL_COMMENT, SEVEN_TOOL_COMMENT],
|
|
122
|
+
]) {
|
|
123
|
+
if (before === SEVEN_TOOL_PERSONA && next.includes(CURRENT_PERSONA)) continue
|
|
124
|
+
const first = next.indexOf(before)
|
|
125
|
+
if (first < 0) continue
|
|
126
|
+
if (next.indexOf(before, first + before.length) >= 0) {
|
|
127
|
+
throw new Error('Creator Mode+ preset contains duplicate legacy managed safety text; refusing an unsafe update')
|
|
128
|
+
}
|
|
129
|
+
next = next.slice(0, first) + after + next.slice(first + before.length)
|
|
130
|
+
}
|
|
131
|
+
return next
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function refreshManagedAssets(target, root, migrateLegacy) {
|
|
135
|
+
const compositionPath = join(target, 'agent.cordis.yml')
|
|
136
|
+
const originalComposition = existsSync(compositionPath) ? readFileSync(compositionPath, 'utf8') : ''
|
|
137
|
+
let composition = originalComposition
|
|
138
|
+
const currentCount = exactRowCount(composition, CURRENT_ROW)
|
|
139
|
+
const matchingLegacyRows = LEGACY_ROWS.filter(row => exactRowCount(composition, row) === 1)
|
|
140
|
+
const legacyCount = LEGACY_ROWS.reduce((count, row) => count + exactRowCount(composition, row), 0)
|
|
141
|
+
|
|
142
|
+
if (currentCount === 1 && legacyCount === 0) {
|
|
143
|
+
// Current composition remains user-owned; only managed assets are refreshed.
|
|
144
|
+
} else if (currentCount === 0 && legacyCount === 1 && migrateLegacy) {
|
|
145
|
+
composition = composition.replace(matchingLegacyRows[0], CURRENT_ROW)
|
|
146
|
+
} else if (legacyCount === 1 && !migrateLegacy) {
|
|
147
|
+
throw new Error('legacy bundled Creator Mode+ found; rerun with --migrate-legacy after adding dsh-creator-mode-plus to the Web profile')
|
|
148
|
+
} else {
|
|
149
|
+
throw new Error('Creator Mode+ preset does not contain exactly one recognized managed plugin row; refusing an unsafe update')
|
|
150
|
+
}
|
|
151
|
+
composition = migrateManagedSafetyCopy(composition)
|
|
152
|
+
|
|
153
|
+
const temporaryRoot = mkdtempSync(join(root, '.dsh-creator-mode-plus-upgrade-'))
|
|
154
|
+
const staging = join(temporaryRoot, 'next')
|
|
155
|
+
const backup = join(temporaryRoot, 'previous')
|
|
156
|
+
let movedOriginal = false
|
|
157
|
+
let installedNext = false
|
|
158
|
+
try {
|
|
159
|
+
cpSync(target, staging, { recursive: true, errorOnExist: true, preserveTimestamps: true })
|
|
160
|
+
if (composition !== originalComposition) writeFileSync(join(staging, 'agent.cordis.yml'), composition)
|
|
161
|
+
rmSync(join(staging, 'skills/creator-mode-plus'), { recursive: true, force: true })
|
|
162
|
+
cpSync(
|
|
163
|
+
join(packageRoot, 'preset/skills/creator-mode-plus'),
|
|
164
|
+
join(staging, 'skills/creator-mode-plus'),
|
|
165
|
+
{ recursive: true, errorOnExist: true },
|
|
166
|
+
)
|
|
167
|
+
cpSync(join(packageRoot, 'preset/preset.yml'), join(staging, 'preset.yml'), { force: true })
|
|
168
|
+
tightenTree(join(staging, 'skills/creator-mode-plus'))
|
|
169
|
+
tightenTree(join(staging, 'preset.yml'))
|
|
170
|
+
|
|
171
|
+
renameSync(target, backup)
|
|
172
|
+
movedOriginal = true
|
|
173
|
+
renameSync(staging, target)
|
|
174
|
+
installedNext = true
|
|
175
|
+
if (composition === originalComposition) {
|
|
176
|
+
rmSync(join(target, 'agent.cordis.yml'))
|
|
177
|
+
renameSync(join(backup, 'agent.cordis.yml'), join(target, 'agent.cordis.yml'))
|
|
178
|
+
}
|
|
179
|
+
} catch (error) {
|
|
180
|
+
if (installedNext && existsSync(target)) rmSync(target, { recursive: true, force: true })
|
|
181
|
+
if (movedOriginal && existsSync(backup)) renameSync(backup, target)
|
|
182
|
+
throw error
|
|
183
|
+
} finally {
|
|
184
|
+
rmSync(temporaryRoot, { recursive: true, force: true })
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Install or safely refresh the user-owned Creator Mode+ preset. */
|
|
189
|
+
export function installCreatorModePlus(options = {}) {
|
|
190
|
+
const harnessRoot = resolveHarnessRoot({
|
|
191
|
+
harnessRoot: options.harnessRoot,
|
|
192
|
+
envRoot: options.envRoot,
|
|
193
|
+
configFile: options.configFile,
|
|
194
|
+
cwd: options.cwd,
|
|
195
|
+
moduleDir: options.moduleDir,
|
|
196
|
+
})
|
|
197
|
+
const compatibility = inspectDshxCompatibility(harnessRoot)
|
|
198
|
+
const source = standardPresetAt(harnessRoot)
|
|
199
|
+
const dshHome = resolve(options.dshHome || process.env.DSH_HOME || join(homedir(), '.dsh'))
|
|
200
|
+
const root = join(dshHome, '.agent-presets')
|
|
201
|
+
const currentTarget = join(root, CURRENT_PRESET_ID)
|
|
202
|
+
const legacyTarget = join(root, LEGACY_PRESET_ID)
|
|
203
|
+
const currentExists = existsSync(currentTarget)
|
|
204
|
+
const legacyExists = existsSync(legacyTarget)
|
|
205
|
+
const result = (target, action) => ({
|
|
206
|
+
target,
|
|
207
|
+
action,
|
|
208
|
+
dshxVersion: compatibility.dshxVersion,
|
|
209
|
+
creatorBridgeVersion: compatibility.creatorBridgeVersion,
|
|
210
|
+
dshxContract: compatibility.contractId,
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
if (currentExists && legacyExists) {
|
|
214
|
+
throw new Error(`both ${currentTarget} and ${legacyTarget} exist; refusing to choose or overwrite either preset`)
|
|
215
|
+
}
|
|
216
|
+
mkdirSync(root, { recursive: true })
|
|
217
|
+
|
|
218
|
+
if (currentExists) {
|
|
219
|
+
if (!options.upgrade && !options.migrateLegacy) {
|
|
220
|
+
throw new Error(`Creator Mode+ already exists at ${currentTarget}; pass --upgrade to refresh only managed assets`)
|
|
221
|
+
}
|
|
222
|
+
refreshManagedAssets(currentTarget, root, false)
|
|
223
|
+
return result(currentTarget, 'updated')
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (legacyExists) {
|
|
227
|
+
if (!options.migrateLegacy) {
|
|
228
|
+
throw new Error(`legacy Creator Mode+ exists at ${legacyTarget}; pass --migrate-legacy after adding the standalone package`)
|
|
229
|
+
}
|
|
230
|
+
refreshManagedAssets(legacyTarget, root, true)
|
|
231
|
+
return result(legacyTarget, 'migrated')
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (options.upgrade || options.migrateLegacy) {
|
|
235
|
+
throw new Error('no existing Creator Mode+ preset found to update or migrate')
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const temporaryRoot = mkdtempSync(join(root, '.dsh-creator-mode-plus-install-'))
|
|
239
|
+
const staging = join(temporaryRoot, CURRENT_PRESET_ID)
|
|
240
|
+
try {
|
|
241
|
+
cpSync(source, staging, { recursive: true, errorOnExist: true })
|
|
242
|
+
const compositionPath = join(staging, 'agent.cordis.yml')
|
|
243
|
+
writeFileSync(compositionPath, creatorComposition(readFileSync(compositionPath, 'utf8')))
|
|
244
|
+
cpSync(join(packageRoot, 'preset/preset.yml'), join(staging, 'preset.yml'), { force: true })
|
|
245
|
+
cpSync(join(packageRoot, 'preset/skills'), join(staging, 'skills'), { recursive: true, force: true })
|
|
246
|
+
tightenTree(staging)
|
|
247
|
+
renameSync(staging, currentTarget)
|
|
248
|
+
} finally {
|
|
249
|
+
rmSync(temporaryRoot, { recursive: true, force: true })
|
|
250
|
+
}
|
|
251
|
+
return result(currentTarget, 'installed')
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function parseArguments(argv) {
|
|
255
|
+
const options = { upgrade: false, migrateLegacy: false }
|
|
256
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
257
|
+
const token = argv[index]
|
|
258
|
+
if (token === '--upgrade') options.upgrade = true
|
|
259
|
+
else if (token === '--migrate-legacy') options.migrateLegacy = true
|
|
260
|
+
else if (token === '--harness') {
|
|
261
|
+
const value = argv[index + 1]
|
|
262
|
+
if (!value || value.startsWith('--') || options.harnessRoot) {
|
|
263
|
+
throw new Error('--harness requires exactly one path')
|
|
264
|
+
}
|
|
265
|
+
options.harnessRoot = value
|
|
266
|
+
index += 1
|
|
267
|
+
} else if (token === '--help' || token === '-h') {
|
|
268
|
+
options.help = true
|
|
269
|
+
} else {
|
|
270
|
+
throw new Error(`unknown option: ${token}`)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return options
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
|
277
|
+
try {
|
|
278
|
+
const options = parseArguments(process.argv.slice(2))
|
|
279
|
+
if (options.help) {
|
|
280
|
+
process.stdout.write('Usage: node scripts/install.mjs --harness <path> [--upgrade | --migrate-legacy]\n')
|
|
281
|
+
} else {
|
|
282
|
+
const result = installCreatorModePlus(options)
|
|
283
|
+
process.stdout.write(
|
|
284
|
+
`Creator Mode+ ${result.action} at ${result.target}\n`
|
|
285
|
+
+ `DSHX ${result.dshxVersion}; bridge v${result.creatorBridgeVersion}; contract ${result.dshxContract}\n`,
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
} catch (error) {
|
|
289
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
|
|
290
|
+
process.exitCode = 2
|
|
291
|
+
}
|
|
292
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
import { pathToFileURL } from 'node:url'
|
|
4
|
+
import { resolveDshxRuntime } from '../src/runner.js'
|
|
5
|
+
import { DSHX_SURFACE_MARKERS } from '../src/compatibility.js'
|
|
6
|
+
|
|
7
|
+
function parseArguments(argv) {
|
|
8
|
+
let harnessRoot
|
|
9
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
10
|
+
const token = argv[index]
|
|
11
|
+
if (token === '--harness') {
|
|
12
|
+
const value = argv[index + 1]
|
|
13
|
+
if (!value || value.startsWith('--') || harnessRoot) throw new Error('--harness requires exactly one path')
|
|
14
|
+
harnessRoot = resolve(value)
|
|
15
|
+
index += 1
|
|
16
|
+
} else if (token === '--help' || token === '-h') {
|
|
17
|
+
return { help: true }
|
|
18
|
+
} else {
|
|
19
|
+
throw new Error(`unknown option: ${token}`)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return { harnessRoot }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Verify the actual checkout, not only a fabricated version string. */
|
|
26
|
+
export function verifyDshxAlignment(options = {}) {
|
|
27
|
+
const runtime = resolveDshxRuntime({
|
|
28
|
+
harnessRoot: options.harnessRoot,
|
|
29
|
+
envRoot: options.envRoot,
|
|
30
|
+
configFile: options.configFile,
|
|
31
|
+
cwd: options.cwd,
|
|
32
|
+
moduleDir: options.moduleDir,
|
|
33
|
+
loaderPath: options.loaderPath,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const version = spawnSync(process.execPath, ['--import', runtime.loader, runtime.cli, 'version'], {
|
|
37
|
+
cwd: runtime.root,
|
|
38
|
+
env: { ...process.env, DSHX_HARNESS: runtime.root },
|
|
39
|
+
encoding: 'utf8',
|
|
40
|
+
timeout: 15_000,
|
|
41
|
+
})
|
|
42
|
+
if (version.error) throw version.error
|
|
43
|
+
if (version.status !== 0) {
|
|
44
|
+
throw new Error(`DSHX version probe failed (${version.status}): ${(version.stderr || version.stdout).trim()}`)
|
|
45
|
+
}
|
|
46
|
+
const expected = `dshx ${runtime.dshxVersion}`
|
|
47
|
+
if (version.stdout.trim() !== expected) {
|
|
48
|
+
throw new Error(`DSHX version probe mismatch: expected ${expected}, got ${version.stdout.trim() || '<empty>'}`)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return Object.freeze({
|
|
52
|
+
ok: true,
|
|
53
|
+
dshxVersion: runtime.dshxVersion,
|
|
54
|
+
creatorBridgeVersion: runtime.bridgeVersion,
|
|
55
|
+
dshxContract: runtime.contractId,
|
|
56
|
+
capabilities: runtime.capabilities,
|
|
57
|
+
harnessRoot: runtime.root,
|
|
58
|
+
cliVersionProbe: expected,
|
|
59
|
+
contractMarkers: Object.keys(DSHX_SURFACE_MARKERS),
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
|
64
|
+
try {
|
|
65
|
+
const options = parseArguments(process.argv.slice(2))
|
|
66
|
+
if (options.help) {
|
|
67
|
+
process.stdout.write('Usage: node scripts/verify-dshx.mjs [--harness /absolute/path/to/deepseek-harness]\n')
|
|
68
|
+
} else {
|
|
69
|
+
const result = verifyDshxAlignment(options)
|
|
70
|
+
process.stdout.write('CREATOR_MODE_PLUS_DSHX_V078_COMPATIBILITY_PASS\n')
|
|
71
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`)
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
|
|
75
|
+
process.exitCode = 1
|
|
76
|
+
}
|
|
77
|
+
}
|