@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.
@@ -3338,10 +3338,10 @@ function evictLRU() {
3338
3338
  }
3339
3339
  }
3340
3340
  function evictIdleShards() {
3341
- const now = Date.now();
3341
+ const now2 = Date.now();
3342
3342
  const toEvict = [];
3343
3343
  for (const [name, lastAccess] of _shardLastAccess) {
3344
- if (now - lastAccess > SHARD_IDLE_MS) {
3344
+ if (now2 - lastAccess > SHARD_IDLE_MS) {
3345
3345
  toEvict.push(name);
3346
3346
  }
3347
3347
  }
@@ -3621,22 +3621,22 @@ ${sections.join("\n\n")}
3621
3621
  }
3622
3622
  async function storeGlobalProcedure(input) {
3623
3623
  const id = randomUUID2();
3624
- const now = (/* @__PURE__ */ new Date()).toISOString();
3624
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
3625
3625
  const client = getClient();
3626
3626
  await client.execute({
3627
3627
  sql: `INSERT INTO company_procedures (id, title, content, priority, domain, active, created_at, updated_at)
3628
3628
  VALUES (?, ?, ?, ?, ?, 1, ?, ?)`,
3629
- args: [id, input.title, input.content, input.priority ?? "p0", input.domain ?? null, now, now]
3629
+ args: [id, input.title, input.content, input.priority ?? "p0", input.domain ?? null, now2, now2]
3630
3630
  });
3631
3631
  await loadGlobalProcedures();
3632
3632
  return id;
3633
3633
  }
3634
3634
  async function deactivateGlobalProcedure(id) {
3635
- const now = (/* @__PURE__ */ new Date()).toISOString();
3635
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
3636
3636
  const client = getClient();
3637
3637
  const result = await client.execute({
3638
3638
  sql: "UPDATE company_procedures SET active = 0, updated_at = ? WHERE id = ?",
3639
- args: [now, id]
3639
+ args: [now2, id]
3640
3640
  });
3641
3641
  await loadGlobalProcedures();
3642
3642
  return result.rowsAffected > 0;
@@ -3816,8 +3816,8 @@ function deriveMachineKey() {
3816
3816
  }
3817
3817
  function readMachineId() {
3818
3818
  try {
3819
- const { readFileSync: readFileSync5 } = __require("fs");
3820
- return readFileSync5("/etc/machine-id", "utf-8").trim();
3819
+ const { readFileSync: readFileSync6 } = __require("fs");
3820
+ return readFileSync6("/etc/machine-id", "utf-8").trim();
3821
3821
  } catch {
3822
3822
  return "";
3823
3823
  }
@@ -4125,8 +4125,8 @@ function isMainModule(importMetaUrl) {
4125
4125
  }
4126
4126
 
4127
4127
  // src/bin/backfill-vectors.ts
4128
- import { existsSync as existsSync9, unlinkSync as unlinkSync4 } from "fs";
4129
- import path9 from "path";
4128
+ import { existsSync as existsSync10, unlinkSync as unlinkSync5 } from "fs";
4129
+ import path10 from "path";
4130
4130
 
4131
4131
  // src/lib/worker-gate.ts
4132
4132
  init_config();
@@ -4179,12 +4179,169 @@ function releaseBackfillLock() {
4179
4179
  }
4180
4180
  }
4181
4181
 
4182
+ // src/lib/background-jobs.ts
4183
+ init_config();
4184
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4, unlinkSync as unlinkSync4 } from "fs";
4185
+ import { execFileSync } from "child_process";
4186
+ import os6 from "os";
4187
+ import path9 from "path";
4188
+ var JOB_DIR = path9.join(EXE_AI_DIR, "jobs");
4189
+ var JOBS_FILE = path9.join(JOB_DIR, "jobs.json");
4190
+ var LOCK_DIR = path9.join(JOB_DIR, "locks");
4191
+ var DEFAULT_LOCK_TTL_MS = 6 * 60 * 60 * 1e3;
4192
+ var MAX_HISTORY = 200;
4193
+ function ensureDirs() {
4194
+ mkdirSync4(LOCK_DIR, { recursive: true });
4195
+ }
4196
+ function now() {
4197
+ return (/* @__PURE__ */ new Date()).toISOString();
4198
+ }
4199
+ function isAlive(pid) {
4200
+ if (!pid || pid <= 0) return false;
4201
+ try {
4202
+ process.kill(pid, 0);
4203
+ return true;
4204
+ } catch {
4205
+ return false;
4206
+ }
4207
+ }
4208
+ function readJobsRaw() {
4209
+ ensureDirs();
4210
+ if (!existsSync9(JOBS_FILE)) return [];
4211
+ try {
4212
+ const parsed = JSON.parse(readFileSync5(JOBS_FILE, "utf8"));
4213
+ return Array.isArray(parsed) ? parsed : [];
4214
+ } catch {
4215
+ return [];
4216
+ }
4217
+ }
4218
+ function writeJobsRaw(jobs) {
4219
+ ensureDirs();
4220
+ const running = jobs.filter((j) => j.status === "running");
4221
+ const rest = jobs.filter((j) => j.status !== "running").slice(-MAX_HISTORY);
4222
+ writeFileSync4(JOBS_FILE, JSON.stringify([...rest, ...running], null, 2) + "\n");
4223
+ }
4224
+ function lockPath(type) {
4225
+ return path9.join(LOCK_DIR, `${type.replace(/[^a-zA-Z0-9_.-]/g, "_")}.lock`);
4226
+ }
4227
+ function acquireJobLock(type, ttlMs = DEFAULT_LOCK_TTL_MS) {
4228
+ ensureDirs();
4229
+ const file = lockPath(type);
4230
+ if (existsSync9(file)) {
4231
+ try {
4232
+ const lock = JSON.parse(readFileSync5(file, "utf8"));
4233
+ const age = Date.now() - Date.parse(lock.updatedAt ?? "");
4234
+ if (lock.pid && isAlive(lock.pid) && Number.isFinite(age) && age < ttlMs) return false;
4235
+ } catch {
4236
+ }
4237
+ try {
4238
+ unlinkSync4(file);
4239
+ } catch {
4240
+ }
4241
+ }
4242
+ try {
4243
+ writeFileSync4(file, JSON.stringify({ pid: process.pid, updatedAt: now() }, null, 2) + "\n", { flag: "wx" });
4244
+ return true;
4245
+ } catch {
4246
+ return false;
4247
+ }
4248
+ }
4249
+ function releaseJobLock(type) {
4250
+ const file = lockPath(type);
4251
+ try {
4252
+ if (!existsSync9(file)) return;
4253
+ const lock = JSON.parse(readFileSync5(file, "utf8"));
4254
+ if (lock.pid === process.pid || !lock.pid || !isAlive(lock.pid)) unlinkSync4(file);
4255
+ } catch {
4256
+ try {
4257
+ unlinkSync4(file);
4258
+ } catch {
4259
+ }
4260
+ }
4261
+ }
4262
+ function startManagedJob(options) {
4263
+ const lowPriority = options.lowPriority ?? true;
4264
+ if (!acquireJobLock(options.type, options.lockTtlMs)) return null;
4265
+ if (lowPriority) {
4266
+ try {
4267
+ os6.setPriority(process.pid, 10);
4268
+ } catch {
4269
+ }
4270
+ }
4271
+ const id = `${options.type}-${Date.now()}-${process.pid}`.replace(/[^a-zA-Z0-9_.-]/g, "_");
4272
+ const record = {
4273
+ id,
4274
+ type: options.type,
4275
+ name: options.name,
4276
+ pid: process.pid,
4277
+ command: options.command ?? process.argv.join(" "),
4278
+ cwd: process.cwd(),
4279
+ status: "running",
4280
+ startedAt: now(),
4281
+ updatedAt: now(),
4282
+ lastHeartbeatAt: now(),
4283
+ cancelCommand: `exe-os jobs cancel ${id}`,
4284
+ lowPriority
4285
+ };
4286
+ const upsert = (patch) => {
4287
+ const jobs = readJobsRaw().filter((j) => j.id !== id);
4288
+ Object.assign(record, patch, { updatedAt: now() });
4289
+ writeJobsRaw([...jobs, record]);
4290
+ const file = lockPath(options.type);
4291
+ try {
4292
+ writeFileSync4(file, JSON.stringify({ pid: process.pid, jobId: id, updatedAt: record.updatedAt }, null, 2) + "\n");
4293
+ } catch {
4294
+ }
4295
+ };
4296
+ upsert({});
4297
+ const timer = setInterval(() => upsert({ lastHeartbeatAt: now() }), 3e4);
4298
+ timer.unref?.();
4299
+ const cleanup = (status, error) => {
4300
+ clearInterval(timer);
4301
+ upsert({ status, error, lastHeartbeatAt: now() });
4302
+ releaseJobLock(options.type);
4303
+ };
4304
+ process.once("SIGTERM", () => {
4305
+ cleanup("cancelled");
4306
+ process.exit(0);
4307
+ });
4308
+ process.once("SIGINT", () => {
4309
+ cleanup("cancelled");
4310
+ process.exit(130);
4311
+ });
4312
+ process.once("exit", () => releaseJobLock(options.type));
4313
+ return {
4314
+ id,
4315
+ update(progress) {
4316
+ upsert({ progressCurrent: progress.current, progressTotal: progress.total, progressLabel: progress.label, lastHeartbeatAt: now() });
4317
+ },
4318
+ complete() {
4319
+ cleanup("completed");
4320
+ },
4321
+ fail(err) {
4322
+ cleanup("failed", err instanceof Error ? err.message : String(err));
4323
+ },
4324
+ cancel() {
4325
+ cleanup("cancelled");
4326
+ }
4327
+ };
4328
+ }
4329
+ async function politeBatchPause(ms = 250) {
4330
+ await new Promise((resolve) => setTimeout(resolve, ms));
4331
+ }
4332
+
4182
4333
  // src/bin/backfill-vectors.ts
4183
4334
  var BATCH_SIZE = 100;
4184
- var BACKFILL_FLAG = path9.join(EXE_AI_DIR, "session-cache", "needs-backfill");
4335
+ var BACKFILL_FLAG = path10.join(EXE_AI_DIR, "session-cache", "needs-backfill");
4185
4336
  async function backfillVectors() {
4337
+ const job = startManagedJob({ type: "vector-backfill", name: "Vector embedding backfill", lowPriority: true });
4338
+ if (!job) {
4339
+ process.stderr.write("[backfill] Vector backfill is already running \u2014 exiting\n");
4340
+ return { processed: 0, failed: 0, remaining: -1 };
4341
+ }
4186
4342
  if (!tryAcquireBackfillLock()) {
4187
4343
  process.stderr.write("[backfill] Another backfill is already running \u2014 exiting\n");
4344
+ job.cancel();
4188
4345
  return { processed: 0, failed: 0, remaining: -1 };
4189
4346
  }
4190
4347
  registerWorkerPid(process.pid);
@@ -4205,6 +4362,7 @@ async function backfillVectors() {
4205
4362
  const connected = await connectEmbedDaemon();
4206
4363
  if (!connected) {
4207
4364
  process.stderr.write("[backfill] Cannot connect to embedding daemon \u2014 aborting\n");
4365
+ job.fail(new Error("Cannot connect to embedding daemon"));
4208
4366
  return { processed: 0, failed: 0, remaining: -1 };
4209
4367
  }
4210
4368
  const client = getClient();
@@ -4218,6 +4376,7 @@ async function backfillVectors() {
4218
4376
  if (batch.rows.length === 0) break;
4219
4377
  process.stderr.write(`[backfill] Processing batch of ${batch.rows.length} memories...
4220
4378
  `);
4379
+ job.update({ current: totalProcessed, label: `Processing batch of ${batch.rows.length} memories` });
4221
4380
  for (const row of batch.rows) {
4222
4381
  const id = row.id;
4223
4382
  const rawText = row.raw_text;
@@ -4242,6 +4401,8 @@ async function backfillVectors() {
4242
4401
  }
4243
4402
  process.stderr.write(`[backfill] Batch done. Processed: ${totalProcessed}, Failed: ${totalFailed}
4244
4403
  `);
4404
+ job.update({ current: totalProcessed, label: `Processed ${totalProcessed}, failed ${totalFailed}` });
4405
+ await politeBatchPause(500);
4245
4406
  }
4246
4407
  const remaining = await client.execute({
4247
4408
  sql: "SELECT COUNT(*) as cnt FROM memories WHERE vector IS NULL",
@@ -4250,16 +4411,18 @@ async function backfillVectors() {
4250
4411
  const remainingCount = Number(remaining.rows[0]?.cnt) || 0;
4251
4412
  if (remainingCount === 0) {
4252
4413
  try {
4253
- unlinkSync4(BACKFILL_FLAG);
4414
+ unlinkSync5(BACKFILL_FLAG);
4254
4415
  } catch {
4255
4416
  }
4256
4417
  }
4257
4418
  process.stderr.write(`[backfill] Complete. Processed: ${totalProcessed}, Failed: ${totalFailed}, Remaining: ${remainingCount}
4258
4419
  `);
4420
+ if (totalFailed > 0) job.fail(new Error(`${totalFailed} embeddings failed`));
4421
+ else job.complete();
4259
4422
  return { processed: totalProcessed, failed: totalFailed, remaining: remainingCount };
4260
4423
  }
4261
4424
  function isBackfillNeeded() {
4262
- return existsSync9(BACKFILL_FLAG);
4425
+ return existsSync10(BACKFILL_FLAG);
4263
4426
  }
4264
4427
  if (isMainModule(import.meta.url)) {
4265
4428
  backfillVectors().then((result) => {