Skip to content

Commit 3bc70bf

Browse files
committed
fix: keep daemon lifecycle outside the CLI import closure
1 parent 845dddb commit 3bc70bf

2 files changed

Lines changed: 26 additions & 61 deletions

File tree

‎src/daemon-client/daemon-client-lifecycle.ts‎

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,7 @@ function surfaceUnrecoveredRepairCommitFailure(
523523
* `resume.allowed` (plan-resumability): a held divergence with `allowed: false`
524524
* still holds the session so the agent can inspect and `close` cleanly.
525525
*/
526-
export function isHeldRepairDivergence(response: DaemonResponse | undefined): boolean {
526+
function isHeldRepairDivergence(response: DaemonResponse | undefined): boolean {
527527
if (!response || response.ok) return false;
528528
if (response.error.code !== 'REPLAY_DIVERGENCE') return false;
529529
const resume = readReplayDivergenceResume(response.error.details?.divergence);
@@ -538,7 +538,7 @@ export function isHeldRepairDivergence(response: DaemonResponse | undefined): bo
538538
* selector-miss's own guidance) so the agent's next command knows to target
539539
* the SAME daemon instead of resolving to the default one.
540540
*/
541-
export function attachRepairSessionAddressHint(
541+
function attachRepairSessionAddressHint(
542542
response: Extract<DaemonResponse, { ok: false }>,
543543
stateDir: string,
544544
): Extract<DaemonResponse, { ok: false }> {
@@ -570,7 +570,7 @@ function isOneShotReplayCommand(command: string | undefined): boolean {
570570
* anyway, but the explicit command check keeps that carve-out a decision
571571
* rather than an accident of the response shape.
572572
*/
573-
export function isActiveReplaySessionResponse(
573+
function isActiveReplaySessionResponse(
574574
req: Omit<DaemonRequest, 'token'>,
575575
response: DaemonResponse | undefined,
576576
): boolean {
@@ -756,3 +756,21 @@ function isLoopbackHostname(hostname: string): boolean {
756756
if (net.isIPv6(normalized)) return LOOPBACK_BLOCK_LIST.check(normalized, 'ipv6');
757757
return false;
758758
}
759+
760+
export function attachSessionAddressHints(
761+
response: DaemonResponse,
762+
req: Omit<DaemonRequest, 'token'>,
763+
settings: DaemonClientSettings,
764+
): DaemonResponse {
765+
if (!response.ok) {
766+
return settings.ownedStateDir && isHeldRepairDivergence(response)
767+
? attachRepairSessionAddressHint(response, settings.paths.baseDir)
768+
: response;
769+
}
770+
return isActiveReplaySessionResponse(req, response)
771+
? attachActiveSessionAddressHint(
772+
response,
773+
settings.ownedStateDir ? settings.paths.baseDir : undefined,
774+
)
775+
: response;
776+
}

‎src/daemon-client/daemon-client.ts‎

Lines changed: 5 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,7 @@ import {
1717
prepareRemoteRequestArtifacts,
1818
type PreparedRemoteRequest,
1919
} from '../remote/daemon-artifacts.ts';
20-
import {
21-
attachActiveSessionAddressHint,
22-
attachRepairSessionAddressHint,
23-
cleanupDaemonAfterRequest,
24-
ensureDaemon,
25-
isActiveReplaySessionResponse,
26-
isHeldRepairDivergence,
27-
resolveClientSettings,
28-
type DaemonClientSettings,
29-
type EnsuredDaemon,
30-
} from './daemon-client-lifecycle.ts';
20+
import type { DaemonClientSettings, EnsuredDaemon } from './daemon-client-lifecycle.ts';
3121
import { sendRequest } from './daemon-client-transport.ts';
3222
import { isRemoteDaemon, type DaemonInfo } from './daemon-client-metadata.ts';
3323
import { leaseScopeFromRequest } from '@agent-device/contracts/lease-scope';
@@ -42,6 +32,8 @@ export async function sendToDaemon(
4232
req: Omit<DaemonRequest, 'token'>,
4333
options: DaemonTransportOptions = {},
4434
): Promise<DaemonResponse> {
35+
const { resolveClientSettings, ensureDaemon, attachSessionAddressHints } =
36+
await import('./daemon-client-lifecycle.ts');
4537
const requestId = req.meta?.requestId ?? createRequestId();
4638
const debug = Boolean(req.meta?.debug || req.flags?.verbose);
4739
// A few internal callers build DaemonRequest directly instead of using the
@@ -107,11 +99,7 @@ export async function sendToDaemon(
10799
),
108100
{ requestId, command: req.command },
109101
);
110-
return withActiveSessionAddressHint(
111-
withRepairSessionAddressHintIfOwned(response, settings),
112-
requestWithoutAuthFlag,
113-
settings,
114-
);
102+
return attachSessionAddressHints(response, requestWithoutAuthFlag, settings);
115103
},
116104
);
117105
}
@@ -235,6 +223,7 @@ async function performDaemonRequestWithCleanup(
235223
requestFailed = true;
236224
requestError = error;
237225
}
226+
const { cleanupDaemonAfterRequest } = await import('./daemon-client-lifecycle.ts');
238227
const finalResponse = await cleanupDaemonAfterRequest(req, daemon, settings, response);
239228
if (requestFailed) throw requestError;
240229
if (!finalResponse) {
@@ -246,48 +235,6 @@ async function performDaemonRequestWithCleanup(
246235
return finalResponse;
247236
}
248237

249-
/**
250-
* ADR 0012 decision 6 (Fix 1): the owned ephemeral state dir this daemon was
251-
* started at is otherwise unaddressable by a later invocation — hint it here,
252-
* only when the daemon is actually being kept alive for it
253-
* (`settings.ownedStateDir` means `daemon.startedByClient` is also true).
254-
*/
255-
function withRepairSessionAddressHintIfOwned(
256-
response: DaemonResponse,
257-
settings: DaemonClientSettings,
258-
): DaemonResponse {
259-
if (response.ok || !settings.ownedStateDir || !isHeldRepairDivergence(response)) {
260-
return response;
261-
}
262-
return attachRepairSessionAddressHint(response, settings.paths.baseDir);
263-
}
264-
265-
/**
266-
* ADR 0016 counterpart to `withRepairSessionAddressHintIfOwned` — but unlike
267-
* that one, NOT gated on `settings.ownedStateDir`. An owned ephemeral state
268-
* dir is unaddressable by a later invocation either way, so it's included
269-
* when owned; an explicit `--state-dir`/`AGENT_DEVICE_STATE_DIR` caller
270-
* already knows their own dir, so it's omitted then. But the session's own
271-
* name is cwd-qualified and, per #1394, `session list` cannot rediscover it
272-
* either — so `--session` is still worth hinting even at an explicit state
273-
* dir, which is why this runs for every active-session response regardless
274-
* of `ownedStateDir` (`attachActiveSessionAddressHint` itself decides what,
275-
* if anything, is worth attaching).
276-
*/
277-
function withActiveSessionAddressHint(
278-
response: DaemonResponse,
279-
req: Omit<DaemonRequest, 'token'>,
280-
settings: DaemonClientSettings,
281-
): DaemonResponse {
282-
if (!response.ok || !isActiveReplaySessionResponse(req, response)) {
283-
return response;
284-
}
285-
return attachActiveSessionAddressHint(
286-
response,
287-
settings.ownedStateDir ? settings.paths.baseDir : undefined,
288-
);
289-
}
290-
291238
function writeInstallInProgressNotice(command: string | undefined): void {
292239
if (!isInstallLikeCommand(command) || process.stderr.isTTY !== true || process.env.CI) return;
293240
process.stderr.write(

0 commit comments

Comments
 (0)