@miller-tech/uap 1.119.4 → 1.121.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.
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Reference-counted, session-scoped proxy lifecycle.
3
+ *
4
+ * Goal (per product spec): the UAP Anthropic proxy should start when an agent
5
+ * session is triggered (via the SessionStart hook) and exit WITH the agent —
6
+ * but only when it is safe to do so:
7
+ *
8
+ * - If a proxy is already running, ADOPT it (reuse) — never start a second.
9
+ * - A proxy we adopted (already up), OR a systemd-managed unit, is NEVER
10
+ * stopped by us on session end (systemd units are persistent infra).
11
+ * - A proxy WE spawned as a plain process is reference-counted across
12
+ * sessions: it is stopped on release only when the LAST client (agent
13
+ * session) leaves. If any other client is still using it, it keeps running.
14
+ *
15
+ * This module holds the pure decision logic + a tiny file-backed registry so it
16
+ * is unit-testable without spawning a real proxy or touching systemd. The real
17
+ * side effects (spawn / systemctl / HTTP health probe) are injected as `deps`.
18
+ *
19
+ * Liveness note: a client's `pid` MUST be the long-lived AGENT process (the one
20
+ * that spawns SessionStart and SessionEnd), NOT the ephemeral hook/CLI process
21
+ * — otherwise the dead-pid pruning below evicts every live session moments
22
+ * after it registers. The hook passes `$PPID` (the agent) via `--client-pid`.
23
+ */
24
+ /** Default port the proxy listens on (matches run-anthropic-proxy-continuity.sh). */
25
+ export declare const DEFAULT_PROXY_PORT: number;
26
+ /**
27
+ * Runtime root for the client registry + owner marker. Prefer XDG_RUNTIME_DIR
28
+ * (tmpfs at /run/user/<uid>, mode 0700, cleared on logout) and fall back to
29
+ * ~/.cache. Overridable via UAP_PROXY_RUNTIME_DIR for tests.
30
+ */
31
+ export declare function runtimeDir(): string;
32
+ /** How the proxy we own was launched — only 'process' is ever auto-stopped. */
33
+ export type OwnerKind = 'process' | 'systemd';
34
+ export interface OwnerRecord {
35
+ kind: OwnerKind;
36
+ /** pid of the launched proxy process (kind==='process'). */
37
+ pid?: number;
38
+ /**
39
+ * Opaque process-identity token captured at launch (e.g. /proc start-time).
40
+ * Checked before signaling to defend against PID reuse.
41
+ */
42
+ startToken?: string;
43
+ /** systemd unit name (kind==='systemd'). */
44
+ unit?: string;
45
+ port: number;
46
+ startedAt: string;
47
+ }
48
+ export interface ClientRecord {
49
+ clientId: string;
50
+ /** pid of the long-lived AGENT process, used to prune dead clients. */
51
+ pid: number;
52
+ harness?: string;
53
+ registeredAt: string;
54
+ }
55
+ /** Is a pid still alive? `kill(pid, 0)` throws ESRCH when it isn't. */
56
+ export declare function isPidAlive(pid: number): boolean;
57
+ /** Derive the agent pid encoded in a `ppid-<n>` fallback client id, if present. */
58
+ export declare function pidFromClientId(clientId: string): number | null;
59
+ /** Register (or refresh) a client in the registry. Idempotent per clientId. */
60
+ export declare function registerClient(rt: string, rec: ClientRecord): void;
61
+ /** Remove a client from the registry (no-op if absent). */
62
+ export declare function deregisterClient(rt: string, clientId: string): void;
63
+ /** List live clients, pruning any whose AGENT process has died (self-healing). */
64
+ export declare function listClients(rt: string, opts?: {
65
+ prune?: boolean;
66
+ }): ClientRecord[];
67
+ export declare function readOwner(rt: string): OwnerRecord | null;
68
+ export declare function writeOwner(rt: string, rec: OwnerRecord): void;
69
+ export declare function clearOwner(rt: string): void;
70
+ export declare function acquireStartLock(rt: string, timeoutMs?: number): Promise<boolean>;
71
+ export declare function releaseStartLock(rt: string): void;
72
+ /** Outcome of a start attempt. `owner` is set ONLY for a process WE spawned and
73
+ * confirmed serving — systemd/adopted starts return owner:null (never stopped). */
74
+ export interface StartResult {
75
+ healthy: boolean;
76
+ owner: OwnerRecord | null;
77
+ }
78
+ export interface LifecycleDeps {
79
+ /** Resolve true when the proxy answers a health probe on `port`. */
80
+ probeHealth: (port: number) => Promise<boolean>;
81
+ /** Start the proxy; wait for health; return {healthy, owner}. Owner==null
82
+ * means "do not claim stoppable ownership" (systemd / failed / adopted). */
83
+ startProxy: (port: number) => Promise<StartResult>;
84
+ /** Stop a proxy WE own (kill pid after identity check). */
85
+ stopProxy: (owner: OwnerRecord) => Promise<void>;
86
+ /** ISO timestamp (injectable for deterministic tests). */
87
+ now: () => string;
88
+ }
89
+ export type EnsureAction = 'reused' | 'started' | 'start-failed';
90
+ export interface EnsureResult {
91
+ action: EnsureAction;
92
+ port: number;
93
+ owner: OwnerRecord | null;
94
+ clients: number;
95
+ }
96
+ /**
97
+ * Ensure a proxy is available for this client.
98
+ * - already healthy -> ADOPT (reuse); register client; claim no ownership.
99
+ * - not healthy -> take the start lock, re-probe, then start. Claim ownership
100
+ * ONLY if the adapter returns a stoppable owner (a process we spawned).
101
+ * Fail-open: a failed start returns 'start-failed' and never throws.
102
+ */
103
+ export declare function ensureProxy(rt: string, client: ClientRecord, port: number, deps: LifecycleDeps): Promise<EnsureResult>;
104
+ export type ReleaseAction = 'stopped' | 'left-other-clients' | 'left-adopted' | 'not-running';
105
+ export interface ReleaseResult {
106
+ action: ReleaseAction;
107
+ remainingClients: number;
108
+ }
109
+ /**
110
+ * Release this client. Stop the proxy ONLY when:
111
+ * - we own it (a process WE spawned — owner marker present), AND
112
+ * - no other client remains after removing this one.
113
+ * Otherwise leave it running:
114
+ * - 'left-other-clients' — another agent session is still using it.
115
+ * - 'left-adopted' — we never owned it (pre-existing / systemd / external).
116
+ */
117
+ export declare function releaseProxy(rt: string, clientId: string, deps: LifecycleDeps): Promise<ReleaseResult>;
118
+ /**
119
+ * Derive a stable client id shared across a session's SessionStart + Stop
120
+ * hooks. Order MATCHES the hook derivation (session-start.sh) so a manual CLI
121
+ * call and the hook agree: explicit flag, then harness session ids, then the
122
+ * parent pid (the agent) — spelled `ppid-<n>` to match the hook.
123
+ */
124
+ export declare function resolveClientId(explicit?: string): string;
125
+ //# sourceMappingURL=proxy-lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy-lifecycle.d.ts","sourceRoot":"","sources":["../../src/cli/proxy-lifecycle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,286 @@
1
+ /**
2
+ * Reference-counted, session-scoped proxy lifecycle.
3
+ *
4
+ * Goal (per product spec): the UAP Anthropic proxy should start when an agent
5
+ * session is triggered (via the SessionStart hook) and exit WITH the agent —
6
+ * but only when it is safe to do so:
7
+ *
8
+ * - If a proxy is already running, ADOPT it (reuse) — never start a second.
9
+ * - A proxy we adopted (already up), OR a systemd-managed unit, is NEVER
10
+ * stopped by us on session end (systemd units are persistent infra).
11
+ * - A proxy WE spawned as a plain process is reference-counted across
12
+ * sessions: it is stopped on release only when the LAST client (agent
13
+ * session) leaves. If any other client is still using it, it keeps running.
14
+ *
15
+ * This module holds the pure decision logic + a tiny file-backed registry so it
16
+ * is unit-testable without spawning a real proxy or touching systemd. The real
17
+ * side effects (spawn / systemctl / HTTP health probe) are injected as `deps`.
18
+ *
19
+ * Liveness note: a client's `pid` MUST be the long-lived AGENT process (the one
20
+ * that spawns SessionStart and SessionEnd), NOT the ephemeral hook/CLI process
21
+ * — otherwise the dead-pid pruning below evicts every live session moments
22
+ * after it registers. The hook passes `$PPID` (the agent) via `--client-pid`.
23
+ */
24
+ import { mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, existsSync, } from 'fs';
25
+ import { join } from 'path';
26
+ import os from 'os';
27
+ /** Default port the proxy listens on (matches run-anthropic-proxy-continuity.sh). */
28
+ export const DEFAULT_PROXY_PORT = Number(process.env.PROXY_PORT ?? 4000);
29
+ const DIR_MODE = 0o700;
30
+ const FILE_MODE = 0o600;
31
+ /**
32
+ * Runtime root for the client registry + owner marker. Prefer XDG_RUNTIME_DIR
33
+ * (tmpfs at /run/user/<uid>, mode 0700, cleared on logout) and fall back to
34
+ * ~/.cache. Overridable via UAP_PROXY_RUNTIME_DIR for tests.
35
+ */
36
+ export function runtimeDir() {
37
+ const override = process.env.UAP_PROXY_RUNTIME_DIR;
38
+ if (override)
39
+ return override;
40
+ const base = process.env.XDG_RUNTIME_DIR || join(os.homedir(), '.cache');
41
+ return join(base, 'uap-proxy');
42
+ }
43
+ function clientsDir(rt) {
44
+ return join(rt, 'clients');
45
+ }
46
+ function ownerPath(rt) {
47
+ return join(rt, 'owner.json');
48
+ }
49
+ function lockPath(rt) {
50
+ return join(rt, 'start.lock');
51
+ }
52
+ /** Is a pid still alive? `kill(pid, 0)` throws ESRCH when it isn't. */
53
+ export function isPidAlive(pid) {
54
+ if (!Number.isInteger(pid) || pid <= 0)
55
+ return false;
56
+ try {
57
+ process.kill(pid, 0);
58
+ return true;
59
+ }
60
+ catch (e) {
61
+ // EPERM means the process exists but we can't signal it — still alive.
62
+ return e.code === 'EPERM';
63
+ }
64
+ }
65
+ function safeFilename(clientId) {
66
+ // Registry files are named by client id; keep them filesystem-safe and
67
+ // incapable of escaping the clients dir (no path separators survive).
68
+ return clientId.replace(/[^A-Za-z0-9._-]/g, '_').slice(0, 200) || 'anon';
69
+ }
70
+ /** Derive the agent pid encoded in a `ppid-<n>` fallback client id, if present. */
71
+ export function pidFromClientId(clientId) {
72
+ const m = /^ppid-(\d+)$/.exec(clientId);
73
+ if (!m)
74
+ return null;
75
+ const n = Number(m[1]);
76
+ return Number.isInteger(n) && n > 0 ? n : null;
77
+ }
78
+ /** Atomic write (tmp + rename) with restrictive mode so a torn read is impossible. */
79
+ function atomicWrite(path, data) {
80
+ const tmp = `${path}.tmp.${process.pid}.${Math.floor(process.hrtime()[1] % 1e6)}`;
81
+ writeFileSync(tmp, data, { mode: FILE_MODE });
82
+ renameSync(tmp, path);
83
+ }
84
+ /** Register (or refresh) a client in the registry. Idempotent per clientId. */
85
+ export function registerClient(rt, rec) {
86
+ const dir = clientsDir(rt);
87
+ mkdirSync(dir, { recursive: true, mode: DIR_MODE });
88
+ atomicWrite(join(dir, safeFilename(rec.clientId)), JSON.stringify(rec));
89
+ }
90
+ /** Remove a client from the registry (no-op if absent). */
91
+ export function deregisterClient(rt, clientId) {
92
+ try {
93
+ rmSync(join(clientsDir(rt), safeFilename(clientId)), { force: true });
94
+ }
95
+ catch {
96
+ /* best-effort */
97
+ }
98
+ }
99
+ /** List live clients, pruning any whose AGENT process has died (self-healing). */
100
+ export function listClients(rt, opts = {}) {
101
+ const dir = clientsDir(rt);
102
+ if (!existsSync(dir))
103
+ return [];
104
+ const out = [];
105
+ let entries = [];
106
+ try {
107
+ entries = readdirSync(dir);
108
+ }
109
+ catch {
110
+ return [];
111
+ }
112
+ for (const name of entries) {
113
+ if (name.endsWith('.tmp') || name.includes('.tmp.'))
114
+ continue; // in-flight write
115
+ const full = join(dir, name);
116
+ let rec = null;
117
+ try {
118
+ rec = JSON.parse(readFileSync(full, 'utf8'));
119
+ }
120
+ catch {
121
+ rec = null;
122
+ }
123
+ if (!rec || typeof rec.clientId !== 'string') {
124
+ if (opts.prune !== false)
125
+ rmSync(full, { force: true });
126
+ continue;
127
+ }
128
+ // A client whose AGENT process is gone ended without a clean release
129
+ // (crash). Drop it so it never pins the proxy alive forever. pid<=0 means
130
+ // "liveness unknown" — keep it (rely on clean release) rather than evict.
131
+ if (typeof rec.pid === 'number' && rec.pid > 0 && !isPidAlive(rec.pid)) {
132
+ if (opts.prune !== false)
133
+ rmSync(full, { force: true });
134
+ continue;
135
+ }
136
+ out.push(rec);
137
+ }
138
+ return out;
139
+ }
140
+ export function readOwner(rt) {
141
+ const p = ownerPath(rt);
142
+ if (!existsSync(p))
143
+ return null;
144
+ try {
145
+ const rec = JSON.parse(readFileSync(p, 'utf8'));
146
+ if (rec && (rec.kind === 'process' || rec.kind === 'systemd'))
147
+ return rec;
148
+ }
149
+ catch {
150
+ /* fall through */
151
+ }
152
+ return null;
153
+ }
154
+ export function writeOwner(rt, rec) {
155
+ mkdirSync(rt, { recursive: true, mode: DIR_MODE });
156
+ atomicWrite(ownerPath(rt), JSON.stringify(rec));
157
+ }
158
+ export function clearOwner(rt) {
159
+ try {
160
+ rmSync(ownerPath(rt), { force: true });
161
+ }
162
+ catch {
163
+ /* best-effort */
164
+ }
165
+ }
166
+ // ---------------------------------------------------------------------------
167
+ // Start lock — serialize the probe->start->writeOwner critical section so two
168
+ // simultaneous SessionStarts don't both spawn a proxy onto the same port.
169
+ // ---------------------------------------------------------------------------
170
+ const LOCK_STALE_MS = 30_000;
171
+ export async function acquireStartLock(rt, timeoutMs = 5000) {
172
+ mkdirSync(rt, { recursive: true, mode: DIR_MODE });
173
+ const p = lockPath(rt);
174
+ const deadline = Date.now() + timeoutMs;
175
+ for (;;) {
176
+ try {
177
+ mkdirSync(p); // atomic: succeeds only if it did not exist
178
+ return true;
179
+ }
180
+ catch {
181
+ // Held. Steal it if it is stale (a crashed holder).
182
+ try {
183
+ const st = statSync(p);
184
+ if (Date.now() - st.mtimeMs > LOCK_STALE_MS) {
185
+ rmSync(p, { recursive: true, force: true });
186
+ continue;
187
+ }
188
+ }
189
+ catch {
190
+ continue; // vanished between attempts — retry
191
+ }
192
+ if (Date.now() >= deadline)
193
+ return false; // give up; caller proceeds (fail-open)
194
+ await new Promise((r) => setTimeout(r, 100));
195
+ }
196
+ }
197
+ }
198
+ export function releaseStartLock(rt) {
199
+ try {
200
+ rmSync(lockPath(rt), { recursive: true, force: true });
201
+ }
202
+ catch {
203
+ /* best-effort */
204
+ }
205
+ }
206
+ /**
207
+ * Ensure a proxy is available for this client.
208
+ * - already healthy -> ADOPT (reuse); register client; claim no ownership.
209
+ * - not healthy -> take the start lock, re-probe, then start. Claim ownership
210
+ * ONLY if the adapter returns a stoppable owner (a process we spawned).
211
+ * Fail-open: a failed start returns 'start-failed' and never throws.
212
+ */
213
+ export async function ensureProxy(rt, client, port, deps) {
214
+ listClients(rt); // prune dead clients first
215
+ registerClient(rt, client);
216
+ if (await deps.probeHealth(port)) {
217
+ return { action: 'reused', port, owner: readOwner(rt), clients: listClients(rt).length };
218
+ }
219
+ const locked = await acquireStartLock(rt);
220
+ try {
221
+ // Another session may have started it while we waited for the lock.
222
+ if (await deps.probeHealth(port)) {
223
+ return { action: 'reused', port, owner: readOwner(rt), clients: listClients(rt).length };
224
+ }
225
+ const res = await deps.startProxy(port);
226
+ if (!res.healthy) {
227
+ return { action: 'start-failed', port, owner: null, clients: listClients(rt).length };
228
+ }
229
+ // Own ONLY a process we spawned + confirmed. systemd/adopted -> owner null.
230
+ if (res.owner)
231
+ writeOwner(rt, res.owner);
232
+ return { action: 'started', port, owner: res.owner, clients: listClients(rt).length };
233
+ }
234
+ finally {
235
+ if (locked)
236
+ releaseStartLock(rt);
237
+ }
238
+ }
239
+ /**
240
+ * Release this client. Stop the proxy ONLY when:
241
+ * - we own it (a process WE spawned — owner marker present), AND
242
+ * - no other client remains after removing this one.
243
+ * Otherwise leave it running:
244
+ * - 'left-other-clients' — another agent session is still using it.
245
+ * - 'left-adopted' — we never owned it (pre-existing / systemd / external).
246
+ */
247
+ export async function releaseProxy(rt, clientId, deps) {
248
+ deregisterClient(rt, clientId);
249
+ const remaining = listClients(rt); // prunes dead clients too
250
+ const owner = readOwner(rt);
251
+ if (!owner) {
252
+ return { action: 'left-adopted', remainingClients: remaining.length };
253
+ }
254
+ if (remaining.length > 0) {
255
+ return { action: 'left-other-clients', remainingClients: remaining.length };
256
+ }
257
+ // We own it and we are the last client — shut it down with the agent.
258
+ try {
259
+ await deps.stopProxy(owner);
260
+ }
261
+ finally {
262
+ clearOwner(rt);
263
+ }
264
+ return { action: 'stopped', remainingClients: 0 };
265
+ }
266
+ /**
267
+ * Derive a stable client id shared across a session's SessionStart + Stop
268
+ * hooks. Order MATCHES the hook derivation (session-start.sh) so a manual CLI
269
+ * call and the hook agree: explicit flag, then harness session ids, then the
270
+ * parent pid (the agent) — spelled `ppid-<n>` to match the hook.
271
+ */
272
+ export function resolveClientId(explicit) {
273
+ const candidates = [
274
+ explicit,
275
+ process.env.CLAUDE_SESSION_ID,
276
+ process.env.FACTORY_SESSION_ID,
277
+ process.env.CURSOR_SESSION_ID,
278
+ process.env.UAP_SESSION_ID,
279
+ ];
280
+ for (const c of candidates) {
281
+ if (c && c.trim())
282
+ return c.trim();
283
+ }
284
+ return `ppid-${process.ppid}`;
285
+ }
286
+ //# sourceMappingURL=proxy-lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy-lifecycle.js","sourceRoot":"","sources":["../../src/cli/proxy-lifecycle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * `uap proxy` — reference-counted, session-scoped lifecycle for the UAP
3
+ * Anthropic proxy.
4
+ *
5
+ * uap proxy ensure [--client <id>] [--client-pid <n>] [--port <n>] [--quiet]
6
+ * Start the proxy if none is running, or ADOPT an already-running one.
7
+ * Registers this session as a client. Wired into the SessionStart hook.
8
+ * uap proxy release [--client <id>] [--quiet]
9
+ * Deregister this session; stop the proxy ONLY if WE spawned it (as a
10
+ * plain process) AND no other client remains. systemd/adopted proxies are
11
+ * never stopped. Wired into the Stop / SessionEnd hook.
12
+ * uap proxy status [--port <n>] [--json]
13
+ * uap proxy start | stop | restart manual controls
14
+ * uap proxy enable | disable toggle .uap.json proxy.autostart
15
+ *
16
+ * Everything is fail-open: hook-driven calls never throw and never block the
17
+ * session — a proxy that won't start just means the agent runs without it.
18
+ *
19
+ * Env overrides (test/advanced):
20
+ * PROXY_PORT default port (4000)
21
+ * UAP_PROXY_RUNTIME_DIR registry root (default $XDG_RUNTIME_DIR/uap-proxy)
22
+ * UAP_PROXY_RUN_SCRIPT proxy launch script (default: bundled continuity script)
23
+ * UAP_PROXY_NO_SYSTEMD=1 force the detached-process path (skip systemd)
24
+ * UAP_PROXY_HEALTH_WAIT_MS how long to wait for /health after launch (15000)
25
+ */
26
+ export interface ProxyOptions {
27
+ client?: string;
28
+ clientPid?: string | number;
29
+ ifEnabled?: boolean;
30
+ port?: string | number;
31
+ quiet?: boolean;
32
+ json?: boolean;
33
+ }
34
+ export declare function proxyCommand(sub: string | undefined, options?: ProxyOptions): Promise<void>;
35
+ //# sourceMappingURL=proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../src/cli/proxy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AA0PH,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CA8If"}