@vibelet/cli 0.1.38 → 1.0.0
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 +1062 -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/process-scanner.ts
DELETED
|
@@ -1,514 +0,0 @@
|
|
|
1
|
-
import { execFile } from 'child_process';
|
|
2
|
-
import { readdir, stat } from 'fs/promises';
|
|
3
|
-
import { basename, join } from 'path';
|
|
4
|
-
import { homedir } from 'os';
|
|
5
|
-
import { promisify } from 'util';
|
|
6
|
-
import type { AgentType, ApprovalMode, SessionConfidence } from '@vibelet/shared';
|
|
7
|
-
import {
|
|
8
|
-
extractSessionIdFromSessionFile,
|
|
9
|
-
readSessionFileMeta,
|
|
10
|
-
readSessionRuntimeHintsFromFile,
|
|
11
|
-
} from './scanner.js';
|
|
12
|
-
import { logger as rootLogger } from './logger.js';
|
|
13
|
-
|
|
14
|
-
const execFileAsync = promisify(execFile);
|
|
15
|
-
const log = rootLogger.child({ module: 'process-scanner' });
|
|
16
|
-
const PROCESS_SCAN_COMMAND_TIMEOUT_MS = 1_500;
|
|
17
|
-
const RELEVANT_PROCESS_PGREP_PATTERN = '(^|[^[:alnum:]_.-])(claude|codex)([^[:alnum:]_.-]|$)';
|
|
18
|
-
const RELEVANT_PROCESS_COMMAND_REGEX = /(^|[^A-Za-z0-9_.-])(claude|codex)([^A-Za-z0-9_.-]|$)/;
|
|
19
|
-
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
// Throttle: reuse result if called again within TTL
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
const PROCESS_SCAN_TTL = 15_000; // 15s — explicit invalidation on mutations keeps it fresh
|
|
24
|
-
let processScanCache: { value: RunningSessionCandidate[]; expiresAt: number } | null = null;
|
|
25
|
-
let processScanInflight: Promise<RunningSessionCandidate[]> | null = null;
|
|
26
|
-
|
|
27
|
-
/** Invalidate the process scan cache. */
|
|
28
|
-
export function invalidateProcessScanCache(): void {
|
|
29
|
-
processScanCache = null;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface ProcessSnapshot {
|
|
33
|
-
pid: number;
|
|
34
|
-
agent: AgentType;
|
|
35
|
-
command: string;
|
|
36
|
-
cwd: string;
|
|
37
|
-
sessionFiles: string[];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface RunningSessionCandidate {
|
|
41
|
-
agent: AgentType;
|
|
42
|
-
pid: number;
|
|
43
|
-
cwd: string;
|
|
44
|
-
command: string;
|
|
45
|
-
sessionId: string;
|
|
46
|
-
confidence: SessionConfidence;
|
|
47
|
-
approvalMode?: ApprovalMode;
|
|
48
|
-
sessionFilePath?: string;
|
|
49
|
-
title?: string;
|
|
50
|
-
isResponding?: boolean;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
interface RecentSessionFileMatch {
|
|
54
|
-
path: string;
|
|
55
|
-
mtimeMs: number;
|
|
56
|
-
sessionId: string;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function normalizeToken(token: string): string {
|
|
60
|
-
return token.replace(/^['"]|['"]$/g, '');
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function tokenizeCommand(command: string): string[] {
|
|
64
|
-
return (command.match(/"[^"]*"|'[^']*'|\S+/g) ?? []).map(normalizeToken);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function detectAgentAndArgsFromTokens(tokens: string[]): { agent: AgentType; args: string[] } | null {
|
|
68
|
-
for (let index = 0; index < tokens.length; index += 1) {
|
|
69
|
-
const token = tokens[index];
|
|
70
|
-
const binary = basename(token);
|
|
71
|
-
if (binary === 'claude' || binary === 'codex') {
|
|
72
|
-
return { agent: binary, args: tokens.slice(index + 1) };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Shell wrappers such as `zsh -lc 'cd /repo && claude --resume ...'`
|
|
76
|
-
// surface the inner command as one quoted token. Re-scan that token
|
|
77
|
-
// so explicit resume/session-id flags remain discoverable.
|
|
78
|
-
if (/\s/.test(token)) {
|
|
79
|
-
const nested = detectAgentAndArgs(token);
|
|
80
|
-
if (nested) {
|
|
81
|
-
return nested;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function detectAgentAndArgs(command: string): { agent: AgentType; args: string[] } | null {
|
|
89
|
-
return detectAgentAndArgsFromTokens(tokenizeCommand(command));
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export function isRelevantProcessCommand(command: string): boolean {
|
|
93
|
-
return RELEVANT_PROCESS_COMMAND_REGEX.test(command);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function extractExplicitSessionId(agent: AgentType, command: string): string | null {
|
|
97
|
-
const detected = detectAgentAndArgs(command);
|
|
98
|
-
if (!detected || detected.agent !== agent) return null;
|
|
99
|
-
|
|
100
|
-
const { args } = detected;
|
|
101
|
-
if (agent === 'claude') {
|
|
102
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
103
|
-
const token = args[index];
|
|
104
|
-
if ((token === '--resume' || token === '--session-id') && args[index + 1] && !args[index + 1].startsWith('-')) {
|
|
105
|
-
return args[index + 1];
|
|
106
|
-
}
|
|
107
|
-
if (token.startsWith('--resume=')) {
|
|
108
|
-
return token.slice('--resume='.length);
|
|
109
|
-
}
|
|
110
|
-
if (token.startsWith('--session-id=')) {
|
|
111
|
-
return token.slice('--session-id='.length);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (args[0] !== 'resume') return null;
|
|
118
|
-
for (let index = 1; index < args.length; index += 1) {
|
|
119
|
-
const token = args[index];
|
|
120
|
-
if (!token.startsWith('-')) return token;
|
|
121
|
-
}
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function parseClaudeApprovalModeFromArgs(args: string[]): ApprovalMode | undefined {
|
|
126
|
-
if (args.includes('--dangerously-skip-permissions')) {
|
|
127
|
-
return 'autoApprove';
|
|
128
|
-
}
|
|
129
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
130
|
-
const token = args[index];
|
|
131
|
-
const next = args[index + 1];
|
|
132
|
-
const value = token === '--permission-mode'
|
|
133
|
-
? next
|
|
134
|
-
: token.startsWith('--permission-mode=')
|
|
135
|
-
? token.slice('--permission-mode='.length)
|
|
136
|
-
: undefined;
|
|
137
|
-
if (value === 'plan') return 'plan';
|
|
138
|
-
if (value === 'acceptEdits') return 'acceptEdits';
|
|
139
|
-
if (value === 'default') return 'normal';
|
|
140
|
-
}
|
|
141
|
-
return undefined;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function parseCodexApprovalModeFromArgs(args: string[]): ApprovalMode | undefined {
|
|
145
|
-
if (args.includes('--dangerously-bypass-approvals-and-sandbox')) {
|
|
146
|
-
return 'autoApprove';
|
|
147
|
-
}
|
|
148
|
-
return undefined;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function parseApprovalModeFromProcessCommand(agent: AgentType, command: string): ApprovalMode | undefined {
|
|
152
|
-
const detected = detectAgentAndArgs(command);
|
|
153
|
-
if (!detected || detected.agent !== agent) return undefined;
|
|
154
|
-
return agent === 'claude'
|
|
155
|
-
? parseClaudeApprovalModeFromArgs(detected.args)
|
|
156
|
-
: parseCodexApprovalModeFromArgs(detected.args);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function confidenceRank(confidence: SessionConfidence): number {
|
|
160
|
-
switch (confidence) {
|
|
161
|
-
case 'high': return 3;
|
|
162
|
-
case 'medium': return 2;
|
|
163
|
-
default: return 1;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function isSupportedSessionFilePath(agent: AgentType, filePath: string): boolean {
|
|
168
|
-
if (agent === 'claude') {
|
|
169
|
-
return filePath.includes('/.claude/projects/') && !filePath.includes('/subagents/');
|
|
170
|
-
}
|
|
171
|
-
return filePath.includes('/.codex/sessions/');
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Returns true if the command includes `--continue` (or `-c`), meaning
|
|
176
|
-
* "resume the most recent conversation for this cwd". When no open
|
|
177
|
-
* session file is found, we cannot reliably determine which session
|
|
178
|
-
* `--continue` actually resolved to — the file-based fallback can pick
|
|
179
|
-
* the *wrong* session if another conversation was created after the
|
|
180
|
-
* process started.
|
|
181
|
-
*/
|
|
182
|
-
export function hasContinueFlag(agent: AgentType, command: string): boolean {
|
|
183
|
-
const detected = detectAgentAndArgs(command);
|
|
184
|
-
if (!detected || detected.agent !== agent) return false;
|
|
185
|
-
return detected.args.includes('--continue') || detected.args.includes('-c');
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
export function classifyProcessSnapshot(snapshot: ProcessSnapshot): RunningSessionCandidate | null {
|
|
189
|
-
const uniqueFiles = [...new Set(snapshot.sessionFiles)].filter((filePath) =>
|
|
190
|
-
isSupportedSessionFilePath(snapshot.agent, filePath),
|
|
191
|
-
);
|
|
192
|
-
const explicitSessionId = extractExplicitSessionId(snapshot.agent, snapshot.command);
|
|
193
|
-
const approvalMode = parseApprovalModeFromProcessCommand(snapshot.agent, snapshot.command);
|
|
194
|
-
if (explicitSessionId) {
|
|
195
|
-
return {
|
|
196
|
-
agent: snapshot.agent,
|
|
197
|
-
pid: snapshot.pid,
|
|
198
|
-
cwd: snapshot.cwd,
|
|
199
|
-
command: snapshot.command,
|
|
200
|
-
sessionId: explicitSessionId,
|
|
201
|
-
confidence: 'high',
|
|
202
|
-
...(approvalMode ? { approvalMode } : {}),
|
|
203
|
-
...(uniqueFiles.length === 1 ? { sessionFilePath: uniqueFiles[0] } : {}),
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (uniqueFiles.length !== 1) return null;
|
|
208
|
-
|
|
209
|
-
const sessionId = extractSessionIdFromSessionFile(snapshot.agent, uniqueFiles[0]);
|
|
210
|
-
if (!sessionId) return null;
|
|
211
|
-
|
|
212
|
-
return {
|
|
213
|
-
agent: snapshot.agent,
|
|
214
|
-
pid: snapshot.pid,
|
|
215
|
-
cwd: snapshot.cwd,
|
|
216
|
-
command: snapshot.command,
|
|
217
|
-
sessionId,
|
|
218
|
-
confidence: 'medium',
|
|
219
|
-
...(approvalMode ? { approvalMode } : {}),
|
|
220
|
-
sessionFilePath: uniqueFiles[0],
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function pickBetterCandidate(current: RunningSessionCandidate, next: RunningSessionCandidate): RunningSessionCandidate {
|
|
225
|
-
let winner = current;
|
|
226
|
-
let loser = next;
|
|
227
|
-
|
|
228
|
-
if (confidenceRank(next.confidence) > confidenceRank(current.confidence)) {
|
|
229
|
-
winner = next;
|
|
230
|
-
loser = current;
|
|
231
|
-
} else if (confidenceRank(next.confidence) === confidenceRank(current.confidence) && next.pid < current.pid) {
|
|
232
|
-
winner = next;
|
|
233
|
-
loser = current;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const approvalMode = winner.approvalMode ?? loser.approvalMode;
|
|
237
|
-
return {
|
|
238
|
-
...winner,
|
|
239
|
-
cwd: winner.cwd || loser.cwd,
|
|
240
|
-
...(approvalMode ? { approvalMode } : {}),
|
|
241
|
-
title: winner.title ?? loser.title,
|
|
242
|
-
sessionFilePath: winner.sessionFilePath ?? loser.sessionFilePath,
|
|
243
|
-
isResponding: winner.isResponding ?? loser.isResponding,
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
export function mergeRunningSessionCandidates(candidates: RunningSessionCandidate[]): RunningSessionCandidate[] {
|
|
248
|
-
const merged = new Map<string, RunningSessionCandidate>();
|
|
249
|
-
for (const candidate of candidates) {
|
|
250
|
-
const key = `${candidate.agent}:${candidate.sessionId}`;
|
|
251
|
-
const current = merged.get(key);
|
|
252
|
-
merged.set(key, current ? pickBetterCandidate(current, candidate) : candidate);
|
|
253
|
-
}
|
|
254
|
-
return [...merged.values()];
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
export function isProcessAlive(pid: number): boolean {
|
|
258
|
-
try {
|
|
259
|
-
process.kill(pid, 0);
|
|
260
|
-
return true;
|
|
261
|
-
} catch {
|
|
262
|
-
return false;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
async function runCommand(file: string, args: string[]): Promise<string> {
|
|
267
|
-
try {
|
|
268
|
-
const { stdout } = await execFileAsync(file, args, {
|
|
269
|
-
maxBuffer: 1024 * 1024 * 8,
|
|
270
|
-
timeout: PROCESS_SCAN_COMMAND_TIMEOUT_MS,
|
|
271
|
-
});
|
|
272
|
-
return stdout;
|
|
273
|
-
} catch (error) {
|
|
274
|
-
// Downgrade to debug when the target process simply exited between
|
|
275
|
-
// pgrep and lsof — this is an expected TOCTOU race, not actionable.
|
|
276
|
-
const msg = String(error);
|
|
277
|
-
const isEsrch = msg.includes('ESRCH') || msg.includes('No such process');
|
|
278
|
-
if (isEsrch) {
|
|
279
|
-
log.debug(
|
|
280
|
-
{ file, args, error: msg },
|
|
281
|
-
'process scan command skipped — process exited',
|
|
282
|
-
);
|
|
283
|
-
} else {
|
|
284
|
-
log.warn(
|
|
285
|
-
{ file, args, timeoutMs: PROCESS_SCAN_COMMAND_TIMEOUT_MS, error: msg },
|
|
286
|
-
'process scan command failed',
|
|
287
|
-
);
|
|
288
|
-
}
|
|
289
|
-
return '';
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
async function hydrateRunningSessionCandidate(
|
|
294
|
-
candidate: RunningSessionCandidate,
|
|
295
|
-
): Promise<RunningSessionCandidate | null> {
|
|
296
|
-
if (!candidate.sessionFilePath) return candidate;
|
|
297
|
-
const meta = await readSessionFileMeta(candidate.sessionFilePath, candidate.agent);
|
|
298
|
-
if (!meta) return null;
|
|
299
|
-
const runtimeHints = await readSessionRuntimeHintsFromFile(candidate.sessionFilePath, candidate.agent);
|
|
300
|
-
const approvalMode = meta.approvalMode ?? candidate.approvalMode;
|
|
301
|
-
return {
|
|
302
|
-
...candidate,
|
|
303
|
-
cwd: candidate.cwd || meta.cwd,
|
|
304
|
-
...(approvalMode ? { approvalMode } : {}),
|
|
305
|
-
title: candidate.title ?? meta.title,
|
|
306
|
-
isResponding: runtimeHints.isResponding,
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
async function readProcessCwd(pid: number): Promise<string> {
|
|
311
|
-
if (process.platform === 'win32') {
|
|
312
|
-
// On Windows, lsof is not available. We cannot reliably get process cwd,
|
|
313
|
-
// so return empty and rely on session file-based discovery instead.
|
|
314
|
-
return '';
|
|
315
|
-
}
|
|
316
|
-
const output = await runCommand('lsof', ['-a', '-p', String(pid), '-d', 'cwd', '-Fn']);
|
|
317
|
-
const line = output.split('\n').find((entry) => entry.startsWith('n'));
|
|
318
|
-
return line?.slice(1).trim() ?? '';
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
async function readProcessSessionFiles(pid: number): Promise<string[]> {
|
|
322
|
-
if (process.platform === 'win32') {
|
|
323
|
-
// On Windows, lsof is not available. Return empty and rely on
|
|
324
|
-
// session file-based discovery via findRecentSessionFileForCwd.
|
|
325
|
-
return [];
|
|
326
|
-
}
|
|
327
|
-
const output = await runCommand('lsof', ['-p', String(pid), '-Fn']);
|
|
328
|
-
const results = output
|
|
329
|
-
.split('\n')
|
|
330
|
-
.filter((line) => line.startsWith('n'))
|
|
331
|
-
.map((line) => line.slice(1).trim())
|
|
332
|
-
.filter((filePath) => {
|
|
333
|
-
if (!filePath.endsWith('.jsonl')) return false;
|
|
334
|
-
return filePath.includes('/.claude/projects/') || filePath.includes('/.codex/sessions/');
|
|
335
|
-
});
|
|
336
|
-
return [...new Set(results)];
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
async function listRelevantProcessesUnix(): Promise<Array<{ pid: number; command: string; agent: AgentType }>> {
|
|
340
|
-
const output = await runCommand('pgrep', ['-fal', RELEVANT_PROCESS_PGREP_PATTERN]);
|
|
341
|
-
return output
|
|
342
|
-
.split('\n')
|
|
343
|
-
.map((line) => line.trim())
|
|
344
|
-
.filter(Boolean)
|
|
345
|
-
.map((line) => {
|
|
346
|
-
const match = line.match(/^(\d+)\s+(.*)$/);
|
|
347
|
-
if (!match) return null;
|
|
348
|
-
const pid = Number(match[1]);
|
|
349
|
-
const command = match[2];
|
|
350
|
-
if (!isRelevantProcessCommand(command)) return null;
|
|
351
|
-
const detected = detectAgentAndArgs(command);
|
|
352
|
-
if (!Number.isFinite(pid) || !detected) return null;
|
|
353
|
-
return { pid, command, agent: detected.agent };
|
|
354
|
-
})
|
|
355
|
-
.filter((entry): entry is { pid: number; command: string; agent: AgentType } => Boolean(entry));
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
async function listRelevantProcessesWindows(): Promise<Array<{ pid: number; command: string; agent: AgentType }>> {
|
|
359
|
-
// Use WMIC to list processes with their command lines
|
|
360
|
-
const output = await runCommand('wmic', ['process', 'get', 'ProcessId,CommandLine', '/FORMAT:CSV']);
|
|
361
|
-
return output
|
|
362
|
-
.split('\n')
|
|
363
|
-
.map((line) => line.trim())
|
|
364
|
-
.filter(Boolean)
|
|
365
|
-
.map((line) => {
|
|
366
|
-
// CSV format: Node,CommandLine,ProcessId
|
|
367
|
-
const parts = line.split(',');
|
|
368
|
-
if (parts.length < 3) return null;
|
|
369
|
-
const pid = Number(parts[parts.length - 1]);
|
|
370
|
-
const command = parts.slice(1, parts.length - 1).join(',');
|
|
371
|
-
if (!Number.isFinite(pid) || pid === 0) return null;
|
|
372
|
-
if (!isRelevantProcessCommand(command)) return null;
|
|
373
|
-
const detected = detectAgentAndArgs(command);
|
|
374
|
-
if (!detected) return null;
|
|
375
|
-
return { pid, command, agent: detected.agent };
|
|
376
|
-
})
|
|
377
|
-
.filter((entry): entry is { pid: number; command: string; agent: AgentType } => Boolean(entry));
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
async function listRelevantProcesses(): Promise<Array<{ pid: number; command: string; agent: AgentType }>> {
|
|
381
|
-
if (process.platform === 'win32') return listRelevantProcessesWindows();
|
|
382
|
-
return listRelevantProcessesUnix();
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
export async function scanProcessSnapshots(): Promise<ProcessSnapshot[]> {
|
|
386
|
-
const processes = await listRelevantProcesses();
|
|
387
|
-
// Filter out PIDs that already exited between pgrep and now to avoid
|
|
388
|
-
// pointless lsof calls and noisy log warnings.
|
|
389
|
-
const alive = processes.filter((p) => isProcessAlive(p.pid));
|
|
390
|
-
return Promise.all(alive.map(async (proc) => ({
|
|
391
|
-
pid: proc.pid,
|
|
392
|
-
agent: proc.agent,
|
|
393
|
-
command: proc.command,
|
|
394
|
-
cwd: await readProcessCwd(proc.pid),
|
|
395
|
-
sessionFiles: await readProcessSessionFiles(proc.pid),
|
|
396
|
-
})));
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
function encodeCwd(cwd: string): string {
|
|
400
|
-
return cwd.replace(/[^a-zA-Z0-9]/g, '-');
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
export async function findRecentSessionFileForCwd(agent: AgentType, cwd: string): Promise<{ sessionId: string; filePath: string } | null> {
|
|
404
|
-
if (!cwd) return null;
|
|
405
|
-
|
|
406
|
-
try {
|
|
407
|
-
if (agent === 'claude') {
|
|
408
|
-
const dirPath = join(homedir(), '.claude', 'projects', encodeCwd(cwd));
|
|
409
|
-
const files = await readdir(dirPath).catch(() => [] as string[]);
|
|
410
|
-
const jsonlFiles = files.filter(f => f.endsWith('.jsonl'));
|
|
411
|
-
if (jsonlFiles.length === 0) return null;
|
|
412
|
-
|
|
413
|
-
// Find the most recently modified file
|
|
414
|
-
let newest: { name: string; mtimeMs: number } | null = null;
|
|
415
|
-
await Promise.all(jsonlFiles.map(async (name) => {
|
|
416
|
-
const fileStat = await stat(join(dirPath, name)).catch(() => null);
|
|
417
|
-
if (fileStat && (!newest || fileStat.mtimeMs > newest.mtimeMs)) {
|
|
418
|
-
newest = { name, mtimeMs: fileStat.mtimeMs };
|
|
419
|
-
}
|
|
420
|
-
}));
|
|
421
|
-
|
|
422
|
-
if (!newest) return null;
|
|
423
|
-
const filePath = join(dirPath, (newest as { name: string; mtimeMs: number }).name);
|
|
424
|
-
const sessionId = extractSessionIdFromSessionFile(agent, filePath);
|
|
425
|
-
return sessionId ? { sessionId, filePath } : null;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// Codex: find the most recently modified session file that actually belongs to the cwd.
|
|
429
|
-
const sessionsDir = join(homedir(), '.codex', 'sessions');
|
|
430
|
-
let newest: RecentSessionFileMatch | null = null;
|
|
431
|
-
|
|
432
|
-
async function walk(dir: string): Promise<void> {
|
|
433
|
-
const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
434
|
-
for (const entry of entries) {
|
|
435
|
-
const fullPath = join(dir, entry.name);
|
|
436
|
-
if (entry.isDirectory()) {
|
|
437
|
-
await walk(fullPath);
|
|
438
|
-
} else if (entry.name.endsWith('.jsonl')) {
|
|
439
|
-
const fileStat = await stat(fullPath).catch(() => null);
|
|
440
|
-
if (!fileStat) {
|
|
441
|
-
continue;
|
|
442
|
-
}
|
|
443
|
-
if (newest && fileStat.mtimeMs <= newest.mtimeMs) {
|
|
444
|
-
continue;
|
|
445
|
-
}
|
|
446
|
-
const meta = await readSessionFileMeta(fullPath, agent);
|
|
447
|
-
if (!meta || meta.cwd !== cwd) {
|
|
448
|
-
continue;
|
|
449
|
-
}
|
|
450
|
-
newest = {
|
|
451
|
-
path: fullPath,
|
|
452
|
-
mtimeMs: fileStat.mtimeMs,
|
|
453
|
-
sessionId: meta.sessionId,
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
await walk(sessionsDir);
|
|
460
|
-
const resolvedNewest = newest as RecentSessionFileMatch | null;
|
|
461
|
-
if (!resolvedNewest) return null;
|
|
462
|
-
return { sessionId: resolvedNewest.sessionId, filePath: resolvedNewest.path };
|
|
463
|
-
} catch {
|
|
464
|
-
return null;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
async function scanRunningSessionsImpl(): Promise<RunningSessionCandidate[]> {
|
|
469
|
-
const snapshots = await scanProcessSnapshots();
|
|
470
|
-
const hydrated = await Promise.all(
|
|
471
|
-
snapshots.map(async (snapshot) => {
|
|
472
|
-
const candidate = classifyProcessSnapshot(snapshot);
|
|
473
|
-
if (candidate) return hydrateRunningSessionCandidate(candidate);
|
|
474
|
-
|
|
475
|
-
// Skip the mtime-based fallback for --continue processes without open
|
|
476
|
-
// session files: we cannot reliably determine which session --continue
|
|
477
|
-
// resolved to, and guessing wrong creates ghost "Remote" duplicates.
|
|
478
|
-
if (hasContinueFlag(snapshot.agent, snapshot.command)) return null;
|
|
479
|
-
|
|
480
|
-
// Fallback: find the most recent session file matching the process's cwd
|
|
481
|
-
const recent = await findRecentSessionFileForCwd(snapshot.agent, snapshot.cwd);
|
|
482
|
-
if (!recent) return null;
|
|
483
|
-
return hydrateRunningSessionCandidate({
|
|
484
|
-
agent: snapshot.agent,
|
|
485
|
-
pid: snapshot.pid,
|
|
486
|
-
cwd: snapshot.cwd,
|
|
487
|
-
command: snapshot.command,
|
|
488
|
-
sessionId: recent.sessionId,
|
|
489
|
-
confidence: 'low',
|
|
490
|
-
sessionFilePath: recent.filePath,
|
|
491
|
-
});
|
|
492
|
-
}),
|
|
493
|
-
);
|
|
494
|
-
const result = mergeRunningSessionCandidates(
|
|
495
|
-
hydrated.filter((candidate): candidate is RunningSessionCandidate => candidate !== null),
|
|
496
|
-
);
|
|
497
|
-
|
|
498
|
-
processScanCache = { value: result, expiresAt: Date.now() + PROCESS_SCAN_TTL };
|
|
499
|
-
return result;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
export async function scanRunningSessions(): Promise<RunningSessionCandidate[]> {
|
|
503
|
-
if (processScanCache && Date.now() < processScanCache.expiresAt) {
|
|
504
|
-
return processScanCache.value;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
// Coalesce concurrent callers into a single in-flight scan
|
|
508
|
-
if (processScanInflight) return processScanInflight;
|
|
509
|
-
|
|
510
|
-
processScanInflight = scanRunningSessionsImpl().finally(() => {
|
|
511
|
-
processScanInflight = null;
|
|
512
|
-
});
|
|
513
|
-
return processScanInflight;
|
|
514
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict';
|
|
2
|
-
import test from 'node:test';
|
|
3
|
-
import { handlePushProtocolMessage } from './push-protocol.js';
|
|
4
|
-
|
|
5
|
-
test('handlePushProtocolMessage registers push tokens', () => {
|
|
6
|
-
const registered: Array<{ deviceId: string; token: string }> = [];
|
|
7
|
-
const unregistered: Array<{ deviceId: string; token: string }> = [];
|
|
8
|
-
const responses: Array<{ type: string; id: string; ok: boolean }> = [];
|
|
9
|
-
|
|
10
|
-
handlePushProtocolMessage(
|
|
11
|
-
{
|
|
12
|
-
action: 'push.register',
|
|
13
|
-
id: 'push_1',
|
|
14
|
-
pushToken: 'ExponentPushToken[register]',
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
deviceId: 'device-1',
|
|
18
|
-
registerToken: (deviceId, token) => registered.push({ deviceId, token }),
|
|
19
|
-
unregisterToken: (deviceId, token) => unregistered.push({ deviceId, token }),
|
|
20
|
-
respond: (response) => responses.push(response),
|
|
21
|
-
},
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
assert.deepEqual(registered, [{ deviceId: 'device-1', token: 'ExponentPushToken[register]' }]);
|
|
25
|
-
assert.deepEqual(unregistered, []);
|
|
26
|
-
assert.deepEqual(responses, [{ type: 'response', id: 'push_1', ok: true }]);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test('handlePushProtocolMessage unregisters push tokens', () => {
|
|
30
|
-
const registered: Array<{ deviceId: string; token: string }> = [];
|
|
31
|
-
const unregistered: Array<{ deviceId: string; token: string }> = [];
|
|
32
|
-
const responses: Array<{ type: string; id: string; ok: boolean }> = [];
|
|
33
|
-
|
|
34
|
-
handlePushProtocolMessage(
|
|
35
|
-
{
|
|
36
|
-
action: 'push.unregister',
|
|
37
|
-
id: 'push_2',
|
|
38
|
-
pushToken: 'ExponentPushToken[unregister]',
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
deviceId: 'device-2',
|
|
42
|
-
registerToken: (deviceId, token) => registered.push({ deviceId, token }),
|
|
43
|
-
unregisterToken: (deviceId, token) => unregistered.push({ deviceId, token }),
|
|
44
|
-
respond: (response) => responses.push(response),
|
|
45
|
-
},
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
assert.deepEqual(registered, []);
|
|
49
|
-
assert.deepEqual(unregistered, [{ deviceId: 'device-2', token: 'ExponentPushToken[unregister]' }]);
|
|
50
|
-
assert.deepEqual(responses, [{ type: 'response', id: 'push_2', ok: true }]);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test('handlePushProtocolMessage rejects push registration without a device context', () => {
|
|
54
|
-
const registered: Array<{ deviceId: string; token: string }> = [];
|
|
55
|
-
const unregistered: Array<{ deviceId: string; token: string }> = [];
|
|
56
|
-
const responses: Array<{ type: string; id: string; ok: boolean; error?: string }> = [];
|
|
57
|
-
|
|
58
|
-
handlePushProtocolMessage(
|
|
59
|
-
{
|
|
60
|
-
action: 'push.register',
|
|
61
|
-
id: 'push_3',
|
|
62
|
-
pushToken: 'ExponentPushToken[missing-device]',
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
registerToken: (deviceId, token) => registered.push({ deviceId, token }),
|
|
66
|
-
unregisterToken: (deviceId, token) => unregistered.push({ deviceId, token }),
|
|
67
|
-
respond: (response) => responses.push(response),
|
|
68
|
-
},
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
assert.deepEqual(registered, []);
|
|
72
|
-
assert.deepEqual(unregistered, []);
|
|
73
|
-
assert.deepEqual(responses, [{ type: 'response', id: 'push_3', ok: false, error: 'device_auth_required' }]);
|
|
74
|
-
});
|
package/src/push-protocol.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { ClientMessage, ServerMessage } from '@vibelet/shared';
|
|
2
|
-
|
|
3
|
-
type PushProtocolMessage = Extract<ClientMessage, { action: 'push.register' | 'push.unregister' }>;
|
|
4
|
-
type PushProtocolResponse = Extract<ServerMessage, { type: 'response' }>;
|
|
5
|
-
|
|
6
|
-
export function handlePushProtocolMessage(
|
|
7
|
-
message: PushProtocolMessage,
|
|
8
|
-
handlers: {
|
|
9
|
-
deviceId?: string;
|
|
10
|
-
registerToken: (deviceId: string, token: string) => void;
|
|
11
|
-
unregisterToken: (deviceId: string, token: string) => void;
|
|
12
|
-
respond: (response: PushProtocolResponse) => void;
|
|
13
|
-
},
|
|
14
|
-
): void {
|
|
15
|
-
if (!handlers.deviceId) {
|
|
16
|
-
handlers.respond({
|
|
17
|
-
type: 'response',
|
|
18
|
-
id: message.id,
|
|
19
|
-
ok: false,
|
|
20
|
-
error: 'device_auth_required',
|
|
21
|
-
});
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (message.action === 'push.register') {
|
|
26
|
-
handlers.registerToken(handlers.deviceId, message.pushToken);
|
|
27
|
-
} else {
|
|
28
|
-
handlers.unregisterToken(handlers.deviceId, message.pushToken);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
handlers.respond({
|
|
32
|
-
type: 'response',
|
|
33
|
-
id: message.id,
|
|
34
|
-
ok: true,
|
|
35
|
-
});
|
|
36
|
-
}
|