Session directory resolution had no precedence contract: the selection-time directory short-circuited every lookup, and a persisted runtime value was consulted before the authoritative record. A worktree session selected before its directory store bootstrapped kept the active-directory fallback, and that guess was persisted, so it survived reloads and restarts. Directory resolution now lives in one module and orders sources by whether the server confirmed the path, not by whether the value is local or synced: authoritative (the child store that holds the session) > server-confirmed selection > worktree attachment/metadata (the requested path, pre-canonical) > remembered. A guessed selection is no longer persisted, remembered, or ranked. Chips read the same resolution the composer used, so queue keys cannot diverge. Queued auto-send could strand an item indefinitely: backoff, missing send configuration, and the recent-abort window all returned without scheduling a wake-up, so the queue only retried when an unrelated status or directory change re-ran the effect. A retry scheduler now wakes it at the earliest known time. A rejected send rolls the optimistic message back while the composer stays silent for transport failures, which makes it indistinguishable from nothing happening. Failures are now recorded to a bounded in-memory log surfaced in the About diagnostics report, alongside a directory-resolution breakdown, plus __opencodeDebug.diagnoseSessionDirectory() and getRecentSendFailures(). Prompted by a report of worktree prompting silently failing. That failure was not reproduced locally, so the diagnostics are what will identify it.
132 lines
4.3 KiB
TypeScript
132 lines
4.3 KiB
TypeScript
import { describe, expect, test } from 'bun:test';
|
|
|
|
import {
|
|
describeSessionDirectorySources,
|
|
resolveSessionDirectoryFromSources,
|
|
} from './session-directory-resolution';
|
|
|
|
const WORKTREE = '/repo/.worktrees/feature';
|
|
const MAIN = '/repo';
|
|
|
|
describe('resolveSessionDirectoryFromSources', () => {
|
|
test('authoritative directory beats a selection-time fallback', () => {
|
|
const resolution = resolveSessionDirectoryFromSources({
|
|
authoritative: WORKTREE,
|
|
selected: MAIN,
|
|
});
|
|
|
|
expect(resolution.directory).toBe(WORKTREE);
|
|
expect(resolution.source).toBe('authoritative');
|
|
expect(resolution.conflict).toEqual({ source: 'selected', directory: MAIN });
|
|
});
|
|
|
|
test('authoritative directory beats a directory persisted across restarts', () => {
|
|
const resolution = resolveSessionDirectoryFromSources({
|
|
authoritative: WORKTREE,
|
|
remembered: MAIN,
|
|
});
|
|
|
|
expect(resolution.directory).toBe(WORKTREE);
|
|
expect(resolution.conflict).toEqual({ source: 'remembered', directory: MAIN });
|
|
});
|
|
|
|
test('the indexed directory outranks a locally requested worktree path', () => {
|
|
// attachment/worktreeMetadata hold the path this client asked for, before
|
|
// the server canonicalized it. Letting them win would route prompts to a
|
|
// directory that no child store owns.
|
|
const resolution = resolveSessionDirectoryFromSources({
|
|
attachment: '/requested/worktree',
|
|
worktreeMetadata: '/requested/worktree',
|
|
authoritative: WORKTREE,
|
|
});
|
|
|
|
expect(resolution.directory).toBe(WORKTREE);
|
|
expect(resolution.source).toBe('authoritative');
|
|
expect(resolution.conflict).toEqual({ source: 'attachment', directory: '/requested/worktree' });
|
|
});
|
|
|
|
test('a worktree attachment is used while the session is not indexed yet', () => {
|
|
// A guessed selection is not passed as `selected` at all, so the worktree
|
|
// assignment is the best available value during the bootstrap race.
|
|
const resolution = resolveSessionDirectoryFromSources({
|
|
authoritative: null,
|
|
selected: null,
|
|
attachment: WORKTREE,
|
|
remembered: MAIN,
|
|
});
|
|
|
|
expect(resolution.directory).toBe(WORKTREE);
|
|
expect(resolution.source).toBe('attachment');
|
|
expect(resolution.conflict).toEqual({ source: 'remembered', directory: MAIN });
|
|
});
|
|
|
|
test('a server-confirmed selection outranks the requested worktree path', () => {
|
|
const resolution = resolveSessionDirectoryFromSources({
|
|
authoritative: null,
|
|
selected: '/canonical/worktree',
|
|
attachment: '/requested/worktree',
|
|
worktreeMetadata: '/requested/worktree',
|
|
});
|
|
|
|
expect(resolution.directory).toBe('/canonical/worktree');
|
|
expect(resolution.source).toBe('selected');
|
|
});
|
|
|
|
test('falls back to the selection hint while the session is not indexed yet', () => {
|
|
const resolution = resolveSessionDirectoryFromSources({
|
|
authoritative: null,
|
|
selected: WORKTREE,
|
|
});
|
|
|
|
expect(resolution.directory).toBe(WORKTREE);
|
|
expect(resolution.source).toBe('selected');
|
|
expect(resolution.conflict).toBeNull();
|
|
});
|
|
|
|
test('agreeing sources report no conflict', () => {
|
|
const resolution = resolveSessionDirectoryFromSources({
|
|
authoritative: WORKTREE,
|
|
selected: WORKTREE,
|
|
remembered: WORKTREE,
|
|
});
|
|
|
|
expect(resolution.conflict).toBeNull();
|
|
});
|
|
|
|
test('reports the first disagreeing source, not the last', () => {
|
|
const resolution = resolveSessionDirectoryFromSources({
|
|
authoritative: WORKTREE,
|
|
selected: MAIN,
|
|
remembered: '/somewhere/else',
|
|
});
|
|
|
|
expect(resolution.conflict).toEqual({ source: 'selected', directory: MAIN });
|
|
});
|
|
|
|
test('treats missing and blank values as unknown, never as a directory', () => {
|
|
const resolution = resolveSessionDirectoryFromSources({
|
|
attachment: null,
|
|
worktreeMetadata: ' ',
|
|
authoritative: undefined,
|
|
selected: '',
|
|
});
|
|
|
|
expect(resolution.directory).toBeNull();
|
|
expect(resolution.source).toBe('none');
|
|
expect(resolution.conflict).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('describeSessionDirectorySources', () => {
|
|
test('lists populated sources in precedence order', () => {
|
|
expect(describeSessionDirectorySources({
|
|
remembered: MAIN,
|
|
authoritative: WORKTREE,
|
|
selected: '',
|
|
})).toEqual([
|
|
{ source: 'authoritative', directory: WORKTREE },
|
|
{ source: 'remembered', directory: MAIN },
|
|
]);
|
|
});
|
|
});
|