@askexenow/exe-os 0.9.120 → 0.9.121
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.
- package/dist/assets/ghostty.conf +83 -0
- package/dist/assets/statusline-command.sh +44 -0
- package/dist/assets/tmux.conf +53 -0
- package/dist/bin/age-ontology-load.js +333 -0
- package/dist/bin/agentic-ontology-backfill.js +5357 -0
- package/dist/bin/agentic-reflection-backfill.js +4763 -0
- package/dist/bin/agentic-semantic-label.js +4890 -0
- package/dist/bin/backfill-conversations.js +5904 -0
- package/dist/bin/backfill-responses.js +5732 -0
- package/dist/bin/backfill-vectors.js +4904 -0
- package/dist/bin/bulk-sync-postgres.js +5382 -0
- package/dist/bin/cc-doctor.js +643 -0
- package/dist/bin/cleanup-stale-review-tasks.js +6910 -0
- package/dist/bin/cli.js +39722 -0
- package/dist/bin/customer-readiness.js +267 -0
- package/dist/bin/exe-agent-config.js +323 -0
- package/dist/bin/exe-agent.js +2556 -0
- package/dist/bin/exe-assign.js +5870 -0
- package/dist/bin/exe-boot.js +13663 -0
- package/dist/bin/exe-call.js +1492 -0
- package/dist/bin/exe-cloud.js +8930 -0
- package/dist/bin/exe-dispatch.js +10859 -0
- package/dist/bin/exe-doctor.js +7559 -0
- package/dist/bin/exe-export-behaviors.js +6150 -0
- package/dist/bin/exe-forget.js +6456 -0
- package/dist/bin/exe-gateway.js +16218 -0
- package/dist/bin/exe-healthcheck.js +607 -0
- package/dist/bin/exe-heartbeat.js +7079 -0
- package/dist/bin/exe-kill.js +6036 -0
- package/dist/bin/exe-launch-agent.js +7177 -0
- package/dist/bin/exe-new-employee.js +3902 -0
- package/dist/bin/exe-pending-messages.js +6861 -0
- package/dist/bin/exe-pending-notifications.js +6941 -0
- package/dist/bin/exe-pending-reviews.js +6947 -0
- package/dist/bin/exe-rename.js +5928 -0
- package/dist/bin/exe-repo-drift.js +95 -0
- package/dist/bin/exe-review.js +6119 -0
- package/dist/bin/exe-search.js +7735 -0
- package/dist/bin/exe-session-cleanup.js +11350 -0
- package/dist/bin/exe-settings.js +861 -0
- package/dist/bin/exe-start-codex.js +6919 -0
- package/dist/bin/exe-start-opencode.js +6765 -0
- package/dist/bin/exe-start.sh +170 -0
- package/dist/bin/exe-status.js +7086 -0
- package/dist/bin/exe-support.js +553 -0
- package/dist/bin/exe-team.js +6012 -0
- package/dist/bin/git-sweep.js +10953 -0
- package/dist/bin/graph-backfill.js +6032 -0
- package/dist/bin/graph-export.js +6163 -0
- package/dist/bin/graph-layer-benchmark.js +117 -0
- package/dist/bin/install.js +2489 -0
- package/dist/bin/intercom-check.js +11604 -0
- package/dist/bin/list-providers.js +142 -0
- package/dist/bin/postgres-agentic-reflection-backfill.js +466 -0
- package/dist/bin/postgres-agentic-semantic-backfill.js +516 -0
- package/dist/bin/pre-build-guard.js +98 -0
- package/dist/bin/pre-publish.js +488 -0
- package/dist/bin/registry-proxy.js +208 -0
- package/dist/bin/scan-tasks.js +11087 -0
- package/dist/bin/setup.js +9504 -0
- package/dist/bin/shard-migrate.js +5281 -0
- package/dist/bin/stack-update.js +1079 -0
- package/dist/bin/update.js +974 -0
- package/dist/gateway/index.js +16622 -0
- package/dist/hooks/bug-report-worker.js +10964 -0
- package/dist/hooks/codex-stop-task-finalizer.js +9274 -0
- package/dist/hooks/commit-complete.js +11008 -0
- package/dist/hooks/error-recall.js +8045 -0
- package/dist/hooks/exe-heartbeat-hook.js +381 -0
- package/dist/hooks/ingest-worker.js +595 -0
- package/dist/hooks/ingest.js +11788 -0
- package/dist/hooks/instructions-loaded.js +6297 -0
- package/dist/hooks/notification.js +6138 -0
- package/dist/hooks/post-compact.js +7136 -0
- package/dist/hooks/post-tool-combined.js +9869 -0
- package/dist/hooks/pre-compact.js +11241 -0
- package/dist/hooks/pre-tool-use.js +7482 -0
- package/dist/hooks/prompt-submit.js +13668 -0
- package/dist/hooks/session-end.js +11691 -0
- package/dist/hooks/session-start.js +9615 -0
- package/dist/hooks/stop.js +7451 -0
- package/dist/hooks/subagent-stop.js +6942 -0
- package/dist/hooks/summary-worker.js +8841 -0
- package/dist/index.js +19397 -0
- package/dist/lib/agent-config.js +237 -0
- package/dist/lib/cloud-sync.js +5433 -0
- package/dist/lib/cloudflare-dns.js +117 -0
- package/dist/lib/config.js +292 -0
- package/dist/lib/consolidation.js +1058 -0
- package/dist/lib/crypto.js +51 -0
- package/dist/lib/database.js +2945 -0
- package/dist/lib/db-daemon-client.js +587 -0
- package/dist/lib/db.js +2945 -0
- package/dist/lib/device-registry.js +3044 -0
- package/dist/lib/embedder.js +828 -0
- package/dist/lib/employee-templates.js +1115 -0
- package/dist/lib/employees.js +522 -0
- package/dist/lib/error-detector.js +156 -0
- package/dist/lib/exe-daemon-client.js +580 -0
- package/dist/lib/exe-daemon.js +20284 -0
- package/dist/lib/file-grep.js +215 -0
- package/dist/lib/hybrid-search.js +7682 -0
- package/dist/lib/identity-templates.js +537 -0
- package/dist/lib/identity.js +316 -0
- package/dist/lib/keychain.js +507 -0
- package/dist/lib/license.js +565 -0
- package/dist/lib/messaging.js +1647 -0
- package/dist/lib/post-tool-memory.js +373 -0
- package/dist/lib/registry-proxy.js +162 -0
- package/dist/lib/reminders.js +236 -0
- package/dist/lib/runtime-table.js +24 -0
- package/dist/lib/schedules.js +4620 -0
- package/dist/lib/session-registry.js +63 -0
- package/dist/lib/session-wrappers.js +131 -0
- package/dist/lib/skill-learning.js +2112 -0
- package/dist/lib/status-brief.js +371 -0
- package/dist/lib/store.js +5609 -0
- package/dist/lib/task-router.js +262 -0
- package/dist/lib/tasks.js +5823 -0
- package/dist/lib/tmux-routing.js +5827 -0
- package/dist/lib/tmux-status.js +261 -0
- package/dist/lib/tmux-transport.js +93 -0
- package/dist/lib/token-spend.js +340 -0
- package/dist/lib/transport.js +138 -0
- package/dist/lib/ws-auth.js +19 -0
- package/dist/lib/ws-client.js +189 -0
- package/dist/mcp/register-tools.js +32438 -0
- package/dist/mcp/server.js +32772 -0
- package/dist/mcp/tools/complete-reminder.js +240 -0
- package/dist/mcp/tools/create-reminder.js +225 -0
- package/dist/mcp/tools/create-task.js +6734 -0
- package/dist/mcp/tools/deactivate-behavior.js +479 -0
- package/dist/mcp/tools/list-reminders.js +235 -0
- package/dist/mcp/tools/list-tasks.js +1575 -0
- package/dist/mcp/tools/send-message.js +1676 -0
- package/dist/mcp/tools/update-task.js +6127 -0
- package/dist/runtime/index.js +13452 -0
- package/dist/tui/App.js +24635 -0
- package/package.json +1 -1
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
|
+
var __esm = (fn, res) => function __init() {
|
|
3
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
// src/lib/secure-files.ts
|
|
7
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
8
|
+
import { chmod, mkdir } from "fs/promises";
|
|
9
|
+
var init_secure_files = __esm({
|
|
10
|
+
"src/lib/secure-files.ts"() {
|
|
11
|
+
"use strict";
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// src/lib/config.ts
|
|
16
|
+
import { readFile, writeFile } from "fs/promises";
|
|
17
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
18
|
+
import path from "path";
|
|
19
|
+
import os from "os";
|
|
20
|
+
function resolveDataDir() {
|
|
21
|
+
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
22
|
+
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
23
|
+
const newDir = path.join(os.homedir(), ".exe-os");
|
|
24
|
+
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
25
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
26
|
+
try {
|
|
27
|
+
renameSync(legacyDir, newDir);
|
|
28
|
+
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
29
|
+
`);
|
|
30
|
+
} catch {
|
|
31
|
+
return legacyDir;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return newDir;
|
|
35
|
+
}
|
|
36
|
+
var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG;
|
|
37
|
+
var init_config = __esm({
|
|
38
|
+
"src/lib/config.ts"() {
|
|
39
|
+
"use strict";
|
|
40
|
+
init_secure_files();
|
|
41
|
+
EXE_AI_DIR = resolveDataDir();
|
|
42
|
+
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
43
|
+
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
44
|
+
CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
|
|
45
|
+
LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
|
|
46
|
+
CURRENT_CONFIG_VERSION = 1;
|
|
47
|
+
DEFAULT_CONFIG = {
|
|
48
|
+
config_version: CURRENT_CONFIG_VERSION,
|
|
49
|
+
dbPath: DB_PATH,
|
|
50
|
+
modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
|
|
51
|
+
embeddingDim: 1024,
|
|
52
|
+
batchSize: 20,
|
|
53
|
+
flushIntervalMs: 1e4,
|
|
54
|
+
autoIngestion: true,
|
|
55
|
+
autoRetrieval: true,
|
|
56
|
+
searchMode: "hybrid",
|
|
57
|
+
hookSearchMode: "hybrid",
|
|
58
|
+
fileGrepEnabled: true,
|
|
59
|
+
splashEffect: true,
|
|
60
|
+
consolidationEnabled: true,
|
|
61
|
+
consolidationIntervalMs: 6 * 60 * 60 * 1e3,
|
|
62
|
+
consolidationModel: "claude-haiku-4-5-20251001",
|
|
63
|
+
consolidationMaxCallsPerRun: 20,
|
|
64
|
+
selfQueryRouter: true,
|
|
65
|
+
selfQueryModel: "claude-haiku-4-5-20251001",
|
|
66
|
+
rerankerEnabled: true,
|
|
67
|
+
scalingRoadmap: {
|
|
68
|
+
rerankerAutoTrigger: {
|
|
69
|
+
enabled: true,
|
|
70
|
+
broadQueryMinCardinality: 5e4,
|
|
71
|
+
fetchTopK: 150,
|
|
72
|
+
returnTopK: 5
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
graphRagEnabled: true,
|
|
76
|
+
wikiEnabled: false,
|
|
77
|
+
wikiUrl: "",
|
|
78
|
+
wikiApiKey: "",
|
|
79
|
+
wikiSyncIntervalMs: 30 * 60 * 1e3,
|
|
80
|
+
wikiWorkspaceMapping: {},
|
|
81
|
+
wikiAutoUpdate: true,
|
|
82
|
+
wikiAutoUpdateThreshold: 0.5,
|
|
83
|
+
wikiAutoUpdateCreateNew: true,
|
|
84
|
+
skillLearning: true,
|
|
85
|
+
skillThreshold: 3,
|
|
86
|
+
skillModel: "claude-haiku-4-5-20251001",
|
|
87
|
+
exeHeartbeat: {
|
|
88
|
+
enabled: true,
|
|
89
|
+
intervalSeconds: 60,
|
|
90
|
+
staleInProgressThresholdHours: 2
|
|
91
|
+
},
|
|
92
|
+
sessionLifecycle: {
|
|
93
|
+
idleKillEnabled: true,
|
|
94
|
+
idleKillTicksRequired: 3,
|
|
95
|
+
idleKillIntercomAckWindowMs: 1e4,
|
|
96
|
+
maxAutoInstances: 10
|
|
97
|
+
},
|
|
98
|
+
autoUpdate: {
|
|
99
|
+
checkOnBoot: true,
|
|
100
|
+
autoInstall: false,
|
|
101
|
+
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
102
|
+
},
|
|
103
|
+
support: {
|
|
104
|
+
bugReportEndpoint: "https://askexe.com/v1/support/bug-reports"
|
|
105
|
+
},
|
|
106
|
+
orchestration: {
|
|
107
|
+
phase: "phase_1_coo",
|
|
108
|
+
phaseSetBy: "default"
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// src/lib/employees.ts
|
|
115
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
116
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
117
|
+
import { execSync } from "child_process";
|
|
118
|
+
import path2 from "path";
|
|
119
|
+
import os2 from "os";
|
|
120
|
+
function normalizeRole(role) {
|
|
121
|
+
return (role ?? "").trim().toLowerCase();
|
|
122
|
+
}
|
|
123
|
+
function isCoordinatorRole(role) {
|
|
124
|
+
return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
|
|
125
|
+
}
|
|
126
|
+
function getCoordinatorEmployee(employees) {
|
|
127
|
+
return employees.find((e) => isCoordinatorRole(e.role));
|
|
128
|
+
}
|
|
129
|
+
function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
130
|
+
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
131
|
+
}
|
|
132
|
+
function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
133
|
+
if (!agentName) return false;
|
|
134
|
+
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
135
|
+
}
|
|
136
|
+
function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
|
|
137
|
+
return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
|
|
138
|
+
}
|
|
139
|
+
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
140
|
+
if (!existsSync3(employeesPath)) return [];
|
|
141
|
+
try {
|
|
142
|
+
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
143
|
+
} catch {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function getEmployee(employees, name) {
|
|
148
|
+
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
149
|
+
}
|
|
150
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
|
|
151
|
+
var init_employees = __esm({
|
|
152
|
+
"src/lib/employees.ts"() {
|
|
153
|
+
"use strict";
|
|
154
|
+
init_config();
|
|
155
|
+
EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
156
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
157
|
+
COORDINATOR_ROLE = "COO";
|
|
158
|
+
IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// src/types/memory.ts
|
|
163
|
+
var init_memory = __esm({
|
|
164
|
+
"src/types/memory.ts"() {
|
|
165
|
+
"use strict";
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// src/mcp/tools/deactivate-behavior.ts
|
|
170
|
+
import { z } from "zod";
|
|
171
|
+
|
|
172
|
+
// src/lib/behaviors.ts
|
|
173
|
+
import crypto from "crypto";
|
|
174
|
+
|
|
175
|
+
// src/lib/database.ts
|
|
176
|
+
import { chmodSync as chmodSync2, existsSync as existsSync4, statSync, copyFileSync, unlinkSync as unlinkSync2, openSync, closeSync, mkdirSync as mkdirSync2 } from "fs";
|
|
177
|
+
import { createClient } from "@libsql/client";
|
|
178
|
+
import { homedir } from "os";
|
|
179
|
+
import { join } from "path";
|
|
180
|
+
init_employees();
|
|
181
|
+
|
|
182
|
+
// src/lib/database-adapter.ts
|
|
183
|
+
import os3 from "os";
|
|
184
|
+
import path3 from "path";
|
|
185
|
+
import { createRequire } from "module";
|
|
186
|
+
import { pathToFileURL } from "url";
|
|
187
|
+
var BOOLEAN_COLUMNS_BY_TABLE = {
|
|
188
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
189
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
190
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
191
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
192
|
+
};
|
|
193
|
+
var BOOLEAN_COLUMN_NAMES = new Set(
|
|
194
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
// src/lib/database.ts
|
|
198
|
+
init_memory();
|
|
199
|
+
var _debugDb = process.env.EXE_DEBUG === "1";
|
|
200
|
+
var _resilientClient = null;
|
|
201
|
+
var _daemonClient = null;
|
|
202
|
+
var _adapterClient = null;
|
|
203
|
+
var DB_LOCK_PATH = join(homedir(), ".exe-os", "db.lock");
|
|
204
|
+
function getClient() {
|
|
205
|
+
if (!_adapterClient) {
|
|
206
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
207
|
+
}
|
|
208
|
+
if (process.env.DATABASE_URL && process.env.EXE_USE_POSTGRES === "1") {
|
|
209
|
+
return _adapterClient;
|
|
210
|
+
}
|
|
211
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
212
|
+
return _resilientClient;
|
|
213
|
+
}
|
|
214
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
215
|
+
return _daemonClient;
|
|
216
|
+
}
|
|
217
|
+
if (!_resilientClient) {
|
|
218
|
+
return _adapterClient;
|
|
219
|
+
}
|
|
220
|
+
if (process.env.EXE_DB_READONLY === "1" || process.env.EXE_DB_DIRECT === "1") {
|
|
221
|
+
return _resilientClient;
|
|
222
|
+
}
|
|
223
|
+
if (!process.env.EXE_MCP_MODE) {
|
|
224
|
+
process.stderr.write(
|
|
225
|
+
"[database] WARN: Daemon unavailable \u2014 using direct SQLite (single-writer CLI mode).\n"
|
|
226
|
+
);
|
|
227
|
+
return _resilientClient;
|
|
228
|
+
}
|
|
229
|
+
process.stderr.write(
|
|
230
|
+
"[database] ERROR: Daemon is not running \u2014 refusing direct SQLite write access.\n[database] Direct writes from MCP processes bypass the single-writer gate and corrupt FTS5.\n[database] Restart the daemon: kill $(cat ~/.exe-os/exed.pid) && exe-os update\n"
|
|
231
|
+
);
|
|
232
|
+
throw new Error(
|
|
233
|
+
"Daemon not running. Direct SQLite writes from MCP processes are blocked to prevent FTS5 corruption. Restart daemon to restore service."
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/lib/behaviors.ts
|
|
238
|
+
async function deactivateBehavior(id) {
|
|
239
|
+
const client = getClient();
|
|
240
|
+
const result = await client.execute({
|
|
241
|
+
sql: `UPDATE behaviors SET active = 0, updated_at = ? WHERE id = ? AND active = 1`,
|
|
242
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), id]
|
|
243
|
+
});
|
|
244
|
+
return (result.rowsAffected ?? 0) > 0;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/lib/active-agent.ts
|
|
248
|
+
init_config();
|
|
249
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, unlinkSync as unlinkSync3, readdirSync } from "fs";
|
|
250
|
+
import { execSync as execSync3 } from "child_process";
|
|
251
|
+
import path4 from "path";
|
|
252
|
+
|
|
253
|
+
// src/lib/session-key.ts
|
|
254
|
+
import { execSync as execSync2 } from "child_process";
|
|
255
|
+
var _cached = null;
|
|
256
|
+
var _cachedRuntime = null;
|
|
257
|
+
var RUNTIME_COMMANDS = {
|
|
258
|
+
claude: ["claude", "claude.exe", "claude-native"],
|
|
259
|
+
codex: ["codex"],
|
|
260
|
+
opencode: ["opencode"]
|
|
261
|
+
};
|
|
262
|
+
function normalizeCommand(command) {
|
|
263
|
+
const trimmed = command.trim().toLowerCase();
|
|
264
|
+
const parts = trimmed.split(/[\\/]/);
|
|
265
|
+
return parts[parts.length - 1] ?? trimmed;
|
|
266
|
+
}
|
|
267
|
+
function detectRuntimeFromCommand(command) {
|
|
268
|
+
const normalized = normalizeCommand(command);
|
|
269
|
+
for (const [runtime, commands] of Object.entries(RUNTIME_COMMANDS)) {
|
|
270
|
+
if (commands.includes(normalized)) {
|
|
271
|
+
return runtime;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
function resolveRuntimeProcess() {
|
|
277
|
+
let pid = process.ppid;
|
|
278
|
+
for (let i = 0; i < 10; i++) {
|
|
279
|
+
try {
|
|
280
|
+
const info = execSync2(`ps -p ${pid} -o ppid=,comm=`, {
|
|
281
|
+
encoding: "utf8",
|
|
282
|
+
timeout: 2e3
|
|
283
|
+
}).trim();
|
|
284
|
+
const match = info.match(/^\s*(\d+)\s+(.+)$/);
|
|
285
|
+
if (!match) break;
|
|
286
|
+
const [, ppid, cmd] = match;
|
|
287
|
+
const runtime = detectRuntimeFromCommand(cmd ?? "");
|
|
288
|
+
if (runtime) {
|
|
289
|
+
return { pid: String(pid), runtime };
|
|
290
|
+
}
|
|
291
|
+
pid = parseInt(ppid, 10);
|
|
292
|
+
if (pid <= 1) break;
|
|
293
|
+
} catch {
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
function getSessionKey() {
|
|
300
|
+
if (_cached) return _cached;
|
|
301
|
+
if (process.env.EXE_SESSION_KEY) {
|
|
302
|
+
_cached = process.env.EXE_SESSION_KEY;
|
|
303
|
+
return _cached;
|
|
304
|
+
}
|
|
305
|
+
const resolved = resolveRuntimeProcess();
|
|
306
|
+
if (resolved) {
|
|
307
|
+
_cachedRuntime = resolved.runtime;
|
|
308
|
+
_cached = resolved.pid;
|
|
309
|
+
return _cached;
|
|
310
|
+
}
|
|
311
|
+
_cached = process.env.CLAUDE_CODE_SSE_PORT ?? String(process.ppid);
|
|
312
|
+
return _cached;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// src/lib/agent-context.ts
|
|
316
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
317
|
+
var agentStore = new AsyncLocalStorage();
|
|
318
|
+
function getAgentContext() {
|
|
319
|
+
return agentStore.getStore();
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// src/lib/active-agent.ts
|
|
323
|
+
init_employees();
|
|
324
|
+
var CACHE_DIR = path4.join(EXE_AI_DIR, "session-cache");
|
|
325
|
+
var STALE_MS = 24 * 60 * 60 * 1e3;
|
|
326
|
+
function isNameWithOptionalInstance(candidate, baseName) {
|
|
327
|
+
if (candidate === baseName) return true;
|
|
328
|
+
if (!candidate.startsWith(baseName)) return false;
|
|
329
|
+
return /^\d+$/.test(candidate.slice(baseName.length));
|
|
330
|
+
}
|
|
331
|
+
function resolveEmployeeFromSessionPrefix(prefix, employees) {
|
|
332
|
+
const sorted = [...employees].sort((a, b) => b.name.length - a.name.length);
|
|
333
|
+
for (const employee of sorted) {
|
|
334
|
+
if (isNameWithOptionalInstance(prefix, employee.name)) {
|
|
335
|
+
return { agentId: employee.name, agentRole: employee.role };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
function resolveActiveAgentFromTmuxSession(sessionName) {
|
|
341
|
+
const employees = loadEmployeesSync();
|
|
342
|
+
const coordinator = getCoordinatorEmployee(employees);
|
|
343
|
+
const coordinatorName = coordinator?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
344
|
+
if (isNameWithOptionalInstance(sessionName, coordinatorName)) {
|
|
345
|
+
return {
|
|
346
|
+
agentId: coordinatorName,
|
|
347
|
+
agentRole: coordinator?.role ?? "COO"
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
if (isNameWithOptionalInstance(sessionName, DEFAULT_COORDINATOR_TEMPLATE_NAME)) {
|
|
351
|
+
return {
|
|
352
|
+
agentId: coordinator?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
353
|
+
agentRole: coordinator?.role ?? "COO"
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
if (sessionName.includes("-")) {
|
|
357
|
+
const prefix = sessionName.split("-")[0] ?? "";
|
|
358
|
+
const employee = resolveEmployeeFromSessionPrefix(prefix, employees);
|
|
359
|
+
if (employee) return employee;
|
|
360
|
+
const legacy = prefix.match(/^([a-zA-Z]+)\d*$/);
|
|
361
|
+
if (legacy?.[1] && legacy[1] !== DEFAULT_COORDINATOR_TEMPLATE_NAME) {
|
|
362
|
+
const emp = getEmployee(employees, legacy[1]);
|
|
363
|
+
return { agentId: emp?.name ?? legacy[1], agentRole: emp?.role ?? "employee" };
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
function getMarkerPath() {
|
|
369
|
+
return path4.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
|
|
370
|
+
}
|
|
371
|
+
function getActiveAgent() {
|
|
372
|
+
const httpCtx = getAgentContext();
|
|
373
|
+
if (httpCtx) return httpCtx;
|
|
374
|
+
try {
|
|
375
|
+
const markerPath = getMarkerPath();
|
|
376
|
+
const raw = readFileSync3(markerPath, "utf8");
|
|
377
|
+
const data = JSON.parse(raw);
|
|
378
|
+
if (data.agentId) {
|
|
379
|
+
if (data.startedAt) {
|
|
380
|
+
const age = Date.now() - new Date(data.startedAt).getTime();
|
|
381
|
+
if (age > STALE_MS) {
|
|
382
|
+
try {
|
|
383
|
+
unlinkSync3(markerPath);
|
|
384
|
+
} catch {
|
|
385
|
+
}
|
|
386
|
+
} else {
|
|
387
|
+
return {
|
|
388
|
+
agentId: data.agentId,
|
|
389
|
+
agentRole: data.agentRole || "employee"
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
} else {
|
|
393
|
+
return {
|
|
394
|
+
agentId: data.agentId,
|
|
395
|
+
agentRole: data.agentRole || "employee"
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
} catch {
|
|
400
|
+
}
|
|
401
|
+
try {
|
|
402
|
+
const sessionName = execSync3(
|
|
403
|
+
"tmux display-message -p '#{session_name}' 2>/dev/null",
|
|
404
|
+
{ encoding: "utf8", timeout: 2e3 }
|
|
405
|
+
).trim();
|
|
406
|
+
const resolved = resolveActiveAgentFromTmuxSession(sessionName);
|
|
407
|
+
if (resolved) return resolved;
|
|
408
|
+
} catch {
|
|
409
|
+
}
|
|
410
|
+
return {
|
|
411
|
+
agentId: process.env.AGENT_ID || "default",
|
|
412
|
+
agentRole: process.env.AGENT_ROLE || "employee"
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// src/mcp/tools/deactivate-behavior.ts
|
|
417
|
+
init_employees();
|
|
418
|
+
function registerDeactivateBehavior(server) {
|
|
419
|
+
server.registerTool(
|
|
420
|
+
"deactivate_behavior",
|
|
421
|
+
{
|
|
422
|
+
title: "Deactivate Behavior",
|
|
423
|
+
description: "Soft-delete a behavior by setting active = 0. RESTRICTED: only coordinator or founder sessions can use this. Use list_behaviors to find the behavior ID first.",
|
|
424
|
+
inputSchema: {
|
|
425
|
+
behavior_id: z.string().describe("UUID of the behavior to deactivate")
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
async ({ behavior_id }) => {
|
|
429
|
+
const caller = getActiveAgent();
|
|
430
|
+
const allowed = canCoordinate(caller.agentId, caller.agentRole);
|
|
431
|
+
if (!allowed) {
|
|
432
|
+
return {
|
|
433
|
+
content: [{
|
|
434
|
+
type: "text",
|
|
435
|
+
text: `Permission denied. Only the coordinator or founder sessions can deactivate behaviors. You are "${caller.agentId}".`
|
|
436
|
+
}],
|
|
437
|
+
isError: true
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
const client = getClient();
|
|
441
|
+
const result = await client.execute({
|
|
442
|
+
sql: `SELECT id, agent_id, content, domain, priority FROM behaviors WHERE id = ?`,
|
|
443
|
+
args: [behavior_id]
|
|
444
|
+
});
|
|
445
|
+
if (result.rows.length === 0) {
|
|
446
|
+
return {
|
|
447
|
+
content: [{
|
|
448
|
+
type: "text",
|
|
449
|
+
text: `Behavior not found: ${behavior_id}`
|
|
450
|
+
}],
|
|
451
|
+
isError: true
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
const row = result.rows[0];
|
|
455
|
+
const wasActive = await deactivateBehavior(behavior_id);
|
|
456
|
+
if (!wasActive) {
|
|
457
|
+
return {
|
|
458
|
+
content: [{
|
|
459
|
+
type: "text",
|
|
460
|
+
text: `Behavior ${behavior_id} was already inactive.`
|
|
461
|
+
}]
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
return {
|
|
465
|
+
content: [{
|
|
466
|
+
type: "text",
|
|
467
|
+
text: `Deactivated behavior for ${row.agent_id}.
|
|
468
|
+
ID: ${row.id}
|
|
469
|
+
Domain: ${row.domain ?? "none"}
|
|
470
|
+
Priority: ${row.priority}
|
|
471
|
+
Content: ${row.content}`
|
|
472
|
+
}]
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
export {
|
|
478
|
+
registerDeactivateBehavior
|
|
479
|
+
};
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
|
+
var __esm = (fn, res) => function __init() {
|
|
3
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
// src/lib/secure-files.ts
|
|
7
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
8
|
+
import { chmod, mkdir } from "fs/promises";
|
|
9
|
+
var init_secure_files = __esm({
|
|
10
|
+
"src/lib/secure-files.ts"() {
|
|
11
|
+
"use strict";
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// src/lib/config.ts
|
|
16
|
+
import { readFile, writeFile } from "fs/promises";
|
|
17
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
18
|
+
import path from "path";
|
|
19
|
+
import os from "os";
|
|
20
|
+
function resolveDataDir() {
|
|
21
|
+
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
22
|
+
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
23
|
+
const newDir = path.join(os.homedir(), ".exe-os");
|
|
24
|
+
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
25
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
26
|
+
try {
|
|
27
|
+
renameSync(legacyDir, newDir);
|
|
28
|
+
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
29
|
+
`);
|
|
30
|
+
} catch {
|
|
31
|
+
return legacyDir;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return newDir;
|
|
35
|
+
}
|
|
36
|
+
var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG;
|
|
37
|
+
var init_config = __esm({
|
|
38
|
+
"src/lib/config.ts"() {
|
|
39
|
+
"use strict";
|
|
40
|
+
init_secure_files();
|
|
41
|
+
EXE_AI_DIR = resolveDataDir();
|
|
42
|
+
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
43
|
+
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
44
|
+
CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
|
|
45
|
+
LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
|
|
46
|
+
CURRENT_CONFIG_VERSION = 1;
|
|
47
|
+
DEFAULT_CONFIG = {
|
|
48
|
+
config_version: CURRENT_CONFIG_VERSION,
|
|
49
|
+
dbPath: DB_PATH,
|
|
50
|
+
modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
|
|
51
|
+
embeddingDim: 1024,
|
|
52
|
+
batchSize: 20,
|
|
53
|
+
flushIntervalMs: 1e4,
|
|
54
|
+
autoIngestion: true,
|
|
55
|
+
autoRetrieval: true,
|
|
56
|
+
searchMode: "hybrid",
|
|
57
|
+
hookSearchMode: "hybrid",
|
|
58
|
+
fileGrepEnabled: true,
|
|
59
|
+
splashEffect: true,
|
|
60
|
+
consolidationEnabled: true,
|
|
61
|
+
consolidationIntervalMs: 6 * 60 * 60 * 1e3,
|
|
62
|
+
consolidationModel: "claude-haiku-4-5-20251001",
|
|
63
|
+
consolidationMaxCallsPerRun: 20,
|
|
64
|
+
selfQueryRouter: true,
|
|
65
|
+
selfQueryModel: "claude-haiku-4-5-20251001",
|
|
66
|
+
rerankerEnabled: true,
|
|
67
|
+
scalingRoadmap: {
|
|
68
|
+
rerankerAutoTrigger: {
|
|
69
|
+
enabled: true,
|
|
70
|
+
broadQueryMinCardinality: 5e4,
|
|
71
|
+
fetchTopK: 150,
|
|
72
|
+
returnTopK: 5
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
graphRagEnabled: true,
|
|
76
|
+
wikiEnabled: false,
|
|
77
|
+
wikiUrl: "",
|
|
78
|
+
wikiApiKey: "",
|
|
79
|
+
wikiSyncIntervalMs: 30 * 60 * 1e3,
|
|
80
|
+
wikiWorkspaceMapping: {},
|
|
81
|
+
wikiAutoUpdate: true,
|
|
82
|
+
wikiAutoUpdateThreshold: 0.5,
|
|
83
|
+
wikiAutoUpdateCreateNew: true,
|
|
84
|
+
skillLearning: true,
|
|
85
|
+
skillThreshold: 3,
|
|
86
|
+
skillModel: "claude-haiku-4-5-20251001",
|
|
87
|
+
exeHeartbeat: {
|
|
88
|
+
enabled: true,
|
|
89
|
+
intervalSeconds: 60,
|
|
90
|
+
staleInProgressThresholdHours: 2
|
|
91
|
+
},
|
|
92
|
+
sessionLifecycle: {
|
|
93
|
+
idleKillEnabled: true,
|
|
94
|
+
idleKillTicksRequired: 3,
|
|
95
|
+
idleKillIntercomAckWindowMs: 1e4,
|
|
96
|
+
maxAutoInstances: 10
|
|
97
|
+
},
|
|
98
|
+
autoUpdate: {
|
|
99
|
+
checkOnBoot: true,
|
|
100
|
+
autoInstall: false,
|
|
101
|
+
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
102
|
+
},
|
|
103
|
+
support: {
|
|
104
|
+
bugReportEndpoint: "https://askexe.com/v1/support/bug-reports"
|
|
105
|
+
},
|
|
106
|
+
orchestration: {
|
|
107
|
+
phase: "phase_1_coo",
|
|
108
|
+
phaseSetBy: "default"
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// src/mcp/tools/list-reminders.ts
|
|
115
|
+
import { z } from "zod";
|
|
116
|
+
|
|
117
|
+
// src/lib/reminders.ts
|
|
118
|
+
import crypto from "crypto";
|
|
119
|
+
|
|
120
|
+
// src/lib/database.ts
|
|
121
|
+
import { chmodSync as chmodSync2, existsSync as existsSync4, statSync, copyFileSync, unlinkSync as unlinkSync2, openSync, closeSync, mkdirSync as mkdirSync2 } from "fs";
|
|
122
|
+
import { createClient } from "@libsql/client";
|
|
123
|
+
import { homedir } from "os";
|
|
124
|
+
import { join } from "path";
|
|
125
|
+
|
|
126
|
+
// src/lib/employees.ts
|
|
127
|
+
init_config();
|
|
128
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
129
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
130
|
+
import { execSync } from "child_process";
|
|
131
|
+
import path2 from "path";
|
|
132
|
+
import os2 from "os";
|
|
133
|
+
var EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
134
|
+
var IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
|
|
135
|
+
|
|
136
|
+
// src/lib/database-adapter.ts
|
|
137
|
+
import os3 from "os";
|
|
138
|
+
import path3 from "path";
|
|
139
|
+
import { createRequire } from "module";
|
|
140
|
+
import { pathToFileURL } from "url";
|
|
141
|
+
var BOOLEAN_COLUMNS_BY_TABLE = {
|
|
142
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
143
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
144
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
145
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
146
|
+
};
|
|
147
|
+
var BOOLEAN_COLUMN_NAMES = new Set(
|
|
148
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// src/lib/database.ts
|
|
152
|
+
var _debugDb = process.env.EXE_DEBUG === "1";
|
|
153
|
+
var _resilientClient = null;
|
|
154
|
+
var _daemonClient = null;
|
|
155
|
+
var _adapterClient = null;
|
|
156
|
+
var DB_LOCK_PATH = join(homedir(), ".exe-os", "db.lock");
|
|
157
|
+
function getClient() {
|
|
158
|
+
if (!_adapterClient) {
|
|
159
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
160
|
+
}
|
|
161
|
+
if (process.env.DATABASE_URL && process.env.EXE_USE_POSTGRES === "1") {
|
|
162
|
+
return _adapterClient;
|
|
163
|
+
}
|
|
164
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
165
|
+
return _resilientClient;
|
|
166
|
+
}
|
|
167
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
168
|
+
return _daemonClient;
|
|
169
|
+
}
|
|
170
|
+
if (!_resilientClient) {
|
|
171
|
+
return _adapterClient;
|
|
172
|
+
}
|
|
173
|
+
if (process.env.EXE_DB_READONLY === "1" || process.env.EXE_DB_DIRECT === "1") {
|
|
174
|
+
return _resilientClient;
|
|
175
|
+
}
|
|
176
|
+
if (!process.env.EXE_MCP_MODE) {
|
|
177
|
+
process.stderr.write(
|
|
178
|
+
"[database] WARN: Daemon unavailable \u2014 using direct SQLite (single-writer CLI mode).\n"
|
|
179
|
+
);
|
|
180
|
+
return _resilientClient;
|
|
181
|
+
}
|
|
182
|
+
process.stderr.write(
|
|
183
|
+
"[database] ERROR: Daemon is not running \u2014 refusing direct SQLite write access.\n[database] Direct writes from MCP processes bypass the single-writer gate and corrupt FTS5.\n[database] Restart the daemon: kill $(cat ~/.exe-os/exed.pid) && exe-os update\n"
|
|
184
|
+
);
|
|
185
|
+
throw new Error(
|
|
186
|
+
"Daemon not running. Direct SQLite writes from MCP processes are blocked to prevent FTS5 corruption. Restart daemon to restore service."
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/lib/reminders.ts
|
|
191
|
+
async function listReminders(includeCompleted = false) {
|
|
192
|
+
const client = getClient();
|
|
193
|
+
const sql = includeCompleted ? `SELECT id, text, created_at, due_date, completed_at FROM reminders ORDER BY due_date ASC NULLS LAST LIMIT 500` : `SELECT id, text, created_at, due_date, completed_at FROM reminders WHERE completed_at IS NULL ORDER BY due_date ASC NULLS LAST LIMIT 500`;
|
|
194
|
+
const result = await client.execute(sql);
|
|
195
|
+
return result.rows.map((row) => ({
|
|
196
|
+
id: String(row.id),
|
|
197
|
+
text: String(row.text),
|
|
198
|
+
createdAt: String(row.created_at),
|
|
199
|
+
dueDate: row.due_date ? String(row.due_date) : null,
|
|
200
|
+
completedAt: row.completed_at ? String(row.completed_at) : null
|
|
201
|
+
}));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// src/mcp/tools/list-reminders.ts
|
|
205
|
+
function registerListReminders(server) {
|
|
206
|
+
server.registerTool(
|
|
207
|
+
"list_reminders",
|
|
208
|
+
{
|
|
209
|
+
title: "List Reminders",
|
|
210
|
+
description: "List active reminders. Pass include_completed=true to see all.",
|
|
211
|
+
inputSchema: {
|
|
212
|
+
include_completed: z.boolean().optional().default(false).describe("Include completed reminders")
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
async ({ include_completed }) => {
|
|
216
|
+
const reminders = await listReminders(include_completed);
|
|
217
|
+
if (reminders.length === 0) {
|
|
218
|
+
return {
|
|
219
|
+
content: [{ type: "text", text: "No active reminders." }]
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
const lines = reminders.map((r) => {
|
|
223
|
+
const due = r.dueDate ? ` (due: ${r.dueDate})` : "";
|
|
224
|
+
const done = r.completedAt ? " [DONE]" : "";
|
|
225
|
+
return `\u2022 ${r.text}${due}${done} [id: ${r.id.slice(0, 8)}]`;
|
|
226
|
+
});
|
|
227
|
+
return {
|
|
228
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
export {
|
|
234
|
+
registerListReminders
|
|
235
|
+
};
|