Skip to content

Commit 491ad7e

Browse files
authored
fix: improve session ownership and recovery guidance (#674)
* fix: improve session recovery guidance * fix: quote session recovery commands * test: simplify record session cleanup fixtures * test: cover recovery hint edge cases * test: trim duplicate recovery quoting coverage * fix: scope implicit sessions by caller workspace * fix: surface session state directory * fix: remove unused session store check * test: trim duplicate session recovery coverage
1 parent 041b482 commit 491ad7e

41 files changed

Lines changed: 732 additions & 114 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

skills/dogfood/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ Read current CLI guidance:
2222
agent-device help dogfood
2323
```
2424

25-
Loop: open named session -> snapshot -i + screenshot -> explore flows -> capture evidence per issue -> close.
25+
Loop: open app -> snapshot -i + screenshot -> explore flows -> capture evidence per issue -> close.
2626

2727
Target app is required; infer platform or ask. Findings must come from runtime behavior, not source reads. Let `help dogfood` provide exact report shape, evidence commands, and current workflow guidance.

src/cli.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ export async function runCli(argv: string[], deps: CliDeps = DEFAULT_CLI_DEPS):
215215
currentFlags: CliFlags,
216216
runtime: SessionRuntimeHints | undefined,
217217
): AgentDeviceClientConfig => ({
218-
session: currentFlags.session ?? sessionName,
218+
session: currentFlags.session,
219219
requestId,
220220
stateDir: currentFlags.stateDir,
221221
daemonBaseUrl: currentFlags.daemonBaseUrl,

src/client-normalizers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ export function buildMeta(options: InternalRequestOptions): DaemonRequest['meta'
333333
return stripUndefined({
334334
requestId: options.requestId,
335335
cwd: options.cwd,
336+
sessionExplicit: options.session !== undefined,
336337
debug: options.debug,
337338
lockPolicy: options.lockPolicy,
338339
lockPlatform: options.lockPlatform,

src/client-shared.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ export function serializeOpenResult(result: AppOpenResult): Record<string, unkno
140140
return withSuccessText(
141141
{
142142
session: result.session,
143+
...(result.sessionStateDir ? { sessionStateDir: result.sessionStateDir } : {}),
143144
...(result.appName ? { appName: result.appName } : {}),
144145
...(result.appBundleId ? { appBundleId: result.appBundleId } : {}),
145146
...(result.startup ? { startup: result.startup } : {}),

src/client-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ export type AppOpenOptions = AgentDeviceRequestOverrides &
184184

185185
export type AppOpenResult = {
186186
session: string;
187+
sessionStateDir?: string;
187188
appName?: string;
188189
appBundleId?: string;
189190
appId?: string;

src/client.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ export function createAgentDeviceClient(
152152
const appId = appBundleId;
153153
return {
154154
session,
155+
sessionStateDir: readOptionalString(data, 'sessionStateDir'),
155156
appName: readOptionalString(data, 'appName'),
156157
appBundleId,
157158
appId,

src/commands/__tests__/client-output.test.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,39 @@
11
import { describe, expect, test } from 'vitest';
2-
import { recordCliOutput } from '../client-output.ts';
2+
import { openCliOutput, recordCliOutput } from '../client-output.ts';
3+
4+
describe('openCliOutput', () => {
5+
test('prints session state directory on a second line', () => {
6+
const output = openCliOutput({
7+
session: 'default',
8+
sessionStateDir: '/tmp/agent-device/sessions/cwd_123_default',
9+
identifiers: { session: 'default' },
10+
});
11+
12+
expect(output.text).toBe(
13+
['Opened: default', 'Session state: /tmp/agent-device/sessions/cwd_123_default'].join('\n'),
14+
);
15+
expect(output.data).toMatchObject({
16+
session: 'default',
17+
sessionStateDir: '/tmp/agent-device/sessions/cwd_123_default',
18+
});
19+
});
20+
});
321

422
describe('recordCliOutput', () => {
23+
test('prints session state directory for record-created sessions', () => {
24+
const output = recordCliOutput({
25+
recording: 'started',
26+
outPath: '/tmp/recording.mp4',
27+
sessionStateDir: '/tmp/agent-device/sessions/cwd_123_default',
28+
});
29+
30+
expect(output.text).toBe(
31+
['/tmp/recording.mp4', 'Session state: /tmp/agent-device/sessions/cwd_123_default'].join(
32+
'\n',
33+
),
34+
);
35+
});
36+
537
test('prints chunked Android recording paths clearly for human stdout', () => {
638
const output = recordCliOutput({
739
recording: 'stopped',

src/commands/client-output.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,12 @@ export function sessionCliOutput(result: { sessions: AgentDeviceSession[] }): Cl
5656
}
5757

5858
export function openCliOutput(result: AppOpenResult): CliOutput {
59-
return messageOutput(serializeOpenResult(result));
59+
const data = serializeOpenResult(result);
60+
const lines = [readCommandMessage(data)].filter((line): line is string => Boolean(line));
61+
if (typeof data.sessionStateDir === 'string') {
62+
lines.push(`Session state: ${data.sessionStateDir}`);
63+
}
64+
return { data, text: lines.join('\n') || null };
6065
}
6166

6267
export function closeCliOutput(result: AppCloseResult | SessionCloseResult): CliOutput {
@@ -207,6 +212,8 @@ function defaultCommandCliOutput(result: CommandRequestResult): CliOutput {
207212
function formatRecordSingleOutput(data: Record<string, unknown>, outPath: string): string {
208213
const lines: string[] = [];
209214
if (outPath) lines.push(outPath);
215+
if (typeof data.sessionStateDir === 'string')
216+
lines.push(`Session state: ${data.sessionStateDir}`);
210217
if (typeof data.warning === 'string') lines.push(`Warning: ${data.warning}`);
211218
if (typeof data.overlayWarning === 'string')
212219
lines.push(`Overlay warning: ${data.overlayWarning}`);

src/contracts.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export type DaemonRequestMeta = {
4343
requestId?: string;
4444
debug?: boolean;
4545
cwd?: string;
46+
sessionExplicit?: boolean;
4647
tenantId?: string;
4748
runId?: string;
4849
leaseId?: string;
@@ -348,6 +349,7 @@ export const daemonCommandRequestSchema = schema<DaemonRequest>((input, path) =>
348349
requestId: optionalString(meta, 'requestId', `${path}.meta`),
349350
debug: optionalBoolean(meta, 'debug', `${path}.meta`),
350351
cwd: optionalString(meta, 'cwd', `${path}.meta`),
352+
sessionExplicit: optionalBoolean(meta, 'sessionExplicit', `${path}.meta`),
351353
tenantId: optionalString(meta, 'tenantId', `${path}.meta`),
352354
runId: optionalString(meta, 'runId', `${path}.meta`),
353355
leaseId: optionalString(meta, 'leaseId', `${path}.meta`),

src/daemon-client.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
import { uploadArtifact } from './upload-client.ts';
3030
import { computeDaemonCodeSignature } from './daemon/code-signature.ts';
3131
import { PUBLIC_COMMANDS } from './command-catalog.ts';
32+
import { shellQuote } from './utils/shell-quote.ts';
3233
import {
3334
readDaemonHttpProgressResponse,
3435
shouldReadDaemonProgressStream,
@@ -157,6 +158,7 @@ export async function sendToDaemon(req: Omit<DaemonRequest, 'token'>): Promise<D
157158
requestId,
158159
debug,
159160
cwd: req.meta?.cwd,
161+
sessionExplicit: req.meta?.sessionExplicit,
160162
tenantId: req.meta?.tenantId ?? req.flags?.tenant,
161163
runId: req.meta?.runId ?? req.flags?.runId,
162164
leaseId: req.meta?.leaseId ?? req.flags?.leaseId,
@@ -1783,11 +1785,19 @@ export function resolveDaemonStartupHint(
17831785
process.env.AGENT_DEVICE_STATE_DIR,
17841786
),
17851787
): string {
1788+
const cleanupCommand = buildDaemonMetadataCleanupCommand(paths);
17861789
if (state.hasLock && !state.hasInfo) {
1787-
return `agent-device attempted to clean stale daemon metadata automatically, but ${paths.lockPath} still exists without ${paths.infoPath}. Retry with --debug; if this persists, remove ${paths.lockPath} after confirming no agent-device daemon process is running.`;
1790+
return `agent-device attempted to clean stale daemon metadata automatically, but ${paths.lockPath} still exists without ${paths.infoPath}. Retry with --debug; if this persists after confirming no agent-device daemon process is running, run: ${cleanupCommand}`;
17881791
}
17891792
if (state.hasLock && state.hasInfo) {
1790-
return `agent-device attempted to clean stale daemon metadata automatically, but ${paths.infoPath} and ${paths.lockPath} still remain. Retry with --debug; if this persists, remove both files after confirming no agent-device daemon process is running.`;
1793+
return `agent-device attempted to clean stale daemon metadata automatically, but ${paths.infoPath} and ${paths.lockPath} still remain. Retry with --debug; if this persists after confirming no agent-device daemon process is running, run: ${cleanupCommand}`;
17911794
}
1792-
return `agent-device did not observe reachable daemon metadata after retrying. Stale metadata was cleaned automatically when safe; retry with --debug and check daemon diagnostics logs.`;
1795+
if (state.hasInfo) {
1796+
return `agent-device did not observe reachable daemon metadata after retrying, and ${paths.infoPath} still remains. Stale metadata was cleaned automatically when safe; retry with --debug. If this persists after confirming no agent-device daemon process is running, run: ${cleanupCommand}`;
1797+
}
1798+
return `agent-device did not observe reachable daemon metadata after retrying. Stale metadata was cleaned automatically when safe; retry with --debug and check daemon diagnostics logs. If stale metadata returns after confirming no agent-device daemon process is running, run: ${cleanupCommand}`;
1799+
}
1800+
1801+
function buildDaemonMetadataCleanupCommand(paths: Pick<DaemonPaths, 'infoPath' | 'lockPath'>) {
1802+
return `rm -f ${shellQuote(paths.infoPath)} ${shellQuote(paths.lockPath)}`;
17931803
}

0 commit comments

Comments
 (0)