agentel 0.2.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,217 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const { spawn } = require("child_process");
6
+ const { ensureBaseDirs, paths, readJson, writeJson } = require("./paths");
7
+ const { effectiveImportSources, loadConfig } = require("./config");
8
+ const { startCollector } = require("./collector");
9
+ const { reindexIfNeeded } = require("./search");
10
+ const { hasRemoteTarget, syncArchiveIfConfigured } = require("./sync");
11
+
12
+ let tickRunning = false;
13
+
14
+ function startSupervisorDetached() {
15
+ const p = paths();
16
+ ensureBaseDirs(p);
17
+ const out = fs.openSync(path.join(p.logs, "supervisor.log"), "a");
18
+ const err = fs.openSync(path.join(p.logs, "supervisor.err.log"), "a");
19
+ const child = spawn(process.execPath, [path.resolve(__dirname, "..", "bin", "agentlog.js"), "start", "--foreground"], {
20
+ detached: true,
21
+ stdio: ["ignore", out, err]
22
+ });
23
+ child.unref();
24
+ return child.pid;
25
+ }
26
+
27
+ async function runSupervisorForeground(env = process.env) {
28
+ const p = paths(env);
29
+ ensureBaseDirs(p);
30
+ fs.writeFileSync(p.pid, String(process.pid), { mode: 0o600 });
31
+ log("supervisor started", env);
32
+ const collector = startCollector(env, {
33
+ onError: (error) => log(`collector failed: ${error.message}`, env)
34
+ });
35
+ if (collector) log(`collector listening on ${loadConfig(env).collector.otlpEndpoint}`, env);
36
+
37
+ let stopping = false;
38
+ const stop = () => {
39
+ if (stopping) return;
40
+ stopping = true;
41
+ log("supervisor stopping", env);
42
+ try {
43
+ fs.unlinkSync(p.pid);
44
+ } catch {
45
+ // Already gone.
46
+ }
47
+ process.exit(0);
48
+ };
49
+ process.on("SIGTERM", stop);
50
+ process.on("SIGINT", stop);
51
+
52
+ await tick(env);
53
+ const timer = setInterval(() => {
54
+ if (tickRunning) {
55
+ log("tick skipped: previous tick still running", env);
56
+ return;
57
+ }
58
+ tick(env).catch((error) => log(`tick failed: ${error.message}`, env));
59
+ }, 30 * 1000);
60
+
61
+ await new Promise(() => {});
62
+ }
63
+
64
+ async function tick(env) {
65
+ tickRunning = true;
66
+ try {
67
+ const cfg = loadConfig(env);
68
+ const sources = effectiveImportSources(cfg);
69
+ const importSources = Array.isArray(sources) && sources.length ? sources : ["all"];
70
+ for (const source of importSources) {
71
+ try {
72
+ const importCliHistory = loadImportCliHistory();
73
+ const imports = importCliHistory(supervisorImportOptionsForSource(source, cfg), env);
74
+ for (const result of imports) {
75
+ const summary = supervisorImportResultLog(result, source);
76
+ if (summary) log(summary, env);
77
+ if (result.errors?.length) log(`${result.provider} import errors from ${source}: ${result.errors.length}`, env);
78
+ }
79
+ } catch (error) {
80
+ log(`${source} history import skipped: ${error.message}`, env);
81
+ }
82
+ }
83
+ } catch (error) {
84
+ log(`history import skipped: ${error.message}`, env);
85
+ }
86
+
87
+ try {
88
+ const result = reindexIfNeeded(env);
89
+ if (result.index?.docCount != null) log(`index ready (${result.index.docCount} chunk(s))`, env);
90
+ } catch (error) {
91
+ log(`index skipped: ${error.message}`, env);
92
+ }
93
+
94
+ try {
95
+ const cfg = loadConfig(env);
96
+ if (hasRemoteTarget(cfg, env) && shouldRunScheduledSync(cfg, env)) {
97
+ markScheduledSyncAttempt(env);
98
+ const result = await syncArchiveIfConfigured(env);
99
+ if (result.configured !== false && (result.uploaded || result.errors?.length)) {
100
+ log(`remote sync uploaded=${result.uploaded} current=${result.skipped} retried=${result.retried || 0} errors=${result.errors.length}`, env);
101
+ }
102
+ }
103
+ } catch (error) {
104
+ log(`remote sync skipped: ${error.message}`, env);
105
+ } finally {
106
+ tickRunning = false;
107
+ }
108
+ }
109
+
110
+ function shouldRunScheduledSync(config, env = process.env, now = new Date()) {
111
+ const intervalMinutes = Number(config?.sync?.intervalMinutes ?? 30);
112
+ if (!Number.isFinite(intervalMinutes) || intervalMinutes <= 0) return false;
113
+ const state = readJson(path.join(paths(env).state, "sync-supervisor.json"), {});
114
+ if (!state.lastAttemptedAt) return true;
115
+ const last = new Date(state.lastAttemptedAt);
116
+ if (Number.isNaN(last.getTime())) return true;
117
+ return now.getTime() - last.getTime() >= intervalMinutes * 60 * 1000;
118
+ }
119
+
120
+ function markScheduledSyncAttempt(env = process.env, now = new Date()) {
121
+ writeJson(path.join(paths(env).state, "sync-supervisor.json"), { lastAttemptedAt: now.toISOString() });
122
+ }
123
+
124
+ function supervisorImportOptionsForSource(source, config) {
125
+ const sinceDays = Math.max(1, Number(config?.imports?.defaultSinceDays || 30));
126
+ return {
127
+ source,
128
+ since: `${sinceDays}d`,
129
+ cursorRecovery: false,
130
+ supervisor: true
131
+ };
132
+ }
133
+
134
+ function supervisorImportResultLog(result, source) {
135
+ const parts = [];
136
+ if (result.imported) parts.push(`imported ${result.imported}`);
137
+ if (result.pruned) parts.push(`pruned ${result.pruned}`);
138
+ if (!parts.length) return "";
139
+ const details = [];
140
+ if (result.details?.rawCompanionFragmentsMerged) details.push(`raw companion merged=${result.details.rawCompanionFragmentsMerged}`);
141
+ if (result.details?.rawCompanionFragmentsDropped) details.push(`raw companion dropped=${result.details.rawCompanionFragmentsDropped}`);
142
+ return `${result.provider} from ${source}: ${parts.join(", ")}${details.length ? ` (${details.join(", ")})` : ""}`;
143
+ }
144
+
145
+ function stopSupervisor(env = process.env) {
146
+ const p = paths(env);
147
+ const pid = readPid(env);
148
+ if (!pid) return false;
149
+ try {
150
+ process.kill(pid, "SIGTERM");
151
+ return true;
152
+ } catch (error) {
153
+ if (error?.code !== "EPERM") {
154
+ try {
155
+ fs.unlinkSync(p.pid);
156
+ } catch {
157
+ // No-op.
158
+ }
159
+ }
160
+ return false;
161
+ }
162
+ }
163
+
164
+ function supervisorStatus(env = process.env) {
165
+ const pid = readPid(env);
166
+ return {
167
+ pid,
168
+ running: pid ? isAlive(pid) : false
169
+ };
170
+ }
171
+
172
+ function readPid(env = process.env) {
173
+ try {
174
+ const pid = Number(fs.readFileSync(paths(env).pid, "utf8").trim());
175
+ return Number.isFinite(pid) && pid > 0 ? pid : 0;
176
+ } catch {
177
+ return 0;
178
+ }
179
+ }
180
+
181
+ function isAlive(pid) {
182
+ try {
183
+ process.kill(pid, 0);
184
+ return true;
185
+ } catch (error) {
186
+ if (error?.code === "EPERM") return true;
187
+ return false;
188
+ }
189
+ }
190
+
191
+ function loadImportCliHistory() {
192
+ const modulePath = require.resolve("./importers");
193
+ const loaded = require(modulePath);
194
+ if (typeof loaded.importCliHistory === "function") return loaded.importCliHistory;
195
+
196
+ delete require.cache[modulePath];
197
+ const reloaded = require(modulePath);
198
+ if (typeof reloaded.importCliHistory === "function") return reloaded.importCliHistory;
199
+ throw new Error("importer module did not expose importCliHistory; restart agentlog or reinstall");
200
+ }
201
+
202
+ function log(message, env = process.env) {
203
+ const p = paths(env);
204
+ ensureBaseDirs(p);
205
+ fs.appendFileSync(path.join(p.logs, "supervisor.log"), `${new Date().toISOString()} ${message}\n`);
206
+ }
207
+
208
+ module.exports = {
209
+ runSupervisorForeground,
210
+ startSupervisorDetached,
211
+ stopSupervisor,
212
+ supervisorImportResultLog,
213
+ supervisorImportOptionsForSource,
214
+ supervisorStatus,
215
+ shouldRunScheduledSync,
216
+ tick
217
+ };