@askexenow/exe-os 0.9.67 → 0.9.69

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.
@@ -1,4 +1,113 @@
1
1
  #!/usr/bin/env node
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+
7
+ // src/lib/secure-files.ts
8
+ import { chmodSync, existsSync, mkdirSync } from "fs";
9
+ import { chmod, mkdir } from "fs/promises";
10
+ var init_secure_files = __esm({
11
+ "src/lib/secure-files.ts"() {
12
+ "use strict";
13
+ }
14
+ });
15
+
16
+ // src/lib/config.ts
17
+ import { readFile, writeFile } from "fs/promises";
18
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
19
+ import path from "path";
20
+ import os from "os";
21
+ function resolveDataDir() {
22
+ if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
23
+ if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
24
+ const newDir = path.join(os.homedir(), ".exe-os");
25
+ const legacyDir = path.join(os.homedir(), ".exe-mem");
26
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
27
+ try {
28
+ renameSync(legacyDir, newDir);
29
+ process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
30
+ `);
31
+ } catch {
32
+ return legacyDir;
33
+ }
34
+ }
35
+ return newDir;
36
+ }
37
+ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG;
38
+ var init_config = __esm({
39
+ "src/lib/config.ts"() {
40
+ "use strict";
41
+ init_secure_files();
42
+ EXE_AI_DIR = resolveDataDir();
43
+ DB_PATH = path.join(EXE_AI_DIR, "memories.db");
44
+ MODELS_DIR = path.join(EXE_AI_DIR, "models");
45
+ CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
46
+ LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
47
+ CURRENT_CONFIG_VERSION = 1;
48
+ DEFAULT_CONFIG = {
49
+ config_version: CURRENT_CONFIG_VERSION,
50
+ dbPath: DB_PATH,
51
+ modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
52
+ embeddingDim: 1024,
53
+ batchSize: 20,
54
+ flushIntervalMs: 1e4,
55
+ autoIngestion: true,
56
+ autoRetrieval: true,
57
+ searchMode: "hybrid",
58
+ hookSearchMode: "hybrid",
59
+ fileGrepEnabled: true,
60
+ splashEffect: true,
61
+ consolidationEnabled: true,
62
+ consolidationIntervalMs: 6 * 60 * 60 * 1e3,
63
+ consolidationModel: "claude-haiku-4-5-20251001",
64
+ consolidationMaxCallsPerRun: 20,
65
+ selfQueryRouter: true,
66
+ selfQueryModel: "claude-haiku-4-5-20251001",
67
+ rerankerEnabled: true,
68
+ scalingRoadmap: {
69
+ rerankerAutoTrigger: {
70
+ enabled: true,
71
+ broadQueryMinCardinality: 5e4,
72
+ fetchTopK: 200,
73
+ returnTopK: 20
74
+ }
75
+ },
76
+ graphRagEnabled: true,
77
+ wikiEnabled: false,
78
+ wikiUrl: "",
79
+ wikiApiKey: "",
80
+ wikiSyncIntervalMs: 30 * 60 * 1e3,
81
+ wikiWorkspaceMapping: {},
82
+ wikiAutoUpdate: true,
83
+ wikiAutoUpdateThreshold: 0.5,
84
+ wikiAutoUpdateCreateNew: true,
85
+ skillLearning: true,
86
+ skillThreshold: 3,
87
+ skillModel: "claude-haiku-4-5-20251001",
88
+ exeHeartbeat: {
89
+ enabled: true,
90
+ intervalSeconds: 60,
91
+ staleInProgressThresholdHours: 2
92
+ },
93
+ sessionLifecycle: {
94
+ idleKillEnabled: true,
95
+ idleKillTicksRequired: 3,
96
+ idleKillIntercomAckWindowMs: 1e4,
97
+ maxAutoInstances: 10
98
+ },
99
+ autoUpdate: {
100
+ checkOnBoot: true,
101
+ autoInstall: false,
102
+ checkIntervalMs: 24 * 60 * 60 * 1e3
103
+ },
104
+ orchestration: {
105
+ phase: "phase_1_coo",
106
+ phaseSetBy: "default"
107
+ }
108
+ };
109
+ }
110
+ });
2
111
 
3
112
  // src/bin/postgres-agentic-semantic-backfill.ts
4
113
  import { Client } from "pg";
@@ -136,6 +245,157 @@ function inferSemanticLabel(row) {
136
245
  };
137
246
  }
138
247
 
248
+ // src/lib/background-jobs.ts
249
+ init_config();
250
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync, unlinkSync } from "fs";
251
+ import { execFileSync } from "child_process";
252
+ import os2 from "os";
253
+ import path2 from "path";
254
+ var JOB_DIR = path2.join(EXE_AI_DIR, "jobs");
255
+ var JOBS_FILE = path2.join(JOB_DIR, "jobs.json");
256
+ var LOCK_DIR = path2.join(JOB_DIR, "locks");
257
+ var DEFAULT_LOCK_TTL_MS = 6 * 60 * 60 * 1e3;
258
+ var MAX_HISTORY = 200;
259
+ function ensureDirs() {
260
+ mkdirSync2(LOCK_DIR, { recursive: true });
261
+ }
262
+ function now() {
263
+ return (/* @__PURE__ */ new Date()).toISOString();
264
+ }
265
+ function isAlive(pid) {
266
+ if (!pid || pid <= 0) return false;
267
+ try {
268
+ process.kill(pid, 0);
269
+ return true;
270
+ } catch {
271
+ return false;
272
+ }
273
+ }
274
+ function readJobsRaw() {
275
+ ensureDirs();
276
+ if (!existsSync3(JOBS_FILE)) return [];
277
+ try {
278
+ const parsed = JSON.parse(readFileSync2(JOBS_FILE, "utf8"));
279
+ return Array.isArray(parsed) ? parsed : [];
280
+ } catch {
281
+ return [];
282
+ }
283
+ }
284
+ function writeJobsRaw(jobs) {
285
+ ensureDirs();
286
+ const running = jobs.filter((j) => j.status === "running");
287
+ const rest = jobs.filter((j) => j.status !== "running").slice(-MAX_HISTORY);
288
+ writeFileSync(JOBS_FILE, JSON.stringify([...rest, ...running], null, 2) + "\n");
289
+ }
290
+ function lockPath(type) {
291
+ return path2.join(LOCK_DIR, `${type.replace(/[^a-zA-Z0-9_.-]/g, "_")}.lock`);
292
+ }
293
+ function acquireJobLock(type, ttlMs = DEFAULT_LOCK_TTL_MS) {
294
+ ensureDirs();
295
+ const file = lockPath(type);
296
+ if (existsSync3(file)) {
297
+ try {
298
+ const lock = JSON.parse(readFileSync2(file, "utf8"));
299
+ const age = Date.now() - Date.parse(lock.updatedAt ?? "");
300
+ if (lock.pid && isAlive(lock.pid) && Number.isFinite(age) && age < ttlMs) return false;
301
+ } catch {
302
+ }
303
+ try {
304
+ unlinkSync(file);
305
+ } catch {
306
+ }
307
+ }
308
+ try {
309
+ writeFileSync(file, JSON.stringify({ pid: process.pid, updatedAt: now() }, null, 2) + "\n", { flag: "wx" });
310
+ return true;
311
+ } catch {
312
+ return false;
313
+ }
314
+ }
315
+ function releaseJobLock(type) {
316
+ const file = lockPath(type);
317
+ try {
318
+ if (!existsSync3(file)) return;
319
+ const lock = JSON.parse(readFileSync2(file, "utf8"));
320
+ if (lock.pid === process.pid || !lock.pid || !isAlive(lock.pid)) unlinkSync(file);
321
+ } catch {
322
+ try {
323
+ unlinkSync(file);
324
+ } catch {
325
+ }
326
+ }
327
+ }
328
+ function startManagedJob(options) {
329
+ const lowPriority = options.lowPriority ?? true;
330
+ if (!acquireJobLock(options.type, options.lockTtlMs)) return null;
331
+ if (lowPriority) {
332
+ try {
333
+ os2.setPriority(process.pid, 10);
334
+ } catch {
335
+ }
336
+ }
337
+ const id = `${options.type}-${Date.now()}-${process.pid}`.replace(/[^a-zA-Z0-9_.-]/g, "_");
338
+ const record = {
339
+ id,
340
+ type: options.type,
341
+ name: options.name,
342
+ pid: process.pid,
343
+ command: options.command ?? process.argv.join(" "),
344
+ cwd: process.cwd(),
345
+ status: "running",
346
+ startedAt: now(),
347
+ updatedAt: now(),
348
+ lastHeartbeatAt: now(),
349
+ cancelCommand: `exe-os jobs cancel ${id}`,
350
+ lowPriority
351
+ };
352
+ const upsert = (patch) => {
353
+ const jobs = readJobsRaw().filter((j) => j.id !== id);
354
+ Object.assign(record, patch, { updatedAt: now() });
355
+ writeJobsRaw([...jobs, record]);
356
+ const file = lockPath(options.type);
357
+ try {
358
+ writeFileSync(file, JSON.stringify({ pid: process.pid, jobId: id, updatedAt: record.updatedAt }, null, 2) + "\n");
359
+ } catch {
360
+ }
361
+ };
362
+ upsert({});
363
+ const timer = setInterval(() => upsert({ lastHeartbeatAt: now() }), 3e4);
364
+ timer.unref?.();
365
+ const cleanup = (status, error) => {
366
+ clearInterval(timer);
367
+ upsert({ status, error, lastHeartbeatAt: now() });
368
+ releaseJobLock(options.type);
369
+ };
370
+ process.once("SIGTERM", () => {
371
+ cleanup("cancelled");
372
+ process.exit(0);
373
+ });
374
+ process.once("SIGINT", () => {
375
+ cleanup("cancelled");
376
+ process.exit(130);
377
+ });
378
+ process.once("exit", () => releaseJobLock(options.type));
379
+ return {
380
+ id,
381
+ update(progress) {
382
+ upsert({ progressCurrent: progress.current, progressTotal: progress.total, progressLabel: progress.label, lastHeartbeatAt: now() });
383
+ },
384
+ complete() {
385
+ cleanup("completed");
386
+ },
387
+ fail(err) {
388
+ cleanup("failed", err instanceof Error ? err.message : String(err));
389
+ },
390
+ cancel() {
391
+ cleanup("cancelled");
392
+ }
393
+ };
394
+ }
395
+ async function politeBatchPause(ms = 250) {
396
+ await new Promise((resolve) => setTimeout(resolve, ms));
397
+ }
398
+
139
399
  // src/bin/postgres-agentic-semantic-backfill.ts
140
400
  function arg(name) {
141
401
  const i = process.argv.indexOf(name);
@@ -160,6 +420,11 @@ function scrubJson(value) {
160
420
  return JSON.stringify(value).replace(/\u0000/g, "").replace(/\\u0000/gi, "").replace(/\\\\u0000/gi, "").replace(/\\u00[0-1][0-9a-f]/gi, " ");
161
421
  }
162
422
  async function main() {
423
+ const job = startManagedJob({ type: "postgres-agentic-semantic-backfill", name: "Postgres semantic label backfill", lowPriority: true });
424
+ if (!job) {
425
+ process.stderr.write("[postgres-agentic-semantic-backfill] Another Postgres semantic backfill is already running.\n");
426
+ return;
427
+ }
163
428
  const url = process.env.DATABASE_URL || process.env.EXED_DATABASE_URL;
164
429
  if (!url) throw new Error("DATABASE_URL or EXED_DATABASE_URL is required");
165
430
  const limit = Number(arg("--limit") ?? "20000");
@@ -221,11 +486,16 @@ async function main() {
221
486
  ]);
222
487
  }
223
488
  count++;
224
- if (count % 5e3 === 0) process.stderr.write(`[postgres-agentic-semantic-backfill] labeled ${count}
489
+ if (count % 5e3 === 0) {
490
+ process.stderr.write(`[postgres-agentic-semantic-backfill] labeled ${count}
225
491
  `);
492
+ job.update({ current: count, total: limit, label: `Labeled ${count}/${limit}` });
493
+ await politeBatchPause(1e3);
494
+ }
226
495
  }
227
496
  process.stderr.write(`[postgres-agentic-semantic-backfill] Complete: ${count} semantic labels.
228
497
  `);
498
+ job.complete();
229
499
  } finally {
230
500
  await client.end();
231
501
  }