@askexenow/exe-os 0.9.119 → 0.9.120

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/package.json +1 -1
  2. package/dist/assets/ghostty.conf +0 -83
  3. package/dist/assets/statusline-command.sh +0 -44
  4. package/dist/assets/tmux.conf +0 -53
  5. package/dist/bin/age-ontology-load.js +0 -333
  6. package/dist/bin/agentic-ontology-backfill.js +0 -5357
  7. package/dist/bin/agentic-reflection-backfill.js +0 -4763
  8. package/dist/bin/agentic-semantic-label.js +0 -4890
  9. package/dist/bin/backfill-conversations.js +0 -5904
  10. package/dist/bin/backfill-responses.js +0 -5732
  11. package/dist/bin/backfill-vectors.js +0 -4904
  12. package/dist/bin/bulk-sync-postgres.js +0 -5382
  13. package/dist/bin/cc-doctor.js +0 -643
  14. package/dist/bin/cleanup-stale-review-tasks.js +0 -6910
  15. package/dist/bin/cli.js +0 -39718
  16. package/dist/bin/customer-readiness.js +0 -267
  17. package/dist/bin/exe-agent-config.js +0 -323
  18. package/dist/bin/exe-agent.js +0 -2556
  19. package/dist/bin/exe-assign.js +0 -5870
  20. package/dist/bin/exe-boot.js +0 -13660
  21. package/dist/bin/exe-call.js +0 -1492
  22. package/dist/bin/exe-cloud.js +0 -8930
  23. package/dist/bin/exe-dispatch.js +0 -10856
  24. package/dist/bin/exe-doctor.js +0 -7559
  25. package/dist/bin/exe-export-behaviors.js +0 -6150
  26. package/dist/bin/exe-forget.js +0 -6456
  27. package/dist/bin/exe-gateway.js +0 -16215
  28. package/dist/bin/exe-healthcheck.js +0 -607
  29. package/dist/bin/exe-heartbeat.js +0 -7079
  30. package/dist/bin/exe-kill.js +0 -6036
  31. package/dist/bin/exe-launch-agent.js +0 -7177
  32. package/dist/bin/exe-new-employee.js +0 -3902
  33. package/dist/bin/exe-pending-messages.js +0 -6861
  34. package/dist/bin/exe-pending-notifications.js +0 -6941
  35. package/dist/bin/exe-pending-reviews.js +0 -6947
  36. package/dist/bin/exe-rename.js +0 -5928
  37. package/dist/bin/exe-repo-drift.js +0 -95
  38. package/dist/bin/exe-review.js +0 -6119
  39. package/dist/bin/exe-search.js +0 -7735
  40. package/dist/bin/exe-session-cleanup.js +0 -11347
  41. package/dist/bin/exe-settings.js +0 -861
  42. package/dist/bin/exe-start-codex.js +0 -6919
  43. package/dist/bin/exe-start-opencode.js +0 -6765
  44. package/dist/bin/exe-start.sh +0 -170
  45. package/dist/bin/exe-status.js +0 -7086
  46. package/dist/bin/exe-support.js +0 -553
  47. package/dist/bin/exe-team.js +0 -6012
  48. package/dist/bin/git-sweep.js +0 -10950
  49. package/dist/bin/graph-backfill.js +0 -6032
  50. package/dist/bin/graph-export.js +0 -6163
  51. package/dist/bin/graph-layer-benchmark.js +0 -117
  52. package/dist/bin/install.js +0 -2489
  53. package/dist/bin/intercom-check.js +0 -11568
  54. package/dist/bin/list-providers.js +0 -142
  55. package/dist/bin/postgres-agentic-reflection-backfill.js +0 -466
  56. package/dist/bin/postgres-agentic-semantic-backfill.js +0 -516
  57. package/dist/bin/pre-build-guard.js +0 -98
  58. package/dist/bin/pre-publish.js +0 -488
  59. package/dist/bin/registry-proxy.js +0 -208
  60. package/dist/bin/scan-tasks.js +0 -11084
  61. package/dist/bin/setup.js +0 -9504
  62. package/dist/bin/shard-migrate.js +0 -5281
  63. package/dist/bin/stack-update.js +0 -1079
  64. package/dist/bin/update.js +0 -974
  65. package/dist/gateway/index.js +0 -16619
  66. package/dist/hooks/bug-report-worker.js +0 -10961
  67. package/dist/hooks/codex-stop-task-finalizer.js +0 -9271
  68. package/dist/hooks/commit-complete.js +0 -11005
  69. package/dist/hooks/error-recall.js +0 -8045
  70. package/dist/hooks/exe-heartbeat-hook.js +0 -381
  71. package/dist/hooks/ingest-worker.js +0 -595
  72. package/dist/hooks/ingest.js +0 -11785
  73. package/dist/hooks/instructions-loaded.js +0 -6297
  74. package/dist/hooks/notification.js +0 -6138
  75. package/dist/hooks/post-compact.js +0 -7136
  76. package/dist/hooks/post-tool-combined.js +0 -9869
  77. package/dist/hooks/pre-compact.js +0 -11238
  78. package/dist/hooks/pre-tool-use.js +0 -7482
  79. package/dist/hooks/prompt-submit.js +0 -13606
  80. package/dist/hooks/session-end.js +0 -11688
  81. package/dist/hooks/session-start.js +0 -9615
  82. package/dist/hooks/stop.js +0 -7451
  83. package/dist/hooks/subagent-stop.js +0 -6942
  84. package/dist/hooks/summary-worker.js +0 -8841
  85. package/dist/index.js +0 -19361
  86. package/dist/lib/agent-config.js +0 -237
  87. package/dist/lib/cloud-sync.js +0 -5433
  88. package/dist/lib/cloudflare-dns.js +0 -117
  89. package/dist/lib/config.js +0 -292
  90. package/dist/lib/consolidation.js +0 -1058
  91. package/dist/lib/crypto.js +0 -51
  92. package/dist/lib/database.js +0 -2945
  93. package/dist/lib/db-daemon-client.js +0 -587
  94. package/dist/lib/db.js +0 -2945
  95. package/dist/lib/device-registry.js +0 -3044
  96. package/dist/lib/embedder.js +0 -828
  97. package/dist/lib/employee-templates.js +0 -1115
  98. package/dist/lib/employees.js +0 -522
  99. package/dist/lib/error-detector.js +0 -156
  100. package/dist/lib/exe-daemon-client.js +0 -580
  101. package/dist/lib/exe-daemon.js +0 -20247
  102. package/dist/lib/file-grep.js +0 -215
  103. package/dist/lib/hybrid-search.js +0 -7682
  104. package/dist/lib/identity-templates.js +0 -537
  105. package/dist/lib/identity.js +0 -316
  106. package/dist/lib/keychain.js +0 -507
  107. package/dist/lib/license.js +0 -565
  108. package/dist/lib/messaging.js +0 -1647
  109. package/dist/lib/post-tool-memory.js +0 -373
  110. package/dist/lib/registry-proxy.js +0 -162
  111. package/dist/lib/reminders.js +0 -236
  112. package/dist/lib/runtime-table.js +0 -24
  113. package/dist/lib/schedules.js +0 -4620
  114. package/dist/lib/session-registry.js +0 -63
  115. package/dist/lib/session-wrappers.js +0 -131
  116. package/dist/lib/skill-learning.js +0 -2109
  117. package/dist/lib/status-brief.js +0 -371
  118. package/dist/lib/store.js +0 -5609
  119. package/dist/lib/task-router.js +0 -262
  120. package/dist/lib/tasks.js +0 -5820
  121. package/dist/lib/tmux-routing.js +0 -5824
  122. package/dist/lib/tmux-status.js +0 -261
  123. package/dist/lib/tmux-transport.js +0 -93
  124. package/dist/lib/token-spend.js +0 -340
  125. package/dist/lib/transport.js +0 -138
  126. package/dist/lib/ws-auth.js +0 -19
  127. package/dist/lib/ws-client.js +0 -189
  128. package/dist/mcp/register-tools.js +0 -32277
  129. package/dist/mcp/server.js +0 -32611
  130. package/dist/mcp/tools/complete-reminder.js +0 -240
  131. package/dist/mcp/tools/create-reminder.js +0 -225
  132. package/dist/mcp/tools/create-task.js +0 -6731
  133. package/dist/mcp/tools/deactivate-behavior.js +0 -479
  134. package/dist/mcp/tools/list-reminders.js +0 -235
  135. package/dist/mcp/tools/list-tasks.js +0 -1575
  136. package/dist/mcp/tools/send-message.js +0 -1676
  137. package/dist/mcp/tools/update-task.js +0 -6124
  138. package/dist/runtime/index.js +0 -13416
  139. package/dist/tui/App.js +0 -24632
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askexenow/exe-os",
3
- "version": "0.9.119",
3
+ "version": "0.9.120",
4
4
  "description": "AI employee operating system — persistent memory, task management, and multi-agent coordination for Claude Code.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",
@@ -1,83 +0,0 @@
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=
@@ -1,44 +0,0 @@
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
@@ -1,53 +0,0 @@
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
@@ -1,333 +0,0 @@
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
- });