@productbrain/cli 0.1.0-beta.1919 → 0.1.0-beta.1963
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/conversation.test.d.ts +2 -0
- package/dist/__tests__/conversation.test.d.ts.map +1 -0
- package/dist/__tests__/conversation.test.js +113 -0
- package/dist/__tests__/conversation.test.js.map +1 -0
- package/dist/__tests__/ingest.test.js +9 -0
- package/dist/__tests__/ingest.test.js.map +1 -1
- package/dist/__tests__/orient.test.js +28 -1
- package/dist/__tests__/orient.test.js.map +1 -1
- package/dist/__tests__/profiles.test.js +10 -6
- package/dist/__tests__/profiles.test.js.map +1 -1
- package/dist/__tests__/session-label.test.d.ts +2 -0
- package/dist/__tests__/session-label.test.d.ts.map +1 -0
- package/dist/__tests__/session-label.test.js +46 -0
- package/dist/__tests__/session-label.test.js.map +1 -0
- package/dist/__tests__/session-list-show.test.d.ts +2 -0
- package/dist/__tests__/session-list-show.test.d.ts.map +1 -0
- package/dist/__tests__/session-list-show.test.js +174 -0
- package/dist/__tests__/session-list-show.test.js.map +1 -0
- package/dist/__tests__/session-reset.test.js +23 -7
- package/dist/__tests__/session-reset.test.js.map +1 -1
- package/dist/__tests__/session-resume-backstop.test.js +2 -0
- package/dist/__tests__/session-resume-backstop.test.js.map +1 -1
- package/dist/__tests__/session-state-machine.test.js +190 -9
- package/dist/__tests__/session-state-machine.test.js.map +1 -1
- package/dist/__tests__/session-switch.test.js +4 -2
- package/dist/__tests__/session-switch.test.js.map +1 -1
- package/dist/__tests__/vocabulary-leak.test.js +5 -0
- package/dist/__tests__/vocabulary-leak.test.js.map +1 -1
- package/dist/commands/connect.d.ts.map +1 -1
- package/dist/commands/connect.js +5 -4
- package/dist/commands/connect.js.map +1 -1
- package/dist/commands/ingest.d.ts.map +1 -1
- package/dist/commands/ingest.js +9 -0
- package/dist/commands/ingest.js.map +1 -1
- package/dist/commands/orient.d.ts.map +1 -1
- package/dist/commands/orient.js +35 -6
- package/dist/commands/orient.js.map +1 -1
- package/dist/commands/profile.d.ts.map +1 -1
- package/dist/commands/profile.js +10 -5
- package/dist/commands/profile.js.map +1 -1
- package/dist/commands/session.d.ts +29 -0
- package/dist/commands/session.d.ts.map +1 -1
- package/dist/commands/session.js +141 -23
- package/dist/commands/session.js.map +1 -1
- package/dist/formatters/__tests__/session-list-show.test.d.ts +2 -0
- package/dist/formatters/__tests__/session-list-show.test.d.ts.map +1 -0
- package/dist/formatters/__tests__/session-list-show.test.js +159 -0
- package/dist/formatters/__tests__/session-list-show.test.js.map +1 -0
- package/dist/formatters/session.d.ts +46 -0
- package/dist/formatters/session.d.ts.map +1 -1
- package/dist/formatters/session.js +111 -0
- package/dist/formatters/session.js.map +1 -1
- package/dist/index.js +16 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/conversation.d.ts +36 -0
- package/dist/lib/conversation.d.ts.map +1 -0
- package/dist/lib/conversation.js +66 -0
- package/dist/lib/conversation.js.map +1 -0
- package/dist/lib/productbrain-files.d.ts.map +1 -1
- package/dist/lib/productbrain-files.js +1 -0
- package/dist/lib/productbrain-files.js.map +1 -1
- package/dist/lib/productbrain-files.test.js +4 -2
- package/dist/lib/productbrain-files.test.js.map +1 -1
- package/dist/lib/session.d.ts +58 -12
- package/dist/lib/session.d.ts.map +1 -1
- package/dist/lib/session.js +196 -24
- package/dist/lib/session.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pb session list / pb session show — command wiring tests (WP-479 E4).
|
|
3
|
+
*
|
|
4
|
+
* Verifies:
|
|
5
|
+
* - list calls agent.listSessions and renders quota gauge (pretty) / passes data through (json)
|
|
6
|
+
* - show resolves by BOTH arg kinds: a Convex-id-shaped arg goes out as sessionId (with a
|
|
7
|
+
* conversationId retry when the server rejects it); a non-id-shaped arg goes straight out
|
|
8
|
+
* as conversationId
|
|
9
|
+
* - json mode emits the structured payload
|
|
10
|
+
*/
|
|
11
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
12
|
+
import { setOutputMode } from '../lib/runner.js';
|
|
13
|
+
const kernelCallMock = vi.fn();
|
|
14
|
+
vi.mock('../lib/client.js', () => ({
|
|
15
|
+
kernelCall: (...args) => kernelCallMock(...args),
|
|
16
|
+
kernelCallWithSession: vi.fn(),
|
|
17
|
+
}));
|
|
18
|
+
vi.mock('../lib/config.js', () => ({
|
|
19
|
+
getConfigOrGuide: vi.fn(async () => ({ apiKey: 'pb_sk_test', siteUrl: 'https://test.convex.site' })),
|
|
20
|
+
readLocalProjectConfig: vi.fn(() => null),
|
|
21
|
+
readProjectConfig: vi.fn(() => null),
|
|
22
|
+
resolveEffectiveProfile: vi.fn(() => ({ profile: 'p', source: 'local' })),
|
|
23
|
+
refreshPreviewApiKeyFromDisk: vi.fn(() => 'not-bound'),
|
|
24
|
+
}));
|
|
25
|
+
vi.mock('../lib/profiles.js', () => ({ getActiveProfile: vi.fn(() => 'p') }));
|
|
26
|
+
vi.mock('../lib/session.js', () => ({
|
|
27
|
+
readSession: vi.fn(() => null),
|
|
28
|
+
writeSession: vi.fn(),
|
|
29
|
+
clearSession: vi.fn(),
|
|
30
|
+
discardSession: vi.fn(),
|
|
31
|
+
readClosedMarker: vi.fn(() => null),
|
|
32
|
+
sessionFileExists: vi.fn(() => false),
|
|
33
|
+
RESUME_WINDOW_MS: 2 * 60 * 60 * 1000,
|
|
34
|
+
}));
|
|
35
|
+
vi.mock('../commands/handshake.js', () => ({ runHandshake: vi.fn(async () => { }) }));
|
|
36
|
+
import { runSessionList, runSessionShow } from '../commands/session.js';
|
|
37
|
+
const LIST_FIXTURE = {
|
|
38
|
+
active: [
|
|
39
|
+
{
|
|
40
|
+
sessionId: 'js7abc123def456ghi789jkl012mno',
|
|
41
|
+
conversationId: 'conv-a',
|
|
42
|
+
label: 'session-attribution',
|
|
43
|
+
clientKind: 'cli',
|
|
44
|
+
initiatedBy: 'Randy',
|
|
45
|
+
status: 'active',
|
|
46
|
+
startedAt: Date.now() - 10 * 60_000,
|
|
47
|
+
lastToolCallAt: Date.now(),
|
|
48
|
+
entriesCreatedCount: 2,
|
|
49
|
+
entriesModifiedCount: 1,
|
|
50
|
+
purpose: null,
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
recent: [],
|
|
54
|
+
quota: { activeCount: 1, cap: 50 },
|
|
55
|
+
};
|
|
56
|
+
const SHOW_FIXTURE = {
|
|
57
|
+
conversationId: 'conv-a',
|
|
58
|
+
sessions: [
|
|
59
|
+
{
|
|
60
|
+
sessionId: 'js7abc123def456ghi789jkl012mno',
|
|
61
|
+
conversationId: 'conv-a',
|
|
62
|
+
label: 'session-attribution',
|
|
63
|
+
clientKind: 'cli',
|
|
64
|
+
initiatedBy: 'Randy',
|
|
65
|
+
status: 'active',
|
|
66
|
+
startedAt: Date.now() - 10 * 60_000,
|
|
67
|
+
lastToolCallAt: Date.now(),
|
|
68
|
+
entriesCreatedCount: 1,
|
|
69
|
+
entriesModifiedCount: 0,
|
|
70
|
+
purpose: null,
|
|
71
|
+
entriesCreated: ['e1'],
|
|
72
|
+
entriesModified: [],
|
|
73
|
+
relationsCreated: 0,
|
|
74
|
+
gateFailures: 0,
|
|
75
|
+
contradictionWarnings: 0,
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
serves: [
|
|
79
|
+
{
|
|
80
|
+
servedAt: Date.now() - 60_000,
|
|
81
|
+
entryId: 'STD-12',
|
|
82
|
+
surface: 'orient',
|
|
83
|
+
renderMode: 'brief',
|
|
84
|
+
task: 'a task',
|
|
85
|
+
sessionId: 'js7abc123def456ghi789jkl012mno',
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
let stdoutOutput;
|
|
90
|
+
let stdoutWriteSpy;
|
|
91
|
+
beforeEach(() => {
|
|
92
|
+
stdoutOutput = '';
|
|
93
|
+
stdoutWriteSpy = vi.spyOn(process.stdout, 'write').mockImplementation((chunk) => {
|
|
94
|
+
stdoutOutput += String(chunk);
|
|
95
|
+
return true;
|
|
96
|
+
});
|
|
97
|
+
vi.clearAllMocks();
|
|
98
|
+
setOutputMode('auto');
|
|
99
|
+
});
|
|
100
|
+
afterEach(() => {
|
|
101
|
+
stdoutWriteSpy.mockRestore();
|
|
102
|
+
setOutputMode('auto');
|
|
103
|
+
});
|
|
104
|
+
describe('runSessionList', () => {
|
|
105
|
+
it('calls agent.listSessions and renders the quota gauge + collision-free labels in pretty mode', async () => {
|
|
106
|
+
setOutputMode('pretty');
|
|
107
|
+
kernelCallMock.mockResolvedValue(LIST_FIXTURE);
|
|
108
|
+
await runSessionList();
|
|
109
|
+
expect(kernelCallMock).toHaveBeenCalledWith('agent.listSessions', {});
|
|
110
|
+
expect(stdoutOutput).toContain('Quota: 1 of 50 active');
|
|
111
|
+
expect(stdoutOutput).toContain('session-attribution');
|
|
112
|
+
expect(stdoutOutput).toContain('js7abc123def456ghi789jkl012mno');
|
|
113
|
+
});
|
|
114
|
+
it('passes the structured payload through in json mode (full conversationId, quota object)', async () => {
|
|
115
|
+
setOutputMode('json');
|
|
116
|
+
kernelCallMock.mockResolvedValue({
|
|
117
|
+
...LIST_FIXTURE,
|
|
118
|
+
active: [{ ...LIST_FIXTURE.active[0], conversationId: 'x'.repeat(64) }],
|
|
119
|
+
});
|
|
120
|
+
await runSessionList();
|
|
121
|
+
const parsed = JSON.parse(stdoutOutput);
|
|
122
|
+
expect(parsed.quota).toEqual({ activeCount: 1, cap: 50 });
|
|
123
|
+
// JSON carries the FULL conversationId — truncation is a pretty-mode concern only.
|
|
124
|
+
expect(parsed.active[0].conversationId).toBe('x'.repeat(64));
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
describe('runSessionShow', () => {
|
|
128
|
+
it('a Convex-id-shaped arg is sent as sessionId', async () => {
|
|
129
|
+
setOutputMode('pretty');
|
|
130
|
+
kernelCallMock.mockResolvedValue(SHOW_FIXTURE);
|
|
131
|
+
await runSessionShow('js7abc123def456ghi789jkl012mno');
|
|
132
|
+
expect(kernelCallMock).toHaveBeenCalledWith('agent.showConversation', {
|
|
133
|
+
sessionId: 'js7abc123def456ghi789jkl012mno',
|
|
134
|
+
});
|
|
135
|
+
expect(stdoutOutput).toContain('Conversation: conv-a');
|
|
136
|
+
expect(stdoutOutput).toContain('1 session(s) in lineage');
|
|
137
|
+
expect(stdoutOutput).toContain('STD-12');
|
|
138
|
+
});
|
|
139
|
+
it('a non-id-shaped arg (e.g. UUID with dashes) goes straight out as conversationId', async () => {
|
|
140
|
+
setOutputMode('pretty');
|
|
141
|
+
kernelCallMock.mockResolvedValue(SHOW_FIXTURE);
|
|
142
|
+
await runSessionShow('3f9a-uuid-with-dashes');
|
|
143
|
+
expect(kernelCallMock).toHaveBeenCalledTimes(1);
|
|
144
|
+
expect(kernelCallMock).toHaveBeenCalledWith('agent.showConversation', {
|
|
145
|
+
conversationId: '3f9a-uuid-with-dashes',
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
it('retries an id-shaped arg as conversationId when the sessionId call is rejected', async () => {
|
|
149
|
+
setOutputMode('pretty');
|
|
150
|
+
// A conversationId can coincidentally match the Convex id shape — first attempt 400s,
|
|
151
|
+
// the retry as conversationId succeeds (list-row copyability contract).
|
|
152
|
+
kernelCallMock
|
|
153
|
+
.mockRejectedValueOnce(new Error('ArgumentValidationError: not an Id<agentSessions>'))
|
|
154
|
+
.mockResolvedValueOnce(SHOW_FIXTURE);
|
|
155
|
+
const idShaped = 'abcdefghijklmnopqrstuvwxyz012345';
|
|
156
|
+
await runSessionShow(idShaped);
|
|
157
|
+
expect(kernelCallMock).toHaveBeenNthCalledWith(1, 'agent.showConversation', { sessionId: idShaped });
|
|
158
|
+
expect(kernelCallMock).toHaveBeenNthCalledWith(2, 'agent.showConversation', { conversationId: idShaped });
|
|
159
|
+
expect(stdoutOutput).toContain('Conversation: conv-a');
|
|
160
|
+
});
|
|
161
|
+
it('emits structured JSON in json mode', async () => {
|
|
162
|
+
setOutputMode('json');
|
|
163
|
+
kernelCallMock.mockResolvedValue(SHOW_FIXTURE);
|
|
164
|
+
await runSessionShow('conv-a');
|
|
165
|
+
const parsed = JSON.parse(stdoutOutput);
|
|
166
|
+
expect(parsed.conversationId).toBe('conv-a');
|
|
167
|
+
expect(parsed.sessions).toHaveLength(1);
|
|
168
|
+
expect(parsed.serves[0].entryId).toBe('STD-12');
|
|
169
|
+
});
|
|
170
|
+
it('rejects an empty argument with guidance', async () => {
|
|
171
|
+
await expect(runSessionShow(' ')).rejects.toThrow(/session id or conversation id/i);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
//# sourceMappingURL=session-list-show.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-list-show.test.js","sourceRoot":"","sources":["../../src/__tests__/session-list-show.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,12 +7,18 @@
|
|
|
7
7
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
8
8
|
const mockReadSession = vi.fn();
|
|
9
9
|
const mockDiscardSession = vi.fn();
|
|
10
|
+
const mockDiscardAllSessions = vi.fn();
|
|
10
11
|
const mockSessionFileExists = vi.fn(() => false);
|
|
12
|
+
const mockHasAnyActive = vi.fn(() => false);
|
|
11
13
|
vi.mock('../lib/session.js', () => ({
|
|
12
14
|
readSession: () => mockReadSession(),
|
|
13
15
|
writeSession: vi.fn(),
|
|
14
16
|
clearSession: vi.fn(),
|
|
15
17
|
discardSession: () => mockDiscardSession(),
|
|
18
|
+
discardAllSessions: () => mockDiscardAllSessions(),
|
|
19
|
+
// reset now checks ALL markers (Codex re-review) — mirror sessionFileExists in the mock.
|
|
20
|
+
hasAnySessionMarker: () => mockSessionFileExists(),
|
|
21
|
+
hasAnyActiveSession: () => mockHasAnyActive(),
|
|
16
22
|
sessionFileExists: () => mockSessionFileExists(),
|
|
17
23
|
readClosedMarker: vi.fn(() => null),
|
|
18
24
|
addCapturedEntry: vi.fn(),
|
|
@@ -41,7 +47,7 @@ describe('runSessionReset', () => {
|
|
|
41
47
|
await runSessionReset({});
|
|
42
48
|
const out = writeSpy.mock.calls.map((c) => String(c[0])).join('');
|
|
43
49
|
expect(out).toMatch(/Nothing to reset/);
|
|
44
|
-
expect(
|
|
50
|
+
expect(mockDiscardAllSessions).not.toHaveBeenCalled();
|
|
45
51
|
}
|
|
46
52
|
finally {
|
|
47
53
|
writeSpy.mockRestore();
|
|
@@ -56,7 +62,7 @@ describe('runSessionReset', () => {
|
|
|
56
62
|
const out = writeSpy.mock.calls.map((c) => String(c[0])).join('');
|
|
57
63
|
expect(out).toMatch(/Nothing to reset/);
|
|
58
64
|
expect(out).not.toMatch(/PREVIEW_MODE/);
|
|
59
|
-
expect(
|
|
65
|
+
expect(mockDiscardAllSessions).not.toHaveBeenCalled();
|
|
60
66
|
}
|
|
61
67
|
finally {
|
|
62
68
|
writeSpy.mockRestore();
|
|
@@ -68,7 +74,7 @@ describe('runSessionReset', () => {
|
|
|
68
74
|
try {
|
|
69
75
|
await runSessionReset({});
|
|
70
76
|
expect(mockConfirm).not.toHaveBeenCalled();
|
|
71
|
-
expect(
|
|
77
|
+
expect(mockDiscardAllSessions).toHaveBeenCalled();
|
|
72
78
|
}
|
|
73
79
|
finally {
|
|
74
80
|
writeSpy.mockRestore();
|
|
@@ -81,7 +87,7 @@ describe('runSessionReset', () => {
|
|
|
81
87
|
try {
|
|
82
88
|
await runSessionReset({});
|
|
83
89
|
expect(mockConfirm).toHaveBeenCalled();
|
|
84
|
-
expect(
|
|
90
|
+
expect(mockDiscardAllSessions).not.toHaveBeenCalled();
|
|
85
91
|
}
|
|
86
92
|
finally {
|
|
87
93
|
writeSpy.mockRestore();
|
|
@@ -94,7 +100,7 @@ describe('runSessionReset', () => {
|
|
|
94
100
|
try {
|
|
95
101
|
await runSessionReset({});
|
|
96
102
|
expect(mockConfirm).toHaveBeenCalled();
|
|
97
|
-
expect(
|
|
103
|
+
expect(mockDiscardAllSessions).toHaveBeenCalled();
|
|
98
104
|
}
|
|
99
105
|
finally {
|
|
100
106
|
writeSpy.mockRestore();
|
|
@@ -106,7 +112,7 @@ describe('runSessionReset', () => {
|
|
|
106
112
|
try {
|
|
107
113
|
await runSessionReset({ force: true });
|
|
108
114
|
expect(mockConfirm).not.toHaveBeenCalled();
|
|
109
|
-
expect(
|
|
115
|
+
expect(mockDiscardAllSessions).toHaveBeenCalled();
|
|
110
116
|
}
|
|
111
117
|
finally {
|
|
112
118
|
writeSpy.mockRestore();
|
|
@@ -116,7 +122,17 @@ describe('runSessionReset', () => {
|
|
|
116
122
|
mockReadSession.mockReturnValue(ACTIVE);
|
|
117
123
|
mockIsInteractive.mockReturnValue(false);
|
|
118
124
|
await expect(runSessionReset({})).rejects.toThrow(/refusing to reset/);
|
|
119
|
-
expect(
|
|
125
|
+
expect(mockDiscardAllSessions).not.toHaveBeenCalled();
|
|
126
|
+
});
|
|
127
|
+
it('refuses when only a SIBLING conversation is active (current is null) — Codex re-review', async () => {
|
|
128
|
+
// Current conversation resolves to no session, but another conversation's marker is active.
|
|
129
|
+
mockReadSession.mockReturnValue(null);
|
|
130
|
+
mockSessionFileExists.mockReturnValue(true);
|
|
131
|
+
mockHasAnyActive.mockReturnValue(true);
|
|
132
|
+
mockIsInteractive.mockReturnValue(false);
|
|
133
|
+
// Must NOT silently delete the sibling active marker — requires confirmation/--force.
|
|
134
|
+
await expect(runSessionReset({})).rejects.toThrow(/refusing to reset/);
|
|
135
|
+
expect(mockDiscardAllSessions).not.toHaveBeenCalled();
|
|
120
136
|
});
|
|
121
137
|
});
|
|
122
138
|
//# sourceMappingURL=session-reset.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-reset.test.js","sourceRoot":"","sources":["../../src/__tests__/session-reset.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9D,MAAM,eAAe,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;AAChC,MAAM,kBAAkB,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;AACnC,MAAM,qBAAqB,GAAG,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;AACjD,EAAE,CAAC,IAAI,CAAC,mBAAmB,EAAE,GAAG,EAAE,CAAC,CAAC;IAClC,WAAW,EAAE,GAAG,EAAE,CAAC,eAAe,EAAE;IACpC,YAAY,EAAE,EAAE,CAAC,EAAE,EAAE;IACrB,YAAY,EAAE,EAAE,CAAC,EAAE,EAAE;IACrB,cAAc,EAAE,GAAG,EAAE,CAAC,kBAAkB,EAAE;IAC1C,iBAAiB,EAAE,GAAG,EAAE,CAAC,qBAAqB,EAAE;IAChD,gBAAgB,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC;IACnC,gBAAgB,EAAE,EAAE,CAAC,EAAE,EAAE;CAC1B,CAAC,CAAC,CAAC;AAEJ,MAAM,WAAW,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;AAC5B,MAAM,iBAAiB,GAAG,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;AAC5C,EAAE,CAAC,IAAI,CAAC,mBAAmB,EAAE,GAAG,EAAE,CAAC,CAAC;IAClC,OAAO,EAAE,CAAC,GAAG,CAAY,EAAE,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAC/C,aAAa,EAAE,GAAG,EAAE,CAAC,iBAAiB,EAAE;CACzC,CAAC,CAAC,CAAC;AAEJ,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,MAAM,MAAM,GAAG;IACb,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI;IACvD,SAAS,EAAE,sBAAsB,EAAE,eAAe,EAAE,EAAE;CACvD,CAAC;AAEF,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,aAAa,EAAE,CAAC;QACnB,eAAe,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QACtC,qBAAqB,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;QAC7C,iBAAiB,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,KAAK,IAAI,EAAE;QAC3E,MAAM,QAAQ,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QAClF,IAAI,CAAC;YACH,MAAM,eAAe,CAAC,EAAE,CAAC,CAAC;YAC1B,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAClE,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;YACxC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"session-reset.test.js","sourceRoot":"","sources":["../../src/__tests__/session-reset.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -30,6 +30,8 @@ vi.mock('../lib/session.js', () => ({
|
|
|
30
30
|
clearSession: vi.fn(),
|
|
31
31
|
sessionFileExists: vi.fn(() => false),
|
|
32
32
|
addCapturedEntry: vi.fn(),
|
|
33
|
+
// WP-479 E2: commands/session.ts imports the shared 2h resume-window constant from here.
|
|
34
|
+
RESUME_WINDOW_MS: 2 * 60 * 60 * 1000,
|
|
33
35
|
}));
|
|
34
36
|
vi.mock('../lib/runner.js', () => ({ isJsonMode: () => true }));
|
|
35
37
|
vi.mock('../lib/profiles.js', () => ({ getActiveProfile: () => 'product-brain' }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-resume-backstop.test.js","sourceRoot":"","sources":["../../src/__tests__/session-resume-backstop.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9D,MAAM,cAAc,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;AAC/B,EAAE,CAAC,IAAI,CAAC,kBAAkB,EAAE,GAAG,EAAE,CAAC,CAAC;IACjC,UAAU,EAAE,CAAC,GAAG,CAAY,EAAE,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;IACrD,qBAAqB,EAAE,EAAE,CAAC,EAAE,EAAE;CAC/B,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,kBAAkB,EAAE,GAAG,EAAE,CAAC,CAAC;IACjC,gBAAgB,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAC3B,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,0BAA0B,EAAE,CAAC,CAC/E;IACD,uBAAuB,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IAC9E,sBAAsB,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC;CAC7D,CAAC,CAAC,CAAC;AAGJ,MAAM,eAAe,GAAG,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;AAC1C,MAAM,oBAAoB,GAAG,EAAE,CAAC,EAAE,CAAC,GAAkB,EAAE,CAAC,IAAI,CAAC,CAAC;AAC9D,MAAM,kBAAkB,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;AACnC,MAAM,gBAAgB,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;AACjC,EAAE,CAAC,IAAI,CAAC,mBAAmB,EAAE,GAAG,EAAE,CAAC,CAAC;IAClC,WAAW,EAAE,GAAG,EAAE,CAAC,eAAe,EAAE;IACpC,gBAAgB,EAAE,GAAG,EAAE,CAAC,oBAAoB,EAAE;IAC9C,cAAc,EAAE,GAAG,EAAE,CAAC,kBAAkB,EAAE;IAC1C,YAAY,EAAE,CAAC,GAAG,CAAY,EAAE,EAAE,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;IACzD,YAAY,EAAE,EAAE,CAAC,EAAE,EAAE;IACrB,iBAAiB,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC;IACrC,gBAAgB,EAAE,EAAE,CAAC,EAAE,EAAE;
|
|
1
|
+
{"version":3,"file":"session-resume-backstop.test.js","sourceRoot":"","sources":["../../src/__tests__/session-resume-backstop.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -13,24 +13,48 @@
|
|
|
13
13
|
* 4. clearSession() on active session writes a closed marker (atomic tmp→rename)
|
|
14
14
|
* 5. clearSession() on an existing marker deletes the file (no timestamp refresh)
|
|
15
15
|
*/
|
|
16
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
16
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
17
17
|
// ---------------------------------------------------------------------------
|
|
18
18
|
// Virtual filesystem state — controlled per test
|
|
19
|
+
//
|
|
20
|
+
// WP-479 E2: extended to also virtualize `.productbrain/sessions/<conversationId>.json` — one
|
|
21
|
+
// entry per conversation marker, keyed by filename (e.g. "conv-a.json"). Legacy `session.json`
|
|
22
|
+
// handling is UNCHANGED (same branches, same behavior) — the sessions-dir branches are additive.
|
|
19
23
|
// ---------------------------------------------------------------------------
|
|
20
|
-
const fsState = {
|
|
24
|
+
const fsState = {
|
|
25
|
+
sessionJson: null,
|
|
26
|
+
sessionsDir: {},
|
|
27
|
+
};
|
|
28
|
+
function sessionsDirEntryName(p) {
|
|
29
|
+
return String(p).split('/sessions/')[1].replace(/\.tmp$/, '');
|
|
30
|
+
}
|
|
21
31
|
vi.mock('fs', async (importOriginal) => {
|
|
22
32
|
const actual = await importOriginal();
|
|
23
33
|
return {
|
|
24
34
|
...actual,
|
|
25
35
|
existsSync: (p) => {
|
|
26
|
-
|
|
36
|
+
const s = String(p);
|
|
37
|
+
// Check the conversation-marker directory FIRST — a conversationId could itself end in
|
|
38
|
+
// "session" (e.g. "b-session"), which must never false-match the legacy branch below.
|
|
39
|
+
if (s.includes('/sessions/'))
|
|
40
|
+
return sessionsDirEntryName(s) in fsState.sessionsDir;
|
|
41
|
+
if (s.endsWith('/sessions'))
|
|
42
|
+
return true; // the dir itself — always "exists" for the mock
|
|
43
|
+
if (s.endsWith('session.json'))
|
|
27
44
|
return fsState.sessionJson !== null;
|
|
28
|
-
if (
|
|
45
|
+
if (s.endsWith('session.json.tmp'))
|
|
29
46
|
return false;
|
|
30
47
|
return actual.existsSync(p);
|
|
31
48
|
},
|
|
32
49
|
readFileSync: (p, enc) => {
|
|
33
|
-
|
|
50
|
+
const s = String(p);
|
|
51
|
+
if (s.includes('/sessions/') && enc === 'utf8') {
|
|
52
|
+
const name = sessionsDirEntryName(s);
|
|
53
|
+
if (!(name in fsState.sessionsDir))
|
|
54
|
+
throw new Error('ENOENT');
|
|
55
|
+
return fsState.sessionsDir[name];
|
|
56
|
+
}
|
|
57
|
+
if (s.endsWith('session.json') && enc === 'utf8') {
|
|
34
58
|
if (fsState.sessionJson === null)
|
|
35
59
|
throw new Error('ENOENT');
|
|
36
60
|
return fsState.sessionJson;
|
|
@@ -38,29 +62,58 @@ vi.mock('fs', async (importOriginal) => {
|
|
|
38
62
|
return actual.readFileSync(p, enc);
|
|
39
63
|
},
|
|
40
64
|
writeFileSync: (p, data, enc) => {
|
|
41
|
-
|
|
65
|
+
const s = String(p);
|
|
66
|
+
if (s.includes('/sessions/')) {
|
|
67
|
+
fsState.sessionsDir[sessionsDirEntryName(s)] = String(data);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (s.endsWith('session.json') || s.endsWith('session.json.tmp')) {
|
|
42
71
|
fsState.sessionJson = String(data);
|
|
43
72
|
return;
|
|
44
73
|
}
|
|
45
74
|
return actual.writeFileSync(p, data, enc);
|
|
46
75
|
},
|
|
47
76
|
renameSync: (src, dst) => {
|
|
48
|
-
// tmp → session.json: already written to fsState.sessionJson in writeFileSync above
|
|
49
|
-
if (String(src).endsWith('
|
|
77
|
+
// tmp → session.json: already written to fsState.sessionJson / sessionsDir in writeFileSync above
|
|
78
|
+
if (String(src).endsWith('.tmp'))
|
|
50
79
|
return;
|
|
51
80
|
return actual.renameSync(src, dst);
|
|
52
81
|
},
|
|
53
82
|
unlinkSync: (p) => {
|
|
54
|
-
|
|
83
|
+
const s = String(p);
|
|
84
|
+
if (s.includes('/sessions/')) {
|
|
85
|
+
delete fsState.sessionsDir[sessionsDirEntryName(s)];
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (s.endsWith('session.json')) {
|
|
55
89
|
fsState.sessionJson = null;
|
|
56
90
|
return;
|
|
57
91
|
}
|
|
58
92
|
return actual.unlinkSync(p);
|
|
59
93
|
},
|
|
60
94
|
mkdirSync: vi.fn(), // always succeeds
|
|
95
|
+
readdirSync: (p) => {
|
|
96
|
+
if (String(p).endsWith('/sessions'))
|
|
97
|
+
return Object.keys(fsState.sessionsDir);
|
|
98
|
+
return actual.readdirSync(p);
|
|
99
|
+
},
|
|
61
100
|
};
|
|
62
101
|
});
|
|
63
102
|
import { readSession, writeSession, clearSession, readClosedMarker, discardSession, sessionFileExists, } from '../lib/session.js';
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Conversation-identity env helper — WP-479 E2. Real lib/conversation.js is NOT mocked (it is
|
|
105
|
+
// pure env feature-detection), so isolation tests set/restore the relevant env var directly.
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
const CONV_ENV_KEYS = [
|
|
108
|
+
'PB_CONVERSATION_ID',
|
|
109
|
+
'CLAUDE_CODE_SESSION_ID',
|
|
110
|
+
'CURSOR_CONVERSATION_ID',
|
|
111
|
+
'CODEX_TUI_SESSION_LOG_PATH',
|
|
112
|
+
];
|
|
113
|
+
const savedConvEnv = {};
|
|
114
|
+
function withConversationId(id) {
|
|
115
|
+
process.env.PB_CONVERSATION_ID = id;
|
|
116
|
+
}
|
|
64
117
|
const ACTIVE_SESSION = {
|
|
65
118
|
sessionId: 'rd79aaa',
|
|
66
119
|
workspaceId: 'ws-1',
|
|
@@ -74,8 +127,25 @@ const CLOSED_MARKER = {
|
|
|
74
127
|
};
|
|
75
128
|
beforeEach(() => {
|
|
76
129
|
fsState.sessionJson = null;
|
|
130
|
+
fsState.sessionsDir = {};
|
|
131
|
+
// Guard every test (existing + new) against ambient identity env vars — e.g. this suite may
|
|
132
|
+
// itself run inside a Claude Code shell, where CLAUDE_CODE_SESSION_ID is genuinely set. Existing
|
|
133
|
+
// tests below assume the legacy no-identity path; clearing here makes that assumption hold
|
|
134
|
+
// regardless of the ambient shell (WP-479 E2 zero-config invariant, defense in depth).
|
|
135
|
+
for (const key of CONV_ENV_KEYS) {
|
|
136
|
+
savedConvEnv[key] = process.env[key];
|
|
137
|
+
delete process.env[key];
|
|
138
|
+
}
|
|
77
139
|
vi.clearAllMocks();
|
|
78
140
|
});
|
|
141
|
+
afterEach(() => {
|
|
142
|
+
for (const key of CONV_ENV_KEYS) {
|
|
143
|
+
if (savedConvEnv[key] === undefined)
|
|
144
|
+
delete process.env[key];
|
|
145
|
+
else
|
|
146
|
+
process.env[key] = savedConvEnv[key];
|
|
147
|
+
}
|
|
148
|
+
});
|
|
79
149
|
describe('readSession()', () => {
|
|
80
150
|
it('returns null when no file exists', () => {
|
|
81
151
|
expect(readSession()).toBeNull();
|
|
@@ -195,4 +265,115 @@ describe('SessionState.profile round-trips', () => {
|
|
|
195
265
|
expect(readSession()?.profile).toBe('product-brain');
|
|
196
266
|
});
|
|
197
267
|
});
|
|
268
|
+
// ---------------------------------------------------------------------------
|
|
269
|
+
// WP-479 E2 — conversation-aware marker layout
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
describe('legacy path stays byte-identical when no conversation identity resolves', () => {
|
|
272
|
+
it('with no env signal, writeSession/readSession target session.json — sessionsDir untouched', () => {
|
|
273
|
+
writeSession({
|
|
274
|
+
sessionId: 'legacy-1', workspaceId: 'w1', workspaceName: 'WS',
|
|
275
|
+
startedAt: new Date().toISOString(), entriesCaptured: [],
|
|
276
|
+
});
|
|
277
|
+
expect(fsState.sessionJson).not.toBeNull();
|
|
278
|
+
expect(Object.keys(fsState.sessionsDir)).toHaveLength(0);
|
|
279
|
+
expect(readSession()?.sessionId).toBe('legacy-1');
|
|
280
|
+
// The written state stamps conversationId as undefined, not a stray value.
|
|
281
|
+
expect(readSession()?.conversationId).toBeUndefined();
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
describe('per-conversation marker isolation', () => {
|
|
285
|
+
it('two distinct conversationIds write to two distinct marker files, no cross-talk', () => {
|
|
286
|
+
withConversationId('conv-a');
|
|
287
|
+
writeSession({
|
|
288
|
+
sessionId: 'sess-a', workspaceId: 'w1', workspaceName: 'WS',
|
|
289
|
+
startedAt: new Date().toISOString(), entriesCaptured: [],
|
|
290
|
+
});
|
|
291
|
+
withConversationId('conv-b');
|
|
292
|
+
writeSession({
|
|
293
|
+
sessionId: 'sess-b', workspaceId: 'w1', workspaceName: 'WS',
|
|
294
|
+
startedAt: new Date().toISOString(), entriesCaptured: [],
|
|
295
|
+
});
|
|
296
|
+
expect(Object.keys(fsState.sessionsDir).sort()).toEqual(['conv-a.json', 'conv-b.json']);
|
|
297
|
+
expect(fsState.sessionJson).toBeNull(); // legacy path never touched
|
|
298
|
+
withConversationId('conv-a');
|
|
299
|
+
expect(readSession()?.sessionId).toBe('sess-a');
|
|
300
|
+
withConversationId('conv-b');
|
|
301
|
+
expect(readSession()?.sessionId).toBe('sess-b');
|
|
302
|
+
});
|
|
303
|
+
it('stamps the resolved conversationId onto the written state', () => {
|
|
304
|
+
withConversationId('conv-c');
|
|
305
|
+
writeSession({
|
|
306
|
+
sessionId: 'sess-c', workspaceId: 'w1', workspaceName: 'WS',
|
|
307
|
+
startedAt: new Date().toISOString(), entriesCaptured: [],
|
|
308
|
+
});
|
|
309
|
+
expect(readSession()?.conversationId).toBe('conv-c');
|
|
310
|
+
});
|
|
311
|
+
it('clearSession on one conversation marker does not affect a sibling conversation', () => {
|
|
312
|
+
withConversationId('conv-x');
|
|
313
|
+
writeSession({
|
|
314
|
+
sessionId: 'sess-x', workspaceId: 'w1', workspaceName: 'WS',
|
|
315
|
+
startedAt: new Date().toISOString(), entriesCaptured: [],
|
|
316
|
+
});
|
|
317
|
+
withConversationId('conv-y');
|
|
318
|
+
writeSession({
|
|
319
|
+
sessionId: 'sess-y', workspaceId: 'w1', workspaceName: 'WS',
|
|
320
|
+
startedAt: new Date().toISOString(), entriesCaptured: [],
|
|
321
|
+
});
|
|
322
|
+
withConversationId('conv-x');
|
|
323
|
+
clearSession(); // closes conv-x only
|
|
324
|
+
expect(readSession()).toBeNull(); // conv-x now closed
|
|
325
|
+
withConversationId('conv-y');
|
|
326
|
+
expect(readSession()?.sessionId).toBe('sess-y'); // conv-y untouched
|
|
327
|
+
});
|
|
328
|
+
it('a long/unsafe-charset conversationId env value normalizes to the same hashed filename on every call', () => {
|
|
329
|
+
process.env.PB_CONVERSATION_ID = '../weird path/with spaces';
|
|
330
|
+
writeSession({
|
|
331
|
+
sessionId: 'sess-hashed', workspaceId: 'w1', workspaceName: 'WS',
|
|
332
|
+
startedAt: new Date().toISOString(), entriesCaptured: [],
|
|
333
|
+
});
|
|
334
|
+
const files = Object.keys(fsState.sessionsDir);
|
|
335
|
+
expect(files).toHaveLength(1);
|
|
336
|
+
// Filename is a safe sha256 hex digest, not the raw unsafe string.
|
|
337
|
+
expect(files[0]).toMatch(/^[0-9a-f]{64}\.json$/);
|
|
338
|
+
expect(readSession()?.sessionId).toBe('sess-hashed');
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
describe('GC of expired closed conversation markers', () => {
|
|
342
|
+
it('deletes a closed marker whose closedAt is older than the 2h resume window', () => {
|
|
343
|
+
const stale = {
|
|
344
|
+
sessionId: 'sess-stale', workspaceId: 'w1', workspaceName: 'WS',
|
|
345
|
+
startedAt: '2026-01-01T00:00:00.000Z', entriesCaptured: [],
|
|
346
|
+
closedAt: new Date(Date.now() - 3 * 60 * 60 * 1000).toISOString(), // 3h ago
|
|
347
|
+
conversationId: 'conv-stale',
|
|
348
|
+
};
|
|
349
|
+
fsState.sessionsDir['conv-stale.json'] = JSON.stringify(stale);
|
|
350
|
+
// Any call that touches the sessions dir triggers GC.
|
|
351
|
+
withConversationId('conv-unrelated');
|
|
352
|
+
readSession();
|
|
353
|
+
expect(fsState.sessionsDir['conv-stale.json']).toBeUndefined();
|
|
354
|
+
});
|
|
355
|
+
it('keeps a closed marker still within the 2h resume window', () => {
|
|
356
|
+
const recent = {
|
|
357
|
+
sessionId: 'sess-recent', workspaceId: 'w1', workspaceName: 'WS',
|
|
358
|
+
startedAt: '2026-01-01T00:00:00.000Z', entriesCaptured: [],
|
|
359
|
+
closedAt: new Date(Date.now() - 30 * 60 * 1000).toISOString(), // 30 min ago
|
|
360
|
+
conversationId: 'conv-recent',
|
|
361
|
+
};
|
|
362
|
+
fsState.sessionsDir['conv-recent.json'] = JSON.stringify(recent);
|
|
363
|
+
withConversationId('conv-unrelated');
|
|
364
|
+
readSession();
|
|
365
|
+
expect(fsState.sessionsDir['conv-recent.json']).toBeDefined();
|
|
366
|
+
});
|
|
367
|
+
it('never GCs an ACTIVE (no closedAt) conversation marker regardless of age', () => {
|
|
368
|
+
const activeOld = {
|
|
369
|
+
sessionId: 'sess-active-old', workspaceId: 'w1', workspaceName: 'WS',
|
|
370
|
+
startedAt: '2020-01-01T00:00:00.000Z', entriesCaptured: [],
|
|
371
|
+
conversationId: 'conv-active-old',
|
|
372
|
+
};
|
|
373
|
+
fsState.sessionsDir['conv-active-old.json'] = JSON.stringify(activeOld);
|
|
374
|
+
withConversationId('conv-unrelated');
|
|
375
|
+
readSession();
|
|
376
|
+
expect(fsState.sessionsDir['conv-active-old.json']).toBeDefined();
|
|
377
|
+
});
|
|
378
|
+
});
|
|
198
379
|
//# sourceMappingURL=session-state-machine.test.js.map
|