Skip to content

Commit dd9e8e3

Browse files
refactor!: remove cwd from ResumeSessionOptions (CLI-72) (#42)
cwd on resumeSession was misleading: it only controlled subprocess startup and was never sent to droid.load_session, so it could not change the resumed session's working directory. Remove it from ResumeSessionOptions to make the contract explicit: createSession uses the caller's cwd; resumeSession uses the persisted session's cwd. BREAKING CHANGE: resumeSession() no longer accepts cwd. To run in a different directory, create a new session or fork the existing one. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
1 parent 2268fa2 commit dd9e8e3

6 files changed

Lines changed: 72 additions & 11 deletions

File tree

README.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,7 @@ Use `session.sessionId` to persist the session ID, then resume it later:
112112
```ts
113113
import { resumeSession } from '@factory/droid-sdk';
114114

115-
const session = await resumeSession(savedSessionId, {
116-
cwd: '/my/project',
117-
});
115+
const session = await resumeSession(savedSessionId);
118116
for await (const msg of session.stream('Continue where we left off')) {
119117
// Handle streamed DroidMessage events.
120118
}
@@ -180,7 +178,7 @@ const session = await createSession({ cwd: '/my/project' });
180178
console.log(session.sessionId);
181179
console.log(session.initResult.settings.modelId);
182180

183-
const resumed = await resumeSession(session.sessionId, { cwd: '/my/project' });
181+
const resumed = await resumeSession(session.sessionId);
184182
console.log(resumed.initResult.cwd);
185183

186184
await resumed.close();
@@ -271,7 +269,7 @@ for await (const _msg of session.stream(
271269
}
272270

273271
const { newSessionId } = await session.forkSession();
274-
const fork = await resumeSession(newSessionId, { cwd: '/my/project' });
272+
const fork = await resumeSession(newSessionId);
275273

276274
for await (const msg of fork.stream('What phrase did I ask you to remember?')) {
277275
if (msg.type === DroidMessageType.AssistantTextDelta) {
@@ -458,7 +456,7 @@ Session creation options used by `run()` and `createSession()` include:
458456
- **`askUserHandler`** — callback for interactive questions
459457
- **`abortSignal`** — standard `AbortSignal` for cancellation
460458

461-
`resumeSession()` accepts the process, transport, handler, `cwd`, `mcpServers`, and `abortSignal` options needed to reconnect to an existing session, but does not accept new-session-only options such as `modelId` or `interactionMode`.
459+
`resumeSession()` accepts the process, transport, handler, `mcpServers`, and `abortSignal` options needed to reconnect to an existing session, but does not accept new-session-only options such as `modelId` or `interactionMode`. `cwd` is intentionally not accepted on resume: the persisted session's working directory is always used. To run in a different directory, create a new session or fork the existing one.
462460

463461
Message APIs (`run()` and `session.stream()`) also accept:
464462

docs/typescript-sdk-reference.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,10 @@ function createSession(options?: CreateSessionOptions): Promise<DroidSession>;
7777

7878
### `resumeSession()`
7979

80-
Reconnects to an existing session by ID.
80+
Reconnects to an existing session by ID. The resumed session always runs in the
81+
working directory persisted with the session; `ResumeSessionOptions` does not
82+
accept `cwd`. To run in a different directory, create a new session or fork the
83+
existing one.
8184

8285
```ts
8386
function resumeSession(

examples/fork-session.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ async function main(): Promise<void> {
4040
const { newSessionId } = await session.forkSession();
4141
console.log(`Forked session: ${newSessionId}\n`);
4242

43-
fork = await resumeSession(newSessionId, { cwd: process.cwd() });
43+
fork = await resumeSession(newSessionId);
4444

4545
const result = await streamText(
4646
fork,

examples/init-metadata.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import { createSession, resumeSession } from '@factory/droid-sdk';
99

1010
const session = await createSession({ cwd: process.cwd() });
11-
const resumed = await resumeSession(session.sessionId, { cwd: process.cwd() });
11+
const resumed = await resumeSession(session.sessionId);
1212

1313
console.log(`created session: ${session.sessionId}`);
1414
console.log(`resumed session: ${resumed.sessionId}`);

src/session.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ export interface ResumeSessionOptions extends Pick<
6767
CreateSessionOptions,
6868
| 'execPath'
6969
| 'execArgs'
70-
| 'cwd'
7170
| 'env'
7271
| 'permissionHandler'
7372
| 'askUserHandler'
@@ -410,7 +409,19 @@ export async function createSession(
410409
}
411410
}
412411

413-
/** @throws {SessionNotFoundError} If the session ID does not exist. */
412+
/**
413+
* Resumes an existing Droid session.
414+
*
415+
* The resumed session always runs in the working directory that was persisted
416+
* with the session at creation time. `resumeSession()` intentionally does not
417+
* accept a `cwd` option: the persisted session cwd is authoritative. The
418+
* underlying subprocess is launched in the host process's `process.cwd()`, but
419+
* this does not affect the session's working directory as far as the Droid is
420+
* concerned. To run in a different directory, create a new session or fork the
421+
* existing one.
422+
*
423+
* @throws {SessionNotFoundError} If the session ID does not exist.
424+
*/
414425
export async function resumeSession(
415426
sessionId: string,
416427
options: ResumeSessionOptions = {}

tests/session.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,55 @@ describe('resumeSession()', () => {
333333
expect(transport.isConnected).toBe(false);
334334
});
335335
});
336+
337+
describe('CLI-72: cwd behavior on resume', () => {
338+
it('does not send cwd to loadSession when omitted', async () => {
339+
const transport = new InMemoryTransport();
340+
await transport.connect();
341+
342+
setupLoadResponder(transport, 'sess-resume-cwd-001');
343+
344+
const session = await resumeSession('sess-resume-cwd-001', {
345+
transport,
346+
});
347+
348+
const loadMsg = transport.sentMessages.find(
349+
(m) =>
350+
(m as Record<string, unknown>)['method'] ===
351+
DroidServerMethod.LOAD_SESSION
352+
) as Record<string, unknown>;
353+
const params = loadMsg['params'] as Record<string, unknown>;
354+
355+
expect(params).not.toHaveProperty('cwd');
356+
expect(params['sessionId']).toBe('sess-resume-cwd-001');
357+
358+
await session.close();
359+
});
360+
361+
it('rejects cwd as an option at the type level', async () => {
362+
const transport = new InMemoryTransport();
363+
await transport.connect();
364+
365+
setupLoadResponder(transport, 'sess-resume-cwd-002');
366+
367+
const session = await resumeSession('sess-resume-cwd-002', {
368+
transport,
369+
// @ts-expect-error - cwd is not a valid ResumeSessionOptions field
370+
cwd: '/tmp/should-not-be-allowed',
371+
});
372+
373+
const loadMsg = transport.sentMessages.find(
374+
(m) =>
375+
(m as Record<string, unknown>)['method'] ===
376+
DroidServerMethod.LOAD_SESSION
377+
) as Record<string, unknown>;
378+
const params = loadMsg['params'] as Record<string, unknown>;
379+
380+
expect(params).not.toHaveProperty('cwd');
381+
382+
await session.close();
383+
});
384+
});
336385
});
337386

338387
describe('DroidSession', () => {

0 commit comments

Comments
 (0)