@saadnvd1/agent-os 0.3.498 → 0.3.501
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 +70 -0
- package/app/api/usage/rollups/route.ts +36 -0
- package/components/Devices/ConnectCard.tsx +10 -12
- package/components/Tasks/TaskRow.tsx +2 -0
- package/components/Usage/RatchetView.tsx +249 -0
- package/components/Usage/UsageDialog.tsx +7 -1
- package/components/Usage/WindowCard.tsx +2 -1
- package/data/usage/index.ts +28 -0
- package/lib/chat/drivers/claude-mapper.ts +1 -0
- package/lib/chat/drivers/claude.ts +46 -0
- package/lib/chat/events.ts +3 -0
- package/lib/chat/worker/host.ts +21 -0
- package/lib/chat/worker/protocol.ts +8 -1
- package/lib/connect/cli.ts +51 -72
- package/lib/connect/cloudflared.ts +172 -0
- package/lib/connect/config.ts +75 -26
- package/lib/connect/control-api.ts +107 -48
- package/lib/connect/enrol.ts +33 -107
- package/lib/connect/serve.ts +177 -94
- package/lib/connect/supervise.ts +281 -0
- package/lib/db/migrations.ts +86 -0
- package/lib/orchestrator/armed.ts +2 -1
- package/lib/orchestrator/deliver.ts +49 -7
- package/lib/orchestrator/events.ts +35 -2
- package/lib/security/auth.ts +20 -4
- package/lib/security/gate.ts +8 -5
- package/lib/security/reach.ts +1 -1
- package/lib/tasks/index.ts +8 -0
- package/lib/usage/calls.ts +284 -0
- package/lib/usage/check-runs.ts +40 -19
- package/lib/usage/config.ts +56 -0
- package/lib/usage/nightly.ts +157 -0
- package/lib/usage/quality.ts +239 -0
- package/lib/usage/ratchet.ts +275 -0
- package/lib/usage/rate-limits.ts +192 -0
- package/lib/usage/rollup.ts +90 -0
- package/lib/usage/span.ts +152 -0
- package/lib/usage/transcript-import.ts +226 -0
- package/lib/usage/weights.ts +64 -0
- package/lib/usage/windows.ts +27 -2
- package/package.json +2 -4
- package/server.ts +7 -4
- package/stores/usageUi.ts +3 -3
- package/lib/connect/cert-check.ts +0 -32
- package/lib/connect/cloudflare-dns.ts +0 -56
- package/lib/connect/frames.ts +0 -72
- package/lib/connect/identity.ts +0 -98
- package/lib/connect/mux.ts +0 -110
- package/lib/connect/stream.ts +0 -106
- package/lib/connect/tunnel.ts +0 -98
package/lib/connect/mux.ts
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Both ends of the tunnel: a map of TunnelStreams over one WebSocket. The
|
|
3
|
-
* relay opens streams (a phone connected); the machine receives them.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type WebSocket from "ws";
|
|
7
|
-
import { decode, encode, Frame, json, type Decoded } from "./frames";
|
|
8
|
-
import { TunnelStream } from "./stream";
|
|
9
|
-
|
|
10
|
-
// Past this much unsent data, writers wait for the socket to drain.
|
|
11
|
-
const HIGH_WATER = 1 << 20;
|
|
12
|
-
export const MAX_STREAMS = 256;
|
|
13
|
-
|
|
14
|
-
export interface OpenMeta {
|
|
15
|
-
remote?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export class Mux {
|
|
19
|
-
readonly streams = new Map<number, TunnelStream>();
|
|
20
|
-
bytesIn = 0;
|
|
21
|
-
bytesOut = 0;
|
|
22
|
-
private next = 1;
|
|
23
|
-
|
|
24
|
-
constructor(
|
|
25
|
-
private readonly ws: WebSocket,
|
|
26
|
-
private readonly onOpen?: (stream: TunnelStream, meta: OpenMeta) => void
|
|
27
|
-
) {}
|
|
28
|
-
|
|
29
|
-
private send = (frame: Buffer): boolean => {
|
|
30
|
-
if (this.ws.readyState !== this.ws.OPEN) return true;
|
|
31
|
-
if (frame[0] === Frame.DATA) this.bytesOut += frame.length - 5;
|
|
32
|
-
this.ws.send(frame);
|
|
33
|
-
return this.ws.bufferedAmount < HIGH_WATER;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
private drained = (cb: () => void): void => {
|
|
37
|
-
const check = () => {
|
|
38
|
-
if (
|
|
39
|
-
this.ws.readyState !== this.ws.OPEN ||
|
|
40
|
-
this.ws.bufferedAmount < HIGH_WATER
|
|
41
|
-
)
|
|
42
|
-
cb();
|
|
43
|
-
else setTimeout(check, 20);
|
|
44
|
-
};
|
|
45
|
-
setTimeout(check, 20);
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
private stream(id: number): TunnelStream {
|
|
49
|
-
const s = new TunnelStream(
|
|
50
|
-
id,
|
|
51
|
-
this.send,
|
|
52
|
-
(done) => this.streams.delete(done),
|
|
53
|
-
this.drained
|
|
54
|
-
);
|
|
55
|
-
this.streams.set(id, s);
|
|
56
|
-
return s;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** Relay side: a new client connection, or null if the tunnel is going away. */
|
|
60
|
-
open(meta: OpenMeta): TunnelStream | null {
|
|
61
|
-
if (this.ws.readyState !== this.ws.OPEN) return null;
|
|
62
|
-
const id = this.next++;
|
|
63
|
-
const s = this.stream(id);
|
|
64
|
-
s.remoteAddress = meta.remote;
|
|
65
|
-
this.ws.send(encode(Frame.OPEN, id, meta));
|
|
66
|
-
return s;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** Feed one WebSocket message in. Returns frames this layer doesn't own. */
|
|
70
|
-
handle(data: Buffer): Decoded | null {
|
|
71
|
-
const f = decode(data);
|
|
72
|
-
if (!f) return null;
|
|
73
|
-
const s = this.streams.get(f.stream);
|
|
74
|
-
switch (f.type) {
|
|
75
|
-
case Frame.OPEN: {
|
|
76
|
-
if (!this.onOpen || this.streams.has(f.stream)) return null;
|
|
77
|
-
// The relay doesn't get to open unlimited connections into this machine.
|
|
78
|
-
if (this.streams.size >= MAX_STREAMS) {
|
|
79
|
-
this.ws.send(encode(Frame.CLOSE, f.stream));
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
const meta = json<OpenMeta>(f.payload) ?? {};
|
|
83
|
-
const created = this.stream(f.stream);
|
|
84
|
-
created.remoteAddress = meta.remote;
|
|
85
|
-
this.onOpen(created, meta);
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
case Frame.DATA:
|
|
89
|
-
this.bytesIn += f.payload.length;
|
|
90
|
-
s?.receive(Buffer.from(f.payload));
|
|
91
|
-
return null;
|
|
92
|
-
case Frame.CLOSE:
|
|
93
|
-
s?.remoteClosed();
|
|
94
|
-
return null;
|
|
95
|
-
case Frame.PAUSE:
|
|
96
|
-
s?.setRemotePaused(true);
|
|
97
|
-
return null;
|
|
98
|
-
case Frame.RESUME:
|
|
99
|
-
s?.setRemotePaused(false);
|
|
100
|
-
return null;
|
|
101
|
-
default:
|
|
102
|
-
return f;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
closeAll(): void {
|
|
107
|
-
for (const s of this.streams.values()) s.remoteClosed();
|
|
108
|
-
this.streams.clear();
|
|
109
|
-
}
|
|
110
|
-
}
|
package/lib/connect/stream.ts
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* One tunnelled TCP connection, as a Duplex. Writes become DATA frames; DATA
|
|
3
|
-
* frames from the other side are pushed in. When our reader falls behind we
|
|
4
|
-
* tell the other side to PAUSE that stream, so one slow terminal can't stall
|
|
5
|
-
* the others sharing the WebSocket.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { Duplex } from "stream";
|
|
9
|
-
import { encode, Frame } from "./frames";
|
|
10
|
-
|
|
11
|
-
export type Send = (frame: Buffer) => boolean;
|
|
12
|
-
|
|
13
|
-
const MAX_FRAME = 64 * 1024;
|
|
14
|
-
// PAUSE goes out at the 64 KB high-water mark; this is far past it.
|
|
15
|
-
export const MAX_BUFFERED = 4 * 1024 * 1024;
|
|
16
|
-
|
|
17
|
-
export class TunnelStream extends Duplex {
|
|
18
|
-
private paused = false;
|
|
19
|
-
private remotePaused = false;
|
|
20
|
-
private waiting: (() => void)[] = [];
|
|
21
|
-
private closedByRemote = false;
|
|
22
|
-
/** Where the far end says the connection came from; informational only. */
|
|
23
|
-
remoteAddress?: string;
|
|
24
|
-
|
|
25
|
-
constructor(
|
|
26
|
-
readonly id: number,
|
|
27
|
-
private readonly send: Send,
|
|
28
|
-
private readonly onDone: (id: number) => void,
|
|
29
|
-
private readonly drained: (cb: () => void) => void
|
|
30
|
-
) {
|
|
31
|
-
super({ allowHalfOpen: false });
|
|
32
|
-
this.once("close", () => this.onDone(this.id));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** DATA from the far end. */
|
|
36
|
-
receive(chunk: Buffer): void {
|
|
37
|
-
// A far end that ignores PAUSE can't make us buffer without limit.
|
|
38
|
-
if (this.readableLength + chunk.length > MAX_BUFFERED) {
|
|
39
|
-
// No error object: nothing listens for "error" on these streams, and an
|
|
40
|
-
// unheard error event would take the whole process down.
|
|
41
|
-
this.destroy();
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
if (!this.push(chunk) && !this.paused) {
|
|
45
|
-
this.paused = true;
|
|
46
|
-
this.send(encode(Frame.PAUSE, this.id));
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** The far end asked us to stop or start sending. */
|
|
51
|
-
setRemotePaused(paused: boolean): void {
|
|
52
|
-
this.remotePaused = paused;
|
|
53
|
-
if (!paused) this.waiting.splice(0).forEach((go) => go());
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** The far end closed. */
|
|
57
|
-
remoteClosed(): void {
|
|
58
|
-
this.closedByRemote = true;
|
|
59
|
-
this.push(null);
|
|
60
|
-
this.destroy();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
_read(): void {
|
|
64
|
-
if (this.paused) {
|
|
65
|
-
this.paused = false;
|
|
66
|
-
this.send(encode(Frame.RESUME, this.id));
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
_write(
|
|
71
|
-
chunk: Buffer,
|
|
72
|
-
_enc: BufferEncoding,
|
|
73
|
-
done: (err?: Error) => void
|
|
74
|
-
): void {
|
|
75
|
-
// Frames stay well under the relay's 1 MB message limit.
|
|
76
|
-
const pieces: Buffer[] = [];
|
|
77
|
-
for (let i = 0; i < chunk.length; i += MAX_FRAME) {
|
|
78
|
-
pieces.push(chunk.subarray(i, i + MAX_FRAME));
|
|
79
|
-
}
|
|
80
|
-
const next = (): void => {
|
|
81
|
-
const piece = pieces.shift();
|
|
82
|
-
if (!piece) return done();
|
|
83
|
-
if (this.remotePaused) return void this.waiting.push(next);
|
|
84
|
-
// send() is false while the WebSocket's buffer is full; wait it out.
|
|
85
|
-
if (this.send(encode(Frame.DATA, this.id, piece))) next();
|
|
86
|
-
else this.drained(next);
|
|
87
|
-
};
|
|
88
|
-
next();
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
_destroy(err: Error | null, done: (err: Error | null) => void): void {
|
|
92
|
-
if (!this.closedByRemote) this.send(encode(Frame.CLOSE, this.id));
|
|
93
|
-
done(err);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// What http/tls expect of a socket.
|
|
97
|
-
setNoDelay(): this {
|
|
98
|
-
return this;
|
|
99
|
-
}
|
|
100
|
-
setKeepAlive(): this {
|
|
101
|
-
return this;
|
|
102
|
-
}
|
|
103
|
-
setTimeout(): this {
|
|
104
|
-
return this;
|
|
105
|
-
}
|
|
106
|
-
}
|
package/lib/connect/tunnel.ts
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This machine's end of the tunnel: dial the relay, prove who we are, and
|
|
3
|
-
* hand each incoming stream to `onStream`. Reconnects with backoff forever.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import WebSocket from "ws";
|
|
7
|
-
import { encode, Frame, json } from "./frames";
|
|
8
|
-
import { signHello } from "./identity";
|
|
9
|
-
import { Mux } from "./mux";
|
|
10
|
-
import type { TunnelStream } from "./stream";
|
|
11
|
-
import type { ConnectConfig } from "./config";
|
|
12
|
-
|
|
13
|
-
export interface TunnelOptions {
|
|
14
|
-
config: ConnectConfig;
|
|
15
|
-
machineKey: string;
|
|
16
|
-
onStream: (stream: TunnelStream) => void;
|
|
17
|
-
log?: (line: string) => void;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export type TunnelState = "connecting" | "up" | "denied" | "stopped";
|
|
21
|
-
|
|
22
|
-
export function startTunnel(opts: TunnelOptions) {
|
|
23
|
-
const log = opts.log ?? (() => {});
|
|
24
|
-
let state: TunnelState = "connecting";
|
|
25
|
-
let ws: WebSocket | null = null;
|
|
26
|
-
let delay = 1000;
|
|
27
|
-
let timer: NodeJS.Timeout | null = null;
|
|
28
|
-
|
|
29
|
-
const connect = () => {
|
|
30
|
-
if (state === "stopped") return;
|
|
31
|
-
const { relayUrl, relayServername, relayCa } = opts.config;
|
|
32
|
-
// ws hands these to tls.connect; servername isn't in its own types.
|
|
33
|
-
const tlsOptions = {
|
|
34
|
-
// Frames are at most 64 KB; nothing legitimate comes near this.
|
|
35
|
-
maxPayload: 1 << 20,
|
|
36
|
-
servername: relayServername,
|
|
37
|
-
ca: relayCa,
|
|
38
|
-
headers: relayServername ? { Host: relayServername } : undefined,
|
|
39
|
-
} as WebSocket.ClientOptions;
|
|
40
|
-
try {
|
|
41
|
-
ws = new WebSocket(`${relayUrl}/tunnel`, tlsOptions);
|
|
42
|
-
} catch (err) {
|
|
43
|
-
// A bad relayUrl must not throw out of a reconnect timer.
|
|
44
|
-
log(`connect: can't dial ${relayUrl}: ${(err as Error).message}`);
|
|
45
|
-
state = "denied";
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
const mux = new Mux(ws, (stream) => opts.onStream(stream));
|
|
49
|
-
ws.on("open", () => {
|
|
50
|
-
ws!.send(
|
|
51
|
-
encode(
|
|
52
|
-
Frame.HELLO,
|
|
53
|
-
0,
|
|
54
|
-
signHello(opts.config.machineId, opts.machineKey)
|
|
55
|
-
)
|
|
56
|
-
);
|
|
57
|
-
});
|
|
58
|
-
ws.on("message", (data: Buffer) => {
|
|
59
|
-
const f = mux.handle(data);
|
|
60
|
-
if (f?.type === Frame.READY) {
|
|
61
|
-
state = "up";
|
|
62
|
-
delay = 1000;
|
|
63
|
-
log(
|
|
64
|
-
`connect: up as ${json<{ hostname: string }>(f.payload)?.hostname}`
|
|
65
|
-
);
|
|
66
|
-
} else if (f?.type === Frame.DENIED) {
|
|
67
|
-
state = "denied";
|
|
68
|
-
log(
|
|
69
|
-
`connect: relay refused: ${json<{ error: string }>(f.payload)?.error}`
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
ws.on("close", () => {
|
|
74
|
-
mux.closeAll();
|
|
75
|
-
if (state === "stopped") return;
|
|
76
|
-
// A refusal won't fix itself quickly; back off hard.
|
|
77
|
-
const denied = state === "denied";
|
|
78
|
-
if (!denied) state = "connecting";
|
|
79
|
-
const wait = denied ? 5 * 60_000 : delay;
|
|
80
|
-
delay = Math.min(delay * 2, 30_000);
|
|
81
|
-
timer = setTimeout(() => {
|
|
82
|
-
state = "connecting";
|
|
83
|
-
connect();
|
|
84
|
-
}, wait);
|
|
85
|
-
});
|
|
86
|
-
ws.on("error", (err) => log(`connect: ${err.message}`));
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
connect();
|
|
90
|
-
return {
|
|
91
|
-
state: () => state,
|
|
92
|
-
stop() {
|
|
93
|
-
state = "stopped";
|
|
94
|
-
if (timer) clearTimeout(timer);
|
|
95
|
-
ws?.close();
|
|
96
|
-
},
|
|
97
|
-
};
|
|
98
|
-
}
|