@hydra-acp/archiver 0.1.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,151 @@
1
+ import { Readable } from "node:stream";
2
+ import { google } from "googleapis";
3
+ import { loadGoogleAuth } from "../oauth/google.js";
4
+ import { logger } from "../util/log.js";
5
+ const log = logger("backend.google-drive");
6
+ const FOLDER_MIME = "application/vnd.google-apps.folder";
7
+ const FILE_MIME = "application/json";
8
+ // Drive-backed implementation. Uses the drive.file scope, which limits
9
+ // visibility to files the app created plus those explicitly handed to
10
+ // it — so the archiver never sees the user's other Drive contents.
11
+ export class GoogleDriveBackend {
12
+ opts;
13
+ drive;
14
+ folderId;
15
+ constructor(opts) {
16
+ this.opts = opts;
17
+ }
18
+ async init() {
19
+ const auth = await loadGoogleAuth({
20
+ credentialsPath: this.opts.credentialsPath,
21
+ tokenPath: this.opts.tokenPath,
22
+ });
23
+ this.drive = google.drive({ version: "v3", auth });
24
+ this.folderId = await this.ensureFolder(this.opts.folderName);
25
+ log.info(`google-drive backend ready: folder="${this.opts.folderName}" id=${this.folderId}`);
26
+ }
27
+ async list() {
28
+ const drive = this.requireDrive();
29
+ const parent = this.requireFolderId();
30
+ const entries = [];
31
+ let pageToken;
32
+ do {
33
+ const res = await drive.files.list({
34
+ q: `'${parent}' in parents and trashed = false`,
35
+ fields: "nextPageToken, files(id, name, size, modifiedTime)",
36
+ spaces: "drive",
37
+ pageSize: 200,
38
+ ...(pageToken !== undefined ? { pageToken } : {}),
39
+ });
40
+ for (const f of res.data.files ?? []) {
41
+ if (!f.name) {
42
+ continue;
43
+ }
44
+ entries.push({
45
+ key: f.name,
46
+ size: typeof f.size === "string" ? Number.parseInt(f.size, 10) : 0,
47
+ modifiedAt: f.modifiedTime ?? new Date(0).toISOString(),
48
+ });
49
+ }
50
+ pageToken = res.data.nextPageToken ?? undefined;
51
+ } while (pageToken);
52
+ return entries;
53
+ }
54
+ async get(key) {
55
+ const drive = this.requireDrive();
56
+ const fileId = await this.findFileId(key);
57
+ if (!fileId) {
58
+ throw new Error(`google-drive: no file named ${key}`);
59
+ }
60
+ const res = await drive.files.get({ fileId, alt: "media" }, { responseType: "arraybuffer" });
61
+ return Buffer.from(res.data);
62
+ }
63
+ async put(key, data) {
64
+ const drive = this.requireDrive();
65
+ const parent = this.requireFolderId();
66
+ const existing = await this.findFileId(key);
67
+ const media = {
68
+ mimeType: FILE_MIME,
69
+ body: Readable.from(data),
70
+ };
71
+ if (existing) {
72
+ await drive.files.update({
73
+ fileId: existing,
74
+ media,
75
+ });
76
+ return;
77
+ }
78
+ await drive.files.create({
79
+ requestBody: {
80
+ name: key,
81
+ mimeType: FILE_MIME,
82
+ parents: [parent],
83
+ },
84
+ media,
85
+ fields: "id",
86
+ });
87
+ }
88
+ async delete(key) {
89
+ const drive = this.requireDrive();
90
+ const fileId = await this.findFileId(key);
91
+ if (!fileId) {
92
+ return;
93
+ }
94
+ // Trash rather than hard-delete — recoverable for 30 days.
95
+ await drive.files.update({
96
+ fileId,
97
+ requestBody: { trashed: true },
98
+ });
99
+ }
100
+ requireDrive() {
101
+ if (!this.drive) {
102
+ throw new Error("GoogleDriveBackend used before init()");
103
+ }
104
+ return this.drive;
105
+ }
106
+ requireFolderId() {
107
+ if (!this.folderId) {
108
+ throw new Error("GoogleDriveBackend used before init()");
109
+ }
110
+ return this.folderId;
111
+ }
112
+ async ensureFolder(name) {
113
+ const drive = this.requireDrive();
114
+ const escaped = name.replace(/'/g, "\\'");
115
+ const res = await drive.files.list({
116
+ q: `name = '${escaped}' and mimeType = '${FOLDER_MIME}' and trashed = false`,
117
+ fields: "files(id, name)",
118
+ spaces: "drive",
119
+ pageSize: 10,
120
+ });
121
+ const existing = res.data.files?.[0];
122
+ if (existing?.id) {
123
+ return existing.id;
124
+ }
125
+ const created = await drive.files.create({
126
+ requestBody: {
127
+ name,
128
+ mimeType: FOLDER_MIME,
129
+ },
130
+ fields: "id",
131
+ });
132
+ if (!created.data.id) {
133
+ throw new Error("google-drive: folder create returned no id");
134
+ }
135
+ log.info(`created Drive folder "${name}" id=${created.data.id}`);
136
+ return created.data.id;
137
+ }
138
+ async findFileId(key) {
139
+ const drive = this.requireDrive();
140
+ const parent = this.requireFolderId();
141
+ const escaped = key.replace(/'/g, "\\'");
142
+ const res = await drive.files.list({
143
+ q: `'${parent}' in parents and name = '${escaped}' and trashed = false`,
144
+ fields: "files(id, name)",
145
+ spaces: "drive",
146
+ pageSize: 2,
147
+ });
148
+ return res.data.files?.[0]?.id ?? undefined;
149
+ }
150
+ }
151
+ //# sourceMappingURL=google-drive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google-drive.js","sourceRoot":"","sources":["../../src/backend/google-drive.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/backend/types.ts"],"names":[],"mappings":""}
package/dist/bridge.js ADDED
@@ -0,0 +1,100 @@
1
+ import { AcpAttach } from "./acp/attach.js";
2
+ import { logger } from "./util/log.js";
3
+ const log = logger("bridge");
4
+ // One bridge per discovered session. Listens to session/update for
5
+ // turn_complete and tells the archive loop to schedule an upload.
6
+ // session_info_update keeps the cached meta fresh so any rule logic
7
+ // that reads title/agentId sees the latest values.
8
+ export class ArchiverBridge {
9
+ opts;
10
+ attach;
11
+ meta;
12
+ stopped = false;
13
+ constructor(opts) {
14
+ this.opts = opts;
15
+ this.meta = opts.meta;
16
+ this.attach = new AcpAttach({
17
+ sessionId: opts.sessionId,
18
+ daemonWsUrl: opts.daemonWsUrl,
19
+ token: opts.token,
20
+ });
21
+ this.opts.archive.setMeta(opts.sessionId, this.meta);
22
+ }
23
+ start() {
24
+ this.attach.on("notification", (n) => this.onNotification(n));
25
+ this.attach.on("request", (r) => this.onRequest(r));
26
+ this.attach.on("error", (err) => {
27
+ log.warn(`attach error ${this.opts.sessionId}: ${err.message}`);
28
+ });
29
+ this.attach.start();
30
+ }
31
+ stop() {
32
+ if (this.stopped) {
33
+ return;
34
+ }
35
+ this.stopped = true;
36
+ // Fire-and-forget a final flush so any pending debounced upload
37
+ // lands before we drop the session. The finalFlush method swallows
38
+ // errors and clears the timer + meta itself.
39
+ void this.opts.archive.finalFlush(this.opts.sessionId);
40
+ this.attach.stop();
41
+ }
42
+ updateMeta(meta) {
43
+ this.meta = meta;
44
+ this.opts.archive.setMeta(this.opts.sessionId, meta);
45
+ }
46
+ onNotification(n) {
47
+ if (n.method !== "session/update") {
48
+ return;
49
+ }
50
+ const params = (n.params ?? {});
51
+ const update = (params.update ?? {});
52
+ const kind = typeof update.sessionUpdate === "string" ? update.sessionUpdate : "";
53
+ if (kind === "session_info_update") {
54
+ this.applySessionInfoUpdate(update);
55
+ }
56
+ // turn_complete is the primary upload trigger. Any other kind of
57
+ // session/update is ignored — we don't want to upload after every
58
+ // tool call notification, just after a turn finishes.
59
+ if (kind === "turn_complete") {
60
+ this.opts.archive.markDirty(this.opts.sessionId);
61
+ }
62
+ }
63
+ applySessionInfoUpdate(update) {
64
+ const next = { ...this.meta };
65
+ let changed = false;
66
+ if (typeof update.title === "string" && next.title !== update.title) {
67
+ next.title = update.title;
68
+ changed = true;
69
+ }
70
+ const agentId = readHydraAgentId(update._meta);
71
+ if (agentId !== undefined && next.agentId !== agentId) {
72
+ next.agentId = agentId;
73
+ changed = true;
74
+ }
75
+ if (changed) {
76
+ this.updateMeta(next);
77
+ }
78
+ }
79
+ onRequest(r) {
80
+ // The hydra-acp daemon broadcasts agent→client requests to every
81
+ // attached client and resolves the original on the first response
82
+ // (first-responder-wins). A passive observer like the archiver
83
+ // MUST stay silent on methods it doesn't intend to answer —
84
+ // replying -32601 would race the real client and make permission
85
+ // prompts / fs reads etc. resolve to an error. Just log and drop.
86
+ log.debug(`ignoring inbound request ${r.method} id=${String(r.id)}`);
87
+ }
88
+ }
89
+ function readHydraAgentId(meta) {
90
+ if (!meta || typeof meta !== "object" || Array.isArray(meta)) {
91
+ return undefined;
92
+ }
93
+ const ns = meta["hydra-acp"];
94
+ if (!ns || typeof ns !== "object" || Array.isArray(ns)) {
95
+ return undefined;
96
+ }
97
+ const v = ns.agentId;
98
+ return typeof v === "string" ? v : undefined;
99
+ }
100
+ //# sourceMappingURL=bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import { logger } from "./util/log.js";
2
+ const log = logger("cold-sweep");
3
+ // One-shot scan of every session the daemon knows about, exporting any
4
+ // cold ones. Live sessions are skipped here — they're handled by the
5
+ // per-session bridge once discovery sees them. The archive loop's
6
+ // hash-dedup ensures cold sessions that haven't changed since their
7
+ // last upload are no-ops on the backend.
8
+ //
9
+ // Runs sequentially to avoid hammering the daemon and the backend
10
+ // (especially Drive, which rate-limits). Errors on individual sessions
11
+ // are logged and skipped — partial progress is preferable to bailing.
12
+ export async function runColdSweep(opts) {
13
+ const sessions = await listSessions(opts.daemonUrl, opts.token);
14
+ let cold = 0;
15
+ for (const s of sessions) {
16
+ if (s.status === "live") {
17
+ continue;
18
+ }
19
+ cold += 1;
20
+ opts.archive.setMeta(s.sessionId, {
21
+ ...(s.cwd !== undefined ? { cwd: s.cwd } : {}),
22
+ ...(s.agentId !== undefined ? { agentId: s.agentId } : {}),
23
+ ...(s.title !== undefined ? { title: s.title } : {}),
24
+ });
25
+ try {
26
+ await opts.archive.flushNow(s.sessionId);
27
+ }
28
+ catch (err) {
29
+ log.warn(`cold sweep flush ${s.sessionId} failed: ${err.message}`);
30
+ }
31
+ }
32
+ log.info(`cold sweep done: scanned=${sessions.length} cold=${cold}`);
33
+ return { scanned: sessions.length, cold };
34
+ }
35
+ async function listSessions(daemonUrl, token) {
36
+ const r = await fetch(`${daemonUrl}/v1/sessions`, {
37
+ headers: { Authorization: `Bearer ${token}` },
38
+ });
39
+ if (!r.ok) {
40
+ throw new Error(`daemon /v1/sessions returned ${r.status}`);
41
+ }
42
+ const body = (await r.json());
43
+ return body.sessions;
44
+ }
45
+ //# sourceMappingURL=cold-sweep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cold-sweep.js","sourceRoot":"","sources":["../src/cold-sweep.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvC,MAAM,GAAG,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;AAQjC,uEAAuE;AACvE,qEAAqE;AACrE,kEAAkE;AAClE,oEAAoE;AACpE,yCAAyC;AACzC,EAAE;AACF,kEAAkE;AAClE,uEAAuE;AACvE,sEAAsE;AACtE,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAsB;IAEtB,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAChE,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YACxB,SAAS;QACX,CAAC;QACD,IAAI,IAAI,CAAC,CAAC;QACV,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,EAAE;YAChC,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9C,GAAG,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1D,GAAG,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACrD,CAAC,CAAC;QACH,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAC3C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,IAAI,CACN,oBAAoB,CAAC,CAAC,SAAS,YAAa,GAAa,CAAC,OAAO,EAAE,CACpE,CAAC;QACJ,CAAC;IACH,CAAC;IACD,GAAG,CAAC,IAAI,CAAC,4BAA4B,QAAQ,CAAC,MAAM,SAAS,IAAI,EAAE,CAAC,CAAC;IACrE,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC;AAC5C,CAAC;AAED,KAAK,UAAU,YAAY,CACzB,SAAiB,EACjB,KAAa;IAEb,MAAM,CAAC,GAAG,MAAM,KAAK,CAAC,GAAG,SAAS,cAAc,EAAE;QAChD,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE;KAC9C,CAAC,CAAC;IACH,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9D,CAAC;IACD,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAqC,CAAC;IAClE,OAAO,IAAI,CAAC,QAAQ,CAAC;AACvB,CAAC"}
package/dist/config.js ADDED
@@ -0,0 +1,81 @@
1
+ import { homedir } from "node:os";
2
+ import { resolve } from "node:path";
3
+ function deriveWsUrl(httpUrl) {
4
+ if (httpUrl.startsWith("https://")) {
5
+ return "wss://" + httpUrl.slice("https://".length).replace(/\/$/, "") + "/acp";
6
+ }
7
+ if (httpUrl.startsWith("http://")) {
8
+ return "ws://" + httpUrl.slice("http://".length).replace(/\/$/, "") + "/acp";
9
+ }
10
+ throw new Error(`hydraDaemonUrl must start with http:// or https://: ${httpUrl}`);
11
+ }
12
+ function intEnv(name, fallback) {
13
+ const v = process.env[name];
14
+ if (!v) {
15
+ return fallback;
16
+ }
17
+ const n = Number.parseInt(v, 10);
18
+ return Number.isFinite(n) ? n : fallback;
19
+ }
20
+ const TRUTHY = new Set(["1", "true", "yes", "on", "t"]);
21
+ function boolEnv(name, fallback) {
22
+ const v = process.env[name];
23
+ if (v === undefined) {
24
+ return fallback;
25
+ }
26
+ return TRUTHY.has(v.toLowerCase());
27
+ }
28
+ function backendEnv() {
29
+ const raw = (process.env.HYDRA_ACP_ARCHIVER_BACKEND ?? "google-drive").toLowerCase();
30
+ if (raw === "google-drive" || raw === "fs") {
31
+ return raw;
32
+ }
33
+ throw new Error(`HYDRA_ACP_ARCHIVER_BACKEND must be one of: google-drive, fs (got "${raw}")`);
34
+ }
35
+ export function loadConfig() {
36
+ const hydraDaemonUrl = process.env.HYDRA_ACP_DAEMON_URL ?? "http://127.0.0.1:8765";
37
+ const hydraToken = process.env.HYDRA_ACP_TOKEN ?? "";
38
+ if (!hydraToken) {
39
+ throw new Error("Missing HYDRA_ACP_TOKEN env var. When run as a hydra extension, hydra injects this automatically.");
40
+ }
41
+ const hydraWsUrl = process.env.HYDRA_ACP_WS_URL ?? deriveWsUrl(hydraDaemonUrl);
42
+ const hydraHome = process.env.HYDRA_ACP_HOME ?? resolve(homedir(), ".hydra-acp");
43
+ const ruleConfigPath = process.env.HYDRA_ACP_ARCHIVER_CONFIG ??
44
+ resolve(hydraHome, "archiver.config.js");
45
+ const credentialsPath = process.env.HYDRA_ACP_ARCHIVER_GOOGLE_CREDENTIALS ??
46
+ resolve(hydraHome, "archiver-google-credentials.json");
47
+ const tokenPath = resolve(hydraHome, "archiver-google-token.json");
48
+ const statePath = resolve(hydraHome, "archiver-state.json");
49
+ const fsDir = process.env.HYDRA_ACP_ARCHIVER_FS_DIR ?? resolve(hydraHome, "archive");
50
+ return {
51
+ hydraDaemonUrl,
52
+ hydraWsUrl,
53
+ hydraToken,
54
+ hydraHome,
55
+ hydraPollIntervalMs: intEnv("HYDRA_ACP_ARCHIVER_POLL_MS", 2000),
56
+ ruleConfigPath,
57
+ uploadDebounceMs: intEnv("HYDRA_ACP_ARCHIVER_DEBOUNCE_MS", 5000),
58
+ pullIntervalMs: intEnv("HYDRA_ACP_ARCHIVER_PULL_MS", 60000),
59
+ backend: backendEnv(),
60
+ driveFolderName: process.env.HYDRA_ACP_ARCHIVER_DRIVE_FOLDER ?? "hydra-acp-archive",
61
+ fsDir,
62
+ credentialsPath,
63
+ tokenPath,
64
+ statePath,
65
+ debug: boolEnv("DEBUG", false),
66
+ };
67
+ }
68
+ // loadLoginConfig is for the standalone `hydra-acp-archiver-login` bin —
69
+ // it doesn't need a daemon token, just enough config to know where to
70
+ // read credentials from and write the OAuth tokens to.
71
+ export function loadLoginConfig() {
72
+ const hydraHome = process.env.HYDRA_ACP_HOME ?? resolve(homedir(), ".hydra-acp");
73
+ const credentialsPath = process.env.HYDRA_ACP_ARCHIVER_GOOGLE_CREDENTIALS ??
74
+ resolve(hydraHome, "archiver-google-credentials.json");
75
+ return {
76
+ hydraHome,
77
+ credentialsPath,
78
+ tokenPath: resolve(hydraHome, "archiver-google-token.json"),
79
+ };
80
+ }
81
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/daemon.js ADDED
@@ -0,0 +1,74 @@
1
+ // REST helpers for the small subset of the hydra-acp daemon API the
2
+ // archiver uses. The daemon's WebSocket surface is handled in src/acp/.
3
+ import { logger } from "./util/log.js";
4
+ const log = logger("daemon");
5
+ export class DaemonClient {
6
+ opts;
7
+ constructor(opts) {
8
+ this.opts = opts;
9
+ }
10
+ async listSessionIds() {
11
+ const r = await fetch(`${this.opts.daemonUrl}/v1/sessions`, {
12
+ headers: { Authorization: `Bearer ${this.opts.token}` },
13
+ });
14
+ if (!r.ok) {
15
+ throw new Error(`list sessions: HTTP ${r.status}`);
16
+ }
17
+ const body = (await r.json());
18
+ const out = new Set();
19
+ for (const s of body.sessions) {
20
+ if (typeof s.sessionId === "string") {
21
+ out.add(s.sessionId);
22
+ }
23
+ }
24
+ return out;
25
+ }
26
+ async exportSession(sessionId) {
27
+ const url = `${this.opts.daemonUrl}/v1/sessions/${encodeURIComponent(sessionId)}/export`;
28
+ const r = await fetch(url, {
29
+ headers: { Authorization: `Bearer ${this.opts.token}` },
30
+ });
31
+ if (!r.ok) {
32
+ const text = await safeBody(r);
33
+ throw new Error(`export ${sessionId}: HTTP ${r.status} ${text}`);
34
+ }
35
+ return (await r.json());
36
+ }
37
+ async importBundle(bundle, opts = {}) {
38
+ const body = { bundle };
39
+ if (opts.replace !== undefined) {
40
+ body.replace = opts.replace;
41
+ }
42
+ if (opts.cwd !== undefined) {
43
+ body.cwd = opts.cwd;
44
+ }
45
+ const r = await fetch(`${this.opts.daemonUrl}/v1/sessions/import`, {
46
+ method: "POST",
47
+ headers: {
48
+ Authorization: `Bearer ${this.opts.token}`,
49
+ "content-type": "application/json",
50
+ },
51
+ body: JSON.stringify(body),
52
+ });
53
+ if (!r.ok) {
54
+ const text = await safeBody(r);
55
+ throw new Error(`import lineage=${bundle.session.lineageId}: HTTP ${r.status} ${text}`);
56
+ }
57
+ const out = (await r.json());
58
+ if (!out.sessionId) {
59
+ log.warn(`import returned no sessionId for lineage ${bundle.session.lineageId}`);
60
+ return { sessionId: "" };
61
+ }
62
+ return { sessionId: out.sessionId };
63
+ }
64
+ }
65
+ async function safeBody(r) {
66
+ try {
67
+ const t = await r.text();
68
+ return t.slice(0, 500);
69
+ }
70
+ catch {
71
+ return "";
72
+ }
73
+ }
74
+ //# sourceMappingURL=daemon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ import { logger } from "./util/log.js";
2
+ const log = logger("discovery");
3
+ const DEFAULT_POLL_MS = 2_000;
4
+ export class HydraDiscovery {
5
+ opts;
6
+ timer;
7
+ known = new Map();
8
+ stopped = false;
9
+ inFlight = false;
10
+ constructor(opts) {
11
+ this.opts = opts;
12
+ }
13
+ start() {
14
+ log.info(`polling ${this.opts.daemonUrl}/v1/sessions every ${this.opts.pollIntervalMs ?? DEFAULT_POLL_MS}ms`);
15
+ void this.poll();
16
+ this.timer = setInterval(() => {
17
+ void this.poll();
18
+ }, this.opts.pollIntervalMs ?? DEFAULT_POLL_MS);
19
+ }
20
+ stop() {
21
+ this.stopped = true;
22
+ if (this.timer) {
23
+ clearInterval(this.timer);
24
+ this.timer = undefined;
25
+ }
26
+ }
27
+ async poll() {
28
+ if (this.stopped || this.inFlight) {
29
+ return;
30
+ }
31
+ this.inFlight = true;
32
+ try {
33
+ const r = await fetch(`${this.opts.daemonUrl}/v1/sessions`, {
34
+ headers: { Authorization: `Bearer ${this.opts.token}` },
35
+ });
36
+ if (!r.ok) {
37
+ log.warn(`daemon /v1/sessions returned ${r.status}`);
38
+ return;
39
+ }
40
+ const body = (await r.json());
41
+ const seen = new Map();
42
+ for (const s of body.sessions) {
43
+ if (s.status !== "live") {
44
+ continue;
45
+ }
46
+ seen.set(s.sessionId, s);
47
+ }
48
+ for (const [id, s] of seen) {
49
+ if (!this.known.has(id)) {
50
+ this.known.set(id, s);
51
+ try {
52
+ this.opts.onAdd(s);
53
+ }
54
+ catch (err) {
55
+ log.warn(`onAdd error for ${id}: ${err.message}`);
56
+ }
57
+ }
58
+ else {
59
+ this.known.set(id, s);
60
+ }
61
+ }
62
+ for (const id of [...this.known.keys()]) {
63
+ if (!seen.has(id)) {
64
+ this.known.delete(id);
65
+ try {
66
+ this.opts.onRemove(id);
67
+ }
68
+ catch (err) {
69
+ log.warn(`onRemove error for ${id}: ${err.message}`);
70
+ }
71
+ }
72
+ }
73
+ }
74
+ catch (err) {
75
+ log.debug(`poll error: ${err.message}`);
76
+ }
77
+ finally {
78
+ this.inFlight = false;
79
+ }
80
+ }
81
+ }
82
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"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"}