@ran-sh/dsh-crew 0.3.7 → 0.4.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/README.md +52 -67
- package/README.zh.md +52 -68
- package/docs/gpt-relay-extension.md +103 -0
- package/docs/job-contracts.md +107 -0
- package/docs/readiness-matrix.md +73 -0
- package/lib/client.js +3446 -3446
- package/official-web-bridge/cordis.patch.yml +4 -0
- package/official-web-bridge/entry.mjs +1 -0
- package/official-web-bridge/lib/client.js +3446 -0
- package/official-web-bridge/package.json +26 -0
- package/package.json +8 -3
- package/scripts/build-client.mjs +5 -1
- package/scripts/verify-official-bridge-e2e.mjs +180 -0
- package/src/dsh-cli-runtime.mjs +219 -208
- package/src/extension-contract.mjs +78 -0
- package/src/failure-classification.mjs +29 -0
- package/src/hub/index.mjs +343 -62
- package/src/information-flow.mjs +67 -0
- package/src/install/npx-lifecycle.mjs +149 -5
- package/src/install/official-web.mjs +132 -0
- package/src/job-contracts.mjs +218 -0
- package/src/mcp-runtime.mjs +16 -8
- package/src/official-web-bridge.mjs +211 -0
- package/src/role-profiles.mjs +107 -0
- package/src/runtime-identity.mjs +6 -1
- package/src/server.mjs +141 -98
- package/src/workflow-runtime.mjs +109 -10
- package/src/workspace-context.mjs +146 -0
- package/src/workspace-readiness.mjs +32 -0
package/src/dsh-cli-runtime.mjs
CHANGED
|
@@ -5,27 +5,27 @@
|
|
|
5
5
|
// compatibility fallbacks. Status and uninstall callers can resolve without
|
|
6
6
|
// allowing a download.
|
|
7
7
|
|
|
8
|
-
import { spawnSync } from 'node:child_process';
|
|
9
|
-
import { createRequire } from 'node:module';
|
|
10
|
-
import {
|
|
11
|
-
existsSync,
|
|
12
|
-
lstatSync,
|
|
13
|
-
mkdirSync,
|
|
14
|
-
readFileSync,
|
|
15
|
-
realpathSync,
|
|
16
|
-
symlinkSync,
|
|
17
|
-
unlinkSync,
|
|
18
|
-
writeFileSync,
|
|
19
|
-
} from 'node:fs';
|
|
20
|
-
import { dirname, extname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
8
|
+
import { spawnSync } from 'node:child_process';
|
|
9
|
+
import { createRequire } from 'node:module';
|
|
10
|
+
import {
|
|
11
|
+
existsSync,
|
|
12
|
+
lstatSync,
|
|
13
|
+
mkdirSync,
|
|
14
|
+
readFileSync,
|
|
15
|
+
realpathSync,
|
|
16
|
+
symlinkSync,
|
|
17
|
+
unlinkSync,
|
|
18
|
+
writeFileSync,
|
|
19
|
+
} from 'node:fs';
|
|
20
|
+
import { dirname, extname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
21
21
|
import { homedir } from 'node:os';
|
|
22
22
|
import { crewDshHome, crewProfileDir } from './install/install.mjs';
|
|
23
23
|
|
|
24
|
-
export const DSH_CLI_PACKAGE = '@deepseek-ai/dsh';
|
|
25
|
-
export const CREW_DSH_RUNTIME_DIRNAME = 'runtime';
|
|
26
|
-
const CREW_PROFILE_DEFAULT_BUNDLES = ['@deepseek-ai/dsh-base'];
|
|
27
|
-
const PROFILE_PATCH_TEMPLATE = '[]\n';
|
|
28
|
-
const PROFILE_PNPM_WORKSPACE = 'packages:\n - .\n\nnodeLinker: hoisted\nautoInstallPeers: false\n';
|
|
24
|
+
export const DSH_CLI_PACKAGE = '@deepseek-ai/dsh';
|
|
25
|
+
export const CREW_DSH_RUNTIME_DIRNAME = 'runtime';
|
|
26
|
+
const CREW_PROFILE_DEFAULT_BUNDLES = ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app'];
|
|
27
|
+
const PROFILE_PATCH_TEMPLATE = '[]\n';
|
|
28
|
+
const PROFILE_PNPM_WORKSPACE = 'packages:\n - .\n\nnodeLinker: hoisted\nautoInstallPeers: false\n';
|
|
29
29
|
|
|
30
30
|
function defaultFindCommand(name) {
|
|
31
31
|
const probe = process.platform === 'win32' ? 'where.exe' : 'which';
|
|
@@ -236,170 +236,181 @@ export function ensureCrewDshRuntime({
|
|
|
236
236
|
return { ok: true, cli, reused: false, version: cli.version, runtimeRoot };
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
export function describeDshCli(cli) {
|
|
239
|
+
export function describeDshCli(cli) {
|
|
240
240
|
if (!cli) return 'unavailable';
|
|
241
241
|
const version = cli.version ? `@${cli.version}` : '';
|
|
242
242
|
return `${cli.kind}${version}`;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function isObject(value) {
|
|
246
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function isSafePackageName(name) {
|
|
250
|
-
return typeof name === 'string'
|
|
251
|
-
&& /^(?:@[^/\s]+\/)?[^/\s]+$/u.test(name)
|
|
252
|
-
&& !name.split('/').some((part) => part === '.' || part === '..');
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function isWithin(root, candidate) {
|
|
256
|
-
const rel = relative(root, candidate);
|
|
257
|
-
return rel === '' || (rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function fileSpec(target) {
|
|
261
|
-
return `link:${target.replace(/\\/g, '/')}`;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function profileLinkPath(profileRoot, name) {
|
|
265
|
-
return join(profileRoot, 'node_modules', ...name.split('/'));
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function readPluginRoot(root, expectedName) {
|
|
269
|
-
if (typeof root !== 'string' || !isAbsolute(root)) return { ok: false, code: 'INVALID_CREW_PLUGIN_ROOT' };
|
|
270
|
-
let targetRoot;
|
|
271
|
-
try { targetRoot = realpathSync(root); } catch { return { ok: false, code: 'CREW_PLUGIN_ROOT_NOT_FOUND' }; }
|
|
272
|
-
const packageFile = join(targetRoot, 'package.json');
|
|
273
|
-
let pkg;
|
|
274
|
-
try { pkg = JSON.parse(readFileSync(packageFile, 'utf8')); } catch { return { ok: false, code: 'CREW_PLUGIN_MANIFEST_INVALID' }; }
|
|
275
|
-
if (!isObject(pkg) || !isSafePackageName(pkg.name) || (expectedName !== undefined && pkg.name !== expectedName)) {
|
|
276
|
-
return { ok: false, code: 'CREW_PLUGIN_MANIFEST_INVALID' };
|
|
277
|
-
}
|
|
278
|
-
const patchSpec = pkg.dsh?.bundle?.patch;
|
|
279
|
-
if (typeof patchSpec !== 'string' || !patchSpec.trim()) return { ok: false, code: 'CREW_PLUGIN_BUNDLE_INVALID' };
|
|
280
|
-
const patchPath = resolve(targetRoot, patchSpec);
|
|
281
|
-
if (!isWithin(targetRoot, patchPath) || !existsSync(patchPath)) return { ok: false, code: 'CREW_PLUGIN_BUNDLE_INVALID' };
|
|
282
|
-
return { ok: true, targetRoot, packageFile, packageName: pkg.name, packageVersion: pkg.version ?? null, patchPath, pkg };
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
function readProfileManifestForRegistration(profileRoot) {
|
|
286
|
-
const profileManifest = join(profileRoot, 'package.json');
|
|
287
|
-
if (!existsSync(profileManifest)) {
|
|
288
|
-
return {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (
|
|
303
|
-
if (manifest.dependencies &&
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
if (manifest.dsh
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (stat) {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function isObject(value) {
|
|
246
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function isSafePackageName(name) {
|
|
250
|
+
return typeof name === 'string'
|
|
251
|
+
&& /^(?:@[^/\s]+\/)?[^/\s]+$/u.test(name)
|
|
252
|
+
&& !name.split('/').some((part) => part === '.' || part === '..');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function isWithin(root, candidate) {
|
|
256
|
+
const rel = relative(root, candidate);
|
|
257
|
+
return rel === '' || (rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function fileSpec(target) {
|
|
261
|
+
return `link:${target.replace(/\\/g, '/')}`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function profileLinkPath(profileRoot, name) {
|
|
265
|
+
return join(profileRoot, 'node_modules', ...name.split('/'));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function readPluginRoot(root, expectedName) {
|
|
269
|
+
if (typeof root !== 'string' || !isAbsolute(root)) return { ok: false, code: 'INVALID_CREW_PLUGIN_ROOT' };
|
|
270
|
+
let targetRoot;
|
|
271
|
+
try { targetRoot = realpathSync(root); } catch { return { ok: false, code: 'CREW_PLUGIN_ROOT_NOT_FOUND' }; }
|
|
272
|
+
const packageFile = join(targetRoot, 'package.json');
|
|
273
|
+
let pkg;
|
|
274
|
+
try { pkg = JSON.parse(readFileSync(packageFile, 'utf8')); } catch { return { ok: false, code: 'CREW_PLUGIN_MANIFEST_INVALID' }; }
|
|
275
|
+
if (!isObject(pkg) || !isSafePackageName(pkg.name) || (expectedName !== undefined && pkg.name !== expectedName)) {
|
|
276
|
+
return { ok: false, code: 'CREW_PLUGIN_MANIFEST_INVALID' };
|
|
277
|
+
}
|
|
278
|
+
const patchSpec = pkg.dsh?.bundle?.patch;
|
|
279
|
+
if (typeof patchSpec !== 'string' || !patchSpec.trim()) return { ok: false, code: 'CREW_PLUGIN_BUNDLE_INVALID' };
|
|
280
|
+
const patchPath = resolve(targetRoot, patchSpec);
|
|
281
|
+
if (!isWithin(targetRoot, patchPath) || !existsSync(patchPath)) return { ok: false, code: 'CREW_PLUGIN_BUNDLE_INVALID' };
|
|
282
|
+
return { ok: true, targetRoot, packageFile, packageName: pkg.name, packageVersion: pkg.version ?? null, patchPath, pkg };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function readProfileManifestForRegistration(profileRoot, { create = true, defaultBundles = CREW_PROFILE_DEFAULT_BUNDLES } = {}) {
|
|
286
|
+
const profileManifest = join(profileRoot, 'package.json');
|
|
287
|
+
if (!existsSync(profileManifest)) {
|
|
288
|
+
if (!create) return { ok: false, code: 'PROFILE_NOT_FOUND', profileManifest };
|
|
289
|
+
return {
|
|
290
|
+
profileManifest,
|
|
291
|
+
manifest: {
|
|
292
|
+
name: `dsh-profile-${profileRoot.split(/[\\/]/u).at(-1)}`,
|
|
293
|
+
private: true,
|
|
294
|
+
dependencies: {},
|
|
295
|
+
dsh: { profile: { bundles: [...defaultBundles] } },
|
|
296
|
+
},
|
|
297
|
+
created: true,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
let manifest;
|
|
301
|
+
try { manifest = JSON.parse(readFileSync(profileManifest, 'utf8')); } catch { return { ok: false, code: 'CREW_PROFILE_METADATA_INVALID' }; }
|
|
302
|
+
if (!isObject(manifest)) return { ok: false, code: 'CREW_PROFILE_METADATA_INVALID' };
|
|
303
|
+
if (manifest.dependencies !== undefined && !isObject(manifest.dependencies)) return { ok: false, code: 'CREW_PROFILE_METADATA_INVALID' };
|
|
304
|
+
if (manifest.dependencies && Object.values(manifest.dependencies).some((value) => typeof value !== 'string')) {
|
|
305
|
+
return { ok: false, code: 'CREW_PROFILE_METADATA_INVALID' };
|
|
306
|
+
}
|
|
307
|
+
if (manifest.dsh !== undefined && !isObject(manifest.dsh)) return { ok: false, code: 'CREW_PROFILE_METADATA_INVALID' };
|
|
308
|
+
if (manifest.dsh?.profile !== undefined && !isObject(manifest.dsh.profile)) return { ok: false, code: 'CREW_PROFILE_METADATA_INVALID' };
|
|
309
|
+
const bundles = manifest.dsh?.profile?.bundles;
|
|
310
|
+
if (bundles !== undefined && (!Array.isArray(bundles) || bundles.some((name) => !isSafePackageName(name)))) {
|
|
311
|
+
return { ok: false, code: 'CREW_PROFILE_METADATA_INVALID' };
|
|
312
|
+
}
|
|
313
|
+
return { profileManifest, manifest, created: false };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function ensureProfileScaffold(profileRoot) {
|
|
317
|
+
mkdirSync(profileRoot, { recursive: true });
|
|
318
|
+
let changed = false;
|
|
319
|
+
const patchFile = join(profileRoot, 'cordis.patch.yml');
|
|
320
|
+
if (!existsSync(patchFile)) { writeFileSync(patchFile, PROFILE_PATCH_TEMPLATE); changed = true; }
|
|
321
|
+
const workspaceFile = join(profileRoot, 'pnpm-workspace.yaml');
|
|
322
|
+
if (!existsSync(workspaceFile)) { writeFileSync(workspaceFile, PROFILE_PNPM_WORKSPACE); changed = true; }
|
|
323
|
+
return changed;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function ensureDirectoryLink(linkPath, targetRoot) {
|
|
327
|
+
let stat;
|
|
328
|
+
try { stat = lstatSync(linkPath); } catch { stat = null; }
|
|
329
|
+
if (stat && !stat.isSymbolicLink()) return { ok: false, code: 'CREW_PLUGIN_LINK_CONFLICT' };
|
|
330
|
+
if (stat) {
|
|
331
|
+
try {
|
|
332
|
+
if (realpathSync(linkPath) === targetRoot) return { ok: true, changed: false };
|
|
333
|
+
} catch {}
|
|
334
|
+
unlinkSync(linkPath);
|
|
335
|
+
}
|
|
336
|
+
mkdirSync(dirname(linkPath), { recursive: true });
|
|
337
|
+
symlinkSync(targetRoot, linkPath, process.platform === 'win32' ? 'junction' : 'dir');
|
|
338
|
+
return { ok: true, changed: true };
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Register a local Crew plugin without invoking DSH's pnpm-forwarding plugin
|
|
343
|
+
* command. The profile manifest and one loader-visible directory link are the
|
|
344
|
+
* only state changed; no dependency resolution or policy bypass is attempted.
|
|
345
|
+
*/
|
|
346
|
+
export function ensurePluginRegistration({
|
|
347
|
+
profileRoot,
|
|
348
|
+
root,
|
|
349
|
+
name,
|
|
350
|
+
createProfile = true,
|
|
351
|
+
defaultBundles = CREW_PROFILE_DEFAULT_BUNDLES,
|
|
352
|
+
} = {}) {
|
|
353
|
+
const plugin = readPluginRoot(root, name);
|
|
354
|
+
if (!plugin.ok) return plugin;
|
|
355
|
+
if (typeof profileRoot !== 'string' || !isAbsolute(profileRoot)) return { ok: false, code: 'INVALID_PROFILE_ROOT' };
|
|
356
|
+
const profile = readProfileManifestForRegistration(profileRoot, { create: createProfile, defaultBundles });
|
|
357
|
+
if (profile.ok === false) return profile;
|
|
358
|
+
const scaffoldChanged = createProfile ? ensureProfileScaffold(profileRoot) : false;
|
|
359
|
+
const current = profile.manifest;
|
|
360
|
+
const dependencies = { ...(current.dependencies ?? {}) };
|
|
361
|
+
const currentBundles = current.dsh?.profile?.bundles ?? [...CREW_PROFILE_DEFAULT_BUNDLES];
|
|
362
|
+
const nextBundles = [];
|
|
363
|
+
let seen = false;
|
|
364
|
+
for (const bundle of currentBundles) {
|
|
365
|
+
if (bundle === plugin.packageName) {
|
|
366
|
+
if (!seen) { nextBundles.push(bundle); seen = true; }
|
|
367
|
+
} else nextBundles.push(bundle);
|
|
368
|
+
}
|
|
369
|
+
if (!seen) nextBundles.push(plugin.packageName);
|
|
370
|
+
dependencies[plugin.packageName] = fileSpec(plugin.targetRoot);
|
|
371
|
+
const next = {
|
|
372
|
+
...current,
|
|
373
|
+
dependencies,
|
|
374
|
+
dsh: {
|
|
375
|
+
...(current.dsh ?? {}),
|
|
376
|
+
profile: { ...(current.dsh?.profile ?? {}), bundles: nextBundles },
|
|
377
|
+
},
|
|
378
|
+
};
|
|
379
|
+
const manifestChanged = JSON.stringify(current) !== JSON.stringify(next) || profile.created;
|
|
380
|
+
const linkPath = profileLinkPath(profileRoot, plugin.packageName);
|
|
381
|
+
let link;
|
|
382
|
+
try { link = ensureDirectoryLink(linkPath, plugin.targetRoot); } catch { return { ok: false, code: 'CREW_PLUGIN_LINK_FAILED' }; }
|
|
383
|
+
if (!link.ok) return link;
|
|
384
|
+
try {
|
|
385
|
+
if (manifestChanged) writeFileSync(profile.profileManifest, JSON.stringify(next, null, 2) + '\n');
|
|
386
|
+
// Resolve the intended package entry from the validated checkout. Node's
|
|
387
|
+
// package-name resolution cache can retain the old target after a stale
|
|
388
|
+
// junction is replaced in the same process, so loader visibility is proven
|
|
389
|
+
// by the link target plus a normal package entry resolution from that
|
|
390
|
+
// target rather than by trusting a cached package-name lookup.
|
|
391
|
+
const resolvedEntry = createRequire(join(plugin.targetRoot, 'package.json')).resolve('.');
|
|
392
|
+
const resolvedRoot = realpathSync(linkPath);
|
|
393
|
+
if (resolvedRoot !== plugin.targetRoot || !isWithin(plugin.targetRoot, realpathSync(resolvedEntry))) {
|
|
394
|
+
return { ok: false, code: 'CREW_PLUGIN_NOT_LOADABLE' };
|
|
395
|
+
}
|
|
396
|
+
return {
|
|
397
|
+
ok: true,
|
|
398
|
+
changed: scaffoldChanged || manifestChanged || link.changed,
|
|
399
|
+
profileRoot,
|
|
400
|
+
profileManifest: profile.profileManifest,
|
|
401
|
+
linkPath,
|
|
402
|
+
targetRoot: plugin.targetRoot,
|
|
403
|
+
packageName: plugin.packageName,
|
|
404
|
+
packageVersion: plugin.packageVersion,
|
|
405
|
+
patchPath: plugin.patchPath,
|
|
406
|
+
resolvedEntry,
|
|
407
|
+
};
|
|
408
|
+
} catch { return { ok: false, code: 'CREW_PLUGIN_NOT_LOADABLE' }; }
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export function ensureCrewPluginRegistration({ home = homedir(), root, name } = {}) {
|
|
412
|
+
return ensurePluginRegistration({ profileRoot: crewProfileDir({ home }), root, name });
|
|
413
|
+
}
|
|
403
414
|
|
|
404
415
|
/**
|
|
405
416
|
* Remove one Crew plugin registration without invoking a package manager.
|
|
@@ -407,41 +418,41 @@ export function ensureCrewPluginRegistration({ home = homedir(), root, name } =
|
|
|
407
418
|
* status/uninstall probe cannot touch the official web profile or download
|
|
408
419
|
* anything merely to remove a stale registration.
|
|
409
420
|
*/
|
|
410
|
-
export function removeCrewPluginRegistration({ home = homedir(), name, profileRoot = crewProfileDir({ home }) } = {}) {
|
|
411
|
-
if (!isSafePackageName(name)) {
|
|
412
|
-
return { ok: false, code: 'INVALID_CREW_PLUGIN_NAME' };
|
|
413
|
-
}
|
|
421
|
+
export function removeCrewPluginRegistration({ home = homedir(), name, profileRoot = crewProfileDir({ home }) } = {}) {
|
|
422
|
+
if (!isSafePackageName(name)) {
|
|
423
|
+
return { ok: false, code: 'INVALID_CREW_PLUGIN_NAME' };
|
|
424
|
+
}
|
|
414
425
|
const packageFile = join(profileRoot, 'package.json');
|
|
415
426
|
if (!existsSync(packageFile)) return { ok: true, removed: false };
|
|
416
|
-
let pkg;
|
|
417
|
-
try { pkg = JSON.parse(readFileSync(packageFile, 'utf8')); } catch { return { ok: false, code: 'CREW_PROFILE_METADATA_INVALID' }; }
|
|
418
|
-
if (!isObject(pkg)
|
|
419
|
-
|| (pkg.dependencies !== undefined && !isObject(pkg.dependencies))
|
|
420
|
-
|| (pkg.dsh !== undefined && !isObject(pkg.dsh))
|
|
421
|
-
|| (pkg.dsh?.profile !== undefined && !isObject(pkg.dsh.profile))
|
|
422
|
-
|| (pkg.dsh?.profile?.bundles !== undefined
|
|
423
|
-
&& (!Array.isArray(pkg.dsh.profile.bundles)
|
|
424
|
-
|| pkg.dsh.profile.bundles.some((item) => !isSafePackageName(item))))) {
|
|
425
|
-
return { ok: false, code: 'CREW_PROFILE_METADATA_INVALID' };
|
|
426
|
-
}
|
|
427
|
-
const hadDependency = Boolean(pkg.dependencies?.[name]);
|
|
428
|
-
const bundles = Array.isArray(pkg.dsh?.profile?.bundles) ? pkg.dsh.profile.bundles : [];
|
|
427
|
+
let pkg;
|
|
428
|
+
try { pkg = JSON.parse(readFileSync(packageFile, 'utf8')); } catch { return { ok: false, code: 'CREW_PROFILE_METADATA_INVALID' }; }
|
|
429
|
+
if (!isObject(pkg)
|
|
430
|
+
|| (pkg.dependencies !== undefined && !isObject(pkg.dependencies))
|
|
431
|
+
|| (pkg.dsh !== undefined && !isObject(pkg.dsh))
|
|
432
|
+
|| (pkg.dsh?.profile !== undefined && !isObject(pkg.dsh.profile))
|
|
433
|
+
|| (pkg.dsh?.profile?.bundles !== undefined
|
|
434
|
+
&& (!Array.isArray(pkg.dsh.profile.bundles)
|
|
435
|
+
|| pkg.dsh.profile.bundles.some((item) => !isSafePackageName(item))))) {
|
|
436
|
+
return { ok: false, code: 'CREW_PROFILE_METADATA_INVALID' };
|
|
437
|
+
}
|
|
438
|
+
const hadDependency = Boolean(pkg.dependencies?.[name]);
|
|
439
|
+
const bundles = Array.isArray(pkg.dsh?.profile?.bundles) ? pkg.dsh.profile.bundles : [];
|
|
429
440
|
const hadBundle = bundles.includes(name);
|
|
430
441
|
if (!hadDependency && !hadBundle) return { ok: true, removed: false };
|
|
431
|
-
const linkPath = profileLinkPath(profileRoot, name);
|
|
432
|
-
let linkStat;
|
|
433
|
-
try { linkStat = lstatSync(linkPath); } catch { linkStat = null; }
|
|
434
|
-
if (linkStat && !linkStat.isSymbolicLink()) return { ok: false, code: 'CREW_PLUGIN_LINK_CONFLICT' };
|
|
435
|
-
const next = { ...pkg };
|
|
442
|
+
const linkPath = profileLinkPath(profileRoot, name);
|
|
443
|
+
let linkStat;
|
|
444
|
+
try { linkStat = lstatSync(linkPath); } catch { linkStat = null; }
|
|
445
|
+
if (linkStat && !linkStat.isSymbolicLink()) return { ok: false, code: 'CREW_PLUGIN_LINK_CONFLICT' };
|
|
446
|
+
const next = { ...pkg };
|
|
436
447
|
if (hadDependency) {
|
|
437
448
|
next.dependencies = { ...pkg.dependencies };
|
|
438
449
|
delete next.dependencies[name];
|
|
439
450
|
if (Object.keys(next.dependencies).length === 0) delete next.dependencies;
|
|
440
451
|
}
|
|
441
|
-
if (hadBundle) {
|
|
442
|
-
next.dsh = { ...pkg.dsh, profile: { ...pkg.dsh.profile, bundles: bundles.filter((item) => item !== name) } };
|
|
443
|
-
}
|
|
444
|
-
writeFileSync(packageFile, JSON.stringify(next, null, 2) + '\n');
|
|
445
|
-
if (linkStat) unlinkSync(linkPath);
|
|
446
|
-
return { ok: true, removed: true };
|
|
447
|
-
}
|
|
452
|
+
if (hadBundle) {
|
|
453
|
+
next.dsh = { ...pkg.dsh, profile: { ...pkg.dsh.profile, bundles: bundles.filter((item) => item !== name) } };
|
|
454
|
+
}
|
|
455
|
+
writeFileSync(packageFile, JSON.stringify(next, null, 2) + '\n');
|
|
456
|
+
if (linkStat) unlinkSync(linkPath);
|
|
457
|
+
return { ok: true, removed: true };
|
|
458
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Stable extension-capability projection for GPT-first orchestrators. It
|
|
2
|
+
// describes only DSH Crew and deliberately never advertises itself as a top-
|
|
3
|
+
// level Executor or control plane.
|
|
4
|
+
|
|
5
|
+
export const EXTENSION_CONTRACT_SCHEMA_VERSION = 1;
|
|
6
|
+
|
|
7
|
+
function row(matrix, id) {
|
|
8
|
+
return Array.isArray(matrix?.rows) ? matrix.rows.find((entry) => entry?.id === id) : undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function component(status, reasonCode, evidence = null) {
|
|
12
|
+
return { status, reason_code: reasonCode, ...(evidence ? { evidence } : {}) };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function workspaceComponent(workspace) {
|
|
16
|
+
if (workspace?.status === 'CONFLICT' || workspace?.status === 'READ_ONLY') {
|
|
17
|
+
return { ...component('DEGRADED', workspace.reason_code ?? `WORKSPACE_${workspace.status}`), state: workspace.status };
|
|
18
|
+
}
|
|
19
|
+
if (workspace?.status === 'UNAVAILABLE') {
|
|
20
|
+
return { ...component('UNAVAILABLE', workspace.reason_code ?? 'WORKSPACE_UNAVAILABLE'), state: 'UNAVAILABLE' };
|
|
21
|
+
}
|
|
22
|
+
if (workspace?.status === 'READY') {
|
|
23
|
+
return { ...component('READY', workspace.reason_code ?? 'WORKSPACE_READY'), state: 'READY' };
|
|
24
|
+
}
|
|
25
|
+
return workspace?.ok === true
|
|
26
|
+
? { ...component('READY', workspace.context ? 'WORKSPACE_CONTEXT_RESOLVED' : 'WORKSPACE_CONTEXT_NOT_REQUESTED'), state: 'READY' }
|
|
27
|
+
: { ...component('UNAVAILABLE', workspace?.code ?? 'WORKSPACE_NOT_CHECKED'), state: 'UNAVAILABLE' };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readinessFromRow(entry, { pass = 'READY', notRun = 'DEGRADED' } = {}) {
|
|
31
|
+
if (!entry) return component('UNAVAILABLE', 'NO_EVIDENCE');
|
|
32
|
+
if (entry.status === 'PASS') return component(pass, entry.reason_code ?? 'CHECK_PASSED');
|
|
33
|
+
if (entry.status === 'NOT_RUN' || entry.status === 'SKIP') return component(notRun, entry.reason_code ?? 'CHECK_NOT_RUN');
|
|
34
|
+
return component('UNAVAILABLE', entry.reason_code ?? 'CHECK_FAILED');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function buildExtensionContract({ config = {}, readinessMatrix = {}, workspace = null, profiles = null, runtime = null } = {}) {
|
|
38
|
+
const workerEnabled = config.subagents_enabled !== false && config.worker_state !== 'disabled';
|
|
39
|
+
const reviewerEnabled = config.subagents_enabled !== false && config.review_state !== 'disabled';
|
|
40
|
+
const realModelEvidence = ['model_execution', 'deepseek_flash', 'deepseek_pro', 'opencode_go_mimo_qwen']
|
|
41
|
+
.map((id) => row(readinessMatrix, id))
|
|
42
|
+
.find((entry) => entry?.status === 'PASS');
|
|
43
|
+
const catalogEvidence = row(readinessMatrix, 'provider_catalog');
|
|
44
|
+
const modelReadiness = realModelEvidence
|
|
45
|
+
? component('READY', realModelEvidence.reason_code ?? 'MODEL_EXECUTION_PASSED')
|
|
46
|
+
: catalogEvidence?.status === 'PASS' || catalogEvidence?.status === 'SKIP'
|
|
47
|
+
? component('DEGRADED', 'MODEL_CATALOG_ONLY')
|
|
48
|
+
: component('UNAVAILABLE', catalogEvidence?.reason_code ?? 'NO_EVIDENCE');
|
|
49
|
+
const components = {
|
|
50
|
+
harness: readinessFromRow(row(readinessMatrix, 'hub_compatibility')),
|
|
51
|
+
model: modelReadiness,
|
|
52
|
+
workspace: workspaceComponent(workspace),
|
|
53
|
+
reviewer: reviewerEnabled
|
|
54
|
+
? readinessFromRow(row(readinessMatrix, 'reviewer_pipeline'))
|
|
55
|
+
: component('DEGRADED', 'REVIEWER_DISABLED'),
|
|
56
|
+
};
|
|
57
|
+
const states = Object.values(components).map((entry) => entry.status);
|
|
58
|
+
const readiness = states.includes('UNAVAILABLE') ? 'UNAVAILABLE' : states.includes('DEGRADED') ? 'DEGRADED' : 'READY';
|
|
59
|
+
return {
|
|
60
|
+
schema_version: EXTENSION_CONTRACT_SCHEMA_VERSION,
|
|
61
|
+
kind: 'dsh-crew-extension',
|
|
62
|
+
runtime: runtime ?? null,
|
|
63
|
+
capabilities: {
|
|
64
|
+
'deepseek.worker': workerEnabled,
|
|
65
|
+
'deepseek.reviewer': reviewerEnabled,
|
|
66
|
+
'worktree.isolation': true,
|
|
67
|
+
'model.fallback': config.escalate_on_failure === true,
|
|
68
|
+
'job.cancel': true,
|
|
69
|
+
'job.watch': true,
|
|
70
|
+
'job.resume': false,
|
|
71
|
+
'result.evidence': true,
|
|
72
|
+
'events.canonical': true,
|
|
73
|
+
'profiles.roles': profiles?.ok === true,
|
|
74
|
+
'workspace.context': true,
|
|
75
|
+
},
|
|
76
|
+
readiness: { status: readiness, components },
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -69,6 +69,11 @@ const RUNTIME_CODES = new Set([
|
|
|
69
69
|
'GIT_ERROR',
|
|
70
70
|
'WORKTREE_LOCKED',
|
|
71
71
|
'WORKTREE_CREATE_FAILED',
|
|
72
|
+
'PROFILE_NOT_FOUND',
|
|
73
|
+
'PROFILE_ROLE_MISMATCH',
|
|
74
|
+
'WORKSPACE_CONTEXT_NOT_FOUND',
|
|
75
|
+
'WORKSPACE_ROOT_MISMATCH',
|
|
76
|
+
'WORKSPACE_CONTEXT_REFS_INVALID',
|
|
72
77
|
'CANDIDATE_CAPTURE_FAILED',
|
|
73
78
|
'ATTEMPT_INFRA_FAILURE',
|
|
74
79
|
'HUB_REQUEST_FAILED',
|
|
@@ -78,13 +83,37 @@ function normalizedCode(value) {
|
|
|
78
83
|
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
79
84
|
}
|
|
80
85
|
|
|
86
|
+
function failureFamily(category, reasonCode, sourceCode) {
|
|
87
|
+
const code = sourceCode ?? reasonCode ?? '';
|
|
88
|
+
if (category === 'none') return 'NONE';
|
|
89
|
+
if (category === 'compatibility' || code.startsWith('HUB_')) return 'HARNESS';
|
|
90
|
+
if (category === 'provider' || code.startsWith('MODEL_') || code.startsWith('PROVIDER_')) return 'MODEL';
|
|
91
|
+
if (/^(WORK|GIT_|ISOLATION_|CANDIDATE_|PROFILE_)/.test(code)) return 'WORKSPACE';
|
|
92
|
+
if (String(reasonCode).startsWith('REVIEW_')) return 'REVIEW';
|
|
93
|
+
if (category === 'policy') return 'POLICY';
|
|
94
|
+
return 'JOB';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function failureDisposition(category, family) {
|
|
98
|
+
if (category === 'none') return 'none';
|
|
99
|
+
if (category === 'cancelled') return 'terminal';
|
|
100
|
+
if (category === 'compatibility') return 'retry';
|
|
101
|
+
if (category === 'provider') return 'fallback';
|
|
102
|
+
if (category === 'policy' || family === 'WORKSPACE' || family === 'REVIEW') return 'human';
|
|
103
|
+
if (category === 'runtime') return 'retry';
|
|
104
|
+
return 'terminal';
|
|
105
|
+
}
|
|
106
|
+
|
|
81
107
|
function result(category, reasonCode, { sourceCode = null, terminalReason = null } = {}) {
|
|
108
|
+
const family = failureFamily(category, reasonCode, sourceCode);
|
|
82
109
|
return {
|
|
83
110
|
schema_version: 1,
|
|
84
111
|
category,
|
|
85
112
|
reason_code: reasonCode,
|
|
86
113
|
...(sourceCode ? { source_code: sourceCode } : {}),
|
|
87
114
|
...(terminalReason ? { terminal_reason: terminalReason } : {}),
|
|
115
|
+
family,
|
|
116
|
+
disposition: failureDisposition(category, family),
|
|
88
117
|
};
|
|
89
118
|
}
|
|
90
119
|
|