@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
|
@@ -40,6 +40,7 @@ import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
|
40
40
|
import { fileURLToPath } from 'node:url';
|
|
41
41
|
import { homedir } from 'node:os';
|
|
42
42
|
import * as realInstaller from './install.mjs';
|
|
43
|
+
import { samePayloadContent, capturePayloadContent } from './payload-content.mjs';
|
|
43
44
|
import { crewDshHome, crewProfileDir } from './install.mjs';
|
|
44
45
|
import { ensureCrewDshRuntime, ensureCrewPluginRegistration, removeCrewPluginRegistration, migrateCrewDshRuntime, installDshInto, restoreRetainedRuntime, crewDshRuntimeRoot, payloadDshVersion, TARGET_DSH_VERSION } from '../dsh-cli-runtime.mjs';
|
|
45
46
|
import {
|
|
@@ -47,6 +48,13 @@ import {
|
|
|
47
48
|
officialWebIntegrationStatus,
|
|
48
49
|
removeOfficialWebIntegration,
|
|
49
50
|
} from './official-web.mjs';
|
|
51
|
+
import {
|
|
52
|
+
convergeWindowsSupervisor,
|
|
53
|
+
PENDING_RUNTIME_ACTIVATION,
|
|
54
|
+
releaseWindowsSupervisorConvergenceReservation,
|
|
55
|
+
reserveWindowsSupervisorConvergence,
|
|
56
|
+
windowsSupervisorConvergencePending,
|
|
57
|
+
} from './windows-supervisor-adapter.mjs';
|
|
50
58
|
|
|
51
59
|
export const CREW_APP_DIRNAME = 'app';
|
|
52
60
|
export const RELEASES_DIRNAME = 'releases';
|
|
@@ -848,6 +856,8 @@ export function stageCandidatePayload({
|
|
|
848
856
|
if (!existsSync(join(sourceRoot, 'src', 'server.mjs')) || !existsSync(join(sourceRoot, 'cordis.patch.yml'))) {
|
|
849
857
|
return { ok: false, code: 'CANDIDATE_NOT_RUNNABLE' };
|
|
850
858
|
}
|
|
859
|
+
const content = capturePayloadContent(sourceRoot, manifest);
|
|
860
|
+
if (!content) return { ok: false, code: 'CANDIDATE_CONTENT_INVALID' };
|
|
851
861
|
|
|
852
862
|
const releasesDir = crewReleasesDir({ home });
|
|
853
863
|
mkdirSync(releasesDir, { recursive: true });
|
|
@@ -856,26 +866,12 @@ export function stageCandidatePayload({
|
|
|
856
866
|
mkdirSync(stageDir, { recursive: true });
|
|
857
867
|
writeFileSync(join(stageDir, INCOMPLETE_MARKER), String(Date.now()) + '\n');
|
|
858
868
|
|
|
859
|
-
// Copy
|
|
860
|
-
|
|
861
|
-
const
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
const base = dirname(pattern);
|
|
866
|
-
const regex = new RegExp(`^${pattern.split('*').map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('[^/]*')}$`);
|
|
867
|
-
const scanDir = join(sourceRoot, base);
|
|
868
|
-
if (!existsSync(scanDir)) continue;
|
|
869
|
-
for (const item of readdirSync(scanDir)) {
|
|
870
|
-
const candidatePath = base === '.' ? item : `${base.replace(/\\/g, '/')}/${item}`;
|
|
871
|
-
if (regex.test(candidatePath)) entries.add(candidatePath);
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
for (const entry of entries) {
|
|
876
|
-
const source = join(sourceRoot, entry);
|
|
877
|
-
if (!existsSync(source)) continue;
|
|
878
|
-
cpSync(source, join(stageDir, entry), { recursive: true, force: true, dereference: true });
|
|
869
|
+
// Copy the validated byte snapshot, never re-resolve source paths here.
|
|
870
|
+
for (const directory of content.directories) mkdirSync(join(stageDir, directory), { recursive: true });
|
|
871
|
+
for (const [entry, bytes] of content.files) {
|
|
872
|
+
const destination = join(stageDir, entry);
|
|
873
|
+
mkdirSync(dirname(destination), { recursive: true });
|
|
874
|
+
writeFileSync(destination, bytes, { mode: content.modes.get(entry) });
|
|
879
875
|
}
|
|
880
876
|
|
|
881
877
|
// Persist an adjusted manifest: identity/runtime fields stay; production
|
|
@@ -1023,11 +1019,18 @@ export function validateInstalledPayload(dir, { expectedName, expectedVersion, a
|
|
|
1023
1019
|
}
|
|
1024
1020
|
}
|
|
1025
1021
|
}
|
|
1026
|
-
|
|
1022
|
+
const requiredArtifacts = [
|
|
1027
1023
|
'cordis.patch.yml', 'src/server.mjs', 'src/hub/entry.mjs', 'lib/client.js', 'bin/dsh-crew.mjs',
|
|
1028
1024
|
'official-web-bridge/package.json', 'official-web-bridge/cordis.patch.yml',
|
|
1029
1025
|
'official-web-bridge/entry.mjs', 'official-web-bridge/lib/client.js',
|
|
1030
|
-
]
|
|
1026
|
+
];
|
|
1027
|
+
if (manifest?.dshCrew?.windowsSupervisorHandoff === 1) {
|
|
1028
|
+
requiredArtifacts.push(
|
|
1029
|
+
'windows/start-dsh-crew.ps1', 'windows/start-dsh-crew.cmd',
|
|
1030
|
+
'windows/start-dsh-crew.vbs', 'windows/supervisor-control.ps1',
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
1033
|
+
for (const rel of requiredArtifacts) {
|
|
1031
1034
|
if (!existsSync(join(dir, rel))) errors.push(`payload artifact missing: ${rel}`);
|
|
1032
1035
|
}
|
|
1033
1036
|
if (!allowIncomplete && existsSync(join(dir, INCOMPLETE_MARKER))) errors.push('release is still marked incomplete');
|
|
@@ -1040,6 +1043,9 @@ export function validateInstalledPayload(dir, { expectedName, expectedVersion, a
|
|
|
1040
1043
|
// A crash at any point before the pointer write leaves the prior release
|
|
1041
1044
|
// authoritative and the journal behind for reconcileUpdateJournal.
|
|
1042
1045
|
export function beginReleaseActivation({ stageDir, manifest, home, prior = null }) {
|
|
1046
|
+
// Survives pointer commit and updater crashes; versions alone cannot prove
|
|
1047
|
+
// that a same-version code replacement has reached the running process.
|
|
1048
|
+
writeFileSync(join(stageDir, PENDING_RUNTIME_ACTIVATION), `${manifest.version}\n`);
|
|
1043
1049
|
// Journal FIRST, marker removal second: a crash between the two leaves a
|
|
1044
1050
|
// journaled (recoverable) candidate, never a marker-free orphan that
|
|
1045
1051
|
// looks complete but was never in a transaction. Marker removal failure
|
|
@@ -1287,12 +1293,18 @@ export async function verifyRollbackTarget({ crewVersion, dshVersion, fetchImpl
|
|
|
1287
1293
|
// requests. The npx process owns the runtime TREE swap; the supervisor owns
|
|
1288
1294
|
// the PROCESS stop/start. The Node sidecar supervisor is deliberately not
|
|
1289
1295
|
// used here: two authorities must never share the same kill rights.
|
|
1290
|
-
function
|
|
1296
|
+
export function createCrewSupervisor({
|
|
1297
|
+
home = homedir(),
|
|
1298
|
+
fetchImpl = globalThis.fetch,
|
|
1299
|
+
pollMs = 1_000,
|
|
1300
|
+
timeoutMs = 90_000,
|
|
1301
|
+
identityTimeoutMs = 3_000,
|
|
1302
|
+
sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
1303
|
+
now = Date.now,
|
|
1304
|
+
} = {}) {
|
|
1291
1305
|
// The ONLY Crew app root both the hub and the Windows launcher agree on.
|
|
1292
1306
|
// Requests written anywhere else are invisible to the supervisor.
|
|
1293
1307
|
const appRoot = join(home, '.config', 'dsh-crew');
|
|
1294
|
-
const pollMs = 1_000;
|
|
1295
|
-
const timeoutMs = 90_000;
|
|
1296
1308
|
// One maintenance transaction = one lease + the pre-stop identity proven
|
|
1297
1309
|
// live. stop() fetches the CURRENT live runtime_id (the process exists);
|
|
1298
1310
|
// start() reuses that proven identity + lease (the process is stopped by
|
|
@@ -1300,13 +1312,30 @@ function crewSupervisor({ home = homedir() } = {}) {
|
|
|
1300
1312
|
// single one-shot transaction instead of two independent requests.
|
|
1301
1313
|
let transaction = null;
|
|
1302
1314
|
async function fetchCrewIdentity() {
|
|
1315
|
+
const controller = new AbortController();
|
|
1316
|
+
// Keep the discovery deadline alive even if an adapter has no active I/O.
|
|
1317
|
+
const timer = setTimeout(() => controller.abort(new Error('maintenance identity timeout')), identityTimeoutMs);
|
|
1303
1318
|
try {
|
|
1304
|
-
const response = await
|
|
1319
|
+
const response = await fetchImpl('http://127.0.0.1:3210/_dsh/dsh-crew/extension', {
|
|
1320
|
+
headers: { accept: 'application/json' },
|
|
1321
|
+
signal: controller.signal,
|
|
1322
|
+
});
|
|
1305
1323
|
if (!response.ok) return null;
|
|
1306
1324
|
const body = await response.json();
|
|
1307
1325
|
const runtime = body?.extension?.runtime ?? null;
|
|
1308
|
-
|
|
1326
|
+
const canonical = body?.ok === true
|
|
1327
|
+
&& runtime?.service === 'dsh-crew-hub'
|
|
1328
|
+
&& runtime?.execution_plane === 'hub-3210'
|
|
1329
|
+
&& runtime?.profile === 'dsh-crew'
|
|
1330
|
+
&& Number(runtime?.listen_port) === 3210
|
|
1331
|
+
&& Number(runtime?.protocol_version) === 1
|
|
1332
|
+
&& typeof runtime?.runtime_id === 'string'
|
|
1333
|
+
&& runtime.runtime_id.length > 0;
|
|
1334
|
+
return canonical
|
|
1335
|
+
? { execution_plane: 'hub-3210', profile: 'dsh-crew', listen_port: 3210, runtime_id: runtime.runtime_id }
|
|
1336
|
+
: null;
|
|
1309
1337
|
} catch { return null; }
|
|
1338
|
+
finally { clearTimeout(timer); }
|
|
1310
1339
|
}
|
|
1311
1340
|
async function writeMaintenance({ operation, lease, identity, extra = null }) {
|
|
1312
1341
|
const { createSupervisorRequest, maintenanceResultsDir } = await import('../supervisor/restart-request.mjs');
|
|
@@ -1318,10 +1347,10 @@ function crewSupervisor({ home = homedir() } = {}) {
|
|
|
1318
1347
|
extra,
|
|
1319
1348
|
});
|
|
1320
1349
|
if (!created.ok) return created;
|
|
1321
|
-
const deadline =
|
|
1350
|
+
const deadline = now() + timeoutMs;
|
|
1322
1351
|
for (;;) {
|
|
1323
|
-
await
|
|
1324
|
-
if (
|
|
1352
|
+
await sleep(pollMs);
|
|
1353
|
+
if (now() > deadline) {
|
|
1325
1354
|
return { ok: false, code: 'MAINTENANCE_TIMEOUT', error: `${operation} ${created.request.request_id} not completed within ${timeoutMs}ms` };
|
|
1326
1355
|
}
|
|
1327
1356
|
const resultFile = join(maintenanceResultsDir(appRoot), `${created.request.request_id}.json`);
|
|
@@ -1331,21 +1360,70 @@ function crewSupervisor({ home = homedir() } = {}) {
|
|
|
1331
1360
|
result = JSON.parse(readFileSync(resultFile, 'utf8'));
|
|
1332
1361
|
}
|
|
1333
1362
|
} catch { /* not yet */ }
|
|
1334
|
-
if (!result
|
|
1335
|
-
|
|
1336
|
-
|
|
1363
|
+
if (!result) continue;
|
|
1364
|
+
const identityMatches = result.schema_version === 1
|
|
1365
|
+
&& result.request_id === created.request.request_id
|
|
1366
|
+
&& result.operation === created.request.operation
|
|
1367
|
+
&& result.lease === created.request.lease
|
|
1368
|
+
&& result.runtime_id === created.request.runtime_id;
|
|
1369
|
+
if (!identityMatches) {
|
|
1370
|
+
return {
|
|
1371
|
+
ok: false,
|
|
1372
|
+
code: 'MAINTENANCE_RESULT_IDENTITY_MISMATCH',
|
|
1373
|
+
error: 'maintenance result does not match the exact request identity',
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
const expectedState = operation === 'maintenance-stop' ? 'STOPPED' : 'VERIFIED';
|
|
1377
|
+
if (result.state === expectedState) {
|
|
1378
|
+
return {
|
|
1379
|
+
ok: true,
|
|
1380
|
+
state: result.state,
|
|
1381
|
+
request_id: result.request_id,
|
|
1382
|
+
lease: result.lease,
|
|
1383
|
+
runtime_id: result.runtime_id,
|
|
1384
|
+
detail: result.detail ?? null,
|
|
1385
|
+
};
|
|
1337
1386
|
}
|
|
1338
1387
|
return { ok: false, code: `MAINTENANCE_${result.state ?? 'FAILED'}`, error: `maintenance ended in state ${result.state}`, detail: result.detail ?? null };
|
|
1339
1388
|
}
|
|
1340
1389
|
}
|
|
1341
1390
|
return {
|
|
1342
|
-
stopOwnedBackend: async () => {
|
|
1391
|
+
stopOwnedBackend: async ({ lease = null, runtimeId = null } = {}) => {
|
|
1392
|
+
const { readMaintenanceSession } = await import('../supervisor/restart-request.mjs');
|
|
1393
|
+
const durable = readMaintenanceSession(appRoot);
|
|
1394
|
+
if (!durable.ok) {
|
|
1395
|
+
transaction = null;
|
|
1396
|
+
return { ok: false, code: durable.code, error: 'durable maintenance session is malformed' };
|
|
1397
|
+
}
|
|
1398
|
+
if (durable.state === 'present') {
|
|
1399
|
+
const session = durable.session;
|
|
1400
|
+
if (lease && runtimeId && session.lease === lease && session.runtime_id === runtimeId) {
|
|
1401
|
+
transaction = { lease, runtime_id: runtimeId };
|
|
1402
|
+
return {
|
|
1403
|
+
ok: true,
|
|
1404
|
+
state: 'STOPPED',
|
|
1405
|
+
lease,
|
|
1406
|
+
runtime_id: runtimeId,
|
|
1407
|
+
resumed: true,
|
|
1408
|
+
request_id: session.request_id,
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
transaction = null;
|
|
1412
|
+
return { ok: false, code: 'MAINTENANCE_SESSION_CONFLICT', error: 'a different durable maintenance session owns the stopped runtime window' };
|
|
1413
|
+
}
|
|
1343
1414
|
const identity = await fetchCrewIdentity();
|
|
1344
1415
|
if (!identity?.runtime_id) {
|
|
1345
1416
|
transaction = null;
|
|
1346
1417
|
return { ok: false, code: 'MAINTENANCE_IDENTITY_UNAVAILABLE', error: 'live 3210 runtime_id unavailable' };
|
|
1347
1418
|
}
|
|
1348
|
-
|
|
1419
|
+
if (runtimeId && runtimeId !== identity.runtime_id) {
|
|
1420
|
+
transaction = null;
|
|
1421
|
+
return { ok: false, code: 'MAINTENANCE_IDENTITY_CHANGED', error: 'live 3210 runtime_id changed before maintenance-stop' };
|
|
1422
|
+
}
|
|
1423
|
+
transaction = {
|
|
1424
|
+
lease: lease ?? `txn-${now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`,
|
|
1425
|
+
runtime_id: identity.runtime_id,
|
|
1426
|
+
};
|
|
1349
1427
|
const result = await writeMaintenance({ operation: 'maintenance-stop', lease: transaction.lease, identity });
|
|
1350
1428
|
if (!result.ok) {
|
|
1351
1429
|
// A failed stop must not leave a usable lease behind: clear it so a
|
|
@@ -1355,20 +1433,223 @@ function crewSupervisor({ home = homedir() } = {}) {
|
|
|
1355
1433
|
}
|
|
1356
1434
|
return result;
|
|
1357
1435
|
},
|
|
1358
|
-
startOwnedBackend: async () => {
|
|
1436
|
+
startOwnedBackend: async ({ lease = null, runtimeId = null, expectedCrewVersion = null, expectedDshVersion = null } = {}) => {
|
|
1359
1437
|
// Reuse the proven stop-phase identity + lease: the process is stopped
|
|
1360
1438
|
// by design and fetching identity now would always fail.
|
|
1361
|
-
|
|
1439
|
+
const active = lease && runtimeId ? { lease, runtime_id: runtimeId } : transaction;
|
|
1440
|
+
if (!active?.lease || !active?.runtime_id) {
|
|
1362
1441
|
return { ok: false, code: 'MAINTENANCE_NO_TRANSACTION', error: 'maintenance-start requires a completed maintenance-stop in the same supervisor' };
|
|
1363
1442
|
}
|
|
1364
|
-
const identity = { execution_plane: 'hub-3210', profile: 'dsh-crew', listen_port: 3210, runtime_id:
|
|
1365
|
-
const
|
|
1443
|
+
const identity = { execution_plane: 'hub-3210', profile: 'dsh-crew', listen_port: 3210, runtime_id: active.runtime_id };
|
|
1444
|
+
const extra = expectedCrewVersion || expectedDshVersion ? {
|
|
1445
|
+
...(expectedCrewVersion ? { expected_crew_version: expectedCrewVersion } : {}),
|
|
1446
|
+
...(expectedDshVersion ? { expected_dsh_version: expectedDshVersion } : {}),
|
|
1447
|
+
} : null;
|
|
1448
|
+
const result = await writeMaintenance({ operation: 'maintenance-start', lease: active.lease, identity, extra });
|
|
1366
1449
|
transaction = null;
|
|
1367
1450
|
return result;
|
|
1368
1451
|
},
|
|
1369
1452
|
};
|
|
1370
1453
|
}
|
|
1371
1454
|
|
|
1455
|
+
const crewSupervisor = createCrewSupervisor;
|
|
1456
|
+
|
|
1457
|
+
function sameFilesystemPath(left, right) {
|
|
1458
|
+
if (typeof left !== 'string' || typeof right !== 'string') return false;
|
|
1459
|
+
const a = resolve(left);
|
|
1460
|
+
const b = resolve(right);
|
|
1461
|
+
return process.platform === 'win32' ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
function usesProductionWindowsSupervisor(home, supervisorConverger) {
|
|
1465
|
+
return typeof supervisorConverger !== 'function'
|
|
1466
|
+
&& process.platform === 'win32'
|
|
1467
|
+
&& sameFilesystemPath(home, homedir());
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
async function acquireLifecycleUpdateLock({
|
|
1471
|
+
home,
|
|
1472
|
+
log,
|
|
1473
|
+
supervisorConverger,
|
|
1474
|
+
supervisorPendingChecker,
|
|
1475
|
+
}) {
|
|
1476
|
+
const production = usesProductionWindowsSupervisor(home, supervisorConverger);
|
|
1477
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
1478
|
+
const updateLock = acquireUpdateLock({ home });
|
|
1479
|
+
if (!updateLock.ok) return updateLock;
|
|
1480
|
+
let pending;
|
|
1481
|
+
try {
|
|
1482
|
+
pending = typeof supervisorPendingChecker === 'function'
|
|
1483
|
+
? await supervisorPendingChecker({ home })
|
|
1484
|
+
: production
|
|
1485
|
+
? windowsSupervisorConvergencePending({ home })
|
|
1486
|
+
: false;
|
|
1487
|
+
} catch (error) {
|
|
1488
|
+
releaseUpdateLock({ home, nonce: updateLock.nonce ?? null });
|
|
1489
|
+
return { ok: false, code: 'SUPERVISOR_HANDOFF_PENDING_CHECK_FAILED', error: error?.message ?? String(error) };
|
|
1490
|
+
}
|
|
1491
|
+
// Holding updateLock makes this a stable observation: no other conforming
|
|
1492
|
+
// lifecycle operation can reserve a new handoff until we either return
|
|
1493
|
+
// this lock or release it for recovery.
|
|
1494
|
+
if (!pending) return updateLock;
|
|
1495
|
+
|
|
1496
|
+
const released = releaseUpdateLock({ home, nonce: updateLock.nonce ?? null });
|
|
1497
|
+
if (!released.ok || existsSync(updateLockFile({ home }))) {
|
|
1498
|
+
return { ok: false, code: 'UPDATE_LOCK_RELEASE_UNVERIFIED' };
|
|
1499
|
+
}
|
|
1500
|
+
const active = readCurrentPointer({ home });
|
|
1501
|
+
if (!active?.path || !existsSync(active.path)) {
|
|
1502
|
+
return { ok: false, code: 'SUPERVISOR_HANDOFF_RECOVERY_RELEASE_MISSING' };
|
|
1503
|
+
}
|
|
1504
|
+
const converge = typeof supervisorConverger === 'function' ? supervisorConverger : convergeWindowsSupervisor;
|
|
1505
|
+
let recovery;
|
|
1506
|
+
try {
|
|
1507
|
+
recovery = await converge({
|
|
1508
|
+
home,
|
|
1509
|
+
root: active.path,
|
|
1510
|
+
maintenanceClient: createCrewSupervisor({ home }),
|
|
1511
|
+
});
|
|
1512
|
+
} catch (error) {
|
|
1513
|
+
recovery = { ok: false, code: 'SUPERVISOR_HANDOFF_RECOVERY_THROWN', error: error?.message ?? String(error) };
|
|
1514
|
+
}
|
|
1515
|
+
if (recovery?.ok !== true) {
|
|
1516
|
+
log?.(`✗ pending Windows supervisor handoff recovery failed (${recovery?.code ?? 'unknown'}).`);
|
|
1517
|
+
return { ok: false, code: 'SUPERVISOR_HANDOFF_RECOVERY_FAILED', recovery };
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
return { ok: false, code: 'SUPERVISOR_HANDOFF_RECOVERY_CHURN' };
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
async function reservePostCommitSupervisor({
|
|
1524
|
+
home,
|
|
1525
|
+
result,
|
|
1526
|
+
supervisorConverger,
|
|
1527
|
+
supervisorReserver,
|
|
1528
|
+
}) {
|
|
1529
|
+
if (result?.ok !== true) return { result, reservation: null };
|
|
1530
|
+
const production = usesProductionWindowsSupervisor(home, supervisorConverger);
|
|
1531
|
+
const reserve = typeof supervisorReserver === 'function'
|
|
1532
|
+
? supervisorReserver
|
|
1533
|
+
: production
|
|
1534
|
+
? reserveWindowsSupervisorConvergence
|
|
1535
|
+
: null;
|
|
1536
|
+
if (!reserve) return { result, reservation: null };
|
|
1537
|
+
const active = readCurrentPointer({ home });
|
|
1538
|
+
if (!active?.path
|
|
1539
|
+
|| !sameFilesystemPath(active.path, result.path)
|
|
1540
|
+
|| (result.version && active.version !== result.version)) {
|
|
1541
|
+
return {
|
|
1542
|
+
result: {
|
|
1543
|
+
...result,
|
|
1544
|
+
ok: false,
|
|
1545
|
+
committed: true,
|
|
1546
|
+
code: 'SUPERVISOR_CONVERGENCE_RELEASE_CHANGED',
|
|
1547
|
+
error: 'active Crew release changed before Windows supervisor reservation',
|
|
1548
|
+
},
|
|
1549
|
+
reservation: null,
|
|
1550
|
+
};
|
|
1551
|
+
}
|
|
1552
|
+
const reservation = await reserve({ home, root: active.path });
|
|
1553
|
+
if (reservation?.ok !== true) {
|
|
1554
|
+
return {
|
|
1555
|
+
result: {
|
|
1556
|
+
...result,
|
|
1557
|
+
ok: false,
|
|
1558
|
+
committed: true,
|
|
1559
|
+
code: 'SUPERVISOR_CONVERGENCE_RESERVATION_FAILED',
|
|
1560
|
+
supervisor_convergence: reservation ?? null,
|
|
1561
|
+
},
|
|
1562
|
+
reservation: null,
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
return { result, reservation };
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
async function finalizeSupervisorConvergence({
|
|
1569
|
+
home,
|
|
1570
|
+
result,
|
|
1571
|
+
log,
|
|
1572
|
+
supervisorConverger,
|
|
1573
|
+
reservation = null,
|
|
1574
|
+
}) {
|
|
1575
|
+
if (result?.ok !== true) return result;
|
|
1576
|
+
|
|
1577
|
+
const defaultWindowsHome = process.platform === 'win32' && sameFilesystemPath(home, homedir());
|
|
1578
|
+
const converge = typeof supervisorConverger === 'function'
|
|
1579
|
+
? supervisorConverger
|
|
1580
|
+
: defaultWindowsHome
|
|
1581
|
+
? convergeWindowsSupervisor
|
|
1582
|
+
: null;
|
|
1583
|
+
if (!converge) return result;
|
|
1584
|
+
|
|
1585
|
+
const active = readCurrentPointer({ home });
|
|
1586
|
+
if (!active?.path
|
|
1587
|
+
|| !sameFilesystemPath(active.path, result.path)
|
|
1588
|
+
|| (result.version && active.version !== result.version)) {
|
|
1589
|
+
if (reservation) releaseWindowsSupervisorConvergenceReservation(reservation);
|
|
1590
|
+
return {
|
|
1591
|
+
...result,
|
|
1592
|
+
ok: false,
|
|
1593
|
+
committed: true,
|
|
1594
|
+
code: 'SUPERVISOR_CONVERGENCE_RELEASE_CHANGED',
|
|
1595
|
+
error: 'active Crew release changed before Windows supervisor convergence',
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
let convergence;
|
|
1600
|
+
try {
|
|
1601
|
+
convergence = await converge({
|
|
1602
|
+
home,
|
|
1603
|
+
root: active.path,
|
|
1604
|
+
maintenanceClient: createCrewSupervisor({ home }),
|
|
1605
|
+
...(reservation ? { reservation } : {}),
|
|
1606
|
+
});
|
|
1607
|
+
} catch (error) {
|
|
1608
|
+
convergence = {
|
|
1609
|
+
ok: false,
|
|
1610
|
+
code: 'SUPERVISOR_CONVERGENCE_THROWN',
|
|
1611
|
+
error: error?.message ?? String(error),
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
if (convergence?.ok !== true) {
|
|
1615
|
+
log?.(`✗ Windows supervisor convergence failed (${convergence?.code ?? 'unknown'}); the payload remains committed and the handoff can be resumed.`);
|
|
1616
|
+
return {
|
|
1617
|
+
...result,
|
|
1618
|
+
ok: false,
|
|
1619
|
+
committed: true,
|
|
1620
|
+
code: 'SUPERVISOR_CONVERGENCE_FAILED',
|
|
1621
|
+
supervisor_convergence: convergence ?? null,
|
|
1622
|
+
};
|
|
1623
|
+
}
|
|
1624
|
+
try { rmSync(join(active.path, PENDING_RUNTIME_ACTIVATION), { force: true }); }
|
|
1625
|
+
catch {
|
|
1626
|
+
return { ...result, ok: false, committed: true, code: 'RUNTIME_ACTIVATION_MARKER_CLEAR_FAILED', supervisor_convergence: convergence };
|
|
1627
|
+
}
|
|
1628
|
+
return { ...result, supervisor_convergence: convergence };
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
async function finishLifecycleAfterUpdateLock({
|
|
1632
|
+
home,
|
|
1633
|
+
result,
|
|
1634
|
+
lockRelease,
|
|
1635
|
+
log,
|
|
1636
|
+
supervisorConverger,
|
|
1637
|
+
reservation = null,
|
|
1638
|
+
}) {
|
|
1639
|
+
if (result?.ok !== true) return result;
|
|
1640
|
+
if (lockRelease?.ok !== true || existsSync(updateLockFile({ home }))) {
|
|
1641
|
+
if (reservation) releaseWindowsSupervisorConvergenceReservation(reservation);
|
|
1642
|
+
return {
|
|
1643
|
+
...result,
|
|
1644
|
+
ok: false,
|
|
1645
|
+
committed: true,
|
|
1646
|
+
code: 'UPDATE_LOCK_RELEASE_UNVERIFIED',
|
|
1647
|
+
error: 'update lock release could not be verified; supervisor convergence was not attempted',
|
|
1648
|
+
};
|
|
1649
|
+
}
|
|
1650
|
+
return finalizeSupervisorConvergence({ home, result, log, supervisorConverger, reservation });
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1372
1653
|
// ---- release cohort resolution -------------------------------------------------
|
|
1373
1654
|
|
|
1374
1655
|
// Lifecycle-owned cohort sidecar for a managed release directory. Historical
|
|
@@ -1470,19 +1751,27 @@ export async function npxRollback({
|
|
|
1470
1751
|
restart,
|
|
1471
1752
|
verifyRuntime,
|
|
1472
1753
|
supervisorFactory = crewSupervisor,
|
|
1754
|
+
supervisorConverger,
|
|
1755
|
+
supervisorPendingChecker,
|
|
1756
|
+
supervisorReserver,
|
|
1473
1757
|
} = {}) {
|
|
1474
1758
|
const targetVersion = typeof version === 'string' ? version.trim() : '';
|
|
1475
1759
|
if (!targetVersion) return { ok: false, error: 'rollback requires a target version' };
|
|
1476
1760
|
// Rollback shares the update mutual-exclusion lock: it mutates the same
|
|
1477
1761
|
// pointer/registration/runtime surfaces as install/update, and the watch
|
|
1478
1762
|
// supervisor must observe-only while it runs.
|
|
1479
|
-
const updateLock =
|
|
1763
|
+
const updateLock = await acquireLifecycleUpdateLock({ home, log, supervisorConverger, supervisorPendingChecker });
|
|
1480
1764
|
if (!updateLock.ok) return { ok: false, error: `another update is in progress (${updateLock.code})` };
|
|
1765
|
+
let result;
|
|
1766
|
+
let lockRelease;
|
|
1767
|
+
let reservation = null;
|
|
1481
1768
|
try {
|
|
1482
|
-
|
|
1769
|
+
result = await npxRollbackInner({ home, version: targetVersion, log, installer, ensureRuntime, validatePayload, activate, restart, verifyRuntime, supervisorFactory });
|
|
1770
|
+
({ result, reservation } = await reservePostCommitSupervisor({ home, result, supervisorConverger, supervisorReserver }));
|
|
1483
1771
|
} finally {
|
|
1484
|
-
releaseUpdateLock({ home, nonce: updateLock.nonce ?? null });
|
|
1772
|
+
lockRelease = releaseUpdateLock({ home, nonce: updateLock.nonce ?? null });
|
|
1485
1773
|
}
|
|
1774
|
+
return finishLifecycleAfterUpdateLock({ home, result, lockRelease, log, supervisorConverger, reservation });
|
|
1486
1775
|
}
|
|
1487
1776
|
|
|
1488
1777
|
async function npxRollbackInner({ home, version: targetVersion, log, installer, ensureRuntime, validatePayload, activate, restart, verifyRuntime, supervisorFactory }) {
|
|
@@ -2044,7 +2333,7 @@ function currentInstallationHealth({ home }) {
|
|
|
2044
2333
|
return { installed: true, healthy: validated.ok && registered, validated, registered, pointer };
|
|
2045
2334
|
}
|
|
2046
2335
|
|
|
2047
|
-
async function activateRelease({ home, releaseDir, manifest, log, installer }) {
|
|
2336
|
+
async function activateRelease({ home, releaseDir, manifest, log, installer, supervisorRoot = runningPackageRoot() }) {
|
|
2048
2337
|
const registration = ensureCrewPluginRegistration({ home, root: releaseDir, name: manifest.name });
|
|
2049
2338
|
if (!registration.ok) {
|
|
2050
2339
|
log(`✗ Harness registration failed (${registration.code ?? 'unknown'})`);
|
|
@@ -2068,7 +2357,7 @@ async function activateRelease({ home, releaseDir, manifest, log, installer }) {
|
|
|
2068
2357
|
log('✓ ZCode integration');
|
|
2069
2358
|
}
|
|
2070
2359
|
|
|
2071
|
-
const startup = installer.installWindowsStartup?.({ home, root:
|
|
2360
|
+
const startup = installer.installWindowsStartup?.({ home, root: supervisorRoot });
|
|
2072
2361
|
if (startup?.ok === false) {
|
|
2073
2362
|
log(`✗ Windows login startup failed (${startup.code ?? 'unknown'})`);
|
|
2074
2363
|
return false;
|
|
@@ -2121,15 +2410,23 @@ export async function npxInstall({
|
|
|
2121
2410
|
installer = realInstaller,
|
|
2122
2411
|
ensureRuntime,
|
|
2123
2412
|
npmInstaller,
|
|
2413
|
+
supervisorConverger,
|
|
2414
|
+
supervisorPendingChecker,
|
|
2415
|
+
supervisorReserver,
|
|
2124
2416
|
} = {}) {
|
|
2125
2417
|
log('DSH Crew installer (npx-managed)');
|
|
2126
|
-
const updateLock =
|
|
2418
|
+
const updateLock = await acquireLifecycleUpdateLock({ home, log, supervisorConverger, supervisorPendingChecker });
|
|
2127
2419
|
if (!updateLock.ok) return { ok: false, error: `another update is in progress (${updateLock.code})` };
|
|
2420
|
+
let result;
|
|
2421
|
+
let lockRelease;
|
|
2422
|
+
let reservation = null;
|
|
2128
2423
|
try {
|
|
2129
|
-
|
|
2424
|
+
result = await npxInstallInner({ home, log, sourceRoot, installer, ensureRuntime, npmInstaller });
|
|
2425
|
+
({ result, reservation } = await reservePostCommitSupervisor({ home, result, supervisorConverger, supervisorReserver }));
|
|
2130
2426
|
} finally {
|
|
2131
|
-
releaseUpdateLock({ home, nonce: updateLock.nonce ?? null });
|
|
2427
|
+
lockRelease = releaseUpdateLock({ home, nonce: updateLock.nonce ?? null });
|
|
2132
2428
|
}
|
|
2429
|
+
return finishLifecycleAfterUpdateLock({ home, result, lockRelease, log, supervisorConverger, reservation });
|
|
2133
2430
|
}
|
|
2134
2431
|
|
|
2135
2432
|
async function npxInstallInner({ home, log, sourceRoot, installer, ensureRuntime, npmInstaller }) {
|
|
@@ -2140,7 +2437,8 @@ async function npxInstallInner({ home, log, sourceRoot, installer, ensureRuntime
|
|
|
2140
2437
|
if (!manifest?.name || !manifest?.version) return { ok: false, error: 'candidate package manifest invalid' };
|
|
2141
2438
|
|
|
2142
2439
|
const health = currentInstallationHealth({ home });
|
|
2143
|
-
if (health.installed && health.pointer.version === manifest.version && health.validated.ok
|
|
2440
|
+
if (health.installed && health.pointer.version === manifest.version && health.validated.ok
|
|
2441
|
+
&& samePayloadContent(candidateRoot, health.pointer.path)) {
|
|
2144
2442
|
// Same-version reinstall: repair activation surfaces without restaging.
|
|
2145
2443
|
log(`- installed payload ${manifest.version} already present and valid; repairing activation`);
|
|
2146
2444
|
const activated = await activateRelease({ home, releaseDir: health.pointer.path, manifest, log, installer });
|
|
@@ -2153,7 +2451,7 @@ async function npxInstallInner({ home, log, sourceRoot, installer, ensureRuntime
|
|
|
2153
2451
|
|
|
2154
2452
|
const staged = stageCandidatePayload({ sourceRoot: candidateRoot, home, log, npmInstaller });
|
|
2155
2453
|
if (!staged.ok) {
|
|
2156
|
-
log(`✗ staging failed (${staged.code})${staged.detail ? `: ${staged.detail.join('; ')}` : ''}`);
|
|
2454
|
+
log(`✗ staging failed (${staged.code})${staged.detail ? `: ${Array.isArray(staged.detail) ? staged.detail.join('; ') : String(staged.detail)}` : ''}`);
|
|
2157
2455
|
return { ok: false, error: `staging failed (${staged.code})` };
|
|
2158
2456
|
}
|
|
2159
2457
|
log(`✓ candidate payload staged (${manifest.version})`);
|
|
@@ -2179,7 +2477,7 @@ async function npxInstallInner({ home, log, sourceRoot, installer, ensureRuntime
|
|
|
2179
2477
|
|
|
2180
2478
|
log('');
|
|
2181
2479
|
log('Done.');
|
|
2182
|
-
log('
|
|
2480
|
+
log('Managed Windows supervisor convergence follows automatically when supported.');
|
|
2183
2481
|
return { ok: true, version: manifest.version, path: releaseDir };
|
|
2184
2482
|
}
|
|
2185
2483
|
|
|
@@ -2329,15 +2627,23 @@ export async function npxUpdate({
|
|
|
2329
2627
|
ensureRuntime,
|
|
2330
2628
|
npmInstaller,
|
|
2331
2629
|
runner = spawnSync,
|
|
2630
|
+
supervisorConverger,
|
|
2631
|
+
supervisorPendingChecker,
|
|
2632
|
+
supervisorReserver,
|
|
2332
2633
|
} = {}) {
|
|
2333
2634
|
log('DSH Crew updater');
|
|
2334
|
-
const updateLock =
|
|
2635
|
+
const updateLock = await acquireLifecycleUpdateLock({ home, log, supervisorConverger, supervisorPendingChecker });
|
|
2335
2636
|
if (!updateLock.ok) return { ok: false, error: `another update is in progress (${updateLock.code})` };
|
|
2637
|
+
let result;
|
|
2638
|
+
let lockRelease;
|
|
2639
|
+
let reservation = null;
|
|
2336
2640
|
try {
|
|
2337
|
-
|
|
2641
|
+
result = await npxUpdateInner({ home, log, sourceRoot, candidate, spec, installer, ensureRuntime, npmInstaller, runner });
|
|
2642
|
+
({ result, reservation } = await reservePostCommitSupervisor({ home, result, supervisorConverger, supervisorReserver }));
|
|
2338
2643
|
} finally {
|
|
2339
|
-
releaseUpdateLock({ home, nonce: updateLock.nonce ?? null });
|
|
2644
|
+
lockRelease = releaseUpdateLock({ home, nonce: updateLock.nonce ?? null });
|
|
2340
2645
|
}
|
|
2646
|
+
return finishLifecycleAfterUpdateLock({ home, result, lockRelease, log, supervisorConverger, reservation });
|
|
2341
2647
|
}
|
|
2342
2648
|
|
|
2343
2649
|
async function npxUpdateInner({ home, log, sourceRoot, candidate, spec, installer, ensureRuntime, npmInstaller, runner = spawnSync }) {
|
|
@@ -2373,7 +2679,8 @@ async function npxUpdateInner({ home, log, sourceRoot, candidate, spec, installe
|
|
|
2373
2679
|
|
|
2374
2680
|
const health = currentInstallationHealth({ home });
|
|
2375
2681
|
|
|
2376
|
-
if (health.installed && health.healthy && health.pointer.version === manifest.version
|
|
2682
|
+
if (health.installed && health.healthy && health.pointer.version === manifest.version
|
|
2683
|
+
&& samePayloadContent(resolved.sourceRoot, health.pointer.path)) {
|
|
2377
2684
|
log(`- already current (${manifest.version}); repairing registration/integrations idempotently`);
|
|
2378
2685
|
const activated = await activateRelease({ home, releaseDir: health.pointer.path, manifest, log, installer });
|
|
2379
2686
|
if (!activated) return { ok: false, error: 'activation failed' };
|
|
@@ -2402,7 +2709,7 @@ async function npxUpdateInner({ home, log, sourceRoot, candidate, spec, installe
|
|
|
2402
2709
|
|
|
2403
2710
|
const staged = stageCandidatePayload({ sourceRoot: resolved.sourceRoot, home, log, npmInstaller });
|
|
2404
2711
|
if (!staged.ok) {
|
|
2405
|
-
log(`✗ staging failed (${staged.code})${staged.detail ? `: ${staged.detail.join('; ')}` : ''}`);
|
|
2712
|
+
log(`✗ staging failed (${staged.code})${staged.detail ? `: ${Array.isArray(staged.detail) ? staged.detail.join('; ') : String(staged.detail)}` : ''}`);
|
|
2406
2713
|
return { ok: false, error: `staging failed (${staged.code})` };
|
|
2407
2714
|
}
|
|
2408
2715
|
log(`✓ candidate payload staged and validated (${manifest.version})`);
|
|
@@ -2478,8 +2785,8 @@ async function npxUpdateInner({ home, log, sourceRoot, candidate, spec, installe
|
|
|
2478
2785
|
noteLauncherDivergence({ log, home });
|
|
2479
2786
|
log('');
|
|
2480
2787
|
log('Done.');
|
|
2481
|
-
log('
|
|
2482
|
-
return { ok: true, updated: true, version: manifest.version, path:
|
|
2788
|
+
log('Managed Windows supervisor convergence follows automatically when supported.');
|
|
2789
|
+
return { ok: true, updated: true, version: manifest.version, path: committedRelease.path, coordinated: true, dsh_version: candidateDsh };
|
|
2483
2790
|
}
|
|
2484
2791
|
|
|
2485
2792
|
if (!await ensureRuntimeStep({ home, log, ensureRuntime })) return { ok: false, error: 'Crew DSH runtime bootstrap failed' };
|
|
@@ -2501,7 +2808,7 @@ async function npxUpdateInner({ home, log, sourceRoot, candidate, spec, installe
|
|
|
2501
2808
|
noteLauncherDivergence({ log, home });
|
|
2502
2809
|
log('');
|
|
2503
2810
|
log('Done.');
|
|
2504
|
-
log('
|
|
2811
|
+
log('Managed Windows supervisor convergence follows automatically when supported.');
|
|
2505
2812
|
return { ok: true, updated: true, version: manifest.version, path: releaseDir };
|
|
2506
2813
|
} finally {
|
|
2507
2814
|
resolved.cleanup?.();
|
|
@@ -2576,15 +2883,17 @@ export function npxStatus({
|
|
|
2576
2883
|
} catch { dshPlugin = 'unknown'; }
|
|
2577
2884
|
}
|
|
2578
2885
|
|
|
2886
|
+
const managedRoot = pointer?.path ?? runningPackageRoot();
|
|
2579
2887
|
const st = installer.installStatus
|
|
2580
|
-
? installer.installStatus({ home, root:
|
|
2581
|
-
: realInstaller.installStatus({ home, root:
|
|
2582
|
-
const
|
|
2583
|
-
const
|
|
2584
|
-
const
|
|
2888
|
+
? installer.installStatus({ home, root: managedRoot })
|
|
2889
|
+
: realInstaller.installStatus({ home, root: managedRoot });
|
|
2890
|
+
const integrationLabel = (entry) => !entry?.installed ? 'not installed' : entry.ready === true ? 'installed' : 'needs repair';
|
|
2891
|
+
const codex = integrationLabel(st?.codex);
|
|
2892
|
+
const zcode = integrationLabel(st?.zcode);
|
|
2893
|
+
const claude = integrationLabel(st?.claude);
|
|
2585
2894
|
const official = officialWebIntegrationStatus({ home, releaseDir: pointer?.path });
|
|
2586
2895
|
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)';
|
|
2587
|
-
const startupState = installer.windowsStartupStatus?.({ home });
|
|
2896
|
+
const startupState = installer.windowsStartupStatus?.({ home, root: runningPackageRoot() });
|
|
2588
2897
|
const windowsStartup = !startupState?.supported ? 'not supported'
|
|
2589
2898
|
: startupState.ready ? 'installed' : startupState.installed ? 'needs repair' : 'not installed';
|
|
2590
2899
|
|