@ran-sh/dsh-crew 2.2.1 → 2.2.3
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 +37 -7
- 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/install-legacy.mjs +8 -1
- package/src/install/npx-lifecycle.mjs +158 -15
- 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,
|
|
@@ -3175,7 +3284,13 @@ export function npxStatus({
|
|
|
3175
3284
|
const integrationLabel = (entry) => !entry?.installed ? 'not installed' : entry.ready === true ? 'installed' : 'needs repair';
|
|
3176
3285
|
const codex = integrationLabel(st?.codex);
|
|
3177
3286
|
const zcode = integrationLabel(st?.zcode);
|
|
3178
|
-
|
|
3287
|
+
// Claude Code installs best-effort: on a host without the CLI the settings are
|
|
3288
|
+
// registered and the CLI step — the only one that can create the plugin snapshot
|
|
3289
|
+
// — is skipped, so such a machine carries a footprint that can never be ready.
|
|
3290
|
+
// Nothing there is repairable, and "needs repair" told operators to fix an
|
|
3291
|
+
// integration they never installed. A host whose CLI IS present with an
|
|
3292
|
+
// unfinished install still reads as needing repair.
|
|
3293
|
+
const claude = st?.claude?.host_detected === false ? 'not installed' : integrationLabel(st?.claude);
|
|
3179
3294
|
const official = officialWebIntegrationStatus({ home, releaseDir: pointer?.path });
|
|
3180
3295
|
const officialWeb = !official.legacy_present ? 'not present (native 3210 control plane)' : official.healthy ? 'legacy full bridge present (deprecated; manual cleanup available)' : 'legacy full bridge record present but unhealthy (deprecated)';
|
|
3181
3296
|
const startupState = installer.windowsStartupStatus?.({ home, root: runningPackageRoot() });
|
|
@@ -3196,6 +3311,16 @@ export function npxStatus({
|
|
|
3196
3311
|
}
|
|
3197
3312
|
log(`DSH plugin: ${dshPlugin} (dedicated dsh-crew profile on 3210)`);
|
|
3198
3313
|
log(`Official 3080 UI bridge: ${officialWeb}`);
|
|
3314
|
+
// The desktop app carries its own bridge entry, which pins a revision: after a
|
|
3315
|
+
// payload update that entry is stale until it is re-pointed, so say which of the
|
|
3316
|
+
// three states the machine is in instead of leaving the panel silently old.
|
|
3317
|
+
const desktop = desktopStatus({ home });
|
|
3318
|
+
log(`Desktop app integration: ${
|
|
3319
|
+
!desktop.file_present ? 'not attached (run: dsh-crew desktop attach)'
|
|
3320
|
+
: desktop.attached && desktop.current ? 'attached'
|
|
3321
|
+
: desktop.attached ? 'attached to a stale bridge revision (run: dsh-crew desktop attach)'
|
|
3322
|
+
: 'not attached (run: dsh-crew desktop attach)'
|
|
3323
|
+
}`);
|
|
3199
3324
|
log(`Codex Desktop integration: ${codex}`);
|
|
3200
3325
|
log(`ZCode integration: ${zcode}`);
|
|
3201
3326
|
log(`Claude Code integration: ${claude}`);
|
|
@@ -3291,6 +3416,16 @@ Commands:
|
|
|
3291
3416
|
install persist the candidate package into Crew-owned state and register it
|
|
3292
3417
|
integrate disabled: the official web profile is read-only (legacy bridge retired)
|
|
3293
3418
|
detach disabled: the official web profile is read-only (legacy bridge retired)
|
|
3419
|
+
desktop the official DSH desktop app: attach|detach|status the Crew panel in its
|
|
3420
|
+
own patch layer (~/.dsh/profiles/desktop/cordis.patch.yml, hot-watched
|
|
3421
|
+
by the Harness). The only official-home write Crew performs, and only
|
|
3422
|
+
when this command is run; attach is idempotent and follows the current
|
|
3423
|
+
bridge revision, detach removes exactly the block it wrote
|
|
3424
|
+
config import|status the operator's user-level DSH configuration (models,
|
|
3425
|
+
providers, policy, MCP servers) from the official home into Crew's own
|
|
3426
|
+
home, so the Crew hub matches the desktop app. Read-only on ~/.dsh; no
|
|
3427
|
+
secrets are copied (provider entries keep their apiKeyEnv references);
|
|
3428
|
+
entries naming packages absent from Crew's runtime cohort are skipped
|
|
3294
3429
|
status read-only report of launcher/installed versions and integrations
|
|
3295
3430
|
inspect print the machine-readable extension capability/readiness contract
|
|
3296
3431
|
jobs machine-first job API: list|get|watch|cancel|submit
|
|
@@ -3312,6 +3447,7 @@ Options:
|
|
|
3312
3447
|
--replacement-default <id> with providers delete-plan: replacement Harness Default provider
|
|
3313
3448
|
--confirm with providers delete: confirm the destructive mutation
|
|
3314
3449
|
--purge with uninstall: also remove ~/.config/dsh-crew config/backups (destructive)
|
|
3450
|
+
--dry-run with desktop attach|detach: report the change without writing
|
|
3315
3451
|
--help show this help
|
|
3316
3452
|
|
|
3317
3453
|
Primary install: npm install -g @ran-sh/dsh-crew (then run: dsh-crew install)
|
|
@@ -3622,6 +3758,7 @@ function normalizeCommand(argv) {
|
|
|
3622
3758
|
let expectedRevision;
|
|
3623
3759
|
let replacementDefault;
|
|
3624
3760
|
let confirm = false;
|
|
3761
|
+
let dryRun = false;
|
|
3625
3762
|
let purgeOrphanCredentials = false;
|
|
3626
3763
|
for (let index = 0; index < flags.length; index += 1) {
|
|
3627
3764
|
if (flags[index] === '--candidate') {
|
|
@@ -3661,16 +3798,18 @@ function normalizeCommand(argv) {
|
|
|
3661
3798
|
replacementDefault = flags[index].slice('--replacement-default='.length); flags.splice(index, 1); index -= 1;
|
|
3662
3799
|
} else if (flags[index] === '--confirm') {
|
|
3663
3800
|
confirm = true; flags.splice(index, 1); index -= 1;
|
|
3801
|
+
} else if (flags[index] === '--dry-run') {
|
|
3802
|
+
dryRun = true; flags.splice(index, 1); index -= 1;
|
|
3664
3803
|
} else if (flags[index] === '--purge-orphan-credentials') {
|
|
3665
3804
|
purgeOrphanCredentials = true; flags.splice(index, 1); index -= 1;
|
|
3666
3805
|
}
|
|
3667
3806
|
}
|
|
3668
|
-
const knownFlags = new Set(['--purge']);
|
|
3807
|
+
const knownFlags = new Set(['--purge', '--dry-run']);
|
|
3669
3808
|
const unknown = flags.filter((f) => f.startsWith('--') && !knownFlags.has(f));
|
|
3670
3809
|
const args = flags.filter((f) => !f.startsWith('--'));
|
|
3671
3810
|
if (!Number.isInteger(after) || after < 0) unknown.push('--after');
|
|
3672
3811
|
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 };
|
|
3812
|
+
return { command: argv[0], purge: flags.includes('--purge'), candidate, after, detail, request, planId, expectedRevision, replacementDefault, confirm, dryRun, purgeOrphanCredentials, args, unknown };
|
|
3674
3813
|
}
|
|
3675
3814
|
|
|
3676
3815
|
/**
|
|
@@ -3682,7 +3821,7 @@ export async function runNpxCli({
|
|
|
3682
3821
|
error = console.error,
|
|
3683
3822
|
commands = {},
|
|
3684
3823
|
} = {}) {
|
|
3685
|
-
const { command, purge, candidate, after, detail, request, planId, expectedRevision, replacementDefault, confirm, purgeOrphanCredentials, args, unknown } = normalizeCommand(argv);
|
|
3824
|
+
const { command, purge, candidate, after, detail, request, planId, expectedRevision, replacementDefault, confirm, dryRun, purgeOrphanCredentials, args, unknown } = normalizeCommand(argv);
|
|
3686
3825
|
if (command === '--help' || command === '-h' || command === 'help') {
|
|
3687
3826
|
log(USAGE);
|
|
3688
3827
|
return 0;
|
|
@@ -3691,7 +3830,7 @@ export async function runNpxCli({
|
|
|
3691
3830
|
error(USAGE);
|
|
3692
3831
|
return 1;
|
|
3693
3832
|
}
|
|
3694
|
-
if (unknown.length > 0 || !['install', 'integrate', 'detach', 'status', 'inspect', 'jobs', 'providers', 'credentials', 'releases', 'rollback', 'update', 'uninstall'].includes(command)) {
|
|
3833
|
+
if (unknown.length > 0 || !['install', 'integrate', 'detach', 'desktop', 'config', 'status', 'inspect', 'jobs', 'providers', 'credentials', 'releases', 'rollback', 'update', 'uninstall'].includes(command)) {
|
|
3695
3834
|
error(`unknown command: ${command ?? '<none>'}\n\n${USAGE}`);
|
|
3696
3835
|
return 1;
|
|
3697
3836
|
}
|
|
@@ -3700,6 +3839,8 @@ export async function runNpxCli({
|
|
|
3700
3839
|
install: commands.install ?? npxInstall,
|
|
3701
3840
|
integrate: commands.integrate ?? npxIntegrate,
|
|
3702
3841
|
detach: commands.detach ?? npxDetach,
|
|
3842
|
+
desktop: commands.desktop ?? npxDesktop,
|
|
3843
|
+
config: commands.config ?? npxConfig,
|
|
3703
3844
|
status: commands.status ?? npxStatus,
|
|
3704
3845
|
inspect: commands.inspect ?? npxInspect,
|
|
3705
3846
|
jobs: commands.jobs ?? npxJobs,
|
|
@@ -3713,6 +3854,8 @@ export async function runNpxCli({
|
|
|
3713
3854
|
let result;
|
|
3714
3855
|
if (command === 'uninstall') result = await actions.uninstall({ purge, log });
|
|
3715
3856
|
else if (command === 'update') result = await actions.update({ candidate, log });
|
|
3857
|
+
else if (command === 'desktop') result = await actions.desktop({ args, dryRun, log });
|
|
3858
|
+
else if (command === 'config') result = await actions.config({ args, dryRun, log });
|
|
3716
3859
|
else if (command === 'jobs') result = await actions.jobs({ args, after, detail, request, log });
|
|
3717
3860
|
else if (command === 'providers') result = await actions.providers({ args, planId, expectedRevision, replacementDefault, confirm, purgeOrphanCredentials, log });
|
|
3718
3861
|
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.3';
|
|
40
40
|
export const HUB_PROTOCOL_VERSION = 1;
|
|
41
41
|
|
|
42
42
|
export const HUB_CAPABILITIES = Object.freeze([
|