@ran-sh/dsh-crew 1.1.1 → 1.2.0-rc.4
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/.claude-plugin/marketplace.json +16 -17
- package/.claude-plugin/plugin.json +7 -1
- package/README.md +44 -15
- package/README.zh.md +37 -12
- package/agents/ds-flash.md +16 -18
- package/agents/ds-pro.md +16 -24
- package/agents/ds-reviewer.md +15 -15
- package/agents/ds-worker.md +16 -16
- package/bin/dsh-crew.mjs +16 -12
- package/codex/AGENTS.md +37 -85
- package/codex/agents/ds-flash.toml +18 -14
- package/codex/agents/ds-pro.toml +18 -15
- package/codex/agents/ds-reviewer.toml +17 -13
- package/codex/agents/ds-worker.toml +17 -13
- package/codex/prompts/dsh-config.md +17 -1
- package/codex/prompts/dsh-status.md +5 -1
- package/commands/config.md +16 -4
- package/commands/status.md +6 -2
- package/docs/installation.md +54 -9
- package/docs/readiness-matrix.md +14 -6
- package/docs/ui-surfaces.md +32 -17
- package/lib/client.js +5434 -4844
- package/official-web-bridge/lib/client.js +567 -479
- package/official-web-bridge/overlay-entry.mjs +59 -0
- package/package.json +6 -3
- package/scripts/build-client.mjs +2 -0
- package/scripts/setup.mjs +8 -7
- package/scripts/verify-crew-ui-polish.mjs +145 -0
- package/scripts/verify-history-ui.mjs +97 -0
- package/scripts/verify-npm-install.mjs +1 -1
- package/src/client/history-panel.tsx +108 -0
- package/src/client/host-readiness.mjs +15 -6
- package/src/client/index.tsx +86 -64
- package/src/client/model-callability-view.mjs +17 -0
- package/src/client/panel-chrome.tsx +40 -0
- package/src/client/quick-panel.tsx +88 -73
- package/src/client/readiness-envelope.mjs +34 -0
- package/src/config-readiness.mjs +181 -115
- package/src/extension-contract.mjs +44 -26
- package/src/history/admission-gate.mjs +51 -0
- package/src/history/archive-store.mjs +261 -0
- package/src/history/cleanup-plan.mjs +64 -0
- package/src/history/http.mjs +32 -0
- package/src/history/operation.mjs +86 -0
- package/src/history/runner.mjs +51 -0
- package/src/history/runtime.mjs +28 -0
- package/src/history/service.mjs +137 -0
- package/src/history/state.mjs +27 -0
- package/src/hub/index.mjs +141 -24
- package/src/hub-client.mjs +17 -15
- package/src/image-output.mjs +71 -0
- package/src/install/install-legacy.mjs +126 -23
- package/src/install/npx-lifecycle.mjs +373 -64
- package/src/install/official-frontend-assets.mjs +78 -0
- package/src/install/payload-content.mjs +88 -0
- package/src/install/windows-startup.mjs +107 -19
- package/src/install/windows-supervisor-adapter.mjs +443 -0
- package/src/install/windows-supervisor-lifecycle.mjs +782 -0
- package/src/install/zcode.mjs +53 -14
- package/src/job-contracts.mjs +37 -31
- package/src/jobs.mjs +2 -2
- package/src/mcp-runtime.mjs +74 -9
- package/src/model-callability-contract.mjs +79 -0
- package/src/multimodal.mjs +0 -0
- package/src/official-web-bridge.mjs +0 -2
- package/src/provider-health.mjs +130 -124
- package/src/readiness-matrix.mjs +20 -15
- package/src/runtime-identity-contract.mjs +34 -0
- package/src/runtime-identity.mjs +14 -4
- package/src/runtime-readiness-snapshot.mjs +285 -87
- package/src/server.mjs +86 -76
- package/src/standalone-sdk.mjs +23 -0
- package/src/supervisor/restart-request.mjs +64 -10
- package/src/workflow-runtime.mjs +28 -13
- package/windows/start-dsh-crew.cmd +57 -57
- package/windows/start-dsh-crew.ps1 +314 -51
- package/windows/supervisor-control.ps1 +467 -0
- package/zcode/AGENTS.md +4 -1
- package/zcode/agents/ds-reviewer.md +31 -36
- package/zcode/agents/ds-worker.md +31 -39
- package/zcode/commands/dsh-config.md +17 -6
- package/zcode/commands/dsh-status.md +5 -4
- package/.mcp.json +0 -8
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// render Codex agent roles with real paths. Called from the CLI entry or the
|
|
3
3
|
// DSH settings page. All edits are backed up and idempotent.
|
|
4
4
|
|
|
5
|
-
import { readFileSync, writeFileSync, mkdirSync, copyFileSync, existsSync, readdirSync, rmSync, statSync } from 'node:fs';
|
|
5
|
+
import { readFileSync, writeFileSync, mkdirSync, copyFileSync, existsSync, readdirSync, rmSync, statSync, lstatSync } from 'node:fs';
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
6
7
|
import { dirname, join, resolve, relative } from 'node:path';
|
|
7
8
|
import { fileURLToPath } from 'node:url';
|
|
8
9
|
import { homedir } from 'node:os';
|
|
@@ -14,6 +15,9 @@ const MARKETPLACE_NAME = 'dsh-crew';
|
|
|
14
15
|
const PLUGIN_KEY = `dsh-crew@${MARKETPLACE_NAME}`;
|
|
15
16
|
const POLICY_START = '<!-- DSH CREW MANAGED POLICY:START -->';
|
|
16
17
|
const POLICY_END = '<!-- DSH CREW MANAGED POLICY:END -->';
|
|
18
|
+
export const CODEX_LEGACY_POLICY_HASHES = Object.freeze([
|
|
19
|
+
'2d6f3839bb3df4bda90f481726281292b1a4b4585298b1cf9ec56215295b5c78',
|
|
20
|
+
]);
|
|
17
21
|
// dsh_worker_config is included so the session commands (/dsh-crew:config,
|
|
18
22
|
// /dsh-config) and any orchestrator policy lookup run without an extra
|
|
19
23
|
// authorization prompt.
|
|
@@ -36,12 +40,46 @@ function readText(file) {
|
|
|
36
40
|
try { return readFileSync(file, 'utf8'); } catch { return null; }
|
|
37
41
|
}
|
|
38
42
|
|
|
43
|
+
function normalizedPath(value) {
|
|
44
|
+
if (typeof value !== 'string' || !value.trim()) return null;
|
|
45
|
+
try {
|
|
46
|
+
const path = resolve(value);
|
|
47
|
+
return process.platform === 'win32' ? path.toLowerCase() : path;
|
|
48
|
+
} catch { return null; }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function renderedCodexRole(root, file) {
|
|
52
|
+
const source = readText(join(root, 'codex', 'agents', file));
|
|
53
|
+
if (source === null) return null;
|
|
54
|
+
const renderedPath = join(root, 'src', 'server.mjs').replace(/\\/g, '/');
|
|
55
|
+
return source.replace(/args = \[.*server\.mjs"\]/, `args = ["${renderedPath}"]`);
|
|
56
|
+
}
|
|
57
|
+
|
|
39
58
|
function managedPolicyBlock(root) {
|
|
40
59
|
const policy = readText(join(root, 'codex', 'AGENTS.md'))?.trim();
|
|
41
60
|
if (!policy) return null;
|
|
42
61
|
return `${POLICY_START}\n${policy}\n${POLICY_END}`;
|
|
43
62
|
}
|
|
44
63
|
|
|
64
|
+
export function codexLegacyPolicyDigest(text) {
|
|
65
|
+
const canonical = typeof text === 'string' ? text.replace(/\r\n/g, '\n').trim() : '';
|
|
66
|
+
return canonical ? createHash('sha256').update(canonical, 'utf8').digest('hex') : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function stripKnownLegacyCodexPolicy(text, { knownHashes = CODEX_LEGACY_POLICY_HASHES } = {}) {
|
|
70
|
+
if (typeof text !== 'string') return text;
|
|
71
|
+
const start = POLICY_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
72
|
+
const end = POLICY_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
73
|
+
const match = new RegExp(`${start}[\\s\\S]*?${end}`, 'm').exec(text);
|
|
74
|
+
if (!match) return text;
|
|
75
|
+
const allowed = new Set(Array.isArray(knownHashes) ? knownHashes : []);
|
|
76
|
+
const prefix = text.slice(0, match.index);
|
|
77
|
+
const suffix = text.slice(match.index + match[0].length);
|
|
78
|
+
const keptPrefix = allowed.has(codexLegacyPolicyDigest(prefix)) ? '' : prefix;
|
|
79
|
+
const keptSuffix = allowed.has(codexLegacyPolicyDigest(suffix)) ? '' : suffix;
|
|
80
|
+
return `${keptPrefix}${match[0]}${keptSuffix}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
45
83
|
function installGlobalCodexPolicy({ home, root }) {
|
|
46
84
|
const file = join(home, '.codex', 'AGENTS.md');
|
|
47
85
|
const block = managedPolicyBlock(root);
|
|
@@ -54,6 +92,7 @@ function installGlobalCodexPolicy({ home, root }) {
|
|
|
54
92
|
if (managed.test(current)) next = current.replace(managed, block);
|
|
55
93
|
else if (current.trim() === template) next = `${block}\n`;
|
|
56
94
|
else next = `${current.trimEnd()}${current.trim() ? '\n\n' : ''}${block}\n`;
|
|
95
|
+
next = stripKnownLegacyCodexPolicy(next);
|
|
57
96
|
if (next !== current) {
|
|
58
97
|
backup(file);
|
|
59
98
|
writeFileSync(file, next);
|
|
@@ -125,17 +164,61 @@ function isFile(file) {
|
|
|
125
164
|
}
|
|
126
165
|
|
|
127
166
|
function claudePluginRootReady(root) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
167
|
+
if (typeof root !== 'string' || !root.trim() || !isFile(join(root, 'src', 'server.mjs'))) return false;
|
|
168
|
+
const plugin = readJson(join(root, '.claude-plugin', 'plugin.json'), null);
|
|
169
|
+
const mcp = plugin?.mcpServers?.['dsh-crew'];
|
|
170
|
+
return !!plugin
|
|
171
|
+
&& typeof plugin.version === 'string'
|
|
172
|
+
&& mcp?.command === 'node'
|
|
173
|
+
&& Array.isArray(mcp.args)
|
|
174
|
+
&& mcp.args.length === 1
|
|
175
|
+
&& mcp.args[0] === '${CLAUDE_PLUGIN_ROOT}/src/server.mjs';
|
|
132
176
|
}
|
|
133
177
|
|
|
134
|
-
function
|
|
178
|
+
function managedClaudeFileManifest(root, { maxFiles = 512, maxBytes = 8 * 1024 * 1024 } = {}) {
|
|
179
|
+
const files = [];
|
|
180
|
+
let bytes = 0;
|
|
181
|
+
const add = (file, relativePath) => {
|
|
182
|
+
const info = lstatSync(file);
|
|
183
|
+
if (info.isSymbolicLink() || !info.isFile()) throw new Error('unsupported snapshot entry');
|
|
184
|
+
const content = readFileSync(file);
|
|
185
|
+
bytes += content.length;
|
|
186
|
+
if (bytes > maxBytes || files.length >= maxFiles) throw new Error('snapshot manifest bound exceeded');
|
|
187
|
+
files.push([relativePath.replace(/\\/g, '/'), createHash('sha256').update(content).digest('hex')]);
|
|
188
|
+
};
|
|
189
|
+
const walk = (directory, relativeDirectory) => {
|
|
190
|
+
if (!existsSync(directory)) return;
|
|
191
|
+
const info = lstatSync(directory);
|
|
192
|
+
if (info.isSymbolicLink() || !info.isDirectory()) throw new Error('unsupported snapshot directory');
|
|
193
|
+
for (const entry of readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
194
|
+
const file = join(directory, entry.name);
|
|
195
|
+
const relativePath = join(relativeDirectory, entry.name);
|
|
196
|
+
if (entry.isSymbolicLink()) throw new Error('snapshot symlink not allowed');
|
|
197
|
+
if (entry.isDirectory()) walk(file, relativePath);
|
|
198
|
+
else if (entry.isFile()) add(file, relativePath);
|
|
199
|
+
else throw new Error('unsupported snapshot entry');
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
try {
|
|
203
|
+
add(join(root, '.claude-plugin', 'plugin.json'), join('.claude-plugin', 'plugin.json'));
|
|
204
|
+
if (isFile(join(root, 'package.json'))) add(join(root, 'package.json'), 'package.json');
|
|
205
|
+
for (const directory of ['agents', 'commands', 'src']) walk(join(root, directory), directory);
|
|
206
|
+
return files;
|
|
207
|
+
} catch { return null; }
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function sameManagedClaudeFiles(expectedRoot, snapshotRoot) {
|
|
211
|
+
if (!claudePluginRootReady(expectedRoot) || !claudePluginRootReady(snapshotRoot)) return false;
|
|
212
|
+
const expected = managedClaudeFileManifest(expectedRoot);
|
|
213
|
+
const snapshot = managedClaudeFileManifest(snapshotRoot);
|
|
214
|
+
return expected !== null && snapshot !== null && JSON.stringify(snapshot) === JSON.stringify(expected);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function claudeSnapshotReady(home, root) {
|
|
135
218
|
const installed = readJson(join(home, '.claude', 'plugins', 'installed_plugins.json'), {});
|
|
136
219
|
const record = installed?.plugins?.[PLUGIN_KEY];
|
|
137
220
|
const entries = Array.isArray(record) ? record : [record];
|
|
138
|
-
return entries.some((entry) =>
|
|
221
|
+
return entries.some((entry) => sameManagedClaudeFiles(root, entry?.installPath));
|
|
139
222
|
}
|
|
140
223
|
|
|
141
224
|
function claudePermissionsReady(settings) {
|
|
@@ -302,33 +385,43 @@ export function writeGlobalConfig(patch) {
|
|
|
302
385
|
}
|
|
303
386
|
|
|
304
387
|
/** What is currently installed where — drives the settings-page buttons. */
|
|
305
|
-
export function installStatus({ home = homedir(), root } = {}) {
|
|
388
|
+
export function installStatus({ home = homedir(), root = ROOT } = {}) {
|
|
306
389
|
const settings = readJson(join(home, '.claude', 'settings.json'), {});
|
|
307
390
|
const enabled = settings.enabledPlugins;
|
|
308
391
|
const claudeInstalled = !!(enabled && !Array.isArray(enabled) && enabled[PLUGIN_KEY]);
|
|
309
392
|
const hudWired = typeof settings.statusLine?.command === 'string'
|
|
310
393
|
&& settings.statusLine.command.includes('worker-segment.sh');
|
|
394
|
+
const marketplaceRoot = settings?.extraKnownMarketplaces?.[MARKETPLACE_NAME]?.source?.path;
|
|
395
|
+
const installedPluginRecord = readJson(join(home, '.claude', 'plugins', 'installed_plugins.json'), {})?.plugins?.[PLUGIN_KEY];
|
|
396
|
+
const claudeFootprint = claudeInstalled || typeof marketplaceRoot === 'string' || installedPluginRecord !== undefined;
|
|
311
397
|
const claudeComponents = {
|
|
312
398
|
enabled: claudeInstalled,
|
|
313
|
-
marketplace: claudePluginRootReady(
|
|
314
|
-
snapshot: claudeSnapshotReady(home),
|
|
399
|
+
marketplace: normalizedPath(marketplaceRoot) === normalizedPath(root) && claudePluginRootReady(root),
|
|
400
|
+
snapshot: claudeSnapshotReady(home, root),
|
|
315
401
|
permissions: claudePermissionsReady(settings),
|
|
316
402
|
};
|
|
317
403
|
const claudeMissing = Object.entries(claudeComponents).filter(([, present]) => !present).map(([key]) => key);
|
|
318
404
|
const codexRoot = join(home, '.codex');
|
|
319
405
|
const configFile = join(codexRoot, 'config.toml');
|
|
320
406
|
const configText = readText(configFile) ?? '';
|
|
321
|
-
const
|
|
322
|
-
const
|
|
407
|
+
const workerFile = join(codexRoot, 'agents', 'ds-worker.toml');
|
|
408
|
+
const reviewerFile = join(codexRoot, 'agents', 'ds-reviewer.toml');
|
|
409
|
+
const workerTarget = codexRoleTarget(workerFile, 'ds-worker');
|
|
410
|
+
const reviewerTarget = codexRoleTarget(reviewerFile, 'ds-reviewer');
|
|
323
411
|
const mcpTarget = codexMcpTarget(configText);
|
|
412
|
+
const expectedTarget = normalizedPath(join(root, 'src', 'server.mjs'));
|
|
413
|
+
const expectedWorkerRole = renderedCodexRole(root, 'ds-worker.toml');
|
|
414
|
+
const expectedReviewerRole = renderedCodexRole(root, 'ds-reviewer.toml');
|
|
415
|
+
const expectedConfigPrompt = readText(join(root, 'codex', 'prompts', 'dsh-config.md'));
|
|
416
|
+
const expectedStatusPrompt = readText(join(root, 'codex', 'prompts', 'dsh-status.md'));
|
|
324
417
|
const components = {
|
|
325
|
-
worker_role:
|
|
326
|
-
reviewer_role:
|
|
327
|
-
config_prompt:
|
|
328
|
-
status_prompt:
|
|
329
|
-
mcp: !!mcpTarget,
|
|
330
|
-
target_alignment: !!
|
|
331
|
-
global_policy: globalCodexPolicyReady({ home }),
|
|
418
|
+
worker_role: expectedWorkerRole !== null && readText(workerFile) === expectedWorkerRole,
|
|
419
|
+
reviewer_role: expectedReviewerRole !== null && readText(reviewerFile) === expectedReviewerRole,
|
|
420
|
+
config_prompt: expectedConfigPrompt !== null && readText(join(codexRoot, 'prompts', 'dsh-config.md')) === expectedConfigPrompt,
|
|
421
|
+
status_prompt: expectedStatusPrompt !== null && readText(join(codexRoot, 'prompts', 'dsh-status.md')) === expectedStatusPrompt,
|
|
422
|
+
mcp: !!expectedTarget && mcpTarget === expectedTarget,
|
|
423
|
+
target_alignment: !!expectedTarget && workerTarget === expectedTarget && reviewerTarget === expectedTarget && mcpTarget === expectedTarget,
|
|
424
|
+
global_policy: globalCodexPolicyReady({ home, root }),
|
|
332
425
|
};
|
|
333
426
|
const codexInstalled = Object.values(components).some(Boolean)
|
|
334
427
|
|| existsSync(join(codexRoot, 'agents', 'ds-flash.toml'))
|
|
@@ -336,14 +429,14 @@ export function installStatus({ home = homedir(), root } = {}) {
|
|
|
336
429
|
const missing = Object.entries(components).filter(([, present]) => !present).map(([key]) => key);
|
|
337
430
|
return {
|
|
338
431
|
claude: {
|
|
339
|
-
installed:
|
|
432
|
+
installed: claudeFootprint,
|
|
340
433
|
ready: claudeMissing.length === 0,
|
|
341
434
|
hud: hudWired,
|
|
342
435
|
components: claudeComponents,
|
|
343
436
|
missing: claudeMissing,
|
|
344
437
|
},
|
|
345
438
|
codex: { installed: codexInstalled, ready: missing.length === 0, components, missing },
|
|
346
|
-
zcode: zcodeStatus({ home,
|
|
439
|
+
zcode: zcodeStatus({ home, root }),
|
|
347
440
|
};
|
|
348
441
|
}
|
|
349
442
|
|
|
@@ -450,6 +543,17 @@ export async function installClaudeCode({ home = homedir(), statusline = false,
|
|
|
450
543
|
// in the plugin cache (settings alone leave a stale/absent cache entry) and
|
|
451
544
|
// pulls the plugin so the next session loads it. Best-effort: settings are
|
|
452
545
|
// already correct, so a missing CLI just means one manual `plugin install`.
|
|
546
|
+
const registered = readJson(join(home, '.claude', 'plugins', 'known_marketplaces.json'), {})?.[MARKETPLACE_NAME];
|
|
547
|
+
const installedRecord = readJson(join(home, '.claude', 'plugins', 'installed_plugins.json'), {})?.plugins?.[PLUGIN_KEY];
|
|
548
|
+
const installedEntries = Array.isArray(installedRecord) ? installedRecord : [installedRecord];
|
|
549
|
+
const marketplaceCurrent = registered?.source?.source === 'directory'
|
|
550
|
+
&& normalizedPath(registered.source.path) === normalizedPath(root)
|
|
551
|
+
&& normalizedPath(registered.installLocation) === normalizedPath(root);
|
|
552
|
+
if (marketplaceCurrent && installedEntries.some((entry) => entry?.scope === 'user'
|
|
553
|
+
&& sameManagedClaudeFiles(root, entry.installPath))) {
|
|
554
|
+
actions.push('cli: skipped (registered marketplace and snapshot already current)');
|
|
555
|
+
return { ok: true, actions };
|
|
556
|
+
}
|
|
453
557
|
if (home !== homedir()) {
|
|
454
558
|
actions.push('cli: skipped (non-default home; test mode)');
|
|
455
559
|
return { ok: true, actions };
|
|
@@ -493,8 +597,7 @@ export function installCodex({ home = homedir(), scope, root = ROOT } = {}) {
|
|
|
493
597
|
const dest = join(agentsDir, f);
|
|
494
598
|
const bak = backup(dest);
|
|
495
599
|
if (bak) actions.push(`backup: ${bak}`);
|
|
496
|
-
const rendered =
|
|
497
|
-
.replace(/args = \[.*server\.mjs"\]/, `args = ["${renderedPath}"]`);
|
|
600
|
+
const rendered = renderedCodexRole(root, f);
|
|
498
601
|
writeFileSync(dest, rendered);
|
|
499
602
|
actions.push(`role: ${dest}`);
|
|
500
603
|
}
|