@adhdev/daemon-core 1.0.18-rc.20 → 1.0.18-rc.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/dist/cli-adapters/provider-cli-shared.d.ts +0 -33
- package/dist/commands/cli-manager.d.ts +0 -9
- package/dist/commands/upgrade-helper.d.ts +0 -1
- package/dist/commands/windows-atomic-upgrade.d.ts +0 -5
- package/dist/config/mesh-json-config.d.ts +0 -62
- package/dist/index.d.ts +2 -4
- package/dist/index.js +3998 -6112
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3855 -5966
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/coordinator-prompt.d.ts +0 -76
- package/dist/mesh/mesh-active-work.d.ts +1 -1
- package/dist/mesh/mesh-ledger.d.ts +1 -28
- package/dist/mesh/mesh-node-identity.d.ts +0 -23
- package/dist/mesh/mesh-refine-gates.d.ts +0 -89
- package/dist/mesh/mesh-remote-event-pull.d.ts +0 -3
- package/dist/mesh/mesh-runtime-store.d.ts +0 -11
- package/dist/mesh/mesh-work-queue.d.ts +1 -24
- package/dist/providers/chat-message-normalization.d.ts +0 -22
- package/dist/providers/cli-provider-instance-types.d.ts +0 -4
- package/dist/providers/cli-provider-instance.d.ts +0 -78
- package/dist/providers/contracts.d.ts +0 -17
- package/dist/providers/provider-schema.d.ts +0 -1
- package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +0 -14
- package/dist/providers/types/interactive-prompt.d.ts +0 -18
- package/dist/repo-mesh-types.d.ts +6 -38
- package/dist/shared-types.d.ts +2 -4
- package/package.json +3 -3
- package/src/boot/daemon-lifecycle.ts +0 -10
- package/src/cli-adapters/cli-state-engine.ts +1 -17
- package/src/cli-adapters/provider-cli-adapter.ts +6 -2
- package/src/cli-adapters/provider-cli-shared.ts +0 -48
- package/src/commands/cli-manager.ts +8 -72
- package/src/commands/high-family/mesh-coordinator-launch.ts +2 -17
- package/src/commands/high-family/mesh-events.ts +2 -13
- package/src/commands/med-family/mesh-crud.ts +0 -155
- package/src/commands/med-family/mesh-queue.ts +1 -74
- package/src/commands/router-refine.ts +4 -71
- package/src/commands/router.ts +0 -8
- package/src/commands/upgrade-helper.ts +15 -78
- package/src/commands/windows-atomic-upgrade.ts +40 -194
- package/src/config/mesh-json-config.ts +0 -111
- package/src/index.ts +1 -21
- package/src/mesh/coordinator-prompt.ts +11 -258
- package/src/mesh/mesh-active-work.ts +1 -11
- package/src/mesh/mesh-completion-synthesis.ts +1 -12
- package/src/mesh/mesh-event-classify.ts +0 -19
- package/src/mesh/mesh-event-forwarding.ts +6 -64
- package/src/mesh/mesh-events-utils.ts +0 -42
- package/src/mesh/mesh-ledger.ts +0 -77
- package/src/mesh/mesh-node-identity.ts +0 -49
- package/src/mesh/mesh-queue-assignment.ts +11 -210
- package/src/mesh/mesh-reconcile-loop.ts +3 -306
- package/src/mesh/mesh-refine-gates.ts +0 -300
- package/src/mesh/mesh-remote-event-pull.ts +47 -68
- package/src/mesh/mesh-runtime-store.ts +0 -21
- package/src/mesh/mesh-work-queue.ts +2 -85
- package/src/providers/chat-message-normalization.ts +0 -53
- package/src/providers/cli-provider-instance-types.ts +0 -53
- package/src/providers/cli-provider-instance.ts +15 -497
- package/src/providers/contracts.ts +1 -25
- package/src/providers/provider-schema.ts +0 -83
- package/src/providers/sdk/v1/builders/cli/detect-status.ts +0 -23
- package/src/providers/sdk/v1/builders/cli/parse-approval.ts +0 -20
- package/src/providers/sdk/v1/schemas/cli/provider.schema.json +0 -44
- package/src/providers/types/interactive-prompt.ts +0 -77
- package/src/repo-mesh-types.ts +12 -112
- package/src/shared-types.ts +1 -9
- package/src/status/reporter.ts +0 -1
- package/src/status/snapshot.ts +0 -2
- package/dist/mesh/mesh-disk-retention.d.ts +0 -105
- package/dist/providers/auto-approve-modes.d.ts +0 -14
- package/src/mesh/mesh-disk-retention.ts +0 -370
- package/src/providers/auto-approve-modes.ts +0 -103
|
@@ -9,67 +9,6 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { readStringValue } from '../router.js';
|
|
11
11
|
import type { MedFamilyContext, MedFamilyHandler } from './types.js';
|
|
12
|
-
import { LOG } from '../../logging/logger.js';
|
|
13
|
-
import type { CancelledTaskAssignment } from '../../mesh/mesh-work-queue.js';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* CANCEL-STICKY-TERMINAL (authoritative cancel): stop the worker a just-cancelled task was
|
|
17
|
-
* bound to. Mirrors the transport-aware stop mesh-event-forwarding's stopStaleMeshWorker
|
|
18
|
-
* performs for reclaimed workers, but runs from the command layer using ctx.deps (which the
|
|
19
|
-
* pure queue-store module lacks): local adapter → stop_cli directly; otherwise resolve the
|
|
20
|
-
* worker node's daemon id from mesh config and dispatch stop_cli over P2P/relay. Fully
|
|
21
|
-
* best-effort — a failed stop only loses the belt-and-suspenders; the cancelled row is already
|
|
22
|
-
* terminal and cannot be resurrected (updateTaskStatus terminal guard).
|
|
23
|
-
*/
|
|
24
|
-
async function stopCancelledTaskWorker(
|
|
25
|
-
ctx: MedFamilyContext,
|
|
26
|
-
meshId: string,
|
|
27
|
-
prior: CancelledTaskAssignment,
|
|
28
|
-
): Promise<void> {
|
|
29
|
-
const { sessionId, nodeId, providerType } = prior;
|
|
30
|
-
const stopArgs: Record<string, unknown> = {
|
|
31
|
-
targetSessionId: sessionId,
|
|
32
|
-
...(providerType ? { cliType: providerType } : {}),
|
|
33
|
-
mode: 'hard',
|
|
34
|
-
reason: 'mesh_task_cancelled',
|
|
35
|
-
};
|
|
36
|
-
try {
|
|
37
|
-
const cliManager = ctx.deps.cliManager as any;
|
|
38
|
-
const isLocal = cliManager?.adapters?.has?.(sessionId) === true;
|
|
39
|
-
if (isLocal) {
|
|
40
|
-
if (!stopArgs.cliType) {
|
|
41
|
-
const localType = cliManager?.adapters?.get?.(sessionId)?.cliType;
|
|
42
|
-
if (localType) stopArgs.cliType = localType;
|
|
43
|
-
}
|
|
44
|
-
await Promise.resolve(cliManager?.handleCliCommand?.('stop_cli', stopArgs))
|
|
45
|
-
.catch((e: any) => LOG.warn('MeshQueue', `Local stop of cancelled worker ${sessionId} failed: ${e?.message || e}`));
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
// Remote: resolve the worker node's daemon id from mesh config, then dispatch stop_cli.
|
|
49
|
-
const dispatch = ctx.deps.dispatchMeshCommand;
|
|
50
|
-
let daemonId: string | undefined;
|
|
51
|
-
if (nodeId) {
|
|
52
|
-
try {
|
|
53
|
-
const [{ getMesh }, { meshNodeIdMatches }, { readMeshNodeDaemonId }] = await Promise.all([
|
|
54
|
-
import('../../config/mesh-config.js'),
|
|
55
|
-
import('@adhdev/mesh-shared'),
|
|
56
|
-
import('../../mesh/mesh-node-identity.js'),
|
|
57
|
-
]);
|
|
58
|
-
const mesh = getMesh(meshId) as { nodes?: any[] } | undefined;
|
|
59
|
-
const node = mesh?.nodes?.find((n: any) => meshNodeIdMatches(n, nodeId));
|
|
60
|
-
daemonId = node ? readMeshNodeDaemonId(node) || undefined : undefined;
|
|
61
|
-
} catch { /* best-effort resolution */ }
|
|
62
|
-
}
|
|
63
|
-
if (daemonId && dispatch) {
|
|
64
|
-
await Promise.resolve(dispatch(daemonId, 'stop_cli', stopArgs))
|
|
65
|
-
.catch((e: any) => LOG.warn('MeshQueue', `Remote stop of cancelled worker ${sessionId} on daemon ${daemonId} failed: ${e?.message || e}`));
|
|
66
|
-
} else {
|
|
67
|
-
LOG.warn('MeshQueue', `Cannot stop cancelled worker ${sessionId}: no local adapter and no resolvable remote daemon id (node ${nodeId ?? '?'}). Row is already terminal; if the worker completes it strand-fails harmlessly.`);
|
|
68
|
-
}
|
|
69
|
-
} catch (e: any) {
|
|
70
|
-
LOG.warn('MeshQueue', `stopCancelledTaskWorker error for ${sessionId}: ${e?.message || e}`);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
12
|
|
|
74
13
|
export const meshQueueHandlers: Record<string, MedFamilyHandler> = {
|
|
75
14
|
get_mesh_queue: async (_ctx: MedFamilyContext, args: any) => {
|
|
@@ -111,22 +50,10 @@ export const meshQueueHandlers: Record<string, MedFamilyHandler> = {
|
|
|
111
50
|
const ownerFailure = await ctx.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'queue cancellation');
|
|
112
51
|
if (ownerFailure) return ownerFailure;
|
|
113
52
|
try {
|
|
114
|
-
const { cancelTask
|
|
53
|
+
const { cancelTask } = await import('../../mesh/mesh-work-queue.js');
|
|
115
54
|
const reason = typeof args?.reason === 'string' ? args.reason : undefined;
|
|
116
55
|
const task = cancelTask(meshId, taskId, { reason });
|
|
117
56
|
if (!task) return { success: false, error: `Queue task '${taskId}' not found` };
|
|
118
|
-
// CANCEL-STICKY-TERMINAL (authoritative cancel): stop the worker that was bound to the
|
|
119
|
-
// now-cancelled task. cancelTask already cleared the queue's assignment fields (so the
|
|
120
|
-
// reclaim watchdog no longer sees it as a live assignment), but a still-running worker
|
|
121
|
-
// keeps emitting delivery/turn signals that re-ignite reclaim. cancelTask runs in the
|
|
122
|
-
// pure queue-store module (no DaemonComponents), so the transport-aware stop is done
|
|
123
|
-
// here where ctx.deps holds cliManager / dispatchMeshCommand. Best-effort: a failed
|
|
124
|
-
// stop only loses the belt-and-suspenders — the row is already terminal and un-revivable
|
|
125
|
-
// thanks to the updateTaskStatus terminal guard.
|
|
126
|
-
const prior = takeCancelledTaskAssignment(meshId, taskId);
|
|
127
|
-
if (prior?.sessionId) {
|
|
128
|
-
void stopCancelledTaskWorker(ctx, meshId, prior);
|
|
129
|
-
}
|
|
130
57
|
return { success: true, task };
|
|
131
58
|
} catch (e: any) {
|
|
132
59
|
return { success: false, error: e.message };
|
|
@@ -36,10 +36,8 @@ import {
|
|
|
36
36
|
RefineExecFileAsync,
|
|
37
37
|
RefineStageOutcome,
|
|
38
38
|
classifyPatchEquivalenceFailure,
|
|
39
|
-
convergeDivergedSubmoduleGitlinks,
|
|
40
39
|
recordMeshRefineStage,
|
|
41
40
|
resolveRefineryAutoPublishSubmoduleMainCommits,
|
|
42
|
-
rootRebaseResolvingGitlinks,
|
|
43
41
|
runMeshRefineEffectiveDiffGate,
|
|
44
42
|
runMeshRefinePatchEquivalenceGate,
|
|
45
43
|
runMeshRefineSubmoduleReachabilityGate,
|
|
@@ -541,9 +539,6 @@ async function computeBranchBaseDivergence(
|
|
|
541
539
|
export async function refineSyncBaseStage(self: DaemonCommandRouter, ctx: RefineContext): Promise<RefineStageOutcome> {
|
|
542
540
|
const { repoRoot, baseHead, node, branch, baseBranch, refineStages, execFileAsync } = ctx;
|
|
543
541
|
let branchHead = ctx.branchHead;
|
|
544
|
-
// Converged submodule gitlink resolutions (path → rebased commit) from STEP 1;
|
|
545
|
-
// consumed by the gitlink-aware root rebase (STEP 2) to resolve gitlink conflicts.
|
|
546
|
-
let gitlinkResolutions: Array<{ path: string; rebasedCommit: string }> = [];
|
|
547
542
|
const syncStarted = Date.now();
|
|
548
543
|
const divergence = await computeBranchBaseDivergence(execFileAsync, node.workspace, baseHead, branchHead);
|
|
549
544
|
|
|
@@ -576,87 +571,25 @@ export async function refineSyncBaseStage(self: DaemonCommandRouter, ctx: Refine
|
|
|
576
571
|
const preRebasePe = await runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead);
|
|
577
572
|
const alreadyMerged = !preRebasePe.actualPatchId && !!preRebasePe.expectedPatchId;
|
|
578
573
|
const submoduleConflict = preRebasePe.actionableHint?.kind === 'submodule_conflict';
|
|
579
|
-
if (alreadyMerged) {
|
|
574
|
+
if (alreadyMerged || submoduleConflict) {
|
|
580
575
|
recordMeshRefineStage(refineStages, 'sync_base', 'passed', syncStarted, {
|
|
581
576
|
ahead: divergence.ahead,
|
|
582
577
|
behind: divergence.behind,
|
|
583
578
|
rebased: false,
|
|
584
|
-
reason: 'already_merged_via_other_path_skip_rebase',
|
|
579
|
+
reason: alreadyMerged ? 'already_merged_via_other_path_skip_rebase' : 'submodule_conflict_defer_to_patch_equivalence',
|
|
585
580
|
});
|
|
586
581
|
return { kind: 'continue', ctx };
|
|
587
582
|
}
|
|
588
|
-
if (submoduleConflict) {
|
|
589
|
-
// DS3: a "submodule conflict" here means base and branch advanced the
|
|
590
|
-
// SAME submodule to DIVERGED sibling commits (neither an ancestor of the
|
|
591
|
-
// other), so the gitlink stays in the diff and patch-equivalence fails.
|
|
592
|
-
// Attempt to auto-converge it (STEP 1): rebase the branch-side submodule
|
|
593
|
-
// commit onto the base-side commit INSIDE the worktree submodule, so the
|
|
594
|
-
// base-side commit becomes a strict ancestor of the rebased tip. The root
|
|
595
|
-
// rebase below (STEP 2) then resolves the gitlink conflict to that rebased
|
|
596
|
-
// commit. Together this automates the documented manual strict-ff bypass
|
|
597
|
-
// and keeps the landed oss history linear. On any real submodule content
|
|
598
|
-
// conflict it backs out cleanly → we FALL BACK to the historical
|
|
599
|
-
// defer→patch_equivalence path below.
|
|
600
|
-
const converge = convergeDivergedSubmoduleGitlinks(node.workspace, repoRoot, baseHead, branchHead);
|
|
601
|
-
if (converge.converged) {
|
|
602
|
-
gitlinkResolutions = converge.resolutions;
|
|
603
|
-
recordMeshRefineStage(refineStages, 'submodule_gitlink_converge', 'passed', syncStarted, {
|
|
604
|
-
reason: 'submodule_diverged_auto_rebased',
|
|
605
|
-
gitlinks: converge.gitlinks,
|
|
606
|
-
});
|
|
607
|
-
LOG.info('Mesh', `[Refinery] Auto-converged diverged submodule gitlink(s) onto base for node ${node.id}: `
|
|
608
|
-
+ converge.resolutions.map(r => `${r.path}→${r.rebasedCommit.slice(0, 12)}`).join(', '));
|
|
609
|
-
// Fall through (do NOT return) → the gitlink-aware root rebase below runs.
|
|
610
|
-
} else {
|
|
611
|
-
// Fail-safe: convergence declined (conflict / unreachable / not a real
|
|
612
|
-
// divergence) → preserve the historical defer→blocked_review behavior.
|
|
613
|
-
recordMeshRefineStage(refineStages, 'submodule_gitlink_converge', 'skipped', syncStarted, {
|
|
614
|
-
reason: 'submodule_conflict_defer_to_patch_equivalence',
|
|
615
|
-
convergeReason: converge.reason,
|
|
616
|
-
gitlinks: converge.gitlinks,
|
|
617
|
-
});
|
|
618
|
-
recordMeshRefineStage(refineStages, 'sync_base', 'passed', syncStarted, {
|
|
619
|
-
ahead: divergence.ahead,
|
|
620
|
-
behind: divergence.behind,
|
|
621
|
-
rebased: false,
|
|
622
|
-
reason: 'submodule_conflict_defer_to_patch_equivalence',
|
|
623
|
-
});
|
|
624
|
-
return { kind: 'continue', ctx };
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
583
|
} catch { /* fail-open: on gate error, fall through to the rebase */ }
|
|
628
584
|
|
|
629
585
|
// behind>0: strictly-behind OR diverged. Rebase the branch onto the pinned
|
|
630
586
|
// baseHead. A conflict aborts and terminates blocked_review (retryable=false —
|
|
631
587
|
// a real content conflict needs human resolution, not a base-movement retry).
|
|
632
|
-
//
|
|
633
|
-
// When STEP 1 converged a diverged submodule gitlink, use the gitlink-aware
|
|
634
|
-
// root rebase (STEP 2): git's recursive merge refuses to auto-merge the still-
|
|
635
|
-
// diverged intermediate gitlink, so we drive the rebase and resolve each
|
|
636
|
-
// submodule-gitlink conflict to the converged commit. A non-gitlink conflict
|
|
637
|
-
// aborts and falls through to the same blocked_review handling as a plain
|
|
638
|
-
// rebase conflict below (via the thrown gitlinkRebaseError).
|
|
639
588
|
const rebaseStarted = Date.now();
|
|
640
589
|
try {
|
|
641
|
-
|
|
642
|
-
const gitlinkRebase = rootRebaseResolvingGitlinks(node.workspace, baseHead, gitlinkResolutions);
|
|
643
|
-
if (!gitlinkRebase.ok) {
|
|
644
|
-
// Surface as a rebase failure so the shared blocked_review handling
|
|
645
|
-
// (submodule-hint recovery included) runs — nothing was left mid-rebase
|
|
646
|
-
// (rootRebaseResolvingGitlinks aborts on failure).
|
|
647
|
-
const err: any = new Error(`gitlink-aware rebase aborted: ${gitlinkRebase.reason || 'unknown'}`);
|
|
648
|
-
err.gitlinkRebaseReason = gitlinkRebase.reason;
|
|
649
|
-
err.gitlinkRebaseConflicts = gitlinkRebase.conflictPaths;
|
|
650
|
-
err.alreadyAborted = true;
|
|
651
|
-
throw err;
|
|
652
|
-
}
|
|
653
|
-
} else {
|
|
654
|
-
execFileSync('git', ['rebase', baseHead], { cwd: node.workspace, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
655
|
-
}
|
|
590
|
+
execFileSync('git', ['rebase', baseHead], { cwd: node.workspace, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
656
591
|
} catch (rebaseErr: any) {
|
|
657
|
-
|
|
658
|
-
try { execFileSync('git', ['rebase', '--abort'], { cwd: node.workspace, stdio: 'ignore' }); } catch { /* ignore */ }
|
|
659
|
-
}
|
|
592
|
+
try { execFileSync('git', ['rebase', '--abort'], { cwd: node.workspace, stdio: 'ignore' }); } catch { /* ignore */ }
|
|
660
593
|
// A rebase conflict on a submodule/gitlink divergence is a SPECIAL case the
|
|
661
594
|
// patch-equivalence gate describes with a rich actionable hint (which
|
|
662
595
|
// submodule, base vs branch commit, how to resolve). Run that gate against
|
package/src/commands/router.ts
CHANGED
|
@@ -227,14 +227,6 @@ const MESH_FORWARDABLE_SESSION_COMMANDS = new Set([
|
|
|
227
227
|
// MUTATES the worker PTY, so forwarding to the real owner (not a wrong local session) is
|
|
228
228
|
// doubly important. The daemon re-enforces the destructive-key confirm gate after the forward.
|
|
229
229
|
'send_keys',
|
|
230
|
-
// interactive_prompt_response (mesh_answer_question, mission f1d25e11): the coordinator
|
|
231
|
-
// answers a REMOTE worker's AskUserQuestion (waiting_choice). The answer must reach the
|
|
232
|
-
// OWNING worker session's live instance — its activeInteractivePrompt (the authoritative
|
|
233
|
-
// prompt the labels/indexes resolve against) and its adapter.setInteractivePromptResponse
|
|
234
|
-
// live only there. Without forwarding, the coordinator's local high-family handler returns
|
|
235
|
-
// 'No running instance for session …' and the question is never answered — the exact
|
|
236
|
-
// remote-worker forwarding gap of mission 6938892f, now closed for questions too.
|
|
237
|
-
'interactive_prompt_response',
|
|
238
230
|
]);
|
|
239
231
|
|
|
240
232
|
function normalizeCommandSource(source: string): CommandLogEntry['source'] {
|
|
@@ -144,68 +144,19 @@ function resolveInstallPrefixFromPackageRoot(packageRoot: string, packageName: s
|
|
|
144
144
|
return maybeLibDir;
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
// True when `prefix` is the bin dir of a portable Node 22 the installer manages
|
|
148
|
-
// under ~/.adhdev/tools/node22/<node-vX>/. A `npm i -g adhdev` run while that
|
|
149
|
-
// portable node is the active `node` installs adhdev into node's own default
|
|
150
|
-
// global prefix (= that dir) — a "legacy node22-prefix" install that lives at
|
|
151
|
-
// the FRONT of PATH (Enable-NodePath prepends node22) and shadows the canonical
|
|
152
|
-
// dispatcher shims in ~/.adhdev/npm-global. Because self-upgrade reuses the
|
|
153
|
-
// running prefix, that install then re-installs into the same node22 dir forever
|
|
154
|
-
// and never converts to the dispatcher. Detecting it lets us force convergence.
|
|
155
|
-
function isPortableNode22Prefix(prefix: string | null, homeDir: string): boolean {
|
|
156
|
-
if (!prefix) return false;
|
|
157
|
-
const portableRoot = path.join(homeDir, '.adhdev', 'tools', 'node22');
|
|
158
|
-
const normalizedPrefix = path.resolve(prefix).replace(/[\\/]+$/, '').toLowerCase();
|
|
159
|
-
const normalizedRoot = path.resolve(portableRoot).replace(/[\\/]+$/, '').toLowerCase();
|
|
160
|
-
return normalizedPrefix === normalizedRoot || normalizedPrefix.startsWith(`${normalizedRoot}${path.sep.toLowerCase()}`);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Redirect a legacy node22-prefix install to the canonical dispatcher install
|
|
164
|
-
// root so resolveWindowsInstallerLayout accepts it and the atomic-upgrade
|
|
165
|
-
// publishes the ~/.adhdev/npm-global pointer + shims. Prefer the version the
|
|
166
|
-
// dispatcher pointer already names (so we sit on the real active dispatcher
|
|
167
|
-
// prefix); otherwise synthesize a stable migration sentinel under npm-installs.
|
|
168
|
-
// resolveWindowsInstallerLayout only requires a `npm-installs/version-*` path —
|
|
169
|
-
// performWindowsAtomicUpgrade stages a fresh version- prefix of its own and uses
|
|
170
|
-
// this only as the "old prefix" to stop/clean, so a non-existent path is a no-op.
|
|
171
|
-
function canonicalDispatcherInstallPrefix(homeDir: string): string {
|
|
172
|
-
const installRoot = path.join(homeDir, '.adhdev', 'npm-installs');
|
|
173
|
-
const pointerPath = path.join(homeDir, '.adhdev', 'npm-global', '.adhdev-current');
|
|
174
|
-
try {
|
|
175
|
-
const activeVersion = fs.readFileSync(pointerPath, 'utf8').trim();
|
|
176
|
-
if (activeVersion.startsWith('version-')) return path.join(installRoot, activeVersion);
|
|
177
|
-
} catch {
|
|
178
|
-
// No dispatcher pointer yet (first migration off the legacy layout).
|
|
179
|
-
}
|
|
180
|
-
return path.join(installRoot, 'version-legacy-migrate');
|
|
181
|
-
}
|
|
182
|
-
|
|
183
147
|
export function resolveCurrentGlobalInstallSurface(options: {
|
|
184
148
|
packageName: string;
|
|
185
149
|
currentCliPath?: string;
|
|
186
150
|
nodeExecutable?: string;
|
|
187
151
|
platform?: NodeJS.Platform;
|
|
188
|
-
homeDir?: string;
|
|
189
152
|
}): CurrentGlobalInstallSurface {
|
|
190
153
|
const packageRoot = findCurrentPackageRoot(options.currentCliPath || process.argv[1], options.packageName);
|
|
191
154
|
const npmInvocation = resolveSiblingNpmInvocation(options.nodeExecutable || process.execPath, options.platform);
|
|
192
|
-
const platform = options.platform || process.platform;
|
|
193
|
-
const homeDir = options.homeDir || os.homedir();
|
|
194
|
-
let installPrefix = packageRoot ? resolveInstallPrefixFromPackageRoot(packageRoot, options.packageName) : null;
|
|
195
|
-
// FIX C: on Windows, never let a self-upgrade perpetuate the legacy
|
|
196
|
-
// node22-prefix install. If the running adhdev lives under ~/.adhdev/tools/
|
|
197
|
-
// node22, force the install onto the canonical dispatcher prefix so the update
|
|
198
|
-
// converges to the ~/.adhdev/npm-global pointer + shims. Scoped to win32 AND a
|
|
199
|
-
// tools/node22 prefix so npm-linked dev / standalone / real dispatcher installs
|
|
200
|
-
// are untouched.
|
|
201
|
-
if (platform === 'win32' && isPortableNode22Prefix(installPrefix, homeDir)) {
|
|
202
|
-
installPrefix = canonicalDispatcherInstallPrefix(homeDir);
|
|
203
|
-
}
|
|
204
155
|
return {
|
|
205
156
|
npmExecutable: npmInvocation.executable,
|
|
206
157
|
npmArgsPrefix: npmInvocation.argsPrefix,
|
|
207
158
|
packageRoot,
|
|
208
|
-
installPrefix,
|
|
159
|
+
installPrefix: packageRoot ? resolveInstallPrefixFromPackageRoot(packageRoot, options.packageName) : null,
|
|
209
160
|
execOptions: npmInvocation.execOptions,
|
|
210
161
|
};
|
|
211
162
|
}
|
|
@@ -609,36 +560,22 @@ async function runDaemonUpgradeHelper(payload: DaemonUpgradeHelperPayload): Prom
|
|
|
609
560
|
);
|
|
610
561
|
}
|
|
611
562
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
563
|
+
await performWindowsAtomicUpgrade({
|
|
564
|
+
layout: windowsInstallerLayout,
|
|
565
|
+
packageName: payload.packageName,
|
|
566
|
+
targetVersion: payload.targetVersion,
|
|
567
|
+
portableNode,
|
|
568
|
+
excludePids: upgradePids,
|
|
569
|
+
hooks: createDefaultWindowsAtomicHooks({
|
|
615
570
|
packageName: payload.packageName,
|
|
616
571
|
targetVersion: payload.targetVersion,
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
cwd: payload.cwd || process.cwd(),
|
|
625
|
-
env: Object.fromEntries(Object.entries(process.env).filter(([key]) => key !== UPGRADE_HELPER_ENV)),
|
|
626
|
-
log: appendUpgradeLog,
|
|
627
|
-
}),
|
|
628
|
-
});
|
|
629
|
-
} catch (error: any) {
|
|
630
|
-
// performWindowsAtomicUpgrade already rolled the pointer/shims back to the
|
|
631
|
-
// prior version before rethrowing. Without a durable notice that rollback
|
|
632
|
-
// was silent — the daemon simply kept running the old version (the rc.6
|
|
633
|
-
// stuck-upgrade defect). Leave an actionable last-error file naming the
|
|
634
|
-
// target that failed its health/version gate.
|
|
635
|
-
emitUpgradeFailureNotice([
|
|
636
|
-
`adhdev ${payload.packageName}@${payload.targetVersion} upgrade failed and was rolled back: ${error?.message || String(error)}`,
|
|
637
|
-
`Previous version preserved (active prefix: ${windowsInstallerLayout.activePrefix}).`,
|
|
638
|
-
'See daemon-upgrade.log for the full install/health trace. The next daemon start will retry.',
|
|
639
|
-
]);
|
|
640
|
-
throw error;
|
|
641
|
-
}
|
|
572
|
+
npmCliPath,
|
|
573
|
+
restartArgv,
|
|
574
|
+
cwd: payload.cwd || process.cwd(),
|
|
575
|
+
env: Object.fromEntries(Object.entries(process.env).filter(([key]) => key !== UPGRADE_HELPER_ENV)),
|
|
576
|
+
log: appendUpgradeLog,
|
|
577
|
+
}),
|
|
578
|
+
});
|
|
642
579
|
try { fs.unlinkSync(getUpgradeFailureNoticePath()); } catch { /* no previous failure notice */ }
|
|
643
580
|
appendUpgradeLog('Installer-managed Windows atomic upgrade completed');
|
|
644
581
|
return;
|