mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-06 23:49:41 +02:00
fix(remote): authorize the attach wake first; tell the caller what happened to its bytes
Review round 3 on #439. - The attachRemoteSession branch of POST /api/sessions ran `ensureHostAwake` before the multi-user gates, so a non-admin could have any configured host's `wakeCommand` spawned (or a packet broadcast) and the request held for the wake budget, then be refused for the workingDir. The admin gate now comes first, before the host is even looked up; remote hosts are admin-only infrastructure everywhere else. Route test: wake spy empty, 403. - The non-wait input route answers `{buffered:true}` when the registry took the chunk and `{buffered:true, dropped:true}` when it was over the cap and is gone (`RemoteInputOutcome` gains 'dropped'); additive to the bare `{}`. - The send-and-wait path answers OPERATION_FAILED when the host never comes back, like create and attach, instead of writing into the stalled pane and reporting delivered:true plus a timeout. - The flush writes with `fromUser: true`, so a first prompt buffered through a wake can still name the tab. Docs: api-reference (input route), remote-sessions.md (two invariants), CLAUDE.md key pattern. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QdGP4jUTjc9J2RYYykDrCG
This commit is contained in:
co-authored by
Claude Opus 5
parent
1040f6c489
commit
5bb489addb
@@ -370,6 +370,24 @@ describe('RemoteWakeRegistry', () => {
|
||||
expect(h.events).not.toContain('remote:sessionReconnected');
|
||||
});
|
||||
|
||||
it('reports an oversized chunk as dropped, and flushes as user input so the tab can be named', async () => {
|
||||
const h = harness();
|
||||
h.probe.mockResolvedValue(false);
|
||||
let release: (() => void) | undefined;
|
||||
h.waitUntilReady.mockImplementation(() => new Promise<boolean>((resolve) => (release = () => resolve(true))));
|
||||
await expect(h.registry.handleInput(h.session, 'ok')).resolves.toBe('buffered');
|
||||
// Over the cap: never enters the buffer, and the caller is told — a bare 200 could
|
||||
// not distinguish delivered from buffered from gone.
|
||||
await expect(h.registry.handleInput(h.session, 'x'.repeat(REMOTE_WAKE_PENDING_MAX_BYTES + 1))).resolves.toBe(
|
||||
'dropped'
|
||||
);
|
||||
expect(h.registry.pendingBytes('sess-1')).toBe(2);
|
||||
release?.();
|
||||
await h.registry.wake(h.session);
|
||||
// `fromUser`: a first prompt that was buffered through a wake may still name the tab.
|
||||
expect(h.writeViaMux).toHaveBeenCalledWith('ok', { fromUser: true });
|
||||
});
|
||||
|
||||
it('drops the buffer when a flush write fails, so nothing is replayed by a later wake', async () => {
|
||||
// Retaining the chunk was the earlier behaviour, and it was worse: the wake still
|
||||
// resolves and marks the host reachable, so the next input takes the deliver path
|
||||
|
||||
Reference in New Issue
Block a user