@ran-sh/dsh-crew 2.2.1 → 2.2.2
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/plugin.json +1 -1
- package/official-web-bridge/lib/client.js +1 -1
- package/official-web-bridge/overlay-entry.mjs +5 -1
- package/official-web-bridge/package.json +1 -1
- package/package.json +1 -1
- package/scripts/setup.mjs +33 -6
- package/src/client/quick-panel.tsx +6 -1
- package/src/dsh-cli-runtime.mjs +209 -8
- package/src/hub/index.mjs +57 -6
- package/src/install/crew-paths.mjs +16 -11
- package/src/install/desktop-profile.mjs +168 -0
- package/src/install/harness-config-import.mjs +204 -0
- package/src/install/npx-lifecycle.mjs +151 -14
- package/src/official-web-bridge.mjs +3 -1
- package/src/provider-settings-store.mjs +19 -1
- package/src/runtime-identity.mjs +1 -1
|
@@ -42,12 +42,15 @@ import { homedir } from 'node:os';
|
|
|
42
42
|
import * as realInstaller from './install.mjs';
|
|
43
43
|
import { samePayloadContent, capturePayloadContent } from './payload-content.mjs';
|
|
44
44
|
import { crewDshHome, crewProfileDir, claudeIntegrationLine } from './install.mjs';
|
|
45
|
+
import { integrationRoot } from './crew-paths.mjs';
|
|
45
46
|
import { releaseClaimsState } from '../release-in-use.mjs';
|
|
46
47
|
import { compareProcessToken, processStartToken } from '../process-identity.mjs';
|
|
47
48
|
import { RELEASE_COHORT_FILENAME } from '../dsh-cohort.mjs';
|
|
48
49
|
import { renameTree } from './tree-move.mjs';
|
|
49
50
|
import { checkRuntimeAdvance, normalizeRuntimeState, runtimeStateMayHaveStarted } from './runtime-lifecycle.mjs';
|
|
50
|
-
import { ensureCrewDshRuntime, ensureCrewPluginRegistration, removeCrewPluginRegistration, migrateCrewDshRuntime, installDshInto, restoreRetainedRuntime, crewDshRuntimeRoot, payloadDshVersion, gcRetainedRuntimes, TARGET_DSH_VERSION } from '../dsh-cli-runtime.mjs';
|
|
51
|
+
import { ensureCrewDshRuntime, ensureCrewPluginRegistration, ensureCrewWebProfile, removeCrewPluginRegistration, migrateCrewDshRuntime, installDshInto, restoreRetainedRuntime, crewDshRuntimeRoot, payloadDshVersion, gcRetainedRuntimes, TARGET_DSH_VERSION } from '../dsh-cli-runtime.mjs';
|
|
52
|
+
import { desktopAttach, desktopDetach, desktopStatus } from './desktop-profile.mjs';
|
|
53
|
+
import { mirrorOfficialHarnessConfig, officialConfigMirrorStatus } from './harness-config-import.mjs';
|
|
51
54
|
import {
|
|
52
55
|
ensureOfficialWebIntegration,
|
|
53
56
|
officialWebIntegrationStatus,
|
|
@@ -970,6 +973,28 @@ function defaultNpmInstaller(stageRoot, log) {
|
|
|
970
973
|
return true;
|
|
971
974
|
}
|
|
972
975
|
|
|
976
|
+
// Exact-version specs only (the pinned-cohort shape). Ranges stay with normal
|
|
977
|
+
// resolution: a range override would both fight the resolver and violate the
|
|
978
|
+
// verbatim-spec rule npm applies when a direct dependency shares the name.
|
|
979
|
+
const EXACT_SPEC_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* Lock every exact-pinned dependency to its own version across the whole
|
|
983
|
+
* staged tree. The npm fallback install has no flag channel (sanitized env,
|
|
984
|
+
* fixed args), so a drifting transitive peer — e.g. a projection package
|
|
985
|
+
* peer-requiring a newer cohort than the payload's pin — otherwise aborts
|
|
986
|
+
* staging with ERESOLVE. npm v11 rejects an override whose spec differs from
|
|
987
|
+
* the direct dependency, so each override reuses the dependency's exact spec
|
|
988
|
+
* verbatim.
|
|
989
|
+
*/
|
|
990
|
+
export function exactSpecOverrides(dependencies) {
|
|
991
|
+
const overrides = {};
|
|
992
|
+
for (const [name, spec] of Object.entries(dependencies ?? {})) {
|
|
993
|
+
if (typeof spec === 'string' && EXACT_SPEC_RE.test(spec)) overrides[name] = spec;
|
|
994
|
+
}
|
|
995
|
+
return Object.keys(overrides).length > 0 ? overrides : null;
|
|
996
|
+
}
|
|
997
|
+
|
|
973
998
|
// ---- payload staging ---------------------------------------------------------
|
|
974
999
|
|
|
975
1000
|
/**
|
|
@@ -1029,6 +1054,8 @@ export function stageCandidatePayload({
|
|
|
1029
1054
|
...(manifest.peerDependencies ?? {}),
|
|
1030
1055
|
...(manifest.dependencies ?? {}),
|
|
1031
1056
|
};
|
|
1057
|
+
const overrides = exactSpecOverrides(stagedManifest.dependencies);
|
|
1058
|
+
if (overrides) stagedManifest.overrides = overrides;
|
|
1032
1059
|
writeFileSync(join(stageDir, 'package.json'), JSON.stringify(stagedManifest, null, 2) + '\n');
|
|
1033
1060
|
|
|
1034
1061
|
// Materialize dependencies: prefer replicating the exact bits the candidate
|
|
@@ -2217,7 +2244,7 @@ export function buildProductionMigration({ home, log = () => {}, stopOwned, star
|
|
|
2217
2244
|
});
|
|
2218
2245
|
}
|
|
2219
2246
|
|
|
2220
|
-
async function ensureRuntimeStep({ home, log, ensureRuntime, migrateRuntime, stopOwned, startOwned, verifyOwned, supervisorFactory = crewSupervisor }) { const ensure = ensureRuntime ?? ((opts) => {
|
|
2247
|
+
async function ensureRuntimeStep({ home, log, ensureRuntime, migrateRuntime, ensureWebProfile, stopOwned, startOwned, verifyOwned, supervisorFactory = crewSupervisor }) { const ensure = ensureRuntime ?? ((opts) => {
|
|
2221
2248
|
const r = ensureCrewDshRuntime({ ...opts, env: sanitizedPackageManagerEnv() });
|
|
2222
2249
|
if (!r.ok && r.stderrTail) {
|
|
2223
2250
|
log(` (runtime installer said: ${r.stderrTail})`);
|
|
@@ -2225,17 +2252,37 @@ async function ensureRuntimeStep({ home, log, ensureRuntime, migrateRuntime, sto
|
|
|
2225
2252
|
if (!r.ok && r.code === 'DSH_RUNTIME_COHORT_MISMATCH') {
|
|
2226
2253
|
return { ok: false, code: r.code, error: r.error, installed: r.installed, target: r.target, needsMigration: true };
|
|
2227
2254
|
}
|
|
2228
|
-
return r.ok
|
|
2255
|
+
return r.ok
|
|
2256
|
+
? { ok: true, version: r.cli?.version ?? null, cohortRepaired: r.cohortRepaired === true }
|
|
2257
|
+
: { ok: false, error: r.error ?? r.code ?? 'runtime bootstrap failed' };
|
|
2229
2258
|
});
|
|
2230
2259
|
// Production migration wiring: ONE supervisor instance per migration,
|
|
2231
2260
|
// reused by stop/start/verify/rollback so child identity stays coherent.
|
|
2232
2261
|
// No legacy bridge involved. Missing callbacks fail closed inside
|
|
2233
2262
|
// migrateCrewDshRuntime.
|
|
2234
2263
|
const migrate = migrateRuntime ?? buildProductionMigration({ home, log, stopOwned, startOwned, verifyOwned, supervisorFactory });
|
|
2264
|
+
// The runtime alone cannot boot a managed 3210/3080: the launcher preflight
|
|
2265
|
+
// and the supervisor require <DSH_HOME>/profiles/web. Materialize it here —
|
|
2266
|
+
// before any supervisor convergence, on every path that ends in "runtime
|
|
2267
|
+
// ready" — so a fresh install cannot deadlock on a heartbeat that a
|
|
2268
|
+
// profile-less service can never publish.
|
|
2269
|
+
const convergeWebProfile = () => {
|
|
2270
|
+
const profile = (ensureWebProfile ?? ensureCrewWebProfile)({ home });
|
|
2271
|
+
if (!profile.ok) {
|
|
2272
|
+
log(`✗ Crew web profile bootstrap failed (${profile.code ?? 'unknown error'})`);
|
|
2273
|
+
return false;
|
|
2274
|
+
}
|
|
2275
|
+
if (profile.created) log(`✓ Crew web profile scaffold created (${profile.profileRoot})`);
|
|
2276
|
+
return true;
|
|
2277
|
+
};
|
|
2235
2278
|
const r = await ensure({ home });
|
|
2236
2279
|
if (r?.ok) {
|
|
2237
2280
|
log(`✓ reusable Crew DSH runtime${r.version ? ` (@${r.version})` : ''}`);
|
|
2238
|
-
|
|
2281
|
+
// The install locks the whole family when the registry had published a newer
|
|
2282
|
+
// prerelease than the pinned cohort; say so, because a silent repair is
|
|
2283
|
+
// indistinguishable from a registry that never drifted.
|
|
2284
|
+
if (r.cohortRepaired) log(' the DSH family had drifted from the pinned cohort; installed the pinned family instead');
|
|
2285
|
+
return convergeWebProfile();
|
|
2239
2286
|
}
|
|
2240
2287
|
if (r?.needsMigration === true) {
|
|
2241
2288
|
log(`- runtime cohort stale (${r.installed} -> ${r.target}); migrating via staged transaction`);
|
|
@@ -2245,7 +2292,7 @@ async function ensureRuntimeStep({ home, log, ensureRuntime, migrateRuntime, sto
|
|
|
2245
2292
|
return false;
|
|
2246
2293
|
}
|
|
2247
2294
|
log(`✓ runtime cohort migrated (@${m.version})`);
|
|
2248
|
-
return
|
|
2295
|
+
return convergeWebProfile();
|
|
2249
2296
|
}
|
|
2250
2297
|
log(`✗ reusable Crew DSH runtime unavailable: ${r?.error ?? 'unknown error'}`);
|
|
2251
2298
|
return false;
|
|
@@ -2624,9 +2671,18 @@ async function activateRelease({ home, releaseDir, manifest, log, installer, sup
|
|
|
2624
2671
|
// leave four configurations naming a directory that is gone. Writing the
|
|
2625
2672
|
// release path directly is what made crash recovery unable to delete a
|
|
2626
2673
|
// candidate without breaking Codex, ZCode and Claude Code.
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2674
|
+
//
|
|
2675
|
+
// The root is derived through the SAME integrationRoot() the readiness
|
|
2676
|
+
// checks use, not blindly from registration.linkPath: write and validate
|
|
2677
|
+
// must be one function of (home, releaseDir, name). In the healthy case the
|
|
2678
|
+
// gate passes right after registration re-pointed the link, so this is
|
|
2679
|
+
// still the link path; when the gate fails, both sides fall back to the
|
|
2680
|
+
// release directory together instead of the writer using the link while the
|
|
2681
|
+
// validator reports the release — the disagreement that made every clean
|
|
2682
|
+
// install read back as "needs repair".
|
|
2683
|
+
const integrationWriteRoot = integrationRoot({ home, root: releaseDir, name: manifest.name });
|
|
2684
|
+
|
|
2685
|
+
const codex = installer.installCodex({ home, root: integrationWriteRoot });
|
|
2630
2686
|
if (codex.ok === false) {
|
|
2631
2687
|
log(`✗ Codex Desktop integration failed: ${(codex.actions ?? []).join('; ')}`);
|
|
2632
2688
|
return false;
|
|
@@ -2634,7 +2690,7 @@ async function activateRelease({ home, releaseDir, manifest, log, installer, sup
|
|
|
2634
2690
|
log('✓ Codex Desktop integration');
|
|
2635
2691
|
|
|
2636
2692
|
if (installer.installZCode) {
|
|
2637
|
-
const zcode = installer.installZCode({ home, root:
|
|
2693
|
+
const zcode = installer.installZCode({ home, root: integrationWriteRoot });
|
|
2638
2694
|
if (zcode.ok === false) {
|
|
2639
2695
|
log(`✗ ZCode integration failed (${zcode.code ?? 'unknown'})`);
|
|
2640
2696
|
return false;
|
|
@@ -2649,7 +2705,7 @@ async function activateRelease({ home, releaseDir, manifest, log, installer, sup
|
|
|
2649
2705
|
}
|
|
2650
2706
|
if (startup?.supported) log('✓ Windows login startup');
|
|
2651
2707
|
|
|
2652
|
-
const claude = await installer.installClaudeCode({ home, root:
|
|
2708
|
+
const claude = await installer.installClaudeCode({ home, root: integrationWriteRoot });
|
|
2653
2709
|
if (claude.ok === false) {
|
|
2654
2710
|
log(claudeIntegrationLine(claude));
|
|
2655
2711
|
return false;
|
|
@@ -2688,6 +2744,59 @@ export async function npxDetach({ home = homedir(), log = console.log } = {}) {
|
|
|
2688
2744
|
return { ok: false, error: 'OFFICIAL_WEB_PROFILE_READ_ONLY' };
|
|
2689
2745
|
}
|
|
2690
2746
|
|
|
2747
|
+
// Desktop-client integration. The official desktop app boots its Harness with the
|
|
2748
|
+
// app-exclusive `desktop` profile, so the Crew panel can only reach it through
|
|
2749
|
+
// that profile's own patch layer — the one official-home write Crew performs, and
|
|
2750
|
+
// only because an operator asked for it by name. It never runs during install,
|
|
2751
|
+
// update or supervision.
|
|
2752
|
+
export async function npxDesktop({ home = homedir(), args = [], dryRun = false, log = console.log } = {}) {
|
|
2753
|
+
const action = args[0] ?? 'status';
|
|
2754
|
+
if (!['attach', 'detach', 'status'].includes(action)) {
|
|
2755
|
+
log(`✗ unknown desktop action: ${action}`);
|
|
2756
|
+
return { ok: false, error: 'usage: dsh-crew desktop <attach|detach|status> [--dry-run]' };
|
|
2757
|
+
}
|
|
2758
|
+
if (action === 'status') {
|
|
2759
|
+
const status = desktopStatus({ home });
|
|
2760
|
+
if (!status.file_present) log(`- desktop patch layer: absent (${status.path})`);
|
|
2761
|
+
else if (!status.attached) log(`- desktop patch layer: present, no Crew bridge (${status.path})`);
|
|
2762
|
+
else if (status.current) log(`✓ desktop patch layer carries the current Crew bridge (revision ${status.bridge?.revision})`);
|
|
2763
|
+
else log(`! desktop patch layer carries a stale Crew bridge (current revision ${status.bridge?.revision ?? 'unavailable'})`);
|
|
2764
|
+
if (!status.bridge) log(` (${status.bridge_error})`);
|
|
2765
|
+
log(JSON.stringify(status, null, 2));
|
|
2766
|
+
return { ok: true, status };
|
|
2767
|
+
}
|
|
2768
|
+
const result = action === 'attach'
|
|
2769
|
+
? desktopAttach({ home, dryRun, log })
|
|
2770
|
+
: desktopDetach({ home, dryRun, log });
|
|
2771
|
+
if (!result.ok) log(`✗ desktop ${action} failed: ${result.error ?? result.code}`);
|
|
2772
|
+
return result;
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
// Bring the operator's user-level DSH configuration across from the official home
|
|
2776
|
+
// into Crew's home: the Crew hub then runs the same models, providers, policy and
|
|
2777
|
+
// MCP servers as the desktop app. Read-only on ~/.dsh, no secrets copied (provider
|
|
2778
|
+
// entries keep their apiKeyEnv references, and those variables are inherited).
|
|
2779
|
+
export async function npxConfig({ home = homedir(), args = [], dryRun = false, log = console.log } = {}) {
|
|
2780
|
+
const action = args[0] ?? 'status';
|
|
2781
|
+
if (!['import', 'status'].includes(action)) {
|
|
2782
|
+
log(`✗ unknown config action: ${action}`);
|
|
2783
|
+
return { ok: false, error: 'usage: dsh-crew config <import|status> [--dry-run]' };
|
|
2784
|
+
}
|
|
2785
|
+
if (action === 'status') {
|
|
2786
|
+
const status = officialConfigMirrorStatus({ home });
|
|
2787
|
+
for (const layer of [status.home_layer, status.profile_layer]) {
|
|
2788
|
+
log(`- ${layer.label}: ${!layer.source_present ? `no official source (${layer.source})` : layer.mirrored ? 'mirrored' : 'not imported yet (run: dsh-crew config import)'}`);
|
|
2789
|
+
}
|
|
2790
|
+
log(JSON.stringify(status, null, 2));
|
|
2791
|
+
return { ok: true, status };
|
|
2792
|
+
}
|
|
2793
|
+
const result = mirrorOfficialHarnessConfig({ home, dryRun, log });
|
|
2794
|
+
if (result.ok && result.changed && !dryRun) {
|
|
2795
|
+
log(' the Crew harness hot-watches its patch layers; the hub picks the imported configuration up without a restart');
|
|
2796
|
+
}
|
|
2797
|
+
return result;
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2691
2800
|
export async function npxInstall({
|
|
2692
2801
|
home = homedir(),
|
|
2693
2802
|
log = console.log,
|
|
@@ -3196,6 +3305,16 @@ export function npxStatus({
|
|
|
3196
3305
|
}
|
|
3197
3306
|
log(`DSH plugin: ${dshPlugin} (dedicated dsh-crew profile on 3210)`);
|
|
3198
3307
|
log(`Official 3080 UI bridge: ${officialWeb}`);
|
|
3308
|
+
// The desktop app carries its own bridge entry, which pins a revision: after a
|
|
3309
|
+
// payload update that entry is stale until it is re-pointed, so say which of the
|
|
3310
|
+
// three states the machine is in instead of leaving the panel silently old.
|
|
3311
|
+
const desktop = desktopStatus({ home });
|
|
3312
|
+
log(`Desktop app integration: ${
|
|
3313
|
+
!desktop.file_present ? 'not attached (run: dsh-crew desktop attach)'
|
|
3314
|
+
: desktop.attached && desktop.current ? 'attached'
|
|
3315
|
+
: desktop.attached ? 'attached to a stale bridge revision (run: dsh-crew desktop attach)'
|
|
3316
|
+
: 'not attached (run: dsh-crew desktop attach)'
|
|
3317
|
+
}`);
|
|
3199
3318
|
log(`Codex Desktop integration: ${codex}`);
|
|
3200
3319
|
log(`ZCode integration: ${zcode}`);
|
|
3201
3320
|
log(`Claude Code integration: ${claude}`);
|
|
@@ -3291,6 +3410,16 @@ Commands:
|
|
|
3291
3410
|
install persist the candidate package into Crew-owned state and register it
|
|
3292
3411
|
integrate disabled: the official web profile is read-only (legacy bridge retired)
|
|
3293
3412
|
detach disabled: the official web profile is read-only (legacy bridge retired)
|
|
3413
|
+
desktop the official DSH desktop app: attach|detach|status the Crew panel in its
|
|
3414
|
+
own patch layer (~/.dsh/profiles/desktop/cordis.patch.yml, hot-watched
|
|
3415
|
+
by the Harness). The only official-home write Crew performs, and only
|
|
3416
|
+
when this command is run; attach is idempotent and follows the current
|
|
3417
|
+
bridge revision, detach removes exactly the block it wrote
|
|
3418
|
+
config import|status the operator's user-level DSH configuration (models,
|
|
3419
|
+
providers, policy, MCP servers) from the official home into Crew's own
|
|
3420
|
+
home, so the Crew hub matches the desktop app. Read-only on ~/.dsh; no
|
|
3421
|
+
secrets are copied (provider entries keep their apiKeyEnv references);
|
|
3422
|
+
entries naming packages absent from Crew's runtime cohort are skipped
|
|
3294
3423
|
status read-only report of launcher/installed versions and integrations
|
|
3295
3424
|
inspect print the machine-readable extension capability/readiness contract
|
|
3296
3425
|
jobs machine-first job API: list|get|watch|cancel|submit
|
|
@@ -3312,6 +3441,7 @@ Options:
|
|
|
3312
3441
|
--replacement-default <id> with providers delete-plan: replacement Harness Default provider
|
|
3313
3442
|
--confirm with providers delete: confirm the destructive mutation
|
|
3314
3443
|
--purge with uninstall: also remove ~/.config/dsh-crew config/backups (destructive)
|
|
3444
|
+
--dry-run with desktop attach|detach: report the change without writing
|
|
3315
3445
|
--help show this help
|
|
3316
3446
|
|
|
3317
3447
|
Primary install: npm install -g @ran-sh/dsh-crew (then run: dsh-crew install)
|
|
@@ -3622,6 +3752,7 @@ function normalizeCommand(argv) {
|
|
|
3622
3752
|
let expectedRevision;
|
|
3623
3753
|
let replacementDefault;
|
|
3624
3754
|
let confirm = false;
|
|
3755
|
+
let dryRun = false;
|
|
3625
3756
|
let purgeOrphanCredentials = false;
|
|
3626
3757
|
for (let index = 0; index < flags.length; index += 1) {
|
|
3627
3758
|
if (flags[index] === '--candidate') {
|
|
@@ -3661,16 +3792,18 @@ function normalizeCommand(argv) {
|
|
|
3661
3792
|
replacementDefault = flags[index].slice('--replacement-default='.length); flags.splice(index, 1); index -= 1;
|
|
3662
3793
|
} else if (flags[index] === '--confirm') {
|
|
3663
3794
|
confirm = true; flags.splice(index, 1); index -= 1;
|
|
3795
|
+
} else if (flags[index] === '--dry-run') {
|
|
3796
|
+
dryRun = true; flags.splice(index, 1); index -= 1;
|
|
3664
3797
|
} else if (flags[index] === '--purge-orphan-credentials') {
|
|
3665
3798
|
purgeOrphanCredentials = true; flags.splice(index, 1); index -= 1;
|
|
3666
3799
|
}
|
|
3667
3800
|
}
|
|
3668
|
-
const knownFlags = new Set(['--purge']);
|
|
3801
|
+
const knownFlags = new Set(['--purge', '--dry-run']);
|
|
3669
3802
|
const unknown = flags.filter((f) => f.startsWith('--') && !knownFlags.has(f));
|
|
3670
3803
|
const args = flags.filter((f) => !f.startsWith('--'));
|
|
3671
3804
|
if (!Number.isInteger(after) || after < 0) unknown.push('--after');
|
|
3672
3805
|
if (!['compact', 'full'].includes(detail)) unknown.push('--detail');
|
|
3673
|
-
return { command: argv[0], purge: flags.includes('--purge'), candidate, after, detail, request, planId, expectedRevision, replacementDefault, confirm, purgeOrphanCredentials, args, unknown };
|
|
3806
|
+
return { command: argv[0], purge: flags.includes('--purge'), candidate, after, detail, request, planId, expectedRevision, replacementDefault, confirm, dryRun, purgeOrphanCredentials, args, unknown };
|
|
3674
3807
|
}
|
|
3675
3808
|
|
|
3676
3809
|
/**
|
|
@@ -3682,7 +3815,7 @@ export async function runNpxCli({
|
|
|
3682
3815
|
error = console.error,
|
|
3683
3816
|
commands = {},
|
|
3684
3817
|
} = {}) {
|
|
3685
|
-
const { command, purge, candidate, after, detail, request, planId, expectedRevision, replacementDefault, confirm, purgeOrphanCredentials, args, unknown } = normalizeCommand(argv);
|
|
3818
|
+
const { command, purge, candidate, after, detail, request, planId, expectedRevision, replacementDefault, confirm, dryRun, purgeOrphanCredentials, args, unknown } = normalizeCommand(argv);
|
|
3686
3819
|
if (command === '--help' || command === '-h' || command === 'help') {
|
|
3687
3820
|
log(USAGE);
|
|
3688
3821
|
return 0;
|
|
@@ -3691,7 +3824,7 @@ export async function runNpxCli({
|
|
|
3691
3824
|
error(USAGE);
|
|
3692
3825
|
return 1;
|
|
3693
3826
|
}
|
|
3694
|
-
if (unknown.length > 0 || !['install', 'integrate', 'detach', 'status', 'inspect', 'jobs', 'providers', 'credentials', 'releases', 'rollback', 'update', 'uninstall'].includes(command)) {
|
|
3827
|
+
if (unknown.length > 0 || !['install', 'integrate', 'detach', 'desktop', 'config', 'status', 'inspect', 'jobs', 'providers', 'credentials', 'releases', 'rollback', 'update', 'uninstall'].includes(command)) {
|
|
3695
3828
|
error(`unknown command: ${command ?? '<none>'}\n\n${USAGE}`);
|
|
3696
3829
|
return 1;
|
|
3697
3830
|
}
|
|
@@ -3700,6 +3833,8 @@ export async function runNpxCli({
|
|
|
3700
3833
|
install: commands.install ?? npxInstall,
|
|
3701
3834
|
integrate: commands.integrate ?? npxIntegrate,
|
|
3702
3835
|
detach: commands.detach ?? npxDetach,
|
|
3836
|
+
desktop: commands.desktop ?? npxDesktop,
|
|
3837
|
+
config: commands.config ?? npxConfig,
|
|
3703
3838
|
status: commands.status ?? npxStatus,
|
|
3704
3839
|
inspect: commands.inspect ?? npxInspect,
|
|
3705
3840
|
jobs: commands.jobs ?? npxJobs,
|
|
@@ -3713,6 +3848,8 @@ export async function runNpxCli({
|
|
|
3713
3848
|
let result;
|
|
3714
3849
|
if (command === 'uninstall') result = await actions.uninstall({ purge, log });
|
|
3715
3850
|
else if (command === 'update') result = await actions.update({ candidate, log });
|
|
3851
|
+
else if (command === 'desktop') result = await actions.desktop({ args, dryRun, log });
|
|
3852
|
+
else if (command === 'config') result = await actions.config({ args, dryRun, log });
|
|
3716
3853
|
else if (command === 'jobs') result = await actions.jobs({ args, after, detail, request, log });
|
|
3717
3854
|
else if (command === 'providers') result = await actions.providers({ args, planId, expectedRevision, replacementDefault, confirm, purgeOrphanCredentials, log });
|
|
3718
3855
|
else if (command === 'credentials') result = await actions.credentials({ args, planId, expectedRevision, confirm, log });
|
|
@@ -406,7 +406,9 @@ export function registerOfficialWebBridge(ctx, options = {}) {
|
|
|
406
406
|
ui_role: 'quick-controls',
|
|
407
407
|
execution_plane: 'hub-3210',
|
|
408
408
|
listen_port: 3210,
|
|
409
|
-
|
|
409
|
+
// The usable entry: the hub mints its own session and redirects, because
|
|
410
|
+
// a bare port asks for a token only the hub's own process can sign.
|
|
411
|
+
full_control_plane_url: 'http://127.0.0.1:3210/_dsh/dsh-crew/control-plane',
|
|
410
412
|
});
|
|
411
413
|
},
|
|
412
414
|
});
|
|
@@ -382,9 +382,27 @@ export function inspectProviderSettings(source) {
|
|
|
382
382
|
return { ok: true, schema: 'harness-settings-llm-pi-ai-v1', providerIds: parsed.entries.map((entry) => entry.id), revision };
|
|
383
383
|
}
|
|
384
384
|
|
|
385
|
+
/** Whether the settings document carries a Harness provider section at all. */
|
|
386
|
+
export function hasProviderSettingsSection(source) {
|
|
387
|
+
return typeof source === 'string' && source.split(/\r?\n/u).some((line) => /^llm-pi-ai:\s*$/u.test(line));
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** Whether the settings document declares a Harness default-model authority. */
|
|
391
|
+
export function hasHarnessDefaultBlock(source) {
|
|
392
|
+
return typeof source === 'string' && source.split(/\r?\n/u).some((line) => /^agent-default-model:\s*$/u.test(line));
|
|
393
|
+
}
|
|
394
|
+
|
|
385
395
|
export function readProviderSettingsDeclarations(source, { file = 'harness/settings.yaml' } = {}) {
|
|
386
396
|
const parsed = parseProviderMap(source);
|
|
387
|
-
|
|
397
|
+
// A settings file with no `llm-pi-ai:` section declares no providers. That is
|
|
398
|
+
// not an unsupported schema — it is the state every home starts in and the
|
|
399
|
+
// state a provider migration writes into — so only a section that EXISTS but
|
|
400
|
+
// cannot be parsed is refused.
|
|
401
|
+
if (!parsed.ok) {
|
|
402
|
+
return parsed.code === 'PROVIDER_SETTINGS_SCHEMA_UNSUPPORTED' && !hasProviderSettingsSection(source)
|
|
403
|
+
? { ok: true, declarations: [], absent: true, file }
|
|
404
|
+
: { ok: false, code: parsed.code };
|
|
405
|
+
}
|
|
388
406
|
return {
|
|
389
407
|
ok: true,
|
|
390
408
|
declarations: parsed.entries.map((entry) => {
|
package/src/runtime-identity.mjs
CHANGED
|
@@ -36,7 +36,7 @@ export {
|
|
|
36
36
|
// included in the identity contract.
|
|
37
37
|
const RUNTIME_ID = randomUUID();
|
|
38
38
|
|
|
39
|
-
export const RUNTIME_VERSION = '2.2.
|
|
39
|
+
export const RUNTIME_VERSION = '2.2.2';
|
|
40
40
|
export const HUB_PROTOCOL_VERSION = 1;
|
|
41
41
|
|
|
42
42
|
export const HUB_CAPABILITIES = Object.freeze([
|