@askexenow/exe-os 0.8.52 → 0.8.53

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.
@@ -672,24 +672,70 @@ function summarizeSymlinkResults(results) {
672
672
  }
673
673
 
674
674
  // src/bin/install.ts
675
- import { existsSync as existsSync5, readFileSync as readFileSync4, unlinkSync } from "fs";
675
+ import { existsSync as existsSync5, readFileSync as readFileSync4, unlinkSync, openSync, closeSync } from "fs";
676
+ import { spawn } from "child_process";
676
677
  import path5 from "path";
677
678
  import { homedir } from "os";
678
- function killStaleDaemon() {
679
+ var EXE_DIR = path5.join(homedir(), ".exe-os");
680
+ function restartDaemon() {
681
+ const pidPath = path5.join(EXE_DIR, "exed.pid");
682
+ const sockPath = path5.join(EXE_DIR, "exed.sock");
679
683
  try {
680
- const pidPath = path5.join(homedir(), ".exe-os", "exed.pid");
681
- if (!existsSync5(pidPath)) return;
682
- const pid = parseInt(readFileSync4(pidPath, "utf8").trim(), 10);
683
- if (isNaN(pid)) return;
684
- process.kill(pid, "SIGTERM");
684
+ if (existsSync5(pidPath)) {
685
+ const pid = parseInt(readFileSync4(pidPath, "utf8").trim(), 10);
686
+ if (!isNaN(pid) && pid > 0) {
687
+ try {
688
+ process.kill(pid, "SIGTERM");
689
+ } catch {
690
+ }
691
+ }
692
+ try {
693
+ unlinkSync(pidPath);
694
+ } catch {
695
+ }
696
+ }
685
697
  try {
686
- unlinkSync(pidPath);
698
+ unlinkSync(sockPath);
687
699
  } catch {
688
700
  }
689
- process.stderr.write(`exe-os: restarted daemon (PID ${pid}) for new version
690
- `);
691
701
  } catch {
692
702
  }
703
+ try {
704
+ const pkgRoot = resolvePackageRoot();
705
+ const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
706
+ if (!existsSync5(daemonPath)) {
707
+ process.stderr.write(`exe-os: daemon not found at ${daemonPath} \u2014 skipping respawn
708
+ `);
709
+ return;
710
+ }
711
+ const logPath = path5.join(EXE_DIR, "exed.log");
712
+ let stderrFd = "ignore";
713
+ try {
714
+ stderrFd = openSync(logPath, "a");
715
+ } catch {
716
+ }
717
+ const child = spawn(process.execPath, [daemonPath], {
718
+ detached: true,
719
+ stdio: ["ignore", "ignore", stderrFd],
720
+ env: {
721
+ ...process.env,
722
+ EXE_DAEMON_SOCK: sockPath,
723
+ EXE_DAEMON_PID: pidPath
724
+ }
725
+ });
726
+ child.unref();
727
+ if (typeof stderrFd === "number") {
728
+ try {
729
+ closeSync(stderrFd);
730
+ } catch {
731
+ }
732
+ }
733
+ process.stderr.write(`exe-os: daemon restarted (new PID spawned)
734
+ `);
735
+ } catch (err) {
736
+ process.stderr.write(`exe-os: daemon respawn failed: ${err instanceof Error ? err.message : String(err)}
737
+ `);
738
+ }
693
739
  }
694
740
  var args = process.argv.slice(2);
695
741
  if (args.includes("--commands-only")) {
@@ -707,7 +753,7 @@ if (args.includes("--commands-only")) {
707
753
  } else if (args.includes("--global")) {
708
754
  try {
709
755
  await runInstaller();
710
- killStaleDaemon();
756
+ restartDaemon();
711
757
  } catch (err) {
712
758
  console.error(
713
759
  "Installation failed:",
@@ -5854,15 +5854,18 @@ var daemon_orchestration_exports = {};
5854
5854
  __export(daemon_orchestration_exports, {
5855
5855
  IDLE_KILL_INTERCOM_ACK_WINDOW_MS: () => IDLE_KILL_INTERCOM_ACK_WINDOW_MS,
5856
5856
  IDLE_NUDGE_DEDUP_MS: () => IDLE_NUDGE_DEDUP_MS,
5857
+ REVIEW_NUDGE_COOLDOWN_MS: () => REVIEW_NUDGE_COOLDOWN_MS,
5857
5858
  SESSION_CONTEXT_THRESHOLD_PCT: () => SESSION_CONTEXT_THRESHOLD_PCT,
5858
5859
  SESSION_TTL_HOURS: () => SESSION_TTL_HOURS,
5859
5860
  checkSessionTTL: () => checkSessionTTL,
5860
5861
  classifyTtlKillReason: () => classifyTtlKillReason,
5861
5862
  createIdleKillRealDeps: () => createIdleKillRealDeps,
5862
5863
  createIdleNudgeRealDeps: () => createIdleNudgeRealDeps,
5864
+ createReviewNudgeRealDeps: () => createReviewNudgeRealDeps,
5863
5865
  createSessionTTLRealDeps: () => createSessionTTLRealDeps,
5864
5866
  pollIdleEmployees: () => pollIdleEmployees,
5865
5867
  pollIdleKill: () => pollIdleKill,
5868
+ pollReviewNudge: () => pollReviewNudge,
5866
5869
  shouldKillIdleSession: () => shouldKillIdleSession,
5867
5870
  shouldKillSession: () => shouldKillSession,
5868
5871
  shouldNudgeEmployee: () => shouldNudgeEmployee
@@ -6011,6 +6014,64 @@ async function pollIdleKill(deps, idleTickCounts, opts) {
6011
6014
  }
6012
6015
  return killed;
6013
6016
  }
6017
+ async function pollReviewNudge(deps, state) {
6018
+ let sessions;
6019
+ try {
6020
+ sessions = deps.listTmuxSessions().filter((s) => /^exe\d+$/.test(s));
6021
+ } catch {
6022
+ return [];
6023
+ }
6024
+ if (sessions.length === 0) return [];
6025
+ const nudged = [];
6026
+ const now = Date.now();
6027
+ for (const exeSession of sessions) {
6028
+ const prev = state.lastNudge.get(exeSession);
6029
+ if (prev && now - prev.at < REVIEW_NUDGE_COOLDOWN_MS) continue;
6030
+ const sessionState = deps.getSessionState(exeSession);
6031
+ if (sessionState !== "idle") continue;
6032
+ let count;
6033
+ try {
6034
+ count = await deps.countReviewsForScope(exeSession);
6035
+ } catch {
6036
+ continue;
6037
+ }
6038
+ if (count === 0) continue;
6039
+ if (prev && prev.count === count) continue;
6040
+ try {
6041
+ deps.sendNudge(exeSession);
6042
+ state.lastNudge.set(exeSession, { at: now, count });
6043
+ nudged.push(exeSession);
6044
+ } catch {
6045
+ }
6046
+ }
6047
+ return nudged;
6048
+ }
6049
+ function createReviewNudgeRealDeps(getClient2) {
6050
+ return {
6051
+ listTmuxSessions: () => {
6052
+ const { listTmuxSessions: listTmuxSessions2 } = (init_tmux_status(), __toCommonJS(tmux_status_exports));
6053
+ return listTmuxSessions2();
6054
+ },
6055
+ getSessionState: (sessionName) => {
6056
+ const { getSessionState: getSessionState2 } = (init_tmux_routing(), __toCommonJS(tmux_routing_exports));
6057
+ return getSessionState2(sessionName);
6058
+ },
6059
+ countReviewsForScope: async (sessionScope) => {
6060
+ const client = getClient2();
6061
+ const result = await client.execute({
6062
+ sql: `SELECT COUNT(*) as cnt FROM tasks
6063
+ WHERE status = 'needs_review'
6064
+ AND (session_scope = ? OR session_scope IS NULL)`,
6065
+ args: [sessionScope]
6066
+ });
6067
+ return Number(result.rows[0]?.cnt ?? 0);
6068
+ },
6069
+ sendNudge: (sessionName) => {
6070
+ const { getTransport: getTransport2 } = (init_transport(), __toCommonJS(transport_exports));
6071
+ getTransport2().sendKeys(sessionName, "check reviews");
6072
+ }
6073
+ };
6074
+ }
6014
6075
  function createIdleNudgeRealDeps(getClient2) {
6015
6076
  return {
6016
6077
  listRegisteredSessions: () => {
@@ -6108,7 +6169,7 @@ function createIdleKillRealDeps(getClient2, intercomAckWindowMs) {
6108
6169
  }
6109
6170
  };
6110
6171
  }
6111
- var IDLE_NUDGE_DEDUP_MS, SESSION_TTL_HOURS, SESSION_CONTEXT_THRESHOLD_PCT, IDLE_KILL_INTERCOM_ACK_WINDOW_MS;
6172
+ var IDLE_NUDGE_DEDUP_MS, SESSION_TTL_HOURS, SESSION_CONTEXT_THRESHOLD_PCT, IDLE_KILL_INTERCOM_ACK_WINDOW_MS, REVIEW_NUDGE_COOLDOWN_MS;
6112
6173
  var init_daemon_orchestration = __esm({
6113
6174
  "src/lib/daemon-orchestration.ts"() {
6114
6175
  "use strict";
@@ -6117,6 +6178,7 @@ var init_daemon_orchestration = __esm({
6117
6178
  SESSION_TTL_HOURS = 4;
6118
6179
  SESSION_CONTEXT_THRESHOLD_PCT = 50;
6119
6180
  IDLE_KILL_INTERCOM_ACK_WINDOW_MS = 1e4;
6181
+ REVIEW_NUDGE_COOLDOWN_MS = 3e5;
6120
6182
  }
6121
6183
  });
6122
6184
 
@@ -8557,6 +8619,29 @@ function startIdleNudge() {
8557
8619
  process.stderr.write(`[exed] Idle nudge started (every ${REVIEW_POLL_INTERVAL_MS / 6e4}m)
8558
8620
  `);
8559
8621
  }
8622
+ var _reviewNudgeState = { lastNudge: /* @__PURE__ */ new Map() };
8623
+ function startReviewNudge() {
8624
+ const tick = async () => {
8625
+ if (!await ensureStoreForPolling()) return;
8626
+ try {
8627
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
8628
+ const { pollReviewNudge: pollReviewNudge2, createReviewNudgeRealDeps: createReviewNudgeRealDeps2 } = await Promise.resolve().then(() => (init_daemon_orchestration(), daemon_orchestration_exports));
8629
+ const deps = createReviewNudgeRealDeps2(getClient2);
8630
+ const nudged = await pollReviewNudge2(deps, _reviewNudgeState);
8631
+ for (const s of nudged) {
8632
+ process.stderr.write(`[exed] Review nudge: sent "check reviews" to ${s}
8633
+ `);
8634
+ }
8635
+ } catch (err) {
8636
+ process.stderr.write(`[exed] Review nudge error: ${err instanceof Error ? err.message : String(err)}
8637
+ `);
8638
+ }
8639
+ };
8640
+ const timer = setInterval(() => void tick(), REVIEW_POLL_INTERVAL_MS);
8641
+ timer.unref();
8642
+ process.stderr.write(`[exed] Review nudge started (every ${REVIEW_POLL_INTERVAL_MS / 6e4}m)
8643
+ `);
8644
+ }
8560
8645
  var SESSION_TTL_INTERVAL_MS = 5 * 60 * 1e3;
8561
8646
  function startSessionTTL() {
8562
8647
  const tick = async () => {
@@ -8921,6 +9006,7 @@ try {
8921
9006
  startServer();
8922
9007
  startReviewPolling();
8923
9008
  startIdleNudge();
9009
+ startReviewNudge();
8924
9010
  startSessionTTL();
8925
9011
  startIdleKill();
8926
9012
  startConsolidation();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askexenow/exe-os",
3
- "version": "0.8.52",
3
+ "version": "0.8.53",
4
4
  "description": "AI employee operating system — persistent memory, task management, and multi-agent coordination for Claude Code.",
5
5
  "license": "CC-BY-NC-4.0",
6
6
  "type": "module",