From c6429dc297979d065684079e96c09400d880833d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Mon, 28 Sep 2026 14:14:04 +0200 Subject: [PATCH 1/2] refactor(move): relocate replay resume and failure responses --- .../__tests__/session-replay-resume.test.ts | 172 ++++++++++++++++++ ...on-replay-runtime-failure-response.test.ts | 48 +++++ .../src/daemon-port}/session-replay-resume.ts | 0 ...session-replay-runtime-failure-response.ts | 0 .../__tests__/session-replay-resume.test.ts | 166 +---------------- ...on-replay-runtime-failure-response.test.ts | 46 ----- .../internal/session-replay-divergence.ts | 2 +- .../session-replay-maestro-failure.ts | 2 +- .../session-replay-runtime-failure.ts | 2 +- .../session-replay-target-verification.ts | 4 +- 10 files changed, 226 insertions(+), 216 deletions(-) create mode 100644 packages/replay-port/src/daemon-port/__tests__/session-replay-resume.test.ts create mode 100644 packages/replay-port/src/daemon-port/__tests__/session-replay-runtime-failure-response.test.ts rename {src/daemon/replay/internal => packages/replay-port/src/daemon-port}/session-replay-resume.ts (100%) rename {src/daemon/replay/internal => packages/replay-port/src/daemon-port}/session-replay-runtime-failure-response.ts (100%) diff --git a/packages/replay-port/src/daemon-port/__tests__/session-replay-resume.test.ts b/packages/replay-port/src/daemon-port/__tests__/session-replay-resume.test.ts new file mode 100644 index 0000000000..6c557f3cbd --- /dev/null +++ b/packages/replay-port/src/daemon-port/__tests__/session-replay-resume.test.ts @@ -0,0 +1,172 @@ +import type { SessionAction } from '@agent-device/contracts/session'; +import assert from 'node:assert/strict'; +import { test } from 'vitest'; +import { buildReplayDivergenceResume } from '../session-replay-resume.ts'; + +function action(overrides: Partial = {}): SessionAction { + return { ts: 0, command: 'click', positionals: ['label="Save"'], flags: {}, ...overrides }; +} + +test('buildReplayDivergenceResume reports a resumable generic .ad failure', () => { + const actions: SessionAction[] = [ + action({ command: 'open', positionals: ['Demo'] }), + action({ command: 'click', positionals: ['label="Save"'] }), + ]; + // `state-repair` carries no `alternateFrom` (no recorded-action alternate), + // so this stays a clean base-shape assertion — the caution/manual + // `alternateFrom` cases are covered by the dedicated tests below. + const resume = buildReplayDivergenceResume({ + failedIndex: 2, + actions, + planDigest: 'abc123', + repairHint: 'state-repair', + sessionExists: true, + }); + assert.deepEqual(resume, { allowed: true, from: 2, planDigest: 'abc123' }); +}); +// --- repairHint 'record-and-heal' shifts `from` to failedIndex + 1 (ADR 0012 +// decision 6, R2): the agent already performed the diverged step manually, so +// resuming AT it would re-diverge on the exact same step. --- + +test('buildReplayDivergenceResume with repairHint record-and-heal resumes AFTER the failed step', () => { + const actions: SessionAction[] = [ + action({ command: 'open', positionals: ['Demo'] }), + action({ command: 'click', positionals: ['label="Save"'] }), + action({ command: 'click', positionals: ['label="Confirm"'] }), + ]; + const resume = buildReplayDivergenceResume({ + failedIndex: 2, + actions, + planDigest: 'abc123', + repairHint: 'record-and-heal', + sessionExists: true, + }); + assert.deepEqual(resume, { allowed: true, from: 3, planDigest: 'abc123' }); +}); + +test('buildReplayDivergenceResume with repairHint record-and-heal on the LAST plan step is a legal empty-tail resume', () => { + const actions: SessionAction[] = [ + action({ command: 'open', positionals: ['Demo'] }), + action({ command: 'click', positionals: ['label="Save"'] }), + ]; + // failedIndex 2 (the last of 2 actions) shifts to from 3 = actions.length + + // 1 — there is no step 3 to run, but that is not an error: the runtime + // executes zero steps and reaches the normal end-of-plan completion path. + const resume = buildReplayDivergenceResume({ + failedIndex: 2, + actions, + planDigest: 'abc123', + repairHint: 'record-and-heal', + sessionExists: true, + }); + assert.deepEqual(resume, { allowed: true, from: 3, planDigest: 'abc123' }); +}); +// --- buildReplayDivergenceResume: `resume.alternateFrom` (#1262). The +// `caution`/`manual` dual-path's SECOND ordinal (`failedIndex + 1`), present +// ONLY when a `--from failedIndex + 1` request would actually be accepted. +// Generic `.ad` plans contain no runtime variable producers or control +// wrappers, so only the empty-tail session requirement can block it. --- + +test('buildReplayDivergenceResume: caution mid-plan carries alternateFrom = failedIndex + 1', () => { + const actions: SessionAction[] = [ + action({ command: 'open' }), + action({ command: 'click' }), + action({ command: 'click' }), + ]; + const resume = buildReplayDivergenceResume({ + failedIndex: 2, + actions, + planDigest: 'abc123', + repairHint: 'caution', + sessionExists: true, + }); + assert.equal(resume.allowed, true); + assert.equal(resume.from, 2); // unshifted + if (!resume.allowed) return; + assert.equal(resume.alternateFrom, 3); +}); + +test('buildReplayDivergenceResume: manual last-step carries alternateFrom = actions.length + 1', () => { + const actions: SessionAction[] = [action({ command: 'open' }), action({ command: 'click' })]; + const resume = buildReplayDivergenceResume({ + failedIndex: 2, + actions, + planDigest: 'abc123', + repairHint: 'manual', + sessionExists: true, + }); + assert.equal(resume.allowed, true); + assert.equal(resume.from, 2); + if (!resume.allowed) return; + assert.equal(resume.alternateFrom, 3); // empty-tail ordinal +}); + +test('buildReplayDivergenceResume: record-and-heal and state-repair never carry alternateFrom (no separate recorded-action alternate)', () => { + const actions: SessionAction[] = [ + action({ command: 'open' }), + action({ command: 'click' }), + action({ command: 'click' }), + ]; + for (const repairHint of ['record-and-heal', 'state-repair'] as const) { + const resume = buildReplayDivergenceResume({ + failedIndex: 2, + actions, + planDigest: 'abc123', + repairHint, + sessionExists: true, + }); + assert.equal(resume.allowed, true); + if (!resume.allowed) return; + assert.equal(resume.alternateFrom, undefined, `expected no alternateFrom for ${repairHint}`); + } +}); + +// --- #1262 (re-review): the EMPTY-TAIL alternate (`failedIndex + 1 > +// actions.length`) is authorizable only via the `pendingRecordAndHeal` +// watermark, which can only be stamped on a LIVE session. With NO session — a +// one-step `open` failure, or a session closed mid-replay — advertising +// `--from actions.length + 1` would be rejected as out of range, so it must +// not be emitted. A MID-PLAN alternate (in range) needs no watermark and stays +// session-independent. --- + +test('buildReplayDivergenceResume: LAST-step caution/manual with NO session carries NO alternateFrom (empty-tail needs a watermark, which needs a session)', () => { + const actions: SessionAction[] = [action({ command: 'open' }), action({ command: 'click' })]; + for (const repairHint of ['caution', 'manual'] as const) { + const resume = buildReplayDivergenceResume({ + failedIndex: 2, // last step → alternate would be the one-past-end ordinal 3 + actions, + planDigest: 'abc123', + repairHint, + sessionExists: false, + }); + assert.equal(resume.allowed, true); // resuming AT the failed step (2) is still fine + if (!resume.allowed) return; + assert.equal( + resume.alternateFrom, + undefined, + `expected no empty-tail alternateFrom without a session for ${repairHint}`, + ); + } +}); + +test('buildReplayDivergenceResume: MID-PLAN caution/manual with NO session STILL carries alternateFrom (in-range, no watermark needed)', () => { + // 3-step plan; failedIndex 2 → alternate 3 is IN RANGE (<= actions.length), + // so it needs no watermark and is emitted regardless of session existence. + const actions: SessionAction[] = [ + action({ command: 'open' }), + action({ command: 'click' }), + action({ command: 'click' }), + ]; + for (const repairHint of ['caution', 'manual'] as const) { + const resume = buildReplayDivergenceResume({ + failedIndex: 2, + actions, + planDigest: 'abc123', + repairHint, + sessionExists: false, + }); + assert.equal(resume.allowed, true); + if (!resume.allowed) return; + assert.equal(resume.alternateFrom, 3, `expected mid-plan alternateFrom for ${repairHint}`); + } +}); diff --git a/packages/replay-port/src/daemon-port/__tests__/session-replay-runtime-failure-response.test.ts b/packages/replay-port/src/daemon-port/__tests__/session-replay-runtime-failure-response.test.ts new file mode 100644 index 0000000000..52651408fb --- /dev/null +++ b/packages/replay-port/src/daemon-port/__tests__/session-replay-runtime-failure-response.test.ts @@ -0,0 +1,48 @@ +import { test, expect } from 'vitest'; +import { buildReplayDivergenceFailureResponseFromDescriptor } from '../session-replay-runtime-failure-response.ts'; + +test('native replay failure metadata keeps machine fields and daemon-owned paths intact', () => { + const replayPath = '/tmp/flows/ios-login.ad'; + const artifactPath = '/tmp/sessions/default/screenshot-1.png'; + const response = buildReplayDivergenceFailureResponseFromDescriptor({ + error: { + code: 'COMMAND_FAILED', + message: 'Could not tap Continue on ios', + hint: 'Retry Continue on ios', + details: { + reason: 'not_found', + retriable: false, + supportedOn: 'ios', + }, + retriable: false, + supportedOn: 'ios', + }, + actionLabel: 'press Continue', + action: 'press', + positionals: ['Continue'], + step: 2, + replayPath, + artifactPaths: [artifactPath], + divergence: {}, + scrubVars: [ + { name: 'MODE', value: 'on' }, + { name: 'PLATFORM', value: 'ios' }, + { name: 'SESSION', value: 'default' }, + ], + }); + + expect(response.ok).toBe(false); + if (response.ok) return; + expect(response.error.retriable).toBe(false); + expect(response.error.supportedOn).toBe('ios'); + expect(response.error.details).toMatchObject({ + reason: 'not_found', + retriable: false, + supportedOn: 'ios', + replayPath, + positionals: ['Continue'], + artifactPaths: [artifactPath], + }); + expect(response.error.details).toHaveProperty('reason'); + expect(response.error.details).not.toHaveProperty('reas'); +}); diff --git a/src/daemon/replay/internal/session-replay-resume.ts b/packages/replay-port/src/daemon-port/session-replay-resume.ts similarity index 100% rename from src/daemon/replay/internal/session-replay-resume.ts rename to packages/replay-port/src/daemon-port/session-replay-resume.ts diff --git a/src/daemon/replay/internal/session-replay-runtime-failure-response.ts b/packages/replay-port/src/daemon-port/session-replay-runtime-failure-response.ts similarity index 100% rename from src/daemon/replay/internal/session-replay-runtime-failure-response.ts rename to packages/replay-port/src/daemon-port/session-replay-runtime-failure-response.ts diff --git a/src/daemon/replay/internal/__tests__/session-replay-resume.test.ts b/src/daemon/replay/internal/__tests__/session-replay-resume.test.ts index 74ab360220..e892555952 100644 --- a/src/daemon/replay/internal/__tests__/session-replay-resume.test.ts +++ b/src/daemon/replay/internal/__tests__/session-replay-resume.test.ts @@ -1,7 +1,7 @@ import type { SessionAction } from '@agent-device/contracts/session'; import assert from 'node:assert/strict'; import { test } from 'vitest'; -import { buildReplayDivergenceResume } from '../session-replay-resume.ts'; +import { buildReplayDivergenceResume } from '@agent-device/replay-port/session-replay-resume'; import { stampPendingRecordAndHealWatermark } from '../../../session-replay-coordinator.ts'; import type { SessionState } from '../../../session-state.ts'; import { makeIosSession } from '../../../../__tests__/test-utils/session-factories.ts'; @@ -10,170 +10,6 @@ function action(overrides: Partial = {}): SessionAction { return { ts: 0, command: 'click', positionals: ['label="Save"'], flags: {}, ...overrides }; } -test('buildReplayDivergenceResume reports a resumable generic .ad failure', () => { - const actions: SessionAction[] = [ - action({ command: 'open', positionals: ['Demo'] }), - action({ command: 'click', positionals: ['label="Save"'] }), - ]; - // `state-repair` carries no `alternateFrom` (no recorded-action alternate), - // so this stays a clean base-shape assertion — the caution/manual - // `alternateFrom` cases are covered by the dedicated tests below. - const resume = buildReplayDivergenceResume({ - failedIndex: 2, - actions, - planDigest: 'abc123', - repairHint: 'state-repair', - sessionExists: true, - }); - assert.deepEqual(resume, { allowed: true, from: 2, planDigest: 'abc123' }); -}); -// --- repairHint 'record-and-heal' shifts `from` to failedIndex + 1 (ADR 0012 -// decision 6, R2): the agent already performed the diverged step manually, so -// resuming AT it would re-diverge on the exact same step. --- - -test('buildReplayDivergenceResume with repairHint record-and-heal resumes AFTER the failed step', () => { - const actions: SessionAction[] = [ - action({ command: 'open', positionals: ['Demo'] }), - action({ command: 'click', positionals: ['label="Save"'] }), - action({ command: 'click', positionals: ['label="Confirm"'] }), - ]; - const resume = buildReplayDivergenceResume({ - failedIndex: 2, - actions, - planDigest: 'abc123', - repairHint: 'record-and-heal', - sessionExists: true, - }); - assert.deepEqual(resume, { allowed: true, from: 3, planDigest: 'abc123' }); -}); - -test('buildReplayDivergenceResume with repairHint record-and-heal on the LAST plan step is a legal empty-tail resume', () => { - const actions: SessionAction[] = [ - action({ command: 'open', positionals: ['Demo'] }), - action({ command: 'click', positionals: ['label="Save"'] }), - ]; - // failedIndex 2 (the last of 2 actions) shifts to from 3 = actions.length + - // 1 — there is no step 3 to run, but that is not an error: the runtime - // executes zero steps and reaches the normal end-of-plan completion path. - const resume = buildReplayDivergenceResume({ - failedIndex: 2, - actions, - planDigest: 'abc123', - repairHint: 'record-and-heal', - sessionExists: true, - }); - assert.deepEqual(resume, { allowed: true, from: 3, planDigest: 'abc123' }); -}); -// --- buildReplayDivergenceResume: `resume.alternateFrom` (#1262). The -// `caution`/`manual` dual-path's SECOND ordinal (`failedIndex + 1`), present -// ONLY when a `--from failedIndex + 1` request would actually be accepted. -// Generic `.ad` plans contain no runtime variable producers or control -// wrappers, so only the empty-tail session requirement can block it. --- - -test('buildReplayDivergenceResume: caution mid-plan carries alternateFrom = failedIndex + 1', () => { - const actions: SessionAction[] = [ - action({ command: 'open' }), - action({ command: 'click' }), - action({ command: 'click' }), - ]; - const resume = buildReplayDivergenceResume({ - failedIndex: 2, - actions, - planDigest: 'abc123', - repairHint: 'caution', - sessionExists: true, - }); - assert.equal(resume.allowed, true); - assert.equal(resume.from, 2); // unshifted - if (!resume.allowed) return; - assert.equal(resume.alternateFrom, 3); -}); - -test('buildReplayDivergenceResume: manual last-step carries alternateFrom = actions.length + 1', () => { - const actions: SessionAction[] = [action({ command: 'open' }), action({ command: 'click' })]; - const resume = buildReplayDivergenceResume({ - failedIndex: 2, - actions, - planDigest: 'abc123', - repairHint: 'manual', - sessionExists: true, - }); - assert.equal(resume.allowed, true); - assert.equal(resume.from, 2); - if (!resume.allowed) return; - assert.equal(resume.alternateFrom, 3); // empty-tail ordinal -}); - -test('buildReplayDivergenceResume: record-and-heal and state-repair never carry alternateFrom (no separate recorded-action alternate)', () => { - const actions: SessionAction[] = [ - action({ command: 'open' }), - action({ command: 'click' }), - action({ command: 'click' }), - ]; - for (const repairHint of ['record-and-heal', 'state-repair'] as const) { - const resume = buildReplayDivergenceResume({ - failedIndex: 2, - actions, - planDigest: 'abc123', - repairHint, - sessionExists: true, - }); - assert.equal(resume.allowed, true); - if (!resume.allowed) return; - assert.equal(resume.alternateFrom, undefined, `expected no alternateFrom for ${repairHint}`); - } -}); - -// --- #1262 (re-review): the EMPTY-TAIL alternate (`failedIndex + 1 > -// actions.length`) is authorizable only via the `pendingRecordAndHeal` -// watermark, which can only be stamped on a LIVE session. With NO session — a -// one-step `open` failure, or a session closed mid-replay — advertising -// `--from actions.length + 1` would be rejected as out of range, so it must -// not be emitted. A MID-PLAN alternate (in range) needs no watermark and stays -// session-independent. --- - -test('buildReplayDivergenceResume: LAST-step caution/manual with NO session carries NO alternateFrom (empty-tail needs a watermark, which needs a session)', () => { - const actions: SessionAction[] = [action({ command: 'open' }), action({ command: 'click' })]; - for (const repairHint of ['caution', 'manual'] as const) { - const resume = buildReplayDivergenceResume({ - failedIndex: 2, // last step → alternate would be the one-past-end ordinal 3 - actions, - planDigest: 'abc123', - repairHint, - sessionExists: false, - }); - assert.equal(resume.allowed, true); // resuming AT the failed step (2) is still fine - if (!resume.allowed) return; - assert.equal( - resume.alternateFrom, - undefined, - `expected no empty-tail alternateFrom without a session for ${repairHint}`, - ); - } -}); - -test('buildReplayDivergenceResume: MID-PLAN caution/manual with NO session STILL carries alternateFrom (in-range, no watermark needed)', () => { - // 3-step plan; failedIndex 2 → alternate 3 is IN RANGE (<= actions.length), - // so it needs no watermark and is emitted regardless of session existence. - const actions: SessionAction[] = [ - action({ command: 'open' }), - action({ command: 'click' }), - action({ command: 'click' }), - ]; - for (const repairHint of ['caution', 'manual'] as const) { - const resume = buildReplayDivergenceResume({ - failedIndex: 2, - actions, - planDigest: 'abc123', - repairHint, - sessionExists: false, - }); - assert.equal(resume.allowed, true); - if (!resume.allowed) return; - assert.equal(resume.alternateFrom, 3, `expected mid-plan alternateFrom for ${repairHint}`); - } -}); - // --- stampPendingRecordAndHealWatermark (#1262): the watermark is now ALSO // stamped for `caution`/`manual`, but ONLY for the LAST-step empty-tail // alternate (`failedIndex === actions.length`, targeting `failedIndex + 1`). diff --git a/src/daemon/replay/internal/__tests__/session-replay-runtime-failure-response.test.ts b/src/daemon/replay/internal/__tests__/session-replay-runtime-failure-response.test.ts index 790876d0ba..cd4e84dabf 100644 --- a/src/daemon/replay/internal/__tests__/session-replay-runtime-failure-response.test.ts +++ b/src/daemon/replay/internal/__tests__/session-replay-runtime-failure-response.test.ts @@ -10,7 +10,6 @@ vi.mock('../../../snapshot-interactor-capture.ts', () => ({ captureSnapshotWithInteractor: vi.fn(), })); import path from 'node:path'; -import { buildReplayDivergenceFailureResponseFromDescriptor } from '../session-replay-runtime-failure-response.ts'; import { runReplayForTest } from '../../__tests__/replay-command-fixture.ts'; import { SessionStore } from '../../../session-store.ts'; import { makeIosSession } from '../../../../__tests__/test-utils/session-factories.ts'; @@ -34,51 +33,6 @@ beforeEach(() => { mockCaptureSnapshotWithInteractor.mockImplementation(captureSnapshotThroughLegacyDispatchFixture); }); -test('native replay failure metadata keeps machine fields and daemon-owned paths intact', () => { - const replayPath = '/tmp/flows/ios-login.ad'; - const artifactPath = '/tmp/sessions/default/screenshot-1.png'; - const response = buildReplayDivergenceFailureResponseFromDescriptor({ - error: { - code: 'COMMAND_FAILED', - message: 'Could not tap Continue on ios', - hint: 'Retry Continue on ios', - details: { - reason: 'not_found', - retriable: false, - supportedOn: 'ios', - }, - retriable: false, - supportedOn: 'ios', - }, - actionLabel: 'press Continue', - action: 'press', - positionals: ['Continue'], - step: 2, - replayPath, - artifactPaths: [artifactPath], - divergence: {}, - scrubVars: [ - { name: 'MODE', value: 'on' }, - { name: 'PLATFORM', value: 'ios' }, - { name: 'SESSION', value: 'default' }, - ], - }); - - expect(response.ok).toBe(false); - if (response.ok) return; - expect(response.error.retriable).toBe(false); - expect(response.error.supportedOn).toBe('ios'); - expect(response.error.details).toMatchObject({ - reason: 'not_found', - retriable: false, - supportedOn: 'ios', - replayPath, - positionals: ['Continue'], - artifactPaths: [artifactPath], - }); - expect(response.error.details).toHaveProperty('reason'); - expect(response.error.details).not.toHaveProperty('reas'); -}); test('divergence cause and action strings pass through the central redactor at construction', async () => { const root = mkdtempForTestSync('agent-device-replay-divergence-redact-'); const sessionStore = new SessionStore(path.join(root, 'sessions')); diff --git a/src/daemon/replay/internal/session-replay-divergence.ts b/src/daemon/replay/internal/session-replay-divergence.ts index 95bcd80022..f24caa1d61 100644 --- a/src/daemon/replay/internal/session-replay-divergence.ts +++ b/src/daemon/replay/internal/session-replay-divergence.ts @@ -10,7 +10,7 @@ import type { SnapshotNode } from '@agent-device/kernel/snapshot'; import { collectReplaySelectorCandidates } from '@agent-device/replay-port/session-replay-heal'; import { buildSelectorCandidates, resolveReplaySuggestionCandidate } from '@agent-device/selectors'; import { collectSettleChromeRefs } from '@agent-device/capture-kit/snapshot-chrome'; -import { buildAndPersistReplayDivergenceResume } from './session-replay-resume.ts'; +import { buildAndPersistReplayDivergenceResume } from '@agent-device/replay-port/session-replay-resume'; import { formatDivergenceActionLabel, isTouchTargetCommand } from '@agent-device/ad-script'; import { computeReplayRepairHint, diff --git a/src/daemon/replay/internal/session-replay-maestro-failure.ts b/src/daemon/replay/internal/session-replay-maestro-failure.ts index 831affec37..84e76a235a 100644 --- a/src/daemon/replay/internal/session-replay-maestro-failure.ts +++ b/src/daemon/replay/internal/session-replay-maestro-failure.ts @@ -30,7 +30,7 @@ import { computeReplayRepairHint } from '@agent-device/replay-port/repair-hint'; import { buildReplayDivergenceFailureResponseFromDescriptor, hoistReplayFailureCauseDiagnosticMeta, -} from './session-replay-runtime-failure-response.ts'; +} from '@agent-device/replay-port/session-replay-runtime-failure-response'; import { type DaemonResponse } from '@agent-device/kernel/contracts'; export type MaestroFailureReportAction = Pick< diff --git a/src/daemon/replay/internal/session-replay-runtime-failure.ts b/src/daemon/replay/internal/session-replay-runtime-failure.ts index 6c4978b54a..e885fb21c9 100644 --- a/src/daemon/replay/internal/session-replay-runtime-failure.ts +++ b/src/daemon/replay/internal/session-replay-runtime-failure.ts @@ -10,7 +10,7 @@ import { buildReplayFailureDivergence } from './session-replay-divergence.ts'; import { buildReplayDivergenceFailureResponse, hoistReplayFailureCauseDiagnosticMeta, -} from './session-replay-runtime-failure-response.ts'; +} from '@agent-device/replay-port/session-replay-runtime-failure-response'; import { getRequestSignal } from '@agent-device/host-kit/request'; import type { ReplayResumeStamper, diff --git a/src/daemon/replay/internal/session-replay-target-verification.ts b/src/daemon/replay/internal/session-replay-target-verification.ts index 736aa9540d..0bf3feb9a4 100644 --- a/src/daemon/replay/internal/session-replay-target-verification.ts +++ b/src/daemon/replay/internal/session-replay-target-verification.ts @@ -50,8 +50,8 @@ import { computeReplayRepairHint, type ReplayRepairHintCapture, } from '@agent-device/replay-port/repair-hint'; -import { buildReplayDivergenceFailureResponse } from './session-replay-runtime-failure-response.ts'; -import { buildAndPersistReplayDivergenceResume } from './session-replay-resume.ts'; +import { buildReplayDivergenceFailureResponse } from '@agent-device/replay-port/session-replay-runtime-failure-response'; +import { buildAndPersistReplayDivergenceResume } from '@agent-device/replay-port/session-replay-resume'; import { classifyReplayTarget } from '@agent-device/replay-port/target-classification'; import { extractReplayTargetToken, readRefLabel } from '@agent-device/replay-port/target-token'; From 25c07af39291a5a037f65aa37194933d64da9710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Mon, 28 Sep 2026 14:14:04 +0200 Subject: [PATCH 2/2] chore(gates): declare replay response package dependencies --- packages/replay-port/package.json | 12 +++++++++++- packages/replay-port/tsconfig.json | 2 +- pnpm-lock.yaml | 6 ++++++ .../__tests__/replay-coordinator-ownership.test.ts | 4 ++-- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/replay-port/package.json b/packages/replay-port/package.json index 2c730a0114..0a7b727e4c 100644 --- a/packages/replay-port/package.json +++ b/packages/replay-port/package.json @@ -6,6 +6,7 @@ "type": "module", "description": "Daemon-side replay port: the adapter over the ad-replay, ad-script and replay-test engines.", "dependencies": { + "@agent-device/ad-replay": "workspace:*", "@agent-device/ad-script": "workspace:*", "@agent-device/capture-kit": "workspace:*", "@agent-device/contracts": "workspace:*", @@ -14,7 +15,8 @@ "@agent-device/kernel": "workspace:*", "@agent-device/maestro": "workspace:*", "@agent-device/replay-test": "workspace:*", - "@agent-device/selectors": "workspace:*" + "@agent-device/selectors": "workspace:*", + "@agent-device/session-journal": "workspace:*" }, "exports": { "./command-types": { @@ -57,10 +59,18 @@ "types": "./src/daemon-port/session-replay-report-action.ts", "default": "./src/daemon-port/session-replay-report-action.ts" }, + "./session-replay-resume": { + "types": "./src/daemon-port/session-replay-resume.ts", + "default": "./src/daemon-port/session-replay-resume.ts" + }, "./session-replay-runtime-artifacts": { "types": "./src/daemon-port/session-replay-runtime-artifacts.ts", "default": "./src/daemon-port/session-replay-runtime-artifacts.ts" }, + "./session-replay-runtime-failure-response": { + "types": "./src/daemon-port/session-replay-runtime-failure-response.ts", + "default": "./src/daemon-port/session-replay-runtime-failure-response.ts" + }, "./session-replay-suggestion-ranking": { "types": "./src/daemon-port/session-replay-suggestion-ranking.ts", "default": "./src/daemon-port/session-replay-suggestion-ranking.ts" diff --git a/packages/replay-port/tsconfig.json b/packages/replay-port/tsconfig.json index 935c871a4d..81bef0203e 100644 --- a/packages/replay-port/tsconfig.json +++ b/packages/replay-port/tsconfig.json @@ -8,5 +8,5 @@ "declarationDir": "./dist-types", "rootDir": "./src" }, - "include": ["src"] + "include": ["src", "../command-registry/src/global.d.ts"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b423209e03..1bcb944f51 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -506,6 +506,9 @@ importers: packages/replay-port: dependencies: + '@agent-device/ad-replay': + specifier: workspace:* + version: link:../ad-replay '@agent-device/ad-script': specifier: workspace:* version: link:../ad-script @@ -533,6 +536,9 @@ importers: '@agent-device/selectors': specifier: workspace:* version: link:../selectors + '@agent-device/session-journal': + specifier: workspace:* + version: link:../session-journal packages/replay-test: dependencies: diff --git a/src/daemon/__tests__/replay-coordinator-ownership.test.ts b/src/daemon/__tests__/replay-coordinator-ownership.test.ts index 2f4f94304c..2011a32c73 100644 --- a/src/daemon/__tests__/replay-coordinator-ownership.test.ts +++ b/src/daemon/__tests__/replay-coordinator-ownership.test.ts @@ -32,11 +32,11 @@ const RUNTIME_FILE = 'src/daemon/handlers/session-replay-command.ts'; /** The divergence-report chain: never a second `ReplayCoordinator`, never a bare `SessionStore`. */ const DIVERGENCE_CHAIN_FILES = [ - 'src/daemon/replay/internal/session-replay-resume.ts', + 'packages/replay-port/src/daemon-port/session-replay-resume.ts', 'src/daemon/replay/internal/session-replay-divergence.ts', 'src/daemon/replay/internal/session-replay-target-verification.ts', 'src/daemon/replay/internal/session-replay-runtime-failure.ts', - 'src/daemon/replay/internal/session-replay-runtime-failure-response.ts', + 'packages/replay-port/src/daemon-port/session-replay-runtime-failure-response.ts', ] as const; type ImportSite = {