@vibelet/cli 0.1.38 → 1.0.1
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/README.md +80 -0
- package/bin/cloudflared-quick-tunnel.mjs +11 -0
- package/bin/cloudflared-resolver.mjs +171 -0
- package/bin/vibelet-runtime-policy.mjs +36 -0
- package/bin/vibelet.cjs +12 -0
- package/bin/vibelet.mjs +1235 -0
- package/dist/index.cjs +126 -0
- package/package.json +24 -22
- package/app.json +0 -5
- package/dist/advertised-hosts.d.ts +0 -34
- package/dist/advertised-hosts.d.ts.map +0 -1
- package/dist/advertised-hosts.js +0 -176
- package/dist/advertised-hosts.js.map +0 -1
- package/dist/advertised-hosts.test.d.ts +0 -2
- package/dist/advertised-hosts.test.d.ts.map +0 -1
- package/dist/advertised-hosts.test.js +0 -96
- package/dist/advertised-hosts.test.js.map +0 -1
- package/dist/audit.d.ts +0 -30
- package/dist/audit.d.ts.map +0 -1
- package/dist/audit.js +0 -73
- package/dist/audit.js.map +0 -1
- package/dist/audit.test.d.ts +0 -2
- package/dist/audit.test.d.ts.map +0 -1
- package/dist/audit.test.js +0 -33
- package/dist/audit.test.js.map +0 -1
- package/dist/auth.d.ts +0 -6
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js +0 -27
- package/dist/auth.js.map +0 -1
- package/dist/claude-hooks.d.ts +0 -58
- package/dist/claude-hooks.d.ts.map +0 -1
- package/dist/claude-hooks.js +0 -129
- package/dist/claude-hooks.js.map +0 -1
- package/dist/cli-version.d.ts +0 -3
- package/dist/cli-version.d.ts.map +0 -1
- package/dist/cli-version.js +0 -35
- package/dist/cli-version.js.map +0 -1
- package/dist/cli-version.test.d.ts +0 -2
- package/dist/cli-version.test.d.ts.map +0 -1
- package/dist/cli-version.test.js +0 -38
- package/dist/cli-version.test.js.map +0 -1
- package/dist/config.d.ts +0 -30
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -327
- package/dist/config.js.map +0 -1
- package/dist/config.test.d.ts +0 -2
- package/dist/config.test.d.ts.map +0 -1
- package/dist/config.test.js +0 -184
- package/dist/config.test.js.map +0 -1
- package/dist/dev-auth.test.d.ts +0 -2
- package/dist/dev-auth.test.d.ts.map +0 -1
- package/dist/dev-auth.test.js +0 -154
- package/dist/dev-auth.test.js.map +0 -1
- package/dist/dev-script.test.d.ts +0 -2
- package/dist/dev-script.test.d.ts.map +0 -1
- package/dist/dev-script.test.js +0 -412
- package/dist/dev-script.test.js.map +0 -1
- package/dist/drivers/claude.d.ts +0 -34
- package/dist/drivers/claude.d.ts.map +0 -1
- package/dist/drivers/claude.js +0 -413
- package/dist/drivers/claude.js.map +0 -1
- package/dist/drivers/claude.test.d.ts +0 -2
- package/dist/drivers/claude.test.d.ts.map +0 -1
- package/dist/drivers/claude.test.js +0 -951
- package/dist/drivers/claude.test.js.map +0 -1
- package/dist/drivers/codex.d.ts +0 -38
- package/dist/drivers/codex.d.ts.map +0 -1
- package/dist/drivers/codex.js +0 -771
- package/dist/drivers/codex.js.map +0 -1
- package/dist/drivers/codex.test.d.ts +0 -2
- package/dist/drivers/codex.test.d.ts.map +0 -1
- package/dist/drivers/codex.test.js +0 -939
- package/dist/drivers/codex.test.js.map +0 -1
- package/dist/drivers/types.d.ts +0 -14
- package/dist/drivers/types.d.ts.map +0 -1
- package/dist/drivers/types.js +0 -2
- package/dist/drivers/types.js.map +0 -1
- package/dist/e2e.test.d.ts +0 -2
- package/dist/e2e.test.d.ts.map +0 -1
- package/dist/e2e.test.js +0 -111
- package/dist/e2e.test.js.map +0 -1
- package/dist/identity.d.ts +0 -10
- package/dist/identity.d.ts.map +0 -1
- package/dist/identity.js +0 -66
- package/dist/identity.js.map +0 -1
- package/dist/identity.test.d.ts +0 -2
- package/dist/identity.test.d.ts.map +0 -1
- package/dist/identity.test.js +0 -25
- package/dist/identity.test.js.map +0 -1
- package/dist/index-entry.test.d.ts +0 -2
- package/dist/index-entry.test.d.ts.map +0 -1
- package/dist/index-entry.test.js +0 -272
- package/dist/index-entry.test.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -707
- package/dist/index.js.map +0 -1
- package/dist/logger.d.ts +0 -31
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js +0 -75
- package/dist/logger.js.map +0 -1
- package/dist/metrics.d.ts +0 -52
- package/dist/metrics.d.ts.map +0 -1
- package/dist/metrics.js +0 -89
- package/dist/metrics.js.map +0 -1
- package/dist/pairing-store.d.ts +0 -29
- package/dist/pairing-store.d.ts.map +0 -1
- package/dist/pairing-store.js +0 -131
- package/dist/pairing-store.js.map +0 -1
- package/dist/pairing-store.test.d.ts +0 -2
- package/dist/pairing-store.test.d.ts.map +0 -1
- package/dist/pairing-store.test.js +0 -47
- package/dist/pairing-store.test.js.map +0 -1
- package/dist/paths.d.ts +0 -16
- package/dist/paths.d.ts.map +0 -1
- package/dist/paths.js +0 -18
- package/dist/paths.js.map +0 -1
- package/dist/perf-compare.d.ts +0 -13
- package/dist/perf-compare.d.ts.map +0 -1
- package/dist/perf-compare.js +0 -125
- package/dist/perf-compare.js.map +0 -1
- package/dist/port-conflict.d.ts +0 -9
- package/dist/port-conflict.d.ts.map +0 -1
- package/dist/port-conflict.js +0 -33
- package/dist/port-conflict.js.map +0 -1
- package/dist/port-conflict.test.d.ts +0 -2
- package/dist/port-conflict.test.d.ts.map +0 -1
- package/dist/port-conflict.test.js +0 -38
- package/dist/port-conflict.test.js.map +0 -1
- package/dist/process-scanner.d.ts +0 -43
- package/dist/process-scanner.d.ts.map +0 -1
- package/dist/process-scanner.js +0 -453
- package/dist/process-scanner.js.map +0 -1
- package/dist/process-scanner.perf.test.d.ts +0 -2
- package/dist/process-scanner.perf.test.d.ts.map +0 -1
- package/dist/process-scanner.perf.test.js +0 -186
- package/dist/process-scanner.perf.test.js.map +0 -1
- package/dist/process-scanner.test.d.ts +0 -2
- package/dist/process-scanner.test.d.ts.map +0 -1
- package/dist/process-scanner.test.js +0 -399
- package/dist/process-scanner.test.js.map +0 -1
- package/dist/push-protocol.d.ts +0 -15
- package/dist/push-protocol.d.ts.map +0 -1
- package/dist/push-protocol.js +0 -23
- package/dist/push-protocol.js.map +0 -1
- package/dist/push-protocol.test.d.ts +0 -2
- package/dist/push-protocol.test.d.ts.map +0 -1
- package/dist/push-protocol.test.js +0 -57
- package/dist/push-protocol.test.js.map +0 -1
- package/dist/push-store.d.ts +0 -22
- package/dist/push-store.d.ts.map +0 -1
- package/dist/push-store.js +0 -103
- package/dist/push-store.js.map +0 -1
- package/dist/push-store.test.d.ts +0 -2
- package/dist/push-store.test.d.ts.map +0 -1
- package/dist/push-store.test.js +0 -79
- package/dist/push-store.test.js.map +0 -1
- package/dist/push.d.ts +0 -65
- package/dist/push.d.ts.map +0 -1
- package/dist/push.js +0 -202
- package/dist/push.js.map +0 -1
- package/dist/push.test.d.ts +0 -2
- package/dist/push.test.d.ts.map +0 -1
- package/dist/push.test.js +0 -199
- package/dist/push.test.js.map +0 -1
- package/dist/safe-stdio.d.ts +0 -3
- package/dist/safe-stdio.d.ts.map +0 -1
- package/dist/safe-stdio.js +0 -46
- package/dist/safe-stdio.js.map +0 -1
- package/dist/scanner.d.ts +0 -30
- package/dist/scanner.d.ts.map +0 -1
- package/dist/scanner.js +0 -859
- package/dist/scanner.js.map +0 -1
- package/dist/scanner.perf.test.d.ts +0 -2
- package/dist/scanner.perf.test.d.ts.map +0 -1
- package/dist/scanner.perf.test.js +0 -320
- package/dist/scanner.perf.test.js.map +0 -1
- package/dist/scanner.test.d.ts +0 -2
- package/dist/scanner.test.d.ts.map +0 -1
- package/dist/scanner.test.js +0 -948
- package/dist/scanner.test.js.map +0 -1
- package/dist/session-inventory.d.ts +0 -63
- package/dist/session-inventory.d.ts.map +0 -1
- package/dist/session-inventory.js +0 -525
- package/dist/session-inventory.js.map +0 -1
- package/dist/session-inventory.perf.test.d.ts +0 -2
- package/dist/session-inventory.perf.test.d.ts.map +0 -1
- package/dist/session-inventory.perf.test.js +0 -220
- package/dist/session-inventory.perf.test.js.map +0 -1
- package/dist/session-inventory.test.d.ts +0 -2
- package/dist/session-inventory.test.d.ts.map +0 -1
- package/dist/session-inventory.test.js +0 -712
- package/dist/session-inventory.test.js.map +0 -1
- package/dist/session-manager.d.ts +0 -75
- package/dist/session-manager.d.ts.map +0 -1
- package/dist/session-manager.js +0 -1515
- package/dist/session-manager.js.map +0 -1
- package/dist/session-manager.test.d.ts +0 -2
- package/dist/session-manager.test.d.ts.map +0 -1
- package/dist/session-manager.test.js +0 -2861
- package/dist/session-manager.test.js.map +0 -1
- package/dist/session-store.d.ts +0 -42
- package/dist/session-store.d.ts.map +0 -1
- package/dist/session-store.js +0 -163
- package/dist/session-store.js.map +0 -1
- package/dist/session-store.test.d.ts +0 -2
- package/dist/session-store.test.d.ts.map +0 -1
- package/dist/session-store.test.js +0 -236
- package/dist/session-store.test.js.map +0 -1
- package/dist/session-title.d.ts +0 -6
- package/dist/session-title.d.ts.map +0 -1
- package/dist/session-title.js +0 -105
- package/dist/session-title.js.map +0 -1
- package/dist/session-title.perf.test.d.ts +0 -2
- package/dist/session-title.perf.test.d.ts.map +0 -1
- package/dist/session-title.perf.test.js +0 -99
- package/dist/session-title.perf.test.js.map +0 -1
- package/dist/session-title.test.d.ts +0 -2
- package/dist/session-title.test.d.ts.map +0 -1
- package/dist/session-title.test.js +0 -199
- package/dist/session-title.test.js.map +0 -1
- package/dist/shutdown-endpoint.test.d.ts +0 -2
- package/dist/shutdown-endpoint.test.d.ts.map +0 -1
- package/dist/shutdown-endpoint.test.js +0 -93
- package/dist/shutdown-endpoint.test.js.map +0 -1
- package/dist/storage-housekeeping.d.ts +0 -28
- package/dist/storage-housekeeping.d.ts.map +0 -1
- package/dist/storage-housekeeping.js +0 -76
- package/dist/storage-housekeeping.js.map +0 -1
- package/dist/storage-housekeeping.test.d.ts +0 -2
- package/dist/storage-housekeeping.test.d.ts.map +0 -1
- package/dist/storage-housekeeping.test.js +0 -65
- package/dist/storage-housekeeping.test.js.map +0 -1
- package/dist/test-daemon-harness.d.ts +0 -31
- package/dist/test-daemon-harness.d.ts.map +0 -1
- package/dist/test-daemon-harness.js +0 -337
- package/dist/test-daemon-harness.js.map +0 -1
- package/dist/token-auth.test.d.ts +0 -2
- package/dist/token-auth.test.d.ts.map +0 -1
- package/dist/token-auth.test.js +0 -52
- package/dist/token-auth.test.js.map +0 -1
- package/dist/utils.d.ts +0 -4
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js +0 -40
- package/dist/utils.js.map +0 -1
- package/dist/utils.test.d.ts +0 -2
- package/dist/utils.test.d.ts.map +0 -1
- package/dist/utils.test.js +0 -54
- package/dist/utils.test.js.map +0 -1
- package/dist/ws-data.d.ts +0 -4
- package/dist/ws-data.d.ts.map +0 -1
- package/dist/ws-data.js +0 -20
- package/dist/ws-data.js.map +0 -1
- package/dist/ws-data.test.d.ts +0 -2
- package/dist/ws-data.test.d.ts.map +0 -1
- package/dist/ws-data.test.js +0 -17
- package/dist/ws-data.test.js.map +0 -1
- package/perf-reporter.mjs +0 -138
- package/scripts/build-release.mjs +0 -41
- package/scripts/dev.mjs +0 -537
- package/src/advertised-hosts.test.ts +0 -125
- package/src/advertised-hosts.ts +0 -225
- package/src/audit.test.ts +0 -38
- package/src/audit.ts +0 -117
- package/src/auth.ts +0 -31
- package/src/claude-hooks.ts +0 -195
- package/src/cli-version.test.ts +0 -36
- package/src/cli-version.ts +0 -46
- package/src/config.test.ts +0 -254
- package/src/config.ts +0 -324
- package/src/dev-auth.test.ts +0 -183
- package/src/dev-script.test.ts +0 -511
- package/src/drivers/claude.test.ts +0 -1186
- package/src/drivers/claude.ts +0 -443
- package/src/drivers/codex.test.ts +0 -1096
- package/src/drivers/codex.ts +0 -879
- package/src/drivers/types.ts +0 -15
- package/src/e2e.test.ts +0 -139
- package/src/identity.test.ts +0 -26
- package/src/identity.ts +0 -82
- package/src/index-entry.test.ts +0 -336
- package/src/index.ts +0 -781
- package/src/logger.ts +0 -112
- package/src/metrics.ts +0 -117
- package/src/pairing-store.test.ts +0 -53
- package/src/pairing-store.ts +0 -154
- package/src/paths.ts +0 -19
- package/src/perf-compare.ts +0 -164
- package/src/port-conflict.test.ts +0 -45
- package/src/port-conflict.ts +0 -44
- package/src/process-scanner.perf.test.ts +0 -222
- package/src/process-scanner.test.ts +0 -575
- package/src/process-scanner.ts +0 -514
- package/src/push-protocol.test.ts +0 -74
- package/src/push-protocol.ts +0 -36
- package/src/push-store.test.ts +0 -89
- package/src/push-store.ts +0 -126
- package/src/push.test.ts +0 -234
- package/src/push.ts +0 -318
- package/src/safe-stdio.ts +0 -51
- package/src/scanner.perf.test.ts +0 -359
- package/src/scanner.test.ts +0 -1045
- package/src/scanner.ts +0 -924
- package/src/session-inventory.perf.test.ts +0 -250
- package/src/session-inventory.test.ts +0 -1002
- package/src/session-inventory.ts +0 -721
- package/src/session-manager.test.ts +0 -3430
- package/src/session-manager.ts +0 -1775
- package/src/session-store.test.ts +0 -276
- package/src/session-store.ts +0 -202
- package/src/session-title.perf.test.ts +0 -118
- package/src/session-title.test.ts +0 -286
- package/src/session-title.ts +0 -108
- package/src/shutdown-endpoint.test.ts +0 -95
- package/src/storage-housekeeping.test.ts +0 -78
- package/src/storage-housekeeping.ts +0 -111
- package/src/test-daemon-harness.ts +0 -410
- package/src/token-auth.test.ts +0 -67
- package/src/utils.test.ts +0 -65
- package/src/utils.ts +0 -47
- package/src/ws-data.test.ts +0 -20
- package/src/ws-data.ts +0 -26
- package/tsconfig.json +0 -12
package/src/drivers/types.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { ApprovalRequestPayload, ServerMessage } from '@vibelet/shared';
|
|
2
|
-
|
|
3
|
-
export type MessageHandler = (msg: ServerMessage) => void;
|
|
4
|
-
|
|
5
|
-
export interface Driver {
|
|
6
|
-
start(cwd: string, resumeSessionId?: string, approvalMode?: string): Promise<string>;
|
|
7
|
-
sendPrompt(text: string): void;
|
|
8
|
-
respondApproval(requestId: string, approved: boolean): boolean;
|
|
9
|
-
restorePendingApproval?(approval: ApprovalRequestPayload): void;
|
|
10
|
-
interrupt(): void;
|
|
11
|
-
stop(): void;
|
|
12
|
-
setApprovalMode(mode: string): void;
|
|
13
|
-
onMessage(handler: MessageHandler): void;
|
|
14
|
-
onExit(handler: (code: number | null) => void): void;
|
|
15
|
-
}
|
package/src/e2e.test.ts
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict';
|
|
2
|
-
import { mkdirSync } from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import test from 'node:test';
|
|
5
|
-
import type { SessionInfo } from '@vibelet/shared';
|
|
6
|
-
import {
|
|
7
|
-
cleanupDaemon,
|
|
8
|
-
createWebSocketRecorder,
|
|
9
|
-
openWebSocket,
|
|
10
|
-
spawnDaemon,
|
|
11
|
-
waitForWebSocketClose,
|
|
12
|
-
} from './test-daemon-harness.js';
|
|
13
|
-
|
|
14
|
-
function send(ws: { send: (data: string) => void }, payload: object): void {
|
|
15
|
-
ws.send(JSON.stringify(payload));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function responseData(message: Record<string, unknown>): Record<string, unknown> {
|
|
19
|
-
const data = message.data;
|
|
20
|
-
assert.ok(data && typeof data === 'object' && !Array.isArray(data));
|
|
21
|
-
return data as Record<string, unknown>;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const E2E_WAIT_TIMEOUT_MS = 10_000;
|
|
25
|
-
|
|
26
|
-
test('daemon automated e2e covers list/create/send/stream/resume without real agent binaries', async (t) => {
|
|
27
|
-
const daemon = await spawnDaemon({ useFakeClaudeCli: true });
|
|
28
|
-
t.after(() => cleanupDaemon(daemon));
|
|
29
|
-
|
|
30
|
-
const cwd = path.join(daemon.homeDir, 'workspace');
|
|
31
|
-
mkdirSync(cwd, { recursive: true });
|
|
32
|
-
|
|
33
|
-
const ws1 = await openWebSocket(`ws://127.0.0.1:${daemon.port}?token=${daemon.token}`);
|
|
34
|
-
const recorder1 = createWebSocketRecorder(ws1);
|
|
35
|
-
t.after(() => {
|
|
36
|
-
recorder1.stop();
|
|
37
|
-
try {
|
|
38
|
-
ws1.close();
|
|
39
|
-
} catch {}
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
send(ws1, { action: 'sessions.list', id: 'list-1', agent: 'claude', cwd });
|
|
43
|
-
const listBefore = await recorder1.waitFor((message) => message.type === 'response' && message.id === 'list-1', E2E_WAIT_TIMEOUT_MS);
|
|
44
|
-
assert.equal(listBefore.ok, true);
|
|
45
|
-
assert.deepEqual(responseData(listBefore).sessions, []);
|
|
46
|
-
|
|
47
|
-
send(ws1, { action: 'session.create', id: 'create-1', agent: 'claude', cwd });
|
|
48
|
-
const createResponse = await recorder1.waitFor((message) => message.type === 'response' && message.id === 'create-1', E2E_WAIT_TIMEOUT_MS);
|
|
49
|
-
assert.equal(createResponse.ok, true);
|
|
50
|
-
const pendingSessionId = String(responseData(createResponse).sessionId);
|
|
51
|
-
assert.match(pendingSessionId, /^pending_/);
|
|
52
|
-
|
|
53
|
-
const firstPrompt = 'First fake prompt for automated daemon e2e.';
|
|
54
|
-
send(ws1, {
|
|
55
|
-
action: 'session.send',
|
|
56
|
-
id: 'send-1',
|
|
57
|
-
sessionId: pendingSessionId,
|
|
58
|
-
agent: 'claude',
|
|
59
|
-
message: firstPrompt,
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
const sendResponse = await recorder1.waitFor((message) => message.type === 'response' && message.id === 'send-1', E2E_WAIT_TIMEOUT_MS);
|
|
63
|
-
assert.equal(sendResponse.ok, true);
|
|
64
|
-
|
|
65
|
-
const idUpdate = await recorder1.waitFor((message) =>
|
|
66
|
-
message.type === 'response'
|
|
67
|
-
&& typeof message.id === 'string'
|
|
68
|
-
&& message.id.startsWith('id_update_'),
|
|
69
|
-
E2E_WAIT_TIMEOUT_MS);
|
|
70
|
-
assert.equal(idUpdate.ok, true);
|
|
71
|
-
const idUpdateData = responseData(idUpdate);
|
|
72
|
-
assert.equal(idUpdateData.oldSessionId, pendingSessionId);
|
|
73
|
-
const actualSessionId = String(idUpdateData.sessionId);
|
|
74
|
-
assert.notEqual(actualSessionId, pendingSessionId);
|
|
75
|
-
|
|
76
|
-
await recorder1.waitFor((message) => message.type === 'session.done' && message.sessionId === actualSessionId, E2E_WAIT_TIMEOUT_MS);
|
|
77
|
-
const firstReply = recorder1.messages
|
|
78
|
-
.filter((message) => message.type === 'text.delta' && message.sessionId === actualSessionId)
|
|
79
|
-
.map((message) => String(message.content ?? ''))
|
|
80
|
-
.join('');
|
|
81
|
-
assert.equal(firstReply, `fake claude reply: ${firstPrompt}`);
|
|
82
|
-
|
|
83
|
-
send(ws1, { action: 'sessions.list', id: 'list-2', agent: 'claude', cwd });
|
|
84
|
-
const listAfter = await recorder1.waitFor((message) => message.type === 'response' && message.id === 'list-2', E2E_WAIT_TIMEOUT_MS);
|
|
85
|
-
assert.equal(listAfter.ok, true);
|
|
86
|
-
const listedSessions = responseData(listAfter).sessions as SessionInfo[];
|
|
87
|
-
const listedSession = listedSessions.find((session) => session.sessionId === actualSessionId);
|
|
88
|
-
assert.ok(listedSession);
|
|
89
|
-
assert.equal(listedSession.agent, 'claude');
|
|
90
|
-
assert.ok(listedSession.sources.includes('record'));
|
|
91
|
-
if (listedSession.sources.includes('daemon')) {
|
|
92
|
-
assert.equal(listedSession.runtime.state, 'daemonActive');
|
|
93
|
-
} else {
|
|
94
|
-
assert.equal(listedSession.runtime.state, 'idle');
|
|
95
|
-
}
|
|
96
|
-
assert.match(listedSession.title ?? '', /First fake prompt/);
|
|
97
|
-
|
|
98
|
-
ws1.close();
|
|
99
|
-
const closeCode = await waitForWebSocketClose(ws1, E2E_WAIT_TIMEOUT_MS);
|
|
100
|
-
assert.equal(closeCode, 1005);
|
|
101
|
-
recorder1.stop();
|
|
102
|
-
|
|
103
|
-
const ws2 = await openWebSocket(`ws://127.0.0.1:${daemon.port}?token=${daemon.token}`);
|
|
104
|
-
const recorder2 = createWebSocketRecorder(ws2);
|
|
105
|
-
t.after(() => {
|
|
106
|
-
recorder2.stop();
|
|
107
|
-
try {
|
|
108
|
-
ws2.close();
|
|
109
|
-
} catch {}
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
send(ws2, { action: 'session.resume', id: 'resume-1', sessionId: actualSessionId, agent: 'claude' });
|
|
113
|
-
const historyMessage = await recorder2.waitFor((message) => message.type === 'session.history' && message.sessionId === actualSessionId, E2E_WAIT_TIMEOUT_MS);
|
|
114
|
-
const resumeResponse = await recorder2.waitFor((message) => message.type === 'response' && message.id === 'resume-1', E2E_WAIT_TIMEOUT_MS);
|
|
115
|
-
assert.equal(resumeResponse.ok, true);
|
|
116
|
-
assert.deepEqual(historyMessage.messages, [
|
|
117
|
-
{ role: 'user', content: firstPrompt },
|
|
118
|
-
{ role: 'assistant', content: `fake claude reply: ${firstPrompt}` },
|
|
119
|
-
]);
|
|
120
|
-
|
|
121
|
-
const secondPrompt = 'Second fake prompt after websocket resume.';
|
|
122
|
-
send(ws2, {
|
|
123
|
-
action: 'session.send',
|
|
124
|
-
id: 'send-2',
|
|
125
|
-
sessionId: actualSessionId,
|
|
126
|
-
agent: 'claude',
|
|
127
|
-
message: secondPrompt,
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
const sendResponse2 = await recorder2.waitFor((message) => message.type === 'response' && message.id === 'send-2', E2E_WAIT_TIMEOUT_MS);
|
|
131
|
-
assert.equal(sendResponse2.ok, true);
|
|
132
|
-
await recorder2.waitFor((message) => message.type === 'session.done' && message.sessionId === actualSessionId, E2E_WAIT_TIMEOUT_MS);
|
|
133
|
-
|
|
134
|
-
const secondReply = recorder2.messages
|
|
135
|
-
.filter((message) => message.type === 'text.delta' && message.sessionId === actualSessionId)
|
|
136
|
-
.map((message) => String(message.content ?? ''))
|
|
137
|
-
.join('');
|
|
138
|
-
assert.match(secondReply, /Second fake prompt after websocket resume\./);
|
|
139
|
-
});
|
package/src/identity.test.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import test from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { mkdtemp, readFile, rm } from 'fs/promises';
|
|
4
|
-
import { tmpdir } from 'os';
|
|
5
|
-
import { join } from 'path';
|
|
6
|
-
import { loadOrCreateDaemonIdentity } from './identity.js';
|
|
7
|
-
|
|
8
|
-
test('loadOrCreateDaemonIdentity persists identity details and reuses the daemon id', async () => {
|
|
9
|
-
const dir = await mkdtemp(join(tmpdir(), 'vibelet-identity-'));
|
|
10
|
-
const identityPath = join(dir, 'identity.json');
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
const first = loadOrCreateDaemonIdentity(9876, identityPath);
|
|
14
|
-
const second = loadOrCreateDaemonIdentity(9999, identityPath);
|
|
15
|
-
const persisted = JSON.parse(await readFile(identityPath, 'utf8'));
|
|
16
|
-
|
|
17
|
-
assert.match(first.daemonId, /^d_/);
|
|
18
|
-
assert.equal(second.daemonId, first.daemonId);
|
|
19
|
-
assert.equal(second.daemonSecret, first.daemonSecret);
|
|
20
|
-
assert.equal(second.port, 9999);
|
|
21
|
-
assert.equal(persisted.port, 9999);
|
|
22
|
-
assert.equal(persisted.daemonId, first.daemonId);
|
|
23
|
-
} finally {
|
|
24
|
-
await rm(dir, { recursive: true, force: true });
|
|
25
|
-
}
|
|
26
|
-
});
|
package/src/identity.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
-
import { hostname } from 'os';
|
|
3
|
-
import { randomBytes } from 'crypto';
|
|
4
|
-
import { dirname } from 'path';
|
|
5
|
-
import { IDENTITY_PATH } from './paths.js';
|
|
6
|
-
|
|
7
|
-
export interface DaemonIdentity {
|
|
8
|
-
daemonId: string;
|
|
9
|
-
daemonSecret: string;
|
|
10
|
-
displayName: string;
|
|
11
|
-
canonicalHost: string;
|
|
12
|
-
port: number;
|
|
13
|
-
createdAt: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function base64Url(bytes: number): string {
|
|
17
|
-
return randomBytes(bytes).toString('base64url');
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function buildDaemonId(): string {
|
|
21
|
-
return `d_${base64Url(12)}`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function defaultDisplayName(): string {
|
|
25
|
-
return hostname();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function defaultCanonicalHost(): string {
|
|
29
|
-
const raw = hostname().trim().toLowerCase();
|
|
30
|
-
if (!raw) return 'localhost';
|
|
31
|
-
return raw.endsWith('.local') ? raw : `${raw}.local`;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function createIdentity(port: number): DaemonIdentity {
|
|
35
|
-
return {
|
|
36
|
-
daemonId: buildDaemonId(),
|
|
37
|
-
daemonSecret: base64Url(32),
|
|
38
|
-
displayName: defaultDisplayName(),
|
|
39
|
-
canonicalHost: defaultCanonicalHost(),
|
|
40
|
-
port,
|
|
41
|
-
createdAt: new Date().toISOString(),
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function writeIdentity(identity: DaemonIdentity, identityPath: string): void {
|
|
46
|
-
mkdirSync(dirname(identityPath), { recursive: true });
|
|
47
|
-
writeFileSync(identityPath, JSON.stringify(identity, null, 2) + '\n', 'utf8');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function loadOrCreateDaemonIdentity(port: number, identityPath = IDENTITY_PATH): DaemonIdentity {
|
|
51
|
-
try {
|
|
52
|
-
const raw = readFileSync(identityPath, 'utf8');
|
|
53
|
-
const parsed = JSON.parse(raw) as Partial<DaemonIdentity>;
|
|
54
|
-
if (
|
|
55
|
-
typeof parsed.daemonId === 'string' &&
|
|
56
|
-
typeof parsed.daemonSecret === 'string' &&
|
|
57
|
-
typeof parsed.displayName === 'string' &&
|
|
58
|
-
typeof parsed.canonicalHost === 'string' &&
|
|
59
|
-
typeof parsed.createdAt === 'string'
|
|
60
|
-
) {
|
|
61
|
-
const identity: DaemonIdentity = {
|
|
62
|
-
daemonId: parsed.daemonId,
|
|
63
|
-
daemonSecret: parsed.daemonSecret,
|
|
64
|
-
displayName: parsed.displayName,
|
|
65
|
-
canonicalHost: parsed.canonicalHost,
|
|
66
|
-
port: typeof parsed.port === 'number' && Number.isFinite(parsed.port) ? parsed.port : port,
|
|
67
|
-
createdAt: parsed.createdAt,
|
|
68
|
-
};
|
|
69
|
-
if (identity.port !== port) {
|
|
70
|
-
identity.port = port;
|
|
71
|
-
writeIdentity(identity, identityPath);
|
|
72
|
-
}
|
|
73
|
-
return identity;
|
|
74
|
-
}
|
|
75
|
-
} catch {
|
|
76
|
-
// Fall through to create a new identity.
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const identity = createIdentity(port);
|
|
80
|
-
writeIdentity(identity, identityPath);
|
|
81
|
-
return identity;
|
|
82
|
-
}
|
package/src/index-entry.test.ts
DELETED
|
@@ -1,336 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict';
|
|
2
|
-
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import test from 'node:test';
|
|
5
|
-
import type { PairingQrPayload } from '@vibelet/shared';
|
|
6
|
-
import {
|
|
7
|
-
cleanupDaemon,
|
|
8
|
-
openWebSocket,
|
|
9
|
-
readAuditLog,
|
|
10
|
-
spawnDaemon,
|
|
11
|
-
waitForHealth,
|
|
12
|
-
waitForStdout,
|
|
13
|
-
waitForWebSocketClose,
|
|
14
|
-
waitForWebSocketMessage,
|
|
15
|
-
} from './test-daemon-harness.js';
|
|
16
|
-
|
|
17
|
-
test('health endpoint reports daemon status and pairing flow can create then reset pairings', async (t) => {
|
|
18
|
-
const daemon = await spawnDaemon();
|
|
19
|
-
t.after(() => cleanupDaemon(daemon));
|
|
20
|
-
|
|
21
|
-
const initialHealth = await waitForHealth(daemon.port);
|
|
22
|
-
assert.equal(initialHealth.status, 'ok');
|
|
23
|
-
assert.equal(initialHealth.pairedDevices, 0);
|
|
24
|
-
assert.equal(typeof initialHealth.daemonId, 'string');
|
|
25
|
-
assert.equal(typeof initialHealth.metrics, 'object');
|
|
26
|
-
assert.equal(typeof initialHealth.sessionInventory, 'object');
|
|
27
|
-
assert.equal(
|
|
28
|
-
typeof (initialHealth.sessionInventory as { backfillInFlight: unknown }).backfillInFlight,
|
|
29
|
-
'boolean',
|
|
30
|
-
);
|
|
31
|
-
assert.equal(
|
|
32
|
-
typeof (initialHealth.sessionInventory as { cachedSessions: unknown }).cachedSessions,
|
|
33
|
-
'number',
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
const pairOpenResponse = await fetch(`http://127.0.0.1:${daemon.port}/pair/open`, { method: 'POST' });
|
|
37
|
-
assert.equal(pairOpenResponse.status, 200);
|
|
38
|
-
const pairPayload = await pairOpenResponse.json() as PairingQrPayload;
|
|
39
|
-
assert.equal(pairPayload.type, 'vibelet-pair');
|
|
40
|
-
assert.equal(typeof pairPayload.daemonId, 'string');
|
|
41
|
-
assert.equal(typeof pairPayload.pairNonce, 'string');
|
|
42
|
-
|
|
43
|
-
const createBody = {
|
|
44
|
-
daemonId: pairPayload.daemonId,
|
|
45
|
-
pairNonce: pairPayload.pairNonce,
|
|
46
|
-
deviceId: 'device-1',
|
|
47
|
-
deviceName: 'QA Phone',
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const createResponse = await fetch(`http://127.0.0.1:${daemon.port}/pair/create`, {
|
|
51
|
-
method: 'POST',
|
|
52
|
-
headers: { 'Content-Type': 'application/json' },
|
|
53
|
-
body: JSON.stringify(createBody),
|
|
54
|
-
});
|
|
55
|
-
assert.equal(createResponse.status, 200);
|
|
56
|
-
const createPayload = await createResponse.json() as Record<string, unknown>;
|
|
57
|
-
assert.equal(createPayload.deviceId, 'device-1');
|
|
58
|
-
assert.equal(typeof createPayload.pairToken, 'string');
|
|
59
|
-
|
|
60
|
-
const reusedNonceResponse = await fetch(`http://127.0.0.1:${daemon.port}/pair/create`, {
|
|
61
|
-
method: 'POST',
|
|
62
|
-
headers: { 'Content-Type': 'application/json' },
|
|
63
|
-
body: JSON.stringify(createBody),
|
|
64
|
-
});
|
|
65
|
-
assert.equal(reusedNonceResponse.status, 401);
|
|
66
|
-
assert.equal((await reusedNonceResponse.json() as Record<string, unknown>).error, 'pair_window_expired');
|
|
67
|
-
|
|
68
|
-
const pairedHealth = await waitForHealth(daemon.port);
|
|
69
|
-
assert.equal(pairedHealth.pairedDevices, 1);
|
|
70
|
-
|
|
71
|
-
const resetResponse = await fetch(`http://127.0.0.1:${daemon.port}/pair/reset`, { method: 'POST' });
|
|
72
|
-
assert.equal(resetResponse.status, 200);
|
|
73
|
-
assert.deepEqual(await resetResponse.json(), { ok: true });
|
|
74
|
-
|
|
75
|
-
const resetHealth = await waitForHealth(daemon.port);
|
|
76
|
-
assert.equal(resetHealth.pairedDevices, 0);
|
|
77
|
-
|
|
78
|
-
const missingFileUrl = new URL(`http://127.0.0.1:${daemon.port}/file`);
|
|
79
|
-
missingFileUrl.searchParams.set('token', daemon.token);
|
|
80
|
-
missingFileUrl.searchParams.set('path', path.join(daemon.homeDir, 'does-not-exist.png'));
|
|
81
|
-
|
|
82
|
-
const missingFileResponse = await fetch(missingFileUrl);
|
|
83
|
-
assert.equal(missingFileResponse.status, 404);
|
|
84
|
-
assert.equal(await missingFileResponse.text(), 'File not found');
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test('pairing endpoints return the relay host and port when VIBELET_RELAY_URL is configured', async (t) => {
|
|
88
|
-
const daemon = await spawnDaemon({
|
|
89
|
-
env: {
|
|
90
|
-
VIBELET_RELAY_URL: 'https://relay.example.com',
|
|
91
|
-
},
|
|
92
|
-
});
|
|
93
|
-
t.after(() => cleanupDaemon(daemon));
|
|
94
|
-
|
|
95
|
-
const health = await waitForHealth(daemon.port);
|
|
96
|
-
assert.equal(health.canonicalHost, 'relay.example.com');
|
|
97
|
-
assert.equal(health.relayUrl, 'https://relay.example.com');
|
|
98
|
-
|
|
99
|
-
const pairOpenResponse = await fetch(`http://127.0.0.1:${daemon.port}/pair/open`, { method: 'POST' });
|
|
100
|
-
assert.equal(pairOpenResponse.status, 200);
|
|
101
|
-
const pairPayload = await pairOpenResponse.json() as PairingQrPayload;
|
|
102
|
-
assert.equal(pairPayload.canonicalHost, 'relay.example.com');
|
|
103
|
-
assert.equal(pairPayload.port, 443);
|
|
104
|
-
assert.ok(Array.isArray(pairPayload.fallbackHosts));
|
|
105
|
-
assert.ok((pairPayload.fallbackHosts?.length ?? 0) > 0);
|
|
106
|
-
assert.ok(pairPayload.fallbackHosts?.every((host) => host !== pairPayload.canonicalHost));
|
|
107
|
-
|
|
108
|
-
const createResponse = await fetch(`http://127.0.0.1:${daemon.port}/pair/create`, {
|
|
109
|
-
method: 'POST',
|
|
110
|
-
headers: { 'Content-Type': 'application/json' },
|
|
111
|
-
body: JSON.stringify({
|
|
112
|
-
daemonId: pairPayload.daemonId,
|
|
113
|
-
pairNonce: pairPayload.pairNonce,
|
|
114
|
-
deviceId: 'device-relay',
|
|
115
|
-
deviceName: 'Relay Phone',
|
|
116
|
-
}),
|
|
117
|
-
});
|
|
118
|
-
assert.equal(createResponse.status, 200);
|
|
119
|
-
|
|
120
|
-
const createPayload = await createResponse.json() as Record<string, unknown>;
|
|
121
|
-
assert.equal(createPayload.canonicalHost, 'relay.example.com');
|
|
122
|
-
assert.equal(createPayload.port, 443);
|
|
123
|
-
assert.deepEqual(createPayload.fallbackHosts, pairPayload.fallbackHosts);
|
|
124
|
-
assert.equal(createPayload.deviceId, 'device-relay');
|
|
125
|
-
assert.equal(typeof createPayload.pairToken, 'string');
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test('file endpoint resolves relative paths against cwd and returns markdown content type', async (t) => {
|
|
129
|
-
const daemon = await spawnDaemon();
|
|
130
|
-
t.after(() => cleanupDaemon(daemon));
|
|
131
|
-
|
|
132
|
-
const workspaceDir = path.join(daemon.homeDir, 'workspace');
|
|
133
|
-
const docsDir = path.join(workspaceDir, 'docs');
|
|
134
|
-
mkdirSync(docsDir, { recursive: true });
|
|
135
|
-
writeFileSync(path.join(docsDir, 'guide.md'), '# Remote Guide\n', 'utf8');
|
|
136
|
-
|
|
137
|
-
const fileUrl = new URL(`http://127.0.0.1:${daemon.port}/file`);
|
|
138
|
-
fileUrl.searchParams.set('token', daemon.token);
|
|
139
|
-
fileUrl.searchParams.set('path', 'docs/guide.md');
|
|
140
|
-
fileUrl.searchParams.set('cwd', workspaceDir);
|
|
141
|
-
|
|
142
|
-
const response = await fetch(fileUrl);
|
|
143
|
-
assert.equal(response.status, 200);
|
|
144
|
-
assert.equal(response.headers.get('content-type'), 'text/markdown; charset=utf-8');
|
|
145
|
-
assert.equal(await response.text(), '# Remote Guide\n');
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test('upload endpoint stores image files and returns their daemon path', async (t) => {
|
|
149
|
-
const daemon = await spawnDaemon();
|
|
150
|
-
t.after(() => cleanupDaemon(daemon));
|
|
151
|
-
|
|
152
|
-
const uploadUrl = new URL(`http://127.0.0.1:${daemon.port}/upload`);
|
|
153
|
-
uploadUrl.searchParams.set('token', daemon.token);
|
|
154
|
-
|
|
155
|
-
const response = await fetch(uploadUrl, {
|
|
156
|
-
method: 'POST',
|
|
157
|
-
headers: { 'Content-Type': 'image/heic' },
|
|
158
|
-
body: Buffer.from('fake-heic-data'),
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
assert.equal(response.status, 200);
|
|
162
|
-
const payload = await response.json() as { path: string };
|
|
163
|
-
assert.match(payload.path, /\/uploads\/.+\.heic$/);
|
|
164
|
-
|
|
165
|
-
const fileResponse = await fetch(`http://127.0.0.1:${daemon.port}/file?token=${encodeURIComponent(daemon.token)}&path=${encodeURIComponent(payload.path)}`);
|
|
166
|
-
assert.equal(fileResponse.status, 200);
|
|
167
|
-
assert.equal(fileResponse.headers.get('content-type'), 'image/heic');
|
|
168
|
-
assert.equal(await fileResponse.text(), 'fake-heic-data');
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
test('auth.hello can authenticate a device, dirs.list omits hidden entries, and log.report is forwarded to audit/stdout', async (t) => {
|
|
172
|
-
const daemon = await spawnDaemon();
|
|
173
|
-
t.after(() => cleanupDaemon(daemon));
|
|
174
|
-
|
|
175
|
-
const pairPayload = await (await fetch(`http://127.0.0.1:${daemon.port}/pair/open`, { method: 'POST' })).json() as PairingQrPayload;
|
|
176
|
-
const createResponse = await fetch(`http://127.0.0.1:${daemon.port}/pair/create`, {
|
|
177
|
-
method: 'POST',
|
|
178
|
-
headers: { 'Content-Type': 'application/json' },
|
|
179
|
-
body: JSON.stringify({
|
|
180
|
-
daemonId: pairPayload.daemonId,
|
|
181
|
-
pairNonce: pairPayload.pairNonce,
|
|
182
|
-
deviceId: 'device-auth',
|
|
183
|
-
deviceName: 'Auth Device',
|
|
184
|
-
}),
|
|
185
|
-
});
|
|
186
|
-
const pairing = await createResponse.json() as Record<string, unknown>;
|
|
187
|
-
const pairToken = String(pairing.pairToken);
|
|
188
|
-
|
|
189
|
-
const ws = await openWebSocket(`ws://127.0.0.1:${daemon.port}`);
|
|
190
|
-
t.after(() => {
|
|
191
|
-
try {
|
|
192
|
-
ws.close();
|
|
193
|
-
} catch {}
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
ws.send(JSON.stringify({
|
|
197
|
-
action: 'auth.hello',
|
|
198
|
-
id: 'auth-1',
|
|
199
|
-
daemonId: pairPayload.daemonId,
|
|
200
|
-
deviceId: 'device-auth',
|
|
201
|
-
pairToken,
|
|
202
|
-
}));
|
|
203
|
-
|
|
204
|
-
const authResponse = await waitForWebSocketMessage(ws, (message) => message.type === 'response' && message.id === 'auth-1');
|
|
205
|
-
assert.equal(authResponse.ok, true);
|
|
206
|
-
assert.deepEqual(authResponse.data, {
|
|
207
|
-
daemonId: pairPayload.daemonId,
|
|
208
|
-
displayName: authResponse.data && typeof authResponse.data === 'object' ? (authResponse.data as Record<string, unknown>).displayName : undefined,
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
const dirsRoot = path.join(daemon.homeDir, 'dirs');
|
|
212
|
-
mkdirSync(path.join(dirsRoot, 'alpha'), { recursive: true });
|
|
213
|
-
writeFileSync(path.join(dirsRoot, 'z-last.txt'), 'z');
|
|
214
|
-
writeFileSync(path.join(dirsRoot, 'b.txt'), 'b');
|
|
215
|
-
writeFileSync(path.join(dirsRoot, '.hidden.txt'), 'hidden');
|
|
216
|
-
|
|
217
|
-
ws.send(JSON.stringify({
|
|
218
|
-
action: 'dirs.list',
|
|
219
|
-
id: 'dirs-1',
|
|
220
|
-
path: dirsRoot,
|
|
221
|
-
}));
|
|
222
|
-
|
|
223
|
-
const dirsResponse = await waitForWebSocketMessage(ws, (message) => message.type === 'response' && message.id === 'dirs-1');
|
|
224
|
-
assert.equal(dirsResponse.ok, true);
|
|
225
|
-
assert.deepEqual((dirsResponse.data as { entries: Array<{ name: string; isDirectory: boolean }> }).entries, [
|
|
226
|
-
{ name: 'alpha', isDirectory: true },
|
|
227
|
-
{ name: 'b.txt', isDirectory: false },
|
|
228
|
-
{ name: 'z-last.txt', isDirectory: false },
|
|
229
|
-
]);
|
|
230
|
-
|
|
231
|
-
ws.send(JSON.stringify({
|
|
232
|
-
action: 'log.report',
|
|
233
|
-
id: 'log-1',
|
|
234
|
-
entries: [
|
|
235
|
-
{
|
|
236
|
-
level: 'info',
|
|
237
|
-
event: 'app.session.create',
|
|
238
|
-
data: { agent: 'codex', cwd: '/tmp/repo' },
|
|
239
|
-
ts: '2026-03-29T00:00:00.000Z',
|
|
240
|
-
},
|
|
241
|
-
],
|
|
242
|
-
}));
|
|
243
|
-
|
|
244
|
-
const logResponse = await waitForWebSocketMessage(ws, (message) => message.type === 'response' && message.id === 'log-1');
|
|
245
|
-
assert.equal(logResponse.ok, true);
|
|
246
|
-
|
|
247
|
-
const auditLog = await readAuditLog(daemon.homeDir);
|
|
248
|
-
assert.match(auditLog, /"event":"app\.log"/);
|
|
249
|
-
assert.match(auditLog, /"appEvent":"app\.session\.create"/);
|
|
250
|
-
assert.match(auditLog, /"agent":"codex"/);
|
|
251
|
-
|
|
252
|
-
const stdout = await waitForStdout(daemon, /\[app\] app\.session\.create/);
|
|
253
|
-
assert.match(stdout, /\[app\] app\.session\.create/);
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
test('dirs.list expands tilde paths before reading the directory', async (t) => {
|
|
257
|
-
const daemon = await spawnDaemon();
|
|
258
|
-
t.after(() => cleanupDaemon(daemon));
|
|
259
|
-
|
|
260
|
-
const tildeDir = path.join(daemon.homeDir, 'tilde-browse');
|
|
261
|
-
mkdirSync(path.join(tildeDir, 'nested'), { recursive: true });
|
|
262
|
-
writeFileSync(path.join(tildeDir, 'notes.txt'), 'browse me', 'utf8');
|
|
263
|
-
|
|
264
|
-
const ws = await openWebSocket(`ws://127.0.0.1:${daemon.port}?token=${encodeURIComponent(daemon.token)}`);
|
|
265
|
-
t.after(() => {
|
|
266
|
-
try {
|
|
267
|
-
ws.close();
|
|
268
|
-
} catch {}
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
ws.send(JSON.stringify({
|
|
272
|
-
action: 'dirs.list',
|
|
273
|
-
id: 'dirs-tilde',
|
|
274
|
-
path: '~/tilde-browse',
|
|
275
|
-
}));
|
|
276
|
-
|
|
277
|
-
const response = await waitForWebSocketMessage(ws, (message) => message.type === 'response' && message.id === 'dirs-tilde');
|
|
278
|
-
assert.equal(response.ok, true);
|
|
279
|
-
assert.deepEqual((response.data as { entries: Array<{ name: string; isDirectory: boolean }> }).entries, [
|
|
280
|
-
{ name: 'nested', isDirectory: true },
|
|
281
|
-
{ name: 'notes.txt', isDirectory: false },
|
|
282
|
-
]);
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
test('dirs.list resolves relative paths against the provided cwd', async (t) => {
|
|
286
|
-
const daemon = await spawnDaemon();
|
|
287
|
-
t.after(() => cleanupDaemon(daemon));
|
|
288
|
-
|
|
289
|
-
const repoRoot = path.join(daemon.homeDir, 'repo-root');
|
|
290
|
-
const assetsDir = path.join(repoRoot, 'apps', 'aino-desktop', 'src', 'renderer', 'src', 'assets');
|
|
291
|
-
mkdirSync(assetsDir, { recursive: true });
|
|
292
|
-
writeFileSync(path.join(assetsDir, 'wavy-lines.svg'), '<svg />', 'utf8');
|
|
293
|
-
|
|
294
|
-
const ws = await openWebSocket(`ws://127.0.0.1:${daemon.port}?token=${encodeURIComponent(daemon.token)}`);
|
|
295
|
-
t.after(() => {
|
|
296
|
-
try {
|
|
297
|
-
ws.close();
|
|
298
|
-
} catch {}
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
ws.send(JSON.stringify({
|
|
302
|
-
action: 'dirs.list',
|
|
303
|
-
id: 'dirs-relative',
|
|
304
|
-
path: 'apps/aino-desktop/src/renderer/src/assets/',
|
|
305
|
-
cwd: repoRoot,
|
|
306
|
-
}));
|
|
307
|
-
|
|
308
|
-
const response = await waitForWebSocketMessage(ws, (message) => message.type === 'response' && message.id === 'dirs-relative');
|
|
309
|
-
assert.equal(response.ok, true);
|
|
310
|
-
assert.deepEqual((response.data as { entries: Array<{ name: string; isDirectory: boolean }> }).entries, [
|
|
311
|
-
{ name: 'wavy-lines.svg', isDirectory: false },
|
|
312
|
-
]);
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
test('auth.hello rejects invalid pair tokens and closes the websocket', async (t) => {
|
|
316
|
-
const daemon = await spawnDaemon();
|
|
317
|
-
t.after(() => cleanupDaemon(daemon));
|
|
318
|
-
|
|
319
|
-
const pairPayload = await (await fetch(`http://127.0.0.1:${daemon.port}/pair/open`, { method: 'POST' })).json() as PairingQrPayload;
|
|
320
|
-
const ws = await openWebSocket(`ws://127.0.0.1:${daemon.port}`);
|
|
321
|
-
|
|
322
|
-
ws.send(JSON.stringify({
|
|
323
|
-
action: 'auth.hello',
|
|
324
|
-
id: 'auth-fail',
|
|
325
|
-
daemonId: pairPayload.daemonId,
|
|
326
|
-
deviceId: 'device-auth',
|
|
327
|
-
pairToken: 'invalid-token',
|
|
328
|
-
}));
|
|
329
|
-
|
|
330
|
-
const response = await waitForWebSocketMessage(ws, (message) => message.type === 'response' && message.id === 'auth-fail');
|
|
331
|
-
assert.equal(response.ok, false);
|
|
332
|
-
assert.equal(response.error, 'auth_failed');
|
|
333
|
-
|
|
334
|
-
const closeCode = await waitForWebSocketClose(ws);
|
|
335
|
-
assert.equal(closeCode, 4001);
|
|
336
|
-
});
|