Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
d8f02d5
feat(runtime): connect frontend through one runtime client
qiyuanhuakai Oct 11, 2026
49b5b8b
fix(bridge): escalate Windows graceful-stop rejection to forced teardown
qiyuanhuakai Oct 12, 2026
c8f51f8
feat(runtime): scope reactive state and bound session caches
qiyuanhuakai Oct 12, 2026
bd4af2a
Merge branch 'feat/v090-runtime-connection' into feat/v090-runtime-state
qiyuanhuakai Oct 12, 2026
ea5a88e
fix(bridge): scope ACP workspaces and release blocked history reads
qiyuanhuakai Oct 12, 2026
fc596f2
fix(bridge): resume shared stdout when a Codex raw read is cancelled
qiyuanhuakai Oct 12, 2026
9c104ce
fix(runtime): parse Codex RPC envelopes by JSON depth
qiyuanhuakai Oct 12, 2026
26f923b
fix(bridge): validate history spool ownership per platform
qiyuanhuakai Oct 12, 2026
66f1597
fix(runtime): admit new sessions into the visible window
qiyuanhuakai Oct 12, 2026
c1b011b
fix(runtime): keep channel handles active when finish fails at admission
qiyuanhuakai Oct 12, 2026
74c872f
fix(ci): install Playwright chromium in the validate job
qiyuanhuakai Oct 12, 2026
a55b743
feat(runtime): render one unified session tree across harnesses
qiyuanhuakai Oct 12, 2026
528bcac
fix(runtime): code-split the unified session tree out of the entry bu…
qiyuanhuakai Oct 12, 2026
9891aa6
fix(test): register unified-tree branch points in the dsh guard inven…
qiyuanhuakai Oct 12, 2026
0a12b4c
fix(bridge): wake a parked Codex history read on cancel before the fi…
qiyuanhuakai Oct 12, 2026
7d1172f
fix(runtime): select native sub-session rows by their canonical sessi…
qiyuanhuakai Oct 12, 2026
a8ee4d9
fix(runtime): bound full-index search and guard re-entrant selection …
qiyuanhuakai Oct 12, 2026
09470c6
refactor(ui): route messages and actions by qualified session
qiyuanhuakai Oct 12, 2026
a2292e2
refactor(ui): scope native controls and monitors per harness
qiyuanhuakai Oct 12, 2026
081ff62
refactor(ui): add task 23 evidence capture helpers
qiyuanhuakai Oct 12, 2026
c0c4429
fix(bundle): code-split viewer renderers out of the entry bundle
qiyuanhuakai Oct 12, 2026
292113c
feat(ui): unify workspace access and preserve-data onboarding
qiyuanhuakai Oct 12, 2026
e0f83b8
refactor(ui): add task 24 evidence capture helpers
qiyuanhuakai Oct 12, 2026
4ba0d76
fix(runtime): derive legacy digests without Node crypto in the renderer
qiyuanhuakai Oct 12, 2026
18d8ae8
feat(remote): manage connection and execution profiles
qiyuanhuakai Oct 12, 2026
0b1f3fc
feat(remote): add authenticated SSH transport and host trust
qiyuanhuakai Oct 13, 2026
f6564a8
refactor(ui): add task 26 evidence capture helpers
qiyuanhuakai Oct 13, 2026
0ebcbe0
feat(remote): bootstrap and update bridges on the correct host
qiyuanhuakai Oct 13, 2026
8dbfcd1
feat(hpc): journal Slurm jobs with persistent control ownership
qiyuanhuakai Oct 13, 2026
93c96d5
refactor(ui): add task 28 evidence capture helpers
qiyuanhuakai Oct 13, 2026
495370c
fix(packaging): package the Electron main-process module closure
qiyuanhuakai Oct 13, 2026
c09d733
fix(bridge): serve the profile HTTP API from the real bridge server
qiyuanhuakai Oct 13, 2026
ddbbeda
fix(bridge): serialize profile store writes and use unique temp files
qiyuanhuakai Oct 13, 2026
b7114c6
fix(bridge): retire pending dials on profile disconnect or delete
qiyuanhuakai Oct 13, 2026
a386b51
fix(shared): reject embedded secrets in every connection locator
qiyuanhuakai Oct 13, 2026
620e132
fix(bridge): allocate a valid SSH port, survive spawn errors, close r…
qiyuanhuakai Oct 13, 2026
30a9933
fix(bridge): release the staged package on every remote install failure
qiyuanhuakai Oct 13, 2026
c9a3580
fix(bridge): keep the control-store command alive and release its fence
qiyuanhuakai Oct 13, 2026
5f1ee7c
fix(bridge): make the single-writer fence atomic and recheck state roots
qiyuanhuakai Oct 13, 2026
79ddad2
fix(bridge): validate sbatch fields and fail closed on compute dir er…
qiyuanhuakai Oct 13, 2026
2257176
fix(bridge): classify a cancelled Codex frame by its envelope id
qiyuanhuakai Oct 13, 2026
497c839
fix(app): read the session index through the non-contending client path
qiyuanhuakai Oct 13, 2026
2f5f91a
fix(app): route session actions and driver commands to their real tar…
qiyuanhuakai Oct 13, 2026
3848394
fix(app): restore local app browsing and default-harness provider con…
qiyuanhuakai Oct 13, 2026
c9f89d1
fix(app): align capability resolution with real driver manifests and …
qiyuanhuakai Oct 13, 2026
c0c66b6
fix(app): preserve source checksums in the browser migration adapter
qiyuanhuakai Oct 13, 2026
5d98cc2
test(app): update test doubles for the readIndex seam and dsh branch …
qiyuanhuakai Oct 13, 2026
836f3ea
fix(test): record QA cleanup receipts outside the ignored evidence tree
qiyuanhuakai Oct 13, 2026
0d87869
feat(hpc): relay compute runtimes through Slurm steps
qiyuanhuakai Oct 13, 2026
3418809
refactor(ui): add task 29 evidence capture helpers
qiyuanhuakai Oct 13, 2026
c32cda1
feat(ui): manage SSH and Slurm execution lifecycle
qiyuanhuakai Oct 13, 2026
395a828
refactor(ui): add task 30 evidence capture helpers
qiyuanhuakai Oct 13, 2026
9abd7d3
feat(thread): bind logical threads to native harness sessions
qiyuanhuakai Oct 15, 2026
d03c800
refactor(ui): add task 31 evidence capture helpers
qiyuanhuakai Oct 15, 2026
c27f3c9
feat(thread): project causal multi-harness timelines
qiyuanhuakai Oct 15, 2026
6d58b4b
refactor(ui): add task 32 evidence capture helpers
qiyuanhuakai Oct 15, 2026
3ecc70b
feat(context): add bounded checkpoints and scoped evidence access
qiyuanhuakai Oct 15, 2026
1be0f21
refactor(ui): add task 33 evidence capture helpers
qiyuanhuakai Oct 15, 2026
ad3a101
fix(hpc): close PR #183 credential-boundary and handshake findings
qiyuanhuakai Oct 15, 2026
699ac02
fix(runtime): address the five PR #184 review findings on the connect…
qiyuanhuakai Oct 15, 2026
2995605
fix(bridge,app): stop stale state being reused across a boundary
qiyuanhuakai Oct 15, 2026
7af80f4
Merge branch 'feat/v090-runtime-state' into feat/v090-task-29-slurm-s…
qiyuanhuakai Oct 15, 2026
200eb5a
Merge branch 'feat/v090-task-29-slurm-step-relay' into feat/v090-task…
qiyuanhuakai Oct 15, 2026
60073c3
Merge branch 'feat/v090-task-30-connection-lifecycle' into feat/v090-…
qiyuanhuakai Oct 15, 2026
18cbc99
Merge branch 'feat/v090-task-31-thread-bindings' into feat/v090-task-…
qiyuanhuakai Oct 15, 2026
fa209a8
Merge branch 'feat/v090-task-32-turn-timeline' into feat/v090-task-33…
qiyuanhuakai Oct 15, 2026
5a377dc
fix(shared): close the three PR #189 findings on context checkpoint b…
qiyuanhuakai Oct 17, 2026
f2ccd12
fix(shared): bound the wire budget by the retained payload, not the p…
qiyuanhuakai Oct 18, 2026
5a00472
fix(shared): declare the evidence query ports as the shared reader an…
qiyuanhuakai Oct 19, 2026
0b871ad
fix(bridge): escalate Windows stops instead of waiting out an unstopp…
qiyuanhuakai Oct 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
9 changes: 9 additions & 0 deletions .github/workflows/build-electron.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ jobs:
- name: Lint and type-check
run: pnpm lint

# app/runtime/pinStore.test.ts runs the offline pin store against a real
# IndexedDB by driving Playwright's Chromium. Install exactly the browser
# revision pinned by the exact "playwright" version in package.json
# (1.62.1 -> chromium + chromium-headless-shell 1234) together with its
# system dependencies; otherwise the launch fails because the executable
# is absent. "chromium" includes the headless shell this test uses.
- name: Install Playwright browser for storage tests
run: pnpm exec playwright install --with-deps chromium

- name: Run tests
run: pnpm test

Expand Down
644 changes: 595 additions & 49 deletions app/App.vue

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions app/acpProcessManager.lifecycle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,39 @@ describe('ACP process generation lifecycle', () => {
await manager.stopAll();
}
});

it('keeps the agent running and clears the raw tap when a runtime client disconnects', async () => {
const manager = createAcpProcessManager();
try {
await manager.reconcile([
{
id: 'runtime-detach',
name: 'Runtime detach',
command: process.execPath,
args: ['-e', agentScript],
enabled: true,
},
]);
const client = new TestClient();
const attachment = manager.attach('runtime-detach', client, { runtime: true });
const tapped: string[] = [];
attachment.setRawTap((chunk) => tapped.push(Buffer.from(chunk).toString('utf8')));

client.close();

expect(manager.getStatus()[0]).toMatchObject({
state: 'running',
owned: true,
connected: false,
});
client.emit(
'message',
JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'ping', params: {} }),
);
await new Promise((resolve) => setTimeout(resolve, 50));
expect(tapped).toEqual([]);
} finally {
await manager.stopAll();
}
});
});
35 changes: 35 additions & 0 deletions app/acpProcessManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,4 +277,39 @@ describe('acpProcessManager', () => {
expect(manager.getStatus()[0]?.droppedFrames).toBe(1);
await manager.stopAll();
});

it('delivers runtime stdout to the selected history raw tap and stops once it is cleared', async () => {
const script = [
"const readline = require('node:readline');",
"readline.createInterface({ input: process.stdin }).on('line', () => {",
" process.stdout.write(JSON.stringify({ jsonrpc: '2.0', method: 'note', params: { token: 'RAW' } }) + '\\n');",
'});',
].join('\n');
const manager = createAcpProcessManager();
await manager.reconcile([
{ id: 'tap', name: 'Tap ACP', command: process.execPath, args: ['-e', script], enabled: true },
]);
const client = new TestClient();
const attachment = manager.attach('tap', client, { runtime: true });
const tapped: string[] = [];
attachment.setRawTap((chunk) => tapped.push(Buffer.from(chunk).toString('utf8')));
const trigger = () =>
client.emit(
'message',
JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'ping', params: {} }),
);

trigger();
await vi.waitFor(() => expect(tapped.join('')).toContain('RAW'));
expect(client.sent.some((payload) => payload.includes('RAW'))).toBe(true);

attachment.setRawTap(undefined);
const before = tapped.join('').split('RAW').length;
trigger();
await new Promise((resolve) => setTimeout(resolve, 80));
expect(tapped.join('').split('RAW').length).toBe(before);

await manager.stopAll();
expect(manager.getStatus()[0]?.state).toBe('stopped');
});
});
53 changes: 53 additions & 0 deletions app/acpProcessProtocol.boundaries.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,59 @@ describe('ACP stdout protocol boundaries', () => {
expect(send).toHaveBeenCalledExactlyOnceWith(valid);
});

it('feeds a runtime-owned raw tap the exact stdout bytes before the 2MiB assembly drops them', () => {
const send = vi.fn();
const tapped: Uint8Array[] = [];
const entry = {
agent: { id: 'agent' },
status: { state: 'running', droppedFrames: 0 },
stdoutBuffer: '',
stdoutQueue: Promise.resolve(),
pendingAgentResponses: new Map(),
client: { send, close: vi.fn() },
clientGeneration: 1,
child: { stdin: { write: vi.fn() } },
runtimeOwned: true,
rawTap: (chunk: Uint8Array) => tapped.push(chunk),
};
const entries = new Map([['agent', entry]]);
const forward = createAcpStdoutForwarder({ entries });
const oversized = 'x'.repeat(2 * 1024 * 1024 + 1);
const valid = JSON.stringify({ jsonrpc: '2.0', method: 'session/update', params: {} });
const raw = `${oversized}\n${valid}\n`;

forward(entry, raw);

expect(Buffer.concat(tapped.map((chunk) => Buffer.from(chunk))).toString('utf8')).toBe(raw);
expect(entry.status.droppedFrames).toBe(1);
expect(send).toHaveBeenCalledExactlyOnceWith(valid);
});

it('never taps a legacy client even when a tap function is present', async () => {
const send = vi.fn();
const tap = vi.fn();
const entry = {
agent: { id: 'agent' },
status: { state: 'running', droppedFrames: 0 },
stdoutBuffer: '',
stdoutQueue: Promise.resolve(),
pendingAgentResponses: new Map(),
client: { send },
clientGeneration: 1,
child: { stdin: { write: vi.fn() } },
rawTap: tap,
};
const entries = new Map([['agent', entry]]);
const forward = createAcpStdoutForwarder({ entries });
const line = JSON.stringify({ jsonrpc: '2.0', method: 'session/update', params: {} });

forward(entry, `${line}\n`);
await entry.stdoutQueue;

expect(tap).not.toHaveBeenCalled();
expect(send).toHaveBeenCalledExactlyOnceWith(line);
});

it('preserves UTF-8 code points split across stdout chunks', async () => {
const send = vi.fn();
const entry = {
Expand Down
30 changes: 30 additions & 0 deletions app/acpProcessReconnect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,34 @@ describe('AcpProcessManager reconnect transport', () => {
}),
);
});

it('delivers the same native frame to the runtime client and the raw history tap', async () => {
const script = [
"const readline=require('node:readline')",
"readline.createInterface({input:process.stdin}).on('line',(line)=>{",
'JSON.parse(line)',
"process.stdout.write(JSON.stringify({jsonrpc:'2.0',method:'session/update',params:{sessionId:'s1',update:{sessionUpdate:'agent_message_chunk',content:{type:'text',text:'added'}}}})+'\\n')",
'})',
].join(';');
manager = createAcpProcessManager();
await manager.reconcile([
{
id: 'frame',
name: 'Frame ACP',
command: process.execPath,
args: ['-e', script],
enabled: true,
},
]);
const client = new MockClient();
const attachment = manager.attach('frame', client, { runtime: true });
const tapped: string[] = [];
attachment.setRawTap((chunk) => tapped.push(Buffer.from(chunk).toString('utf8')));

client.emit('message', JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'ping', params: {} }));

await vi.waitFor(() => expect(client.sent).toHaveLength(1));
expect(tapped.join('')).toContain('"text":"added"');
expect(JSON.parse(client.sent[0] ?? '{}')).toMatchObject({ method: 'session/update' });
});
});
19 changes: 17 additions & 2 deletions app/acpTerminalManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ describe('acpTerminalManager', () => {
setInterval(() => {}, 1000);
`;
let descendantPid: number | undefined;
const failures: unknown[] = [];

try {
await manager.create({ command: process.execPath, args: ['-e', script, pidPath] });
Expand All @@ -64,14 +65,28 @@ describe('acpTerminalManager', () => {
await manager.stopAll();

expect(() => process.kill(activeDescendantPid, 0)).toThrow();
} catch (error) {
failures.push(error);
} finally {
if (descendantPid) {
try {
process.kill(descendantPid, 'SIGKILL');
} catch {}
}
await manager.stopAll();
await rm(directory, { recursive: true, force: true });
try {
await manager.stopAll();
} catch (error) {
failures.push(error);
}
try {
await rm(directory, { recursive: true, force: true });
} catch (error) {
failures.push(error);
}
}
if (failures.length === 1) throw failures[0];
if (failures.length > 1) {
throw new AggregateError(failures, 'Terminal shutdown and cleanup failed', { cause: failures[0] });
}
});

Expand Down
Loading
Loading