@askexenow/exe-os 0.9.120 → 0.9.122

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 (139) hide show
  1. package/dist/assets/ghostty.conf +83 -0
  2. package/dist/assets/statusline-command.sh +44 -0
  3. package/dist/assets/tmux.conf +53 -0
  4. package/dist/bin/age-ontology-load.js +333 -0
  5. package/dist/bin/agentic-ontology-backfill.js +5357 -0
  6. package/dist/bin/agentic-reflection-backfill.js +4763 -0
  7. package/dist/bin/agentic-semantic-label.js +4890 -0
  8. package/dist/bin/backfill-conversations.js +5904 -0
  9. package/dist/bin/backfill-responses.js +5732 -0
  10. package/dist/bin/backfill-vectors.js +4904 -0
  11. package/dist/bin/bulk-sync-postgres.js +5382 -0
  12. package/dist/bin/cc-doctor.js +643 -0
  13. package/dist/bin/cleanup-stale-review-tasks.js +6910 -0
  14. package/dist/bin/cli.js +39721 -0
  15. package/dist/bin/customer-readiness.js +315 -0
  16. package/dist/bin/exe-agent-config.js +323 -0
  17. package/dist/bin/exe-agent.js +2556 -0
  18. package/dist/bin/exe-assign.js +5870 -0
  19. package/dist/bin/exe-boot.js +13663 -0
  20. package/dist/bin/exe-call.js +1492 -0
  21. package/dist/bin/exe-cloud.js +8930 -0
  22. package/dist/bin/exe-dispatch.js +10859 -0
  23. package/dist/bin/exe-doctor.js +7559 -0
  24. package/dist/bin/exe-export-behaviors.js +6150 -0
  25. package/dist/bin/exe-forget.js +6456 -0
  26. package/dist/bin/exe-gateway.js +16218 -0
  27. package/dist/bin/exe-healthcheck.js +607 -0
  28. package/dist/bin/exe-heartbeat.js +7079 -0
  29. package/dist/bin/exe-kill.js +6036 -0
  30. package/dist/bin/exe-launch-agent.js +7177 -0
  31. package/dist/bin/exe-new-employee.js +3902 -0
  32. package/dist/bin/exe-pending-messages.js +6861 -0
  33. package/dist/bin/exe-pending-notifications.js +6941 -0
  34. package/dist/bin/exe-pending-reviews.js +6947 -0
  35. package/dist/bin/exe-rename.js +5928 -0
  36. package/dist/bin/exe-repo-drift.js +95 -0
  37. package/dist/bin/exe-review.js +6119 -0
  38. package/dist/bin/exe-search.js +7735 -0
  39. package/dist/bin/exe-session-cleanup.js +11350 -0
  40. package/dist/bin/exe-settings.js +861 -0
  41. package/dist/bin/exe-start-codex.js +6919 -0
  42. package/dist/bin/exe-start-opencode.js +6765 -0
  43. package/dist/bin/exe-start.sh +170 -0
  44. package/dist/bin/exe-status.js +7086 -0
  45. package/dist/bin/exe-support.js +553 -0
  46. package/dist/bin/exe-team.js +6012 -0
  47. package/dist/bin/git-sweep.js +10953 -0
  48. package/dist/bin/graph-backfill.js +6032 -0
  49. package/dist/bin/graph-export.js +6163 -0
  50. package/dist/bin/graph-layer-benchmark.js +117 -0
  51. package/dist/bin/install.js +2489 -0
  52. package/dist/bin/intercom-check.js +11604 -0
  53. package/dist/bin/list-providers.js +142 -0
  54. package/dist/bin/postgres-agentic-reflection-backfill.js +466 -0
  55. package/dist/bin/postgres-agentic-semantic-backfill.js +516 -0
  56. package/dist/bin/pre-build-guard.js +98 -0
  57. package/dist/bin/pre-publish.js +488 -0
  58. package/dist/bin/registry-proxy.js +208 -0
  59. package/dist/bin/scan-tasks.js +11087 -0
  60. package/dist/bin/setup.js +9504 -0
  61. package/dist/bin/shard-migrate.js +5281 -0
  62. package/dist/bin/stack-update.js +1079 -0
  63. package/dist/bin/update.js +974 -0
  64. package/dist/gateway/index.js +16622 -0
  65. package/dist/hooks/bug-report-worker.js +10964 -0
  66. package/dist/hooks/codex-stop-task-finalizer.js +9274 -0
  67. package/dist/hooks/commit-complete.js +11008 -0
  68. package/dist/hooks/error-recall.js +8045 -0
  69. package/dist/hooks/exe-heartbeat-hook.js +381 -0
  70. package/dist/hooks/ingest-worker.js +595 -0
  71. package/dist/hooks/ingest.js +11788 -0
  72. package/dist/hooks/instructions-loaded.js +6297 -0
  73. package/dist/hooks/notification.js +6138 -0
  74. package/dist/hooks/post-compact.js +7136 -0
  75. package/dist/hooks/post-tool-combined.js +9869 -0
  76. package/dist/hooks/pre-compact.js +11241 -0
  77. package/dist/hooks/pre-tool-use.js +7482 -0
  78. package/dist/hooks/prompt-submit.js +13668 -0
  79. package/dist/hooks/session-end.js +11691 -0
  80. package/dist/hooks/session-start.js +9615 -0
  81. package/dist/hooks/stop.js +7451 -0
  82. package/dist/hooks/subagent-stop.js +6942 -0
  83. package/dist/hooks/summary-worker.js +8841 -0
  84. package/dist/index.js +19397 -0
  85. package/dist/lib/agent-config.js +237 -0
  86. package/dist/lib/cloud-sync.js +5433 -0
  87. package/dist/lib/cloudflare-dns.js +117 -0
  88. package/dist/lib/config.js +292 -0
  89. package/dist/lib/consolidation.js +1058 -0
  90. package/dist/lib/crypto.js +51 -0
  91. package/dist/lib/database.js +2945 -0
  92. package/dist/lib/db-daemon-client.js +587 -0
  93. package/dist/lib/db.js +2945 -0
  94. package/dist/lib/device-registry.js +3044 -0
  95. package/dist/lib/embedder.js +828 -0
  96. package/dist/lib/employee-templates.js +1115 -0
  97. package/dist/lib/employees.js +522 -0
  98. package/dist/lib/error-detector.js +156 -0
  99. package/dist/lib/exe-daemon-client.js +580 -0
  100. package/dist/lib/exe-daemon.js +20284 -0
  101. package/dist/lib/file-grep.js +215 -0
  102. package/dist/lib/hybrid-search.js +7682 -0
  103. package/dist/lib/identity-templates.js +537 -0
  104. package/dist/lib/identity.js +316 -0
  105. package/dist/lib/keychain.js +507 -0
  106. package/dist/lib/license.js +565 -0
  107. package/dist/lib/messaging.js +1647 -0
  108. package/dist/lib/post-tool-memory.js +373 -0
  109. package/dist/lib/registry-proxy.js +162 -0
  110. package/dist/lib/reminders.js +236 -0
  111. package/dist/lib/runtime-table.js +24 -0
  112. package/dist/lib/schedules.js +4620 -0
  113. package/dist/lib/session-registry.js +63 -0
  114. package/dist/lib/session-wrappers.js +131 -0
  115. package/dist/lib/skill-learning.js +2112 -0
  116. package/dist/lib/status-brief.js +371 -0
  117. package/dist/lib/store.js +5609 -0
  118. package/dist/lib/task-router.js +262 -0
  119. package/dist/lib/tasks.js +5823 -0
  120. package/dist/lib/tmux-routing.js +5827 -0
  121. package/dist/lib/tmux-status.js +261 -0
  122. package/dist/lib/tmux-transport.js +93 -0
  123. package/dist/lib/token-spend.js +340 -0
  124. package/dist/lib/transport.js +138 -0
  125. package/dist/lib/ws-auth.js +19 -0
  126. package/dist/lib/ws-client.js +189 -0
  127. package/dist/mcp/register-tools.js +32445 -0
  128. package/dist/mcp/server.js +32779 -0
  129. package/dist/mcp/tools/complete-reminder.js +240 -0
  130. package/dist/mcp/tools/create-reminder.js +225 -0
  131. package/dist/mcp/tools/create-task.js +6734 -0
  132. package/dist/mcp/tools/deactivate-behavior.js +479 -0
  133. package/dist/mcp/tools/list-reminders.js +235 -0
  134. package/dist/mcp/tools/list-tasks.js +1575 -0
  135. package/dist/mcp/tools/send-message.js +1676 -0
  136. package/dist/mcp/tools/update-task.js +6127 -0
  137. package/dist/runtime/index.js +13452 -0
  138. package/dist/tui/App.js +24635 -0
  139. package/package.json +1 -1
@@ -0,0 +1,83 @@
1
+ # Exe OS — Ghostty defaults
2
+ # Optimized for tmux + Claude Code agent workflows
3
+
4
+ font-size = 11
5
+
6
+ # ─── Theme: warm light background ───────────────────────────
7
+ background = #fff8f0
8
+ foreground = #000000
9
+ cursor-color = #000000
10
+ selection-background = #b4d5fe
11
+
12
+ # ANSI palette (0-15)
13
+ palette = 0=#000000
14
+ palette = 1=#c91b00
15
+ palette = 2=#00c200
16
+ palette = 3=#c7c400
17
+ palette = 4=#0225c7
18
+ palette = 5=#c930c7
19
+ palette = 6=#00c5c7
20
+ palette = 7=#c7c7c7
21
+ palette = 8=#686868
22
+ palette = 9=#ff6e67
23
+ palette = 10=#5ffa68
24
+ palette = 11=#e5daab
25
+ palette = 12=#6871ff
26
+ palette = 13=#ff77ff
27
+ palette = 14=#60fdff
28
+ palette = 15=#ffffff
29
+
30
+ # ─── Ghostty → tmux keybindings ─────────────────────────────
31
+ # Forward Cmd+key combos to tmux as prefix sequences.
32
+ # Tmux prefix is Ctrl+B (\x02).
33
+
34
+ # Splits (native Ghostty splits)
35
+ keybind = super+d=new_split:right
36
+ keybind = super+shift+d=new_split:down
37
+
38
+ # Close pane/tab
39
+ keybind = super+w=close_surface
40
+
41
+ # New tab
42
+ keybind = super+t=text:\x02t
43
+
44
+ # Tab navigation: Cmd+Shift+[ / ]
45
+ keybind = super+shift+[=text:\x02[
46
+ keybind = super+shift+]=text:\x02]
47
+
48
+ # Go to tab by number: Cmd+1..9
49
+ keybind = super+1=text:\x021
50
+ keybind = super+digit_1=text:\x021
51
+ keybind = super+2=text:\x022
52
+ keybind = super+digit_2=text:\x022
53
+ keybind = super+3=text:\x023
54
+ keybind = super+digit_3=text:\x023
55
+ keybind = super+4=text:\x024
56
+ keybind = super+digit_4=text:\x024
57
+ keybind = super+5=text:\x025
58
+ keybind = super+digit_5=text:\x025
59
+ keybind = super+6=text:\x026
60
+ keybind = super+digit_6=text:\x026
61
+ keybind = super+7=text:\x027
62
+ keybind = super+digit_7=text:\x027
63
+ keybind = super+8=text:\x028
64
+ keybind = super+digit_8=text:\x028
65
+ keybind = super+9=text:\x029
66
+
67
+ # Zoom pane (fullscreen): Cmd+Shift+Enter
68
+ keybind = super+shift+enter=text:\x02z
69
+
70
+ # Clear screen: Cmd+K
71
+ keybind = super+k=text:\x02k
72
+
73
+ # Search: Cmd+F
74
+ keybind = super+f=text:\x02f
75
+
76
+ # Resize splits: Cmd+Ctrl+Arrow
77
+ keybind = super+ctrl+arrow_up=text:\x02\x1b[A
78
+ keybind = super+ctrl+arrow_down=text:\x02\x1b[B
79
+ keybind = super+ctrl+arrow_right=text:\x02\x1b[C
80
+ keybind = super+ctrl+arrow_left=text:\x02\x1b[D
81
+
82
+ # Equalize splits: Cmd+Ctrl+=
83
+ keybind = super+ctrl+=text:\x02=
@@ -0,0 +1,44 @@
1
+ #!/bin/bash
2
+ # Claude Code status line — model + visual context bar + percentage
3
+ input=$(cat)
4
+
5
+ MODEL=$(echo "$input" | jq -r '.model.display_name // "Claude"')
6
+ PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
7
+ CTX_SIZE=$(echo "$input" | jq -r '.context_window.context_window_size // empty')
8
+
9
+ # Format context window size (1000000 -> 1M, 200000 -> 200K)
10
+ if [ -n "$CTX_SIZE" ]; then
11
+ if [ "$CTX_SIZE" -ge 1000000 ] 2>/dev/null; then
12
+ CTX_LABEL="$(echo "$CTX_SIZE" | awk '{printf "%.0fM", $1/1000000}')"
13
+ elif [ "$CTX_SIZE" -ge 1000 ] 2>/dev/null; then
14
+ CTX_LABEL="$(echo "$CTX_SIZE" | awk '{printf "%.0fK", $1/1000}')"
15
+ else
16
+ CTX_LABEL="${CTX_SIZE}"
17
+ fi
18
+ fi
19
+
20
+ # Build visual bar (12 chars wide)
21
+ BAR_WIDTH=12
22
+ FILLED=$((PCT * BAR_WIDTH / 100))
23
+ EMPTY=$((BAR_WIDTH - FILLED))
24
+ BAR=""
25
+
26
+ [ "$FILLED" -gt 0 ] && printf -v FILL "%${FILLED}s" && BAR="${FILL// /▓}"
27
+ [ "$EMPTY" -gt 0 ] && printf -v PAD "%${EMPTY}s" && BAR="${BAR}${PAD// /░}"
28
+
29
+ # Color the percentage based on usage
30
+ if [ "$PCT" -ge 80 ]; then
31
+ COLOR="\033[31m" # Red
32
+ elif [ "$PCT" -ge 50 ]; then
33
+ COLOR="\033[33m" # Yellow
34
+ else
35
+ COLOR="\033[32m" # Green
36
+ fi
37
+ RESET="\033[0m"
38
+ DIM="\033[2m"
39
+
40
+ if [ -n "$CTX_SIZE" ] && [ "$PCT" -gt 0 ]; then
41
+ printf "${DIM}%s${RESET} ${COLOR}%s ${PCT}%%${RESET} ${DIM}of ${CTX_LABEL}${RESET}" "$MODEL" "$BAR"
42
+ else
43
+ printf "${DIM}%s${RESET}" "$MODEL"
44
+ fi
@@ -0,0 +1,53 @@
1
+ # Exe OS — Ghostty-compatible tmux defaults
2
+ # Backup of original: ~/.tmux.conf.backup (if existed)
3
+
4
+ # Mouse support
5
+ set -g mouse on
6
+
7
+ # Modern prefix (Ctrl+a instead of Ctrl+b)
8
+ unbind C-b
9
+ set -g prefix C-a
10
+ bind C-a send-prefix
11
+
12
+ # True color support
13
+ set -g default-terminal "tmux-256color"
14
+ set -ga terminal-overrides ",*256col*:Tc"
15
+
16
+ # Scroll history
17
+ set -g history-limit 50000
18
+
19
+ # Copy to macOS clipboard on mouse drag select
20
+ set -g copy-command "pbcopy"
21
+ bind -T copy-mode MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "pbcopy"
22
+ bind -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "pbcopy"
23
+
24
+ # Vi-style copy mode
25
+ setw -g mode-keys vi
26
+ bind -T copy-mode-vi v send-keys -X begin-selection
27
+ bind -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "pbcopy"
28
+
29
+ # Blue selection highlight
30
+ set -g mode-style "bg=#1a73e8,fg=#ffffff"
31
+
32
+ # Pane navigation without prefix
33
+ bind -n M-Left select-pane -L
34
+ bind -n M-Right select-pane -R
35
+ bind -n M-Up select-pane -U
36
+ bind -n M-Down select-pane -D
37
+
38
+ # Status bar — session name only, nothing else
39
+ set -g status-style "bg=#da7756,fg=#ffffff"
40
+ set -g status-left "#[bold] #S "
41
+ set -g status-left-length 30
42
+ set -g window-status-format ""
43
+ set -g window-status-current-format ""
44
+ set -g status-right ""
45
+ set -g status-right-length 0
46
+
47
+ # Disable auto-renaming
48
+ set -g allow-rename off
49
+ set -g automatic-rename off
50
+
51
+ # Start windows and panes at 1
52
+ set -g base-index 1
53
+ setw -g pane-base-index 1
@@ -0,0 +1,333 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/bin/age-ontology-load.ts
4
+ import { Client } from "pg";
5
+
6
+ // src/lib/pg-ssl.ts
7
+ function pgSslConfig() {
8
+ if (process.env.EXE_DB_SSL_DISABLED === "true") return {};
9
+ return { ssl: { rejectUnauthorized: process.env.EXE_DB_SSL_ALLOW_SELFSIGNED !== "true" } };
10
+ }
11
+
12
+ // src/lib/background-jobs.ts
13
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync, unlinkSync } from "fs";
14
+ import { execFileSync } from "child_process";
15
+ import os2 from "os";
16
+ import path2 from "path";
17
+
18
+ // src/lib/config.ts
19
+ import { readFile, writeFile } from "fs/promises";
20
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
21
+ import path from "path";
22
+ import os from "os";
23
+
24
+ // src/lib/secure-files.ts
25
+ import { chmodSync, existsSync, mkdirSync } from "fs";
26
+ import { chmod, mkdir } from "fs/promises";
27
+
28
+ // src/lib/config.ts
29
+ function resolveDataDir() {
30
+ if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
31
+ if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
32
+ const newDir = path.join(os.homedir(), ".exe-os");
33
+ const legacyDir = path.join(os.homedir(), ".exe-mem");
34
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
35
+ try {
36
+ renameSync(legacyDir, newDir);
37
+ process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
38
+ `);
39
+ } catch {
40
+ return legacyDir;
41
+ }
42
+ }
43
+ return newDir;
44
+ }
45
+ var EXE_AI_DIR = resolveDataDir();
46
+ var DB_PATH = path.join(EXE_AI_DIR, "memories.db");
47
+ var MODELS_DIR = path.join(EXE_AI_DIR, "models");
48
+ var CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
49
+ var LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
50
+ var CURRENT_CONFIG_VERSION = 1;
51
+ var DEFAULT_CONFIG = {
52
+ config_version: CURRENT_CONFIG_VERSION,
53
+ dbPath: DB_PATH,
54
+ modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
55
+ embeddingDim: 1024,
56
+ batchSize: 20,
57
+ flushIntervalMs: 1e4,
58
+ autoIngestion: true,
59
+ autoRetrieval: true,
60
+ searchMode: "hybrid",
61
+ hookSearchMode: "hybrid",
62
+ fileGrepEnabled: true,
63
+ splashEffect: true,
64
+ consolidationEnabled: true,
65
+ consolidationIntervalMs: 6 * 60 * 60 * 1e3,
66
+ consolidationModel: "claude-haiku-4-5-20251001",
67
+ consolidationMaxCallsPerRun: 20,
68
+ selfQueryRouter: true,
69
+ selfQueryModel: "claude-haiku-4-5-20251001",
70
+ rerankerEnabled: true,
71
+ scalingRoadmap: {
72
+ rerankerAutoTrigger: {
73
+ enabled: true,
74
+ broadQueryMinCardinality: 5e4,
75
+ fetchTopK: 150,
76
+ returnTopK: 5
77
+ }
78
+ },
79
+ graphRagEnabled: true,
80
+ wikiEnabled: false,
81
+ wikiUrl: "",
82
+ wikiApiKey: "",
83
+ wikiSyncIntervalMs: 30 * 60 * 1e3,
84
+ wikiWorkspaceMapping: {},
85
+ wikiAutoUpdate: true,
86
+ wikiAutoUpdateThreshold: 0.5,
87
+ wikiAutoUpdateCreateNew: true,
88
+ skillLearning: true,
89
+ skillThreshold: 3,
90
+ skillModel: "claude-haiku-4-5-20251001",
91
+ exeHeartbeat: {
92
+ enabled: true,
93
+ intervalSeconds: 60,
94
+ staleInProgressThresholdHours: 2
95
+ },
96
+ sessionLifecycle: {
97
+ idleKillEnabled: true,
98
+ idleKillTicksRequired: 3,
99
+ idleKillIntercomAckWindowMs: 1e4,
100
+ maxAutoInstances: 10
101
+ },
102
+ autoUpdate: {
103
+ checkOnBoot: true,
104
+ autoInstall: false,
105
+ checkIntervalMs: 24 * 60 * 60 * 1e3
106
+ },
107
+ support: {
108
+ bugReportEndpoint: "https://askexe.com/v1/support/bug-reports"
109
+ },
110
+ orchestration: {
111
+ phase: "phase_1_coo",
112
+ phaseSetBy: "default"
113
+ }
114
+ };
115
+
116
+ // src/lib/background-jobs.ts
117
+ var JOB_DIR = path2.join(EXE_AI_DIR, "jobs");
118
+ var JOBS_FILE = path2.join(JOB_DIR, "jobs.json");
119
+ var LOCK_DIR = path2.join(JOB_DIR, "locks");
120
+ var DEFAULT_LOCK_TTL_MS = 6 * 60 * 60 * 1e3;
121
+ var MAX_HISTORY = 200;
122
+ function ensureDirs() {
123
+ mkdirSync2(LOCK_DIR, { recursive: true });
124
+ }
125
+ function now() {
126
+ return (/* @__PURE__ */ new Date()).toISOString();
127
+ }
128
+ function isAlive(pid) {
129
+ if (!pid || pid <= 0) return false;
130
+ try {
131
+ process.kill(pid, 0);
132
+ return true;
133
+ } catch {
134
+ return false;
135
+ }
136
+ }
137
+ function readJobsRaw() {
138
+ ensureDirs();
139
+ if (!existsSync3(JOBS_FILE)) return [];
140
+ try {
141
+ const parsed = JSON.parse(readFileSync2(JOBS_FILE, "utf8"));
142
+ return Array.isArray(parsed) ? parsed : [];
143
+ } catch {
144
+ return [];
145
+ }
146
+ }
147
+ function writeJobsRaw(jobs) {
148
+ ensureDirs();
149
+ const running = jobs.filter((j) => j.status === "running");
150
+ const rest = jobs.filter((j) => j.status !== "running").slice(-MAX_HISTORY);
151
+ writeFileSync(JOBS_FILE, JSON.stringify([...rest, ...running], null, 2) + "\n");
152
+ }
153
+ function lockPath(type) {
154
+ return path2.join(LOCK_DIR, `${type.replace(/[^a-zA-Z0-9_.-]/g, "_")}.lock`);
155
+ }
156
+ function acquireJobLock(type, ttlMs = DEFAULT_LOCK_TTL_MS) {
157
+ ensureDirs();
158
+ const file = lockPath(type);
159
+ if (existsSync3(file)) {
160
+ try {
161
+ const lock = JSON.parse(readFileSync2(file, "utf8"));
162
+ const age = Date.now() - Date.parse(lock.updatedAt ?? "");
163
+ if (lock.pid && isAlive(lock.pid) && Number.isFinite(age) && age < ttlMs) return false;
164
+ } catch {
165
+ }
166
+ try {
167
+ unlinkSync(file);
168
+ } catch {
169
+ }
170
+ }
171
+ try {
172
+ writeFileSync(file, JSON.stringify({ pid: process.pid, updatedAt: now() }, null, 2) + "\n", { flag: "wx" });
173
+ return true;
174
+ } catch {
175
+ return false;
176
+ }
177
+ }
178
+ function releaseJobLock(type) {
179
+ const file = lockPath(type);
180
+ try {
181
+ if (!existsSync3(file)) return;
182
+ const lock = JSON.parse(readFileSync2(file, "utf8"));
183
+ if (lock.pid === process.pid || !lock.pid || !isAlive(lock.pid)) unlinkSync(file);
184
+ } catch {
185
+ try {
186
+ unlinkSync(file);
187
+ } catch {
188
+ }
189
+ }
190
+ }
191
+ function startManagedJob(options) {
192
+ const lowPriority = options.lowPriority ?? true;
193
+ if (!acquireJobLock(options.type, options.lockTtlMs)) return null;
194
+ if (lowPriority) {
195
+ try {
196
+ os2.setPriority(process.pid, 10);
197
+ } catch {
198
+ }
199
+ }
200
+ const id = `${options.type}-${Date.now()}-${process.pid}`.replace(/[^a-zA-Z0-9_.-]/g, "_");
201
+ const record = {
202
+ id,
203
+ type: options.type,
204
+ name: options.name,
205
+ pid: process.pid,
206
+ command: options.command ?? process.argv.join(" "),
207
+ cwd: process.cwd(),
208
+ status: "running",
209
+ startedAt: now(),
210
+ updatedAt: now(),
211
+ lastHeartbeatAt: now(),
212
+ cancelCommand: `exe-os jobs cancel ${id}`,
213
+ lowPriority
214
+ };
215
+ const upsert = (patch) => {
216
+ const jobs = readJobsRaw().filter((j) => j.id !== id);
217
+ Object.assign(record, patch, { updatedAt: now() });
218
+ writeJobsRaw([...jobs, record]);
219
+ const file = lockPath(options.type);
220
+ try {
221
+ writeFileSync(file, JSON.stringify({ pid: process.pid, jobId: id, updatedAt: record.updatedAt }, null, 2) + "\n");
222
+ } catch {
223
+ }
224
+ };
225
+ upsert({});
226
+ const timer = setInterval(() => upsert({ lastHeartbeatAt: now() }), 3e4);
227
+ timer.unref?.();
228
+ const cleanup = (status, error) => {
229
+ clearInterval(timer);
230
+ upsert({ status, error, lastHeartbeatAt: now() });
231
+ releaseJobLock(options.type);
232
+ };
233
+ process.once("SIGTERM", () => {
234
+ cleanup("cancelled");
235
+ process.exit(0);
236
+ });
237
+ process.once("SIGINT", () => {
238
+ cleanup("cancelled");
239
+ process.exit(130);
240
+ });
241
+ process.once("exit", () => releaseJobLock(options.type));
242
+ return {
243
+ id,
244
+ update(progress) {
245
+ upsert({ progressCurrent: progress.current, progressTotal: progress.total, progressLabel: progress.label, lastHeartbeatAt: now() });
246
+ },
247
+ complete() {
248
+ cleanup("completed");
249
+ },
250
+ fail(err) {
251
+ cleanup("failed", err instanceof Error ? err.message : String(err));
252
+ },
253
+ cancel() {
254
+ cleanup("cancelled");
255
+ }
256
+ };
257
+ }
258
+ async function politeBatchPause(ms = 250) {
259
+ await new Promise((resolve) => setTimeout(resolve, ms));
260
+ }
261
+
262
+ // src/bin/age-ontology-load.ts
263
+ function q(value) {
264
+ return `'${String(value ?? "").replace(/\u0000/g, "").slice(0, 500).replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
265
+ }
266
+ function sqlString(value) {
267
+ return `'${value.replace(/'/g, "''")}'`;
268
+ }
269
+ async function main() {
270
+ const job = startManagedJob({ type: "age-ontology-load", name: "Apache AGE ontology loader", lowPriority: true });
271
+ if (!job) {
272
+ process.stderr.write("[age-ontology-load] Another AGE ontology load is already running.\n");
273
+ return;
274
+ }
275
+ const sourceUrl = process.env.DATABASE_URL || process.env.EXED_DATABASE_URL;
276
+ const ageUrl = process.env.AGE_DATABASE_URL;
277
+ if (!sourceUrl) throw new Error("DATABASE_URL or EXED_DATABASE_URL is required for canonical source");
278
+ if (!ageUrl) throw new Error("AGE_DATABASE_URL is required for Apache AGE target");
279
+ const graph = process.env.AGE_GRAPH_NAME || "exe_ontology";
280
+ const limit = Number(process.argv[process.argv.indexOf("--limit") + 1] || "1000");
281
+ const source = new Client({ connectionString: sourceUrl, ...pgSslConfig() });
282
+ const age = new Client({ connectionString: ageUrl, ...pgSslConfig() });
283
+ await source.connect();
284
+ await age.connect();
285
+ try {
286
+ await age.query(`CREATE EXTENSION IF NOT EXISTS age`);
287
+ await age.query(`LOAD 'age'`);
288
+ await age.query(`SET search_path = ag_catalog, "$user", public`);
289
+ if (process.argv.includes("--reset")) {
290
+ try {
291
+ await age.query(`SELECT drop_graph(${sqlString(graph)}, true)`);
292
+ } catch {
293
+ }
294
+ }
295
+ try {
296
+ await age.query(`SELECT create_graph(${sqlString(graph)})`);
297
+ } catch {
298
+ }
299
+ const entities = await source.query(`SELECT id, name, type FROM memory.entities ORDER BY last_seen DESC NULLS LAST, id LIMIT $1`, [limit]);
300
+ let nodeCount = 0;
301
+ for (const entity of entities.rows) {
302
+ const cypher = `CREATE (:Entity {id: ${q(entity.id)}, name: ${q(entity.name)}, type: ${q(entity.type)}})`;
303
+ await age.query(`SELECT * FROM cypher(${sqlString(graph)}, $$ ${cypher} $$) AS (v agtype)`);
304
+ nodeCount++;
305
+ if (nodeCount % 250 === 0) {
306
+ job.update({ current: nodeCount, total: limit, label: `Loaded ${nodeCount} AGE nodes` });
307
+ await politeBatchPause(250);
308
+ }
309
+ }
310
+ const relationships = await source.query(`SELECT source_entity_id, target_entity_id, type, confidence FROM memory.relationships ORDER BY id LIMIT $1`, [limit * 2]);
311
+ let edgeCount = 0;
312
+ for (const rel of relationships.rows) {
313
+ const cypher = `MATCH (a:Entity {id: ${q(rel.source_entity_id)}}), (b:Entity {id: ${q(rel.target_entity_id)}}) CREATE (a)-[:RELATED {type: ${q(rel.type)}}]->(b)`;
314
+ await age.query(`SELECT * FROM cypher(${sqlString(graph)}, $$ ${cypher} $$) AS (e agtype)`);
315
+ edgeCount++;
316
+ if (edgeCount % 500 === 0) {
317
+ job.update({ current: nodeCount + edgeCount, total: limit * 3, label: `Loaded ${nodeCount} nodes, ${edgeCount} edges` });
318
+ await politeBatchPause(250);
319
+ }
320
+ }
321
+ process.stderr.write(`[age-ontology-load] Loaded ${nodeCount} nodes and ${edgeCount} edges into ${graph}.
322
+ `);
323
+ job.complete();
324
+ } finally {
325
+ await age.end();
326
+ await source.end();
327
+ }
328
+ }
329
+ main().catch((err) => {
330
+ process.stderr.write(`[age-ontology-load] FATAL: ${err instanceof Error ? err.message : String(err)}
331
+ `);
332
+ process.exit(1);
333
+ });