@askexenow/exe-os 0.8.36 → 0.8.37

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.
Files changed (62) hide show
  1. package/dist/bin/backfill-conversations.js +9 -1
  2. package/dist/bin/backfill-responses.js +9 -1
  3. package/dist/bin/cli.js +255 -31
  4. package/dist/bin/exe-assign.js +9 -1
  5. package/dist/bin/exe-boot.js +554 -23
  6. package/dist/bin/exe-dispatch.js +43 -3
  7. package/dist/bin/exe-export-behaviors.js +7 -0
  8. package/dist/bin/exe-gateway.js +57 -9
  9. package/dist/bin/exe-heartbeat.js +2 -1
  10. package/dist/bin/exe-kill.js +7 -0
  11. package/dist/bin/exe-launch-agent.js +8 -1
  12. package/dist/bin/exe-link.js +503 -12
  13. package/dist/bin/exe-pending-messages.js +2 -1
  14. package/dist/bin/exe-pending-reviews.js +2 -1
  15. package/dist/bin/exe-review.js +9 -1
  16. package/dist/bin/exe-search.js +9 -1
  17. package/dist/bin/exe-session-cleanup.js +11 -2
  18. package/dist/bin/git-sweep.js +9 -1
  19. package/dist/bin/graph-backfill.js +7 -0
  20. package/dist/bin/graph-export.js +7 -0
  21. package/dist/bin/install.js +35 -5
  22. package/dist/bin/scan-tasks.js +9 -1
  23. package/dist/bin/shard-migrate.js +7 -0
  24. package/dist/bin/wiki-sync.js +7 -0
  25. package/dist/gateway/index.js +57 -9
  26. package/dist/hooks/bug-report-worker.js +45 -5
  27. package/dist/hooks/commit-complete.js +9 -1
  28. package/dist/hooks/error-recall.js +10 -2
  29. package/dist/hooks/exe-heartbeat-hook.js +1 -1
  30. package/dist/hooks/ingest-worker.js +56 -8
  31. package/dist/hooks/ingest.js +1 -1
  32. package/dist/hooks/instructions-loaded.js +10 -2
  33. package/dist/hooks/notification.js +10 -2
  34. package/dist/hooks/post-compact.js +10 -2
  35. package/dist/hooks/pre-compact.js +10 -2
  36. package/dist/hooks/pre-tool-use.js +10 -2
  37. package/dist/hooks/prompt-ingest-worker.js +9 -1
  38. package/dist/hooks/prompt-submit.js +56 -8
  39. package/dist/hooks/response-ingest-worker.js +9 -1
  40. package/dist/hooks/session-end.js +10 -2
  41. package/dist/hooks/session-start.js +10 -2
  42. package/dist/hooks/stop.js +10 -2
  43. package/dist/hooks/subagent-stop.js +10 -2
  44. package/dist/hooks/summary-worker.js +512 -13
  45. package/dist/index.js +65 -15
  46. package/dist/lib/cloud-sync.js +502 -11
  47. package/dist/lib/exe-daemon.js +73 -23
  48. package/dist/lib/hybrid-search.js +9 -1
  49. package/dist/lib/messaging.js +43 -3
  50. package/dist/lib/store.js +9 -1
  51. package/dist/lib/tasks.js +47 -7
  52. package/dist/lib/tmux-routing.js +45 -3
  53. package/dist/mcp/server.js +73 -16
  54. package/dist/mcp/tools/create-task.js +48 -8
  55. package/dist/mcp/tools/deactivate-behavior.js +1 -1
  56. package/dist/mcp/tools/list-tasks.js +2 -1
  57. package/dist/mcp/tools/send-message.js +46 -6
  58. package/dist/mcp/tools/update-task.js +3 -2
  59. package/dist/runtime/index.js +54 -4
  60. package/dist/tui/App.js +54 -4
  61. package/package.json +2 -2
  62. package/src/commands/exe/afk.md +116 -0
package/dist/index.js CHANGED
@@ -3339,6 +3339,7 @@ var init_capacity_monitor = __esm({
3339
3339
  // src/lib/tmux-routing.ts
3340
3340
  var tmux_routing_exports = {};
3341
3341
  __export(tmux_routing_exports, {
3342
+ acquireSpawnLock: () => acquireSpawnLock,
3342
3343
  employeeSessionName: () => employeeSessionName,
3343
3344
  ensureEmployee: () => ensureEmployee,
3344
3345
  extractRootExe: () => extractRootExe,
@@ -3353,6 +3354,7 @@ __export(tmux_routing_exports, {
3353
3354
  notifyParentExe: () => notifyParentExe,
3354
3355
  parseParentExe: () => parseParentExe,
3355
3356
  registerParentExe: () => registerParentExe,
3357
+ releaseSpawnLock: () => releaseSpawnLock,
3356
3358
  resolveExeSession: () => resolveExeSession,
3357
3359
  sendIntercom: () => sendIntercom,
3358
3360
  spawnEmployee: () => spawnEmployee,
@@ -3363,6 +3365,42 @@ import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSy
3363
3365
  import path14 from "path";
3364
3366
  import os6 from "os";
3365
3367
  import { fileURLToPath } from "url";
3368
+ import { unlinkSync as unlinkSync4 } from "fs";
3369
+ function spawnLockPath(sessionName) {
3370
+ return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
3371
+ }
3372
+ function isProcessAlive(pid) {
3373
+ try {
3374
+ process.kill(pid, 0);
3375
+ return true;
3376
+ } catch {
3377
+ return false;
3378
+ }
3379
+ }
3380
+ function acquireSpawnLock(sessionName) {
3381
+ if (!existsSync10(SPAWN_LOCK_DIR)) {
3382
+ mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
3383
+ }
3384
+ const lockFile = spawnLockPath(sessionName);
3385
+ if (existsSync10(lockFile)) {
3386
+ try {
3387
+ const lock = JSON.parse(readFileSync9(lockFile, "utf8"));
3388
+ const age = Date.now() - lock.timestamp;
3389
+ if (isProcessAlive(lock.pid) && age < 6e4) {
3390
+ return false;
3391
+ }
3392
+ } catch {
3393
+ }
3394
+ }
3395
+ writeFileSync5(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
3396
+ return true;
3397
+ }
3398
+ function releaseSpawnLock(sessionName) {
3399
+ try {
3400
+ unlinkSync4(spawnLockPath(sessionName));
3401
+ } catch {
3402
+ }
3403
+ }
3366
3404
  function resolveBehaviorsExporterScript() {
3367
3405
  try {
3368
3406
  const thisFile = fileURLToPath(import.meta.url);
@@ -3461,10 +3499,10 @@ function isEmployeeAlive(sessionName) {
3461
3499
  }
3462
3500
  function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
3463
3501
  const base = employeeSessionName(employeeName, exeSession);
3464
- if (!isAlive(base)) return 0;
3502
+ if (!isAlive(base) && acquireSpawnLock(base)) return 0;
3465
3503
  for (let i = 2; i <= maxInstances; i++) {
3466
3504
  const candidate = employeeSessionName(employeeName, exeSession, i);
3467
- if (!isAlive(candidate)) return i;
3505
+ if (!isAlive(candidate) && acquireSpawnLock(candidate)) return i;
3468
3506
  }
3469
3507
  return null;
3470
3508
  }
@@ -3828,6 +3866,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3828
3866
  command: spawnCommand
3829
3867
  });
3830
3868
  if (spawnResult.error) {
3869
+ releaseSpawnLock(sessionName);
3831
3870
  return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
3832
3871
  }
3833
3872
  transport.pipeLog(sessionName, logFile);
@@ -3865,6 +3904,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3865
3904
  }
3866
3905
  }
3867
3906
  if (!booted) {
3907
+ releaseSpawnLock(sessionName);
3868
3908
  return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
3869
3909
  }
3870
3910
  if (!useExeAgent) {
@@ -3881,9 +3921,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3881
3921
  pid: 0,
3882
3922
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
3883
3923
  });
3924
+ releaseSpawnLock(sessionName);
3884
3925
  return { sessionName };
3885
3926
  }
3886
- var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
3927
+ var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
3887
3928
  var init_tmux_routing = __esm({
3888
3929
  "src/lib/tmux-routing.ts"() {
3889
3930
  "use strict";
@@ -3895,6 +3936,7 @@ var init_tmux_routing = __esm({
3895
3936
  init_provider_table();
3896
3937
  init_intercom_queue();
3897
3938
  init_plan_limits();
3939
+ SPAWN_LOCK_DIR = path14.join(os6.homedir(), ".exe-os", "spawn-locks");
3898
3940
  SESSION_CACHE = path14.join(os6.homedir(), ".exe-os", "session-cache");
3899
3941
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
3900
3942
  VERIFY_PANE_LINES = 200;
@@ -4283,7 +4325,8 @@ async function writeMemory(record) {
4283
4325
  has_error: record.has_error ? 1 : 0,
4284
4326
  raw_text: record.raw_text,
4285
4327
  vector: record.vector,
4286
- version: _nextVersion++,
4328
+ version: 0,
4329
+ // Placeholder — assigned atomically at flush time
4287
4330
  task_id: record.task_id ?? null,
4288
4331
  importance: record.importance ?? 5,
4289
4332
  status: record.status ?? "active",
@@ -4317,6 +4360,13 @@ async function flushBatch() {
4317
4360
  _flushing = true;
4318
4361
  try {
4319
4362
  const batch = _pendingRecords.slice(0);
4363
+ const client = getClient();
4364
+ const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
4365
+ let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
4366
+ for (const row of batch) {
4367
+ row.version = baseVersion++;
4368
+ }
4369
+ _nextVersion = baseVersion;
4320
4370
  const buildStmt = (row) => {
4321
4371
  const hasVector = row.vector !== null;
4322
4372
  const taskId = row.task_id ?? null;
@@ -5247,7 +5297,7 @@ var init_crm_bridge = __esm({
5247
5297
  import net from "net";
5248
5298
  import { spawn } from "child_process";
5249
5299
  import { randomUUID as randomUUID5 } from "crypto";
5250
- import { existsSync as existsSync13, unlinkSync as unlinkSync4, readFileSync as readFileSync10, openSync, closeSync, statSync } from "fs";
5300
+ import { existsSync as existsSync13, unlinkSync as unlinkSync5, readFileSync as readFileSync10, openSync, closeSync, statSync } from "fs";
5251
5301
  import path17 from "path";
5252
5302
  import { fileURLToPath as fileURLToPath2 } from "url";
5253
5303
  function handleData(chunk) {
@@ -5283,11 +5333,11 @@ function cleanupStaleFiles() {
5283
5333
  } catch {
5284
5334
  }
5285
5335
  try {
5286
- unlinkSync4(PID_PATH);
5336
+ unlinkSync5(PID_PATH);
5287
5337
  } catch {
5288
5338
  }
5289
5339
  try {
5290
- unlinkSync4(SOCKET_PATH);
5340
+ unlinkSync5(SOCKET_PATH);
5291
5341
  } catch {
5292
5342
  }
5293
5343
  }
@@ -5339,7 +5389,7 @@ function spawnDaemon() {
5339
5389
  }
5340
5390
  }
5341
5391
  }
5342
- function acquireSpawnLock() {
5392
+ function acquireSpawnLock2() {
5343
5393
  try {
5344
5394
  const fd = openSync(SPAWN_LOCK_PATH, "wx");
5345
5395
  closeSync(fd);
@@ -5349,7 +5399,7 @@ function acquireSpawnLock() {
5349
5399
  const stat = statSync(SPAWN_LOCK_PATH);
5350
5400
  if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
5351
5401
  try {
5352
- unlinkSync4(SPAWN_LOCK_PATH);
5402
+ unlinkSync5(SPAWN_LOCK_PATH);
5353
5403
  } catch {
5354
5404
  }
5355
5405
  try {
@@ -5364,9 +5414,9 @@ function acquireSpawnLock() {
5364
5414
  return false;
5365
5415
  }
5366
5416
  }
5367
- function releaseSpawnLock() {
5417
+ function releaseSpawnLock2() {
5368
5418
  try {
5369
- unlinkSync4(SPAWN_LOCK_PATH);
5419
+ unlinkSync5(SPAWN_LOCK_PATH);
5370
5420
  } catch {
5371
5421
  }
5372
5422
  }
@@ -5411,12 +5461,12 @@ function connectToSocket() {
5411
5461
  async function connectEmbedDaemon() {
5412
5462
  if (_socket && _connected) return true;
5413
5463
  if (await connectToSocket()) return true;
5414
- if (acquireSpawnLock()) {
5464
+ if (acquireSpawnLock2()) {
5415
5465
  try {
5416
5466
  cleanupStaleFiles();
5417
5467
  spawnDaemon();
5418
5468
  } finally {
5419
- releaseSpawnLock();
5469
+ releaseSpawnLock2();
5420
5470
  }
5421
5471
  }
5422
5472
  const start = Date.now();
@@ -5497,11 +5547,11 @@ function killAndRespawnDaemon() {
5497
5547
  _connected = false;
5498
5548
  _buffer = "";
5499
5549
  try {
5500
- unlinkSync4(PID_PATH);
5550
+ unlinkSync5(PID_PATH);
5501
5551
  } catch {
5502
5552
  }
5503
5553
  try {
5504
- unlinkSync4(SOCKET_PATH);
5554
+ unlinkSync5(SOCKET_PATH);
5505
5555
  } catch {
5506
5556
  }
5507
5557
  spawnDaemon();