Skip to content

Commit a46d3d6

Browse files
authored
fix: resolve replay script paths from caller cwd (#77)
1 parent 254b620 commit a46d3d6

8 files changed

Lines changed: 55 additions & 2 deletions

File tree

src/__tests__/cli-diagnostics.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ test('cli forwards --debug as verbose/debug metadata', async () => {
7272
assert.equal(result.calls.length, 1);
7373
assert.equal(result.calls[0]?.flags?.verbose, true);
7474
assert.equal(result.calls[0]?.meta?.debug, true);
75+
assert.equal(result.calls[0]?.meta?.cwd, process.cwd());
7576
assert.equal(typeof result.calls[0]?.meta?.requestId, 'string');
7677
});
7778

src/cli.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ export async function runCli(argv: string[], deps: CliDeps = DEFAULT_CLI_DEPS):
108108
meta: {
109109
requestId,
110110
debug: Boolean(flags.verbose),
111+
cwd: process.cwd(),
111112
},
112113
});
113114
try {

src/daemon-client.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export async function sendToDaemon(req: Omit<DaemonRequest, 'token'>): Promise<D
5656
meta: {
5757
requestId,
5858
debug,
59+
cwd: req.meta?.cwd,
5960
},
6061
};
6162
emitDiagnostic({

src/daemon/__tests__/session-store.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@ function makeSession(name: string): SessionState {
2121
};
2222
}
2323

24+
test('expandHome resolves tilde, relative-with-cwd, and absolute paths', () => {
25+
const homePath = SessionStore.expandHome('~/flows/replay.ad');
26+
assert.equal(homePath.startsWith(os.homedir()), true);
27+
assert.equal(homePath.endsWith(path.join('flows', 'replay.ad')), true);
28+
29+
const relativePath = SessionStore.expandHome('workflows/replay.ad', '/tmp/agent-device-cwd');
30+
assert.equal(relativePath, path.resolve('/tmp/agent-device-cwd', 'workflows/replay.ad'));
31+
32+
const absoluteInput = path.resolve('/tmp', 'agent-device-absolute.ad');
33+
const absolutePath = SessionStore.expandHome(absoluteInput, '/tmp/ignored-cwd');
34+
assert.equal(absolutePath, absoluteInput);
35+
});
36+
2437
test('recordAction stores normalized action entries', () => {
2538
const store = new SessionStore(path.join(os.tmpdir(), 'agent-device-tests'));
2639
const session = makeSession('default');

src/daemon/handlers/__tests__/session.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,6 +1014,39 @@ test('replay parses open --relaunch flag and replays open with relaunch semantic
10141014
assert.equal(invoked[0]?.flags?.relaunch, true);
10151015
});
10161016

1017+
test('replay resolves relative script path against request cwd', async () => {
1018+
const sessionStore = makeSessionStore();
1019+
const replayRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-replay-cwd-'));
1020+
const replayDir = path.join(replayRoot, 'workflows');
1021+
fs.mkdirSync(replayDir, { recursive: true });
1022+
fs.writeFileSync(path.join(replayDir, 'flow.ad'), 'open "Settings"\n');
1023+
1024+
const invoked: DaemonRequest[] = [];
1025+
const response = await handleSessionCommands({
1026+
req: {
1027+
token: 't',
1028+
session: 'default',
1029+
command: 'replay',
1030+
positionals: ['workflows/flow.ad'],
1031+
flags: {},
1032+
meta: { cwd: replayRoot },
1033+
},
1034+
sessionName: 'default',
1035+
logPath: path.join(os.tmpdir(), 'daemon.log'),
1036+
sessionStore,
1037+
invoke: async (req) => {
1038+
invoked.push(req);
1039+
return { ok: true, data: {} };
1040+
},
1041+
});
1042+
1043+
assert.ok(response);
1044+
assert.equal(response?.ok, true);
1045+
assert.equal(invoked.length, 1);
1046+
assert.equal(invoked[0]?.command, 'open');
1047+
assert.deepEqual(invoked[0]?.positionals, ['Settings']);
1048+
});
1049+
10171050
test('replay parses press series flags and passes them to invoke', async () => {
10181051
const sessionStore = makeSessionStore();
10191052
const replayRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-replay-press-series-'));

src/daemon/handlers/session.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@ export async function handleSessionCommands(params: {
544544
return { ok: false, error: { code: 'INVALID_ARGS', message: 'replay requires a path' } };
545545
}
546546
try {
547-
const resolved = SessionStore.expandHome(filePath);
547+
const resolved = SessionStore.expandHome(filePath, req.meta?.cwd);
548548
const script = fs.readFileSync(resolved, 'utf8');
549549
const firstNonWhitespace = script.trimStart()[0];
550550
if (firstNonWhitespace === '{' || firstNonWhitespace === '[') {

src/daemon/session-store.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,13 @@ export class SessionStore {
9191
return path.join(this.sessionsDir, `${safeName}-${timestamp}.trace.log`);
9292
}
9393

94-
static expandHome(filePath: string): string {
94+
static expandHome(filePath: string, cwd?: string): string {
9595
if (filePath.startsWith('~/')) {
9696
return path.join(os.homedir(), filePath.slice(2));
9797
}
98+
if (cwd && !path.isAbsolute(filePath)) {
99+
return path.resolve(cwd, filePath);
100+
}
98101
return path.resolve(filePath);
99102
}
100103

src/daemon/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export type DaemonRequest = {
1212
meta?: {
1313
requestId?: string;
1414
debug?: boolean;
15+
cwd?: string;
1516
};
1617
};
1718

0 commit comments

Comments
 (0)