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
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import {
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdtempSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
rmSync,
|
|
7
|
+
statSync,
|
|
8
|
+
} from 'node:fs'
|
|
9
|
+
import { tmpdir } from 'node:os'
|
|
10
|
+
import { join, resolve } from 'node:path'
|
|
11
|
+
import { pathToFileURL } from 'node:url'
|
|
12
|
+
import { installCreatorModePlus, standardPresetAt } from './install.mjs'
|
|
13
|
+
|
|
14
|
+
function digest(path) {
|
|
15
|
+
return createHash('sha256').update(readFileSync(path)).digest('hex')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parseArguments(argv) {
|
|
19
|
+
let harnessRoot
|
|
20
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
21
|
+
const token = argv[index]
|
|
22
|
+
if (token === '--harness') {
|
|
23
|
+
const value = argv[index + 1]
|
|
24
|
+
if (!value || value.startsWith('--') || harnessRoot) throw new Error('--harness requires exactly one path')
|
|
25
|
+
harnessRoot = resolve(value)
|
|
26
|
+
index += 1
|
|
27
|
+
} else if (token === '--help' || token === '-h') {
|
|
28
|
+
return { help: true }
|
|
29
|
+
} else {
|
|
30
|
+
throw new Error(`unknown option: ${token}`)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (!harnessRoot) throw new Error('--harness is required for RC2 install verification')
|
|
34
|
+
return { harnessRoot }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Exercise the real shipped Standard preset in an isolated disposable DSH_HOME. */
|
|
38
|
+
export function verifyHarnessInstall(harnessRoot) {
|
|
39
|
+
const standardPreset = standardPresetAt(harnessRoot)
|
|
40
|
+
const sourceComposition = join(standardPreset, 'agent.cordis.yml')
|
|
41
|
+
const sourcePreset = join(standardPreset, 'preset.yml')
|
|
42
|
+
const before = {
|
|
43
|
+
composition: digest(sourceComposition),
|
|
44
|
+
preset: digest(sourcePreset),
|
|
45
|
+
}
|
|
46
|
+
const dshHome = mkdtempSync(join(tmpdir(), 'creator-mode-plus-rc2-install-'))
|
|
47
|
+
try {
|
|
48
|
+
const installed = installCreatorModePlus({ harnessRoot, dshHome })
|
|
49
|
+
const compositionPath = join(installed.target, 'agent.cordis.yml')
|
|
50
|
+
const skillPath = join(installed.target, 'skills/creator-mode-plus/SKILL.md')
|
|
51
|
+
const composition = readFileSync(compositionPath, 'utf8')
|
|
52
|
+
const skill = readFileSync(skillPath, 'utf8')
|
|
53
|
+
if (!composition.includes('You are Creator Mode+')) throw new Error('isolated preset is missing the Creator Mode+ persona')
|
|
54
|
+
if (!composition.includes('name: dsh-creator-mode-plus')) throw new Error('isolated preset is missing the standalone bridge row')
|
|
55
|
+
if (!skill.includes('dshx_status') || !skill.includes('dshx_hot_reload')) throw new Error('isolated preset is missing the Creator delivery workflow')
|
|
56
|
+
const maintenance = readFileSync(join(installed.target, 'skills/creator-mode-plus/maintenance.md'), 'utf8')
|
|
57
|
+
if (!skill.includes('(maintenance.md#harness-and-launcher-maintenance)') || !maintenance.includes('update plan → prepare → verify → apply')) throw new Error('isolated preset is missing the referenced Harness maintenance branch')
|
|
58
|
+
if (!existsSync(join(installed.target, 'skills/creator-mode-plus/verification-recovery.md'))) throw new Error('isolated preset is missing the referenced verification branch')
|
|
59
|
+
|
|
60
|
+
const firstStamp = statSync(compositionPath)
|
|
61
|
+
const updated = installCreatorModePlus({ harnessRoot, dshHome, upgrade: true })
|
|
62
|
+
const secondStamp = statSync(compositionPath)
|
|
63
|
+
if (firstStamp.size !== secondStamp.size || firstStamp.mtimeMs !== secondStamp.mtimeMs) {
|
|
64
|
+
throw new Error('managed asset refresh changed the unchanged agent.cordis.yml stamp')
|
|
65
|
+
}
|
|
66
|
+
if (!existsSync(skillPath)) throw new Error('managed upgrade removed the Creator Mode+ skill')
|
|
67
|
+
if (digest(sourceComposition) !== before.composition || digest(sourcePreset) !== before.preset) {
|
|
68
|
+
throw new Error('isolated install changed a shipped Harness preset')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return Object.freeze({
|
|
72
|
+
ok: true,
|
|
73
|
+
harnessRoot,
|
|
74
|
+
dshxVersion: updated.dshxVersion,
|
|
75
|
+
creatorBridgeVersion: updated.creatorBridgeVersion,
|
|
76
|
+
dshxContract: updated.dshxContract,
|
|
77
|
+
freshInstall: installed.action,
|
|
78
|
+
managedUpgrade: updated.action,
|
|
79
|
+
shippedPresetUnchanged: true,
|
|
80
|
+
compositionStampStable: true,
|
|
81
|
+
})
|
|
82
|
+
} finally {
|
|
83
|
+
rmSync(dshHome, { recursive: true, force: true })
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
|
88
|
+
try {
|
|
89
|
+
const options = parseArguments(process.argv.slice(2))
|
|
90
|
+
if (options.help) {
|
|
91
|
+
process.stdout.write('Usage: node scripts/verify-harness-install.mjs --harness /absolute/path/to/deepseek-harness\n')
|
|
92
|
+
} else {
|
|
93
|
+
const result = verifyHarnessInstall(options.harnessRoot)
|
|
94
|
+
process.stdout.write('CREATOR_MODE_PLUS_HARNESS_INSTALL_COMPATIBILITY_PASS\n')
|
|
95
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`)
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
|
|
99
|
+
process.exitCode = 1
|
|
100
|
+
}
|
|
101
|
+
}
|
package/src/auth.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Validate only the official current-Host launch URL; never surface its value. */
|
|
2
|
+
export function privateStartupUrl(getUrl, port) {
|
|
3
|
+
if (getUrl === undefined) return ''
|
|
4
|
+
let value
|
|
5
|
+
try { value = getUrl(port) } catch { throw new Error('WEB_AUTH_HANDOFF_FAILED: official Connection startup handoff failed') }
|
|
6
|
+
if (value === undefined || value === '') return '' // older unauthenticated Web
|
|
7
|
+
try {
|
|
8
|
+
const url = new URL(value)
|
|
9
|
+
if (url.origin !== `http://127.0.0.1:${port}` || url.username || url.password
|
|
10
|
+
|| url.pathname !== '/' || url.hash || [...url.searchParams.keys()].some(key => key !== 'token')
|
|
11
|
+
|| url.searchParams.getAll('token').length > 1 || (url.searchParams.has('token') && !url.searchParams.get('token'))) throw new Error()
|
|
12
|
+
return url.href
|
|
13
|
+
} catch { throw new Error('WEB_AUTH_ORIGIN_MISMATCH: official handoff must target the current loopback Host') }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Redact after stream assembly so secrets split across output chunks stay private. */
|
|
17
|
+
export function redactStartupOutput(text, startup) {
|
|
18
|
+
if (!startup) return text
|
|
19
|
+
let result = text.split(startup).join('<redacted-startup-url>')
|
|
20
|
+
const token = new URL(startup).searchParams.get('token')
|
|
21
|
+
if (token) {
|
|
22
|
+
result = result.split(token).join('<redacted-token>')
|
|
23
|
+
result = result.split(encodeURIComponent(token)).join('<redacted-token>')
|
|
24
|
+
}
|
|
25
|
+
return result
|
|
26
|
+
}
|
package/src/client.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: 'dsh-creator-mode-plus',
|
|
3
|
+
factory: () => {
|
|
4
|
+
const name = 'dsh-creator-mode-plus-client'
|
|
5
|
+
const inject = ['loader']
|
|
6
|
+
const endpoint = '/dsh-creator-mode-plus/client-failure'
|
|
7
|
+
const fuseKey = 'dsh-creator-mode-plus:last-client-failure'
|
|
8
|
+
|
|
9
|
+
function failedEntries(ctx) {
|
|
10
|
+
const ids = []
|
|
11
|
+
for (const entry of ctx.loader.entries()) {
|
|
12
|
+
const state = entry.fiber?.state
|
|
13
|
+
if (state !== 2) ids.push(entry.options.name)
|
|
14
|
+
}
|
|
15
|
+
return [...new Set(ids)].sort()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function bootFailureText() {
|
|
19
|
+
const boot = document.querySelector('[data-dsh-boot]')
|
|
20
|
+
const text = boot?.textContent || ''
|
|
21
|
+
return text.includes('Failed to load plugins') ? text.slice(0, 8000) : undefined
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function apply(ctx) {
|
|
25
|
+
let disposed = false
|
|
26
|
+
let reporting = false
|
|
27
|
+
let timer
|
|
28
|
+
let retryCount = 0
|
|
29
|
+
let sawBootPage = Boolean(document.querySelector('[data-dsh-boot]'))
|
|
30
|
+
|
|
31
|
+
const clearFuseWhenMounted = () => {
|
|
32
|
+
if (document.querySelector('[data-dsh-boot]')) {
|
|
33
|
+
sawBootPage = true
|
|
34
|
+
} else if (sawBootPage) {
|
|
35
|
+
sessionStorage.removeItem(fuseKey)
|
|
36
|
+
sawBootPage = false
|
|
37
|
+
retryCount = 0
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const report = async (message, exactId) => {
|
|
42
|
+
if (disposed || reporting) return
|
|
43
|
+
const failedIds = exactId ? [exactId] : failedEntries(ctx)
|
|
44
|
+
if (failedIds.length === 0) return
|
|
45
|
+
const signature = JSON.stringify(failedIds)
|
|
46
|
+
if (sessionStorage.getItem(fuseKey) === signature) return
|
|
47
|
+
reporting = true
|
|
48
|
+
sessionStorage.setItem(fuseKey, signature)
|
|
49
|
+
try {
|
|
50
|
+
const response = await fetch(endpoint, {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
headers: { 'content-type': 'application/json' },
|
|
53
|
+
body: JSON.stringify({ failedIds, message: String(message || '').slice(0, 8000) }),
|
|
54
|
+
})
|
|
55
|
+
const result = await response.json()
|
|
56
|
+
if (response.ok && result.reload === true && !disposed) {
|
|
57
|
+
location.reload()
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
if (!disposed && retryCount < 1) {
|
|
61
|
+
retryCount += 1
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
sessionStorage.removeItem(fuseKey)
|
|
64
|
+
void report(message, exactId)
|
|
65
|
+
}, 2_000)
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.warn('Creator Mode+ could not report the client-loader failure', error)
|
|
69
|
+
if (!disposed && retryCount < 1) {
|
|
70
|
+
retryCount += 1
|
|
71
|
+
setTimeout(() => {
|
|
72
|
+
sessionStorage.removeItem(fuseKey)
|
|
73
|
+
void report(message, exactId)
|
|
74
|
+
}, 2_000)
|
|
75
|
+
}
|
|
76
|
+
} finally {
|
|
77
|
+
reporting = false
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const scan = () => {
|
|
82
|
+
clearFuseWhenMounted()
|
|
83
|
+
const message = bootFailureText()
|
|
84
|
+
if (message) void report(message)
|
|
85
|
+
}
|
|
86
|
+
const scheduleScan = () => {
|
|
87
|
+
clearTimeout(timer)
|
|
88
|
+
timer = setTimeout(scan, 250)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
ctx.on('internal/status', (fiber) => {
|
|
92
|
+
const id = fiber.entry?.options?.name
|
|
93
|
+
if (fiber.state === 3 && typeof id === 'string') {
|
|
94
|
+
void report(`client loader entry ${id} entered FAILED`, id)
|
|
95
|
+
} else {
|
|
96
|
+
scheduleScan()
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
const observer = new MutationObserver(scheduleScan)
|
|
101
|
+
observer.observe(document.documentElement, { childList: true, subtree: true, characterData: true })
|
|
102
|
+
scheduleScan()
|
|
103
|
+
ctx.effect(() => () => {
|
|
104
|
+
disposed = true
|
|
105
|
+
clearTimeout(timer)
|
|
106
|
+
observer.disconnect()
|
|
107
|
+
}, 'Creator Mode+ browser failure sentry')
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return { name, inject, apply }
|
|
111
|
+
},
|
|
112
|
+
})
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { join, resolve } from 'node:path'
|
|
3
|
+
|
|
4
|
+
export const CREATOR_BRIDGE_VERSION = 2
|
|
5
|
+
|
|
6
|
+
export const DSHX_CONTRACT = Object.freeze({
|
|
7
|
+
id: 'dshx-v0.7/creator-bridge-v2',
|
|
8
|
+
release: 'v0.7.8',
|
|
9
|
+
minimum: '0.7.8',
|
|
10
|
+
maximumExclusive: '0.8.0',
|
|
11
|
+
capabilities: Object.freeze([
|
|
12
|
+
'creator-session-claims',
|
|
13
|
+
'human-confirmed-creator-takeover',
|
|
14
|
+
'workspace-scaffold',
|
|
15
|
+
'bounded-new-client-activation',
|
|
16
|
+
'authenticated-host-proof',
|
|
17
|
+
'private-browser-handoff',
|
|
18
|
+
'session-browser-open',
|
|
19
|
+
'safe-plugin-removal',
|
|
20
|
+
'safe-profile-bundle-removal',
|
|
21
|
+
'external-guardian-recovery',
|
|
22
|
+
'proactive-plugin-integrity-quarantine',
|
|
23
|
+
'same-pid-activation-matrix',
|
|
24
|
+
'bounded-same-pid-server-hot-reload',
|
|
25
|
+
'single-home-web-host',
|
|
26
|
+
'isolated-verify-home',
|
|
27
|
+
'transactional-harness-update-assistant',
|
|
28
|
+
]),
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
export const SUPPORTED_DSHX = Object.freeze({
|
|
32
|
+
minimum: DSHX_CONTRACT.minimum,
|
|
33
|
+
maximumExclusive: DSHX_CONTRACT.maximumExclusive,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
export const CREATOR_MODEL_TOOLS = Object.freeze([
|
|
37
|
+
'dshx_claim_plugin',
|
|
38
|
+
'dshx_request_takeover',
|
|
39
|
+
'dshx_scaffold',
|
|
40
|
+
'dshx_check',
|
|
41
|
+
'dshx_activation_plan',
|
|
42
|
+
'dshx_activate_new_client',
|
|
43
|
+
'dshx_remove_plugin',
|
|
44
|
+
'dshx_hot_reload',
|
|
45
|
+
'dshx_browser_open',
|
|
46
|
+
'dshx_status',
|
|
47
|
+
])
|
|
48
|
+
|
|
49
|
+
export const DSHX_SURFACE_MARKERS = Object.freeze({
|
|
50
|
+
'src/internal/creator-claims.mjs': Object.freeze(['beginTakeover', 'commitTakeover', 'CREATOR_TAKEOVER_GRANT_REQUIRED', 'revocations']),
|
|
51
|
+
'src/creator-plus/takeover.js': Object.freeze(['userQuestions', 'ensureTakeoverFence', 'CREATOR_OWNER_NOT_QUIESCENT']),
|
|
52
|
+
'src/internal/browser-access.ts': Object.freeze(['bindBrowserAccess', 'assertSameBrowserHost', 'BROWSER_ADAPTER_REQUIRED', 'configureSessionBrowserAdapter', 'sessionBrowserAdapter']),
|
|
53
|
+
'src/commands/browser.ts': Object.freeze(['cmdBrowser', 'HTTP_AUTHENTICATED']),
|
|
54
|
+
'src/internal/web-proof-auth.ts': Object.freeze(['createWebProofRequest', 'WEB_AUTH_REQUIRED', 'WEB_AUTH_ORIGIN_MISMATCH']),
|
|
55
|
+
'src/cli.ts': Object.freeze([
|
|
56
|
+
"case 'check'",
|
|
57
|
+
"case 'status'",
|
|
58
|
+
"case 'activation-plan'",
|
|
59
|
+
"case 'activate-new-client'",
|
|
60
|
+
"case 'hot-reload'",
|
|
61
|
+
"case 'creator'",
|
|
62
|
+
"case 'browser'",
|
|
63
|
+
"case 'plugin'",
|
|
64
|
+
"case 'update'",
|
|
65
|
+
]),
|
|
66
|
+
'src/commands/activation.ts': Object.freeze([
|
|
67
|
+
'--change patch|manifest|preset|client|new-client|server|artifact',
|
|
68
|
+
'activationDecision(options.change, facts)',
|
|
69
|
+
]),
|
|
70
|
+
'src/commands/check.ts': Object.freeze(['checkPlugin(loadPlugin(root, name), root)']),
|
|
71
|
+
'src/commands/creator.ts': Object.freeze([
|
|
72
|
+
"action === 'claim'",
|
|
73
|
+
"action === 'scaffold'",
|
|
74
|
+
"action === 'remove'",
|
|
75
|
+
"action === 'client-failure'",
|
|
76
|
+
'recoverCreatorClientFailure(root, failure)',
|
|
77
|
+
]),
|
|
78
|
+
'src/commands/new-client.ts': Object.freeze(['SOURCE_BUILT', 'CLIENT_MANIFEST_PRESENT']),
|
|
79
|
+
'src/commands/hot-reload.ts': Object.freeze([
|
|
80
|
+
'hotReloadPlugin',
|
|
81
|
+
'HOST_MODULE_RELOADED',
|
|
82
|
+
'behaviorVerified: false',
|
|
83
|
+
]),
|
|
84
|
+
'src/commands/plugin.ts': Object.freeze([
|
|
85
|
+
"action !== 'remove'",
|
|
86
|
+
'HOST_TREE_INACTIVE',
|
|
87
|
+
'PROFILE_DEPENDENCY_REMOVED',
|
|
88
|
+
]),
|
|
89
|
+
'src/commands/update.ts': Object.freeze([
|
|
90
|
+
'dshx update plan|prepare|verify|apply|rollback',
|
|
91
|
+
"action === 'apply' || action === 'rollback'",
|
|
92
|
+
]),
|
|
93
|
+
'src/internal/creator.ts': Object.freeze(['bridgeVersion !== 2', 'workspaceRoot']),
|
|
94
|
+
'src/internal/guardian.ts': Object.freeze([
|
|
95
|
+
'export function armGuardian',
|
|
96
|
+
'export async function recoverCreatorClientFailure',
|
|
97
|
+
'export async function runGuardianCycle',
|
|
98
|
+
"reason: 'crash-loop'",
|
|
99
|
+
"reason: 'plugin-integrity-failed'",
|
|
100
|
+
]),
|
|
101
|
+
'src/internal/remove-plugin.ts': Object.freeze([
|
|
102
|
+
'HOST_TREE_INACTIVE',
|
|
103
|
+
'official dsh plugin remove',
|
|
104
|
+
'Source is never',
|
|
105
|
+
]),
|
|
106
|
+
'src/internal/profile-plugin-remove.ts': Object.freeze([
|
|
107
|
+
'current __DSH_BOOT__ has no same-name Loader id',
|
|
108
|
+
'HOST_TREE_INACTIVE was not proved',
|
|
109
|
+
'official dsh plugin remove failed',
|
|
110
|
+
'retained-until-next-boot',
|
|
111
|
+
]),
|
|
112
|
+
'src/internal/io.ts': Object.freeze(["command === 'update'", "args[0] ?? 'plan'"]),
|
|
113
|
+
'src/internal/new-client.ts': Object.freeze([
|
|
114
|
+
'BOOT_MANIFEST_ASSIGNMENT',
|
|
115
|
+
'window.__DSH_BOOT__',
|
|
116
|
+
'globalThis["__DSH_BOOT__"]',
|
|
117
|
+
'no supported __DSH_BOOT__ manifest assignment',
|
|
118
|
+
]),
|
|
119
|
+
'src/internal/hot-reload.ts': Object.freeze([
|
|
120
|
+
'export async function hotReloadPlugin',
|
|
121
|
+
'artifactHashes',
|
|
122
|
+
'targetScope',
|
|
123
|
+
'bytesUnchanged: true',
|
|
124
|
+
'cleanupProved: true',
|
|
125
|
+
]),
|
|
126
|
+
'src/internal/hot-reload-journal.ts': Object.freeze([
|
|
127
|
+
"kind: 'server-hot-reload'",
|
|
128
|
+
'automaticRecovery: false',
|
|
129
|
+
'writeHotReloadJournal',
|
|
130
|
+
]),
|
|
131
|
+
'src/runtime/hot-reload-observer.mjs': Object.freeze([
|
|
132
|
+
"ctx.on('hmr/reload'",
|
|
133
|
+
"publish('MODULE_RELOADED'",
|
|
134
|
+
'targetGenerationStates',
|
|
135
|
+
"publish('OBSERVER_DISPOSED'",
|
|
136
|
+
]),
|
|
137
|
+
'src/runtime/hot-reload-hmr-audit.mjs': Object.freeze([
|
|
138
|
+
'export async function auditActiveHmr',
|
|
139
|
+
'could not resolve the official HMR runtime',
|
|
140
|
+
'another watcher overlapping targetFiles',
|
|
141
|
+
]),
|
|
142
|
+
'src/internal/host-discovery.ts': Object.freeze([
|
|
143
|
+
'discoverWebHosts',
|
|
144
|
+
'parseWebProcessTable',
|
|
145
|
+
'assertNoAffectedWebHosts',
|
|
146
|
+
'processStartedAt',
|
|
147
|
+
'process table unavailable',
|
|
148
|
+
]),
|
|
149
|
+
'src/internal/host.ts': Object.freeze([
|
|
150
|
+
'acquireWebHostOperationLock',
|
|
151
|
+
'processStartedAt',
|
|
152
|
+
'export function probePid',
|
|
153
|
+
"'ESRCH'",
|
|
154
|
+
'export async function probePort',
|
|
155
|
+
"'ECONNREFUSED'",
|
|
156
|
+
]),
|
|
157
|
+
'src/commands/host.ts': Object.freeze([
|
|
158
|
+
'shared-home-collision',
|
|
159
|
+
'already-attached',
|
|
160
|
+
'proveSpawnedWebHost',
|
|
161
|
+
'dshx-verify-home-',
|
|
162
|
+
'--keep is not available for isolated verification',
|
|
163
|
+
]),
|
|
164
|
+
'src/internal/update-apply.ts': Object.freeze([
|
|
165
|
+
'assertNoAffectedWebHosts',
|
|
166
|
+
'withUpdateHostGuard',
|
|
167
|
+
'update apply',
|
|
168
|
+
'update rollback',
|
|
169
|
+
]),
|
|
170
|
+
'knowledge/contracts/creator-mode-plus.md': Object.freeze([
|
|
171
|
+
'creator claim',
|
|
172
|
+
'creator scaffold',
|
|
173
|
+
'creator remove',
|
|
174
|
+
'activate-new-client',
|
|
175
|
+
]),
|
|
176
|
+
'knowledge/contracts/creator-guardian.md': Object.freeze(['Creator+', 'crash-loop fuse', 'quarantine', 'plugin-integrity-failed']),
|
|
177
|
+
'knowledge/contracts/harness-update.md': Object.freeze(['plan → prepare → verify → apply', 'rollback']),
|
|
178
|
+
'knowledge/contracts/live-activation.md': Object.freeze(['SOURCE_BUILT', 'CLIENT_MANIFEST_PRESENT']),
|
|
179
|
+
'knowledge/contracts/compat-0.1.5.md': Object.freeze(['MarkdownText', 'createDrafts', 'assistant/attempt.stream']),
|
|
180
|
+
'src/internal/compat-015.ts': Object.freeze(['compat-015-message-text', 'compat-015-ctx-agent']),
|
|
181
|
+
'knowledge/playbooks/verify-boot.md': Object.freeze(['临时 `DSH_HOME`', '`--keep` 会被拒绝']),
|
|
182
|
+
'skill/dshx/SKILL.md': Object.freeze(['Treat DSH.app, direct `dsh web`', '`--keep` is unsafe and', 'PID-reused is reported as stale']),
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
export const REQUIRED_DSHX_PATHS = Object.freeze(Object.keys(DSHX_SURFACE_MARKERS))
|
|
186
|
+
|
|
187
|
+
function parseVersion(value) {
|
|
188
|
+
if (typeof value !== 'string') return undefined
|
|
189
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(value)
|
|
190
|
+
if (!match) return undefined
|
|
191
|
+
return {
|
|
192
|
+
parts: match.slice(1, 4).map(Number),
|
|
193
|
+
prerelease: match[4] !== undefined,
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function compareVersions(left, right) {
|
|
198
|
+
for (let index = 0; index < 3; index += 1) {
|
|
199
|
+
if (left[index] !== right[index]) return left[index] - right[index]
|
|
200
|
+
}
|
|
201
|
+
return 0
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Return whether a stable DSHX release implements the complete v0.7 contract. */
|
|
205
|
+
export function supportsDshxVersion(version) {
|
|
206
|
+
const parsed = parseVersion(version)
|
|
207
|
+
const minimum = parseVersion(DSHX_CONTRACT.minimum)
|
|
208
|
+
const maximum = parseVersion(DSHX_CONTRACT.maximumExclusive)
|
|
209
|
+
return Boolean(parsed)
|
|
210
|
+
&& parsed.prerelease === false
|
|
211
|
+
&& compareVersions(parsed.parts, minimum.parts) >= 0
|
|
212
|
+
&& compareVersions(parsed.parts, maximum.parts) < 0
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Fail closed unless one Harness checkout carries the full DSHX v0.7 contract. */
|
|
216
|
+
export function inspectDshxCompatibility(harnessRoot) {
|
|
217
|
+
const root = resolve(harnessRoot)
|
|
218
|
+
const dshxRoot = join(root, 'tools/dshx')
|
|
219
|
+
const packagePath = join(dshxRoot, 'package.json')
|
|
220
|
+
let metadata
|
|
221
|
+
try {
|
|
222
|
+
metadata = JSON.parse(readFileSync(packagePath, 'utf8'))
|
|
223
|
+
} catch (error) {
|
|
224
|
+
throw new Error(`dsh-creator-mode-plus: cannot read DSHX package metadata at ${packagePath}: ${error instanceof Error ? error.message : String(error)}`)
|
|
225
|
+
}
|
|
226
|
+
if (metadata.name !== 'dsh-external-plugin-devkit') {
|
|
227
|
+
throw new Error(`dsh-creator-mode-plus: unexpected package at ${packagePath}`)
|
|
228
|
+
}
|
|
229
|
+
if (!supportsDshxVersion(metadata.version)) {
|
|
230
|
+
throw new Error(
|
|
231
|
+
`dsh-creator-mode-plus: dshx ${metadata.version ?? 'unknown'} is incompatible; `
|
|
232
|
+
+ `contract ${DSHX_CONTRACT.id} requires >=${DSHX_CONTRACT.minimum} <${DSHX_CONTRACT.maximumExclusive}`,
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
const missingPaths = REQUIRED_DSHX_PATHS.filter(path => !existsSync(join(dshxRoot, path)))
|
|
236
|
+
if (missingPaths.length > 0) {
|
|
237
|
+
throw new Error(
|
|
238
|
+
`dsh-creator-mode-plus: dshx ${metadata.version} is missing required ${DSHX_CONTRACT.id} surfaces: ${missingPaths.join(', ')}`,
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
const driftedSurfaces = []
|
|
242
|
+
for (const [path, markers] of Object.entries(DSHX_SURFACE_MARKERS)) {
|
|
243
|
+
const source = readFileSync(join(dshxRoot, path), 'utf8')
|
|
244
|
+
const missingMarkers = markers.filter(marker => !source.includes(marker))
|
|
245
|
+
if (missingMarkers.length > 0) driftedSurfaces.push(`${path} [${missingMarkers.join(', ')}]`)
|
|
246
|
+
}
|
|
247
|
+
if (driftedSurfaces.length > 0) {
|
|
248
|
+
throw new Error(
|
|
249
|
+
`dsh-creator-mode-plus: dshx ${metadata.version} contract drift for ${DSHX_CONTRACT.id}: ${driftedSurfaces.join('; ')}`,
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
return Object.freeze({
|
|
253
|
+
root,
|
|
254
|
+
dshxRoot,
|
|
255
|
+
packagePath,
|
|
256
|
+
dshxVersion: metadata.version,
|
|
257
|
+
creatorBridgeVersion: CREATOR_BRIDGE_VERSION,
|
|
258
|
+
contractId: DSHX_CONTRACT.id,
|
|
259
|
+
capabilities: DSHX_CONTRACT.capabilities,
|
|
260
|
+
})
|
|
261
|
+
}
|