@askexenow/exe-os 0.9.16 → 0.9.18
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/bin/backfill-conversations.js +1242 -909
- package/dist/bin/backfill-responses.js +1245 -912
- package/dist/bin/backfill-vectors.js +1244 -906
- package/dist/bin/cleanup-stale-review-tasks.js +1870 -417
- package/dist/bin/cli.js +217 -107
- package/dist/bin/exe-agent-config.js +2 -2
- package/dist/bin/exe-agent.js +62 -0
- package/dist/bin/exe-assign.js +346 -10
- package/dist/bin/exe-boot.js +387 -32
- package/dist/bin/exe-call.js +72 -2
- package/dist/bin/exe-cloud.js +8 -0
- package/dist/bin/exe-dispatch.js +1821 -225
- package/dist/bin/exe-doctor.js +720 -52
- package/dist/bin/exe-export-behaviors.js +1429 -148
- package/dist/bin/exe-forget.js +1408 -34
- package/dist/bin/exe-gateway.js +1629 -1295
- package/dist/bin/exe-heartbeat.js +1899 -448
- package/dist/bin/exe-kill.js +1624 -346
- package/dist/bin/exe-launch-agent.js +726 -90
- package/dist/bin/exe-link.js +27 -8
- package/dist/bin/exe-new-employee.js +75 -9
- package/dist/bin/exe-pending-messages.js +2769 -1316
- package/dist/bin/exe-pending-notifications.js +2829 -1376
- package/dist/bin/exe-pending-reviews.js +2847 -1392
- package/dist/bin/exe-rename.js +89 -8
- package/dist/bin/exe-review.js +1494 -312
- package/dist/bin/exe-search.js +1608 -1300
- package/dist/bin/exe-session-cleanup.js +194 -91
- package/dist/bin/exe-settings.js +10 -2
- package/dist/bin/exe-start-codex.js +769 -120
- package/dist/bin/exe-start-opencode.js +763 -108
- package/dist/bin/exe-status.js +1887 -434
- package/dist/bin/exe-team.js +1782 -324
- package/dist/bin/git-sweep.js +408 -33
- package/dist/bin/graph-backfill.js +681 -27
- package/dist/bin/graph-export.js +1419 -141
- package/dist/bin/install.js +4 -8
- package/dist/bin/intercom-check.js +8641 -0
- package/dist/bin/scan-tasks.js +553 -38
- package/dist/bin/setup.js +82 -10
- package/dist/bin/shard-migrate.js +682 -28
- package/dist/gateway/index.js +1629 -1295
- package/dist/hooks/bug-report-worker.js +1136 -183
- package/dist/hooks/codex-stop-task-finalizer.js +1060 -107
- package/dist/hooks/commit-complete.js +408 -33
- package/dist/hooks/error-recall.js +1608 -1300
- package/dist/hooks/ingest-worker.js +250 -7966
- package/dist/hooks/ingest.js +707 -119
- package/dist/hooks/instructions-loaded.js +383 -20
- package/dist/hooks/notification.js +383 -20
- package/dist/hooks/post-compact.js +384 -21
- package/dist/hooks/{response-ingest-worker.js → post-tool-combined.js} +4157 -1716
- package/dist/hooks/pre-compact.js +486 -45
- package/dist/hooks/pre-tool-use.js +385 -22
- package/dist/hooks/prompt-submit.js +291 -96
- package/dist/hooks/session-end.js +490 -48
- package/dist/hooks/session-start.js +236 -22
- package/dist/hooks/stop.js +192 -50
- package/dist/hooks/subagent-stop.js +95 -18
- package/dist/hooks/summary-worker.js +205 -361
- package/dist/index.js +221 -105
- package/dist/lib/agent-config.js +2 -2
- package/dist/lib/cloud-sync.js +27 -8
- package/dist/lib/consolidation.js +437 -41
- package/dist/lib/database.js +20 -8
- package/dist/lib/db-daemon-client.js +2 -2
- package/dist/lib/db.js +20 -8
- package/dist/lib/device-registry.js +27 -8
- package/dist/lib/employee-templates.js +62 -0
- package/dist/lib/employees.js +2 -2
- package/dist/lib/exe-daemon.js +6703 -6259
- package/dist/lib/hybrid-search.js +1608 -1300
- package/dist/lib/identity.js +1 -1
- package/dist/lib/messaging.js +11 -3
- package/dist/lib/reminders.js +1 -1
- package/dist/lib/schedules.js +718 -26
- package/dist/lib/session-registry.js +11 -0
- package/dist/lib/skill-learning.js +639 -9
- package/dist/lib/store.js +676 -27
- package/dist/lib/tasks.js +665 -27
- package/dist/lib/tmux-routing.js +732 -94
- package/dist/lib/token-spend.js +1 -1
- package/dist/mcp/server.js +856 -383
- package/dist/mcp/tools/complete-reminder.js +1 -1
- package/dist/mcp/tools/create-reminder.js +1 -1
- package/dist/mcp/tools/create-task.js +616 -46
- package/dist/mcp/tools/deactivate-behavior.js +9 -1
- package/dist/mcp/tools/list-reminders.js +1 -1
- package/dist/mcp/tools/list-tasks.js +10 -2
- package/dist/mcp/tools/send-message.js +11 -3
- package/dist/mcp/tools/update-task.js +677 -39
- package/dist/runtime/index.js +425 -37
- package/dist/tui/App.js +365 -34
- package/package.json +5 -2
- package/src/commands/exe/intercom.md +6 -17
- package/dist/bin/wiki-sync.js +0 -2991
- package/dist/hooks/prompt-ingest-worker.js +0 -3979
|
@@ -1,7 +1,19 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
1
2
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
3
|
var __esm = (fn, res) => function __init() {
|
|
3
4
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
4
5
|
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// src/types/memory.ts
|
|
12
|
+
var init_memory = __esm({
|
|
13
|
+
"src/types/memory.ts"() {
|
|
14
|
+
"use strict";
|
|
15
|
+
}
|
|
16
|
+
});
|
|
5
17
|
|
|
6
18
|
// src/lib/db-retry.ts
|
|
7
19
|
var init_db_retry = __esm({
|
|
@@ -13,9 +25,25 @@ var init_db_retry = __esm({
|
|
|
13
25
|
// src/lib/secure-files.ts
|
|
14
26
|
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
15
27
|
import { chmod, mkdir } from "fs/promises";
|
|
28
|
+
function ensurePrivateDirSync(dirPath) {
|
|
29
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
30
|
+
try {
|
|
31
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
32
|
+
} catch {
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function enforcePrivateFileSync(filePath) {
|
|
36
|
+
try {
|
|
37
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
16
42
|
var init_secure_files = __esm({
|
|
17
43
|
"src/lib/secure-files.ts"() {
|
|
18
44
|
"use strict";
|
|
45
|
+
PRIVATE_DIR_MODE = 448;
|
|
46
|
+
PRIVATE_FILE_MODE = 384;
|
|
19
47
|
}
|
|
20
48
|
});
|
|
21
49
|
|
|
@@ -111,11 +139,153 @@ var init_config = __esm({
|
|
|
111
139
|
}
|
|
112
140
|
});
|
|
113
141
|
|
|
142
|
+
// src/lib/runtime-table.ts
|
|
143
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
144
|
+
var init_runtime_table = __esm({
|
|
145
|
+
"src/lib/runtime-table.ts"() {
|
|
146
|
+
"use strict";
|
|
147
|
+
RUNTIME_TABLE = {
|
|
148
|
+
codex: {
|
|
149
|
+
binary: "codex",
|
|
150
|
+
launchMode: "interactive",
|
|
151
|
+
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
152
|
+
inlineFlag: "--no-alt-screen",
|
|
153
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
154
|
+
defaultModel: "gpt-5.4"
|
|
155
|
+
},
|
|
156
|
+
opencode: {
|
|
157
|
+
binary: "opencode",
|
|
158
|
+
launchMode: "exec",
|
|
159
|
+
autoApproveFlag: "--dangerously-skip-permissions",
|
|
160
|
+
inlineFlag: "",
|
|
161
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
162
|
+
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
DEFAULT_RUNTIME = "claude";
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// src/lib/agent-config.ts
|
|
170
|
+
var agent_config_exports = {};
|
|
171
|
+
__export(agent_config_exports, {
|
|
172
|
+
AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
|
|
173
|
+
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
174
|
+
KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
|
|
175
|
+
RUNTIME_LABELS: () => RUNTIME_LABELS,
|
|
176
|
+
clearAgentRuntime: () => clearAgentRuntime,
|
|
177
|
+
getAgentRuntime: () => getAgentRuntime,
|
|
178
|
+
loadAgentConfig: () => loadAgentConfig,
|
|
179
|
+
saveAgentConfig: () => saveAgentConfig,
|
|
180
|
+
setAgentRuntime: () => setAgentRuntime
|
|
181
|
+
});
|
|
182
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
183
|
+
import path2 from "path";
|
|
184
|
+
function loadAgentConfig() {
|
|
185
|
+
if (!existsSync3(AGENT_CONFIG_PATH)) return {};
|
|
186
|
+
try {
|
|
187
|
+
return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
|
|
188
|
+
} catch {
|
|
189
|
+
return {};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function saveAgentConfig(config) {
|
|
193
|
+
const dir = path2.dirname(AGENT_CONFIG_PATH);
|
|
194
|
+
ensurePrivateDirSync(dir);
|
|
195
|
+
writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
196
|
+
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
197
|
+
}
|
|
198
|
+
function getAgentRuntime(agentId) {
|
|
199
|
+
const config = loadAgentConfig();
|
|
200
|
+
const entry = config[agentId];
|
|
201
|
+
if (entry) return entry;
|
|
202
|
+
const orgDefault = config["default"];
|
|
203
|
+
if (orgDefault) return orgDefault;
|
|
204
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
205
|
+
}
|
|
206
|
+
function setAgentRuntime(agentId, runtime, model, reasoning_effort) {
|
|
207
|
+
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
208
|
+
if (!knownModels) {
|
|
209
|
+
return {
|
|
210
|
+
ok: false,
|
|
211
|
+
error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
if (!knownModels.includes(model)) {
|
|
215
|
+
return {
|
|
216
|
+
ok: false,
|
|
217
|
+
error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
const config = loadAgentConfig();
|
|
221
|
+
const entry = { runtime, model };
|
|
222
|
+
if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
|
|
223
|
+
config[agentId] = entry;
|
|
224
|
+
saveAgentConfig(config);
|
|
225
|
+
return { ok: true };
|
|
226
|
+
}
|
|
227
|
+
function clearAgentRuntime(agentId) {
|
|
228
|
+
const config = loadAgentConfig();
|
|
229
|
+
delete config[agentId];
|
|
230
|
+
saveAgentConfig(config);
|
|
231
|
+
}
|
|
232
|
+
var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
|
|
233
|
+
var init_agent_config = __esm({
|
|
234
|
+
"src/lib/agent-config.ts"() {
|
|
235
|
+
"use strict";
|
|
236
|
+
init_config();
|
|
237
|
+
init_runtime_table();
|
|
238
|
+
init_secure_files();
|
|
239
|
+
AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
|
|
240
|
+
KNOWN_RUNTIMES = {
|
|
241
|
+
claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
242
|
+
codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
|
|
243
|
+
opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
|
|
244
|
+
};
|
|
245
|
+
RUNTIME_LABELS = {
|
|
246
|
+
claude: "Claude Code (Anthropic)",
|
|
247
|
+
codex: "Codex (OpenAI)",
|
|
248
|
+
opencode: "OpenCode (open source)"
|
|
249
|
+
};
|
|
250
|
+
DEFAULT_MODELS = {
|
|
251
|
+
claude: "claude-opus-4.6",
|
|
252
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
253
|
+
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
114
258
|
// src/lib/employees.ts
|
|
259
|
+
var employees_exports = {};
|
|
260
|
+
__export(employees_exports, {
|
|
261
|
+
COORDINATOR_ROLE: () => COORDINATOR_ROLE,
|
|
262
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
263
|
+
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
264
|
+
addEmployee: () => addEmployee,
|
|
265
|
+
baseAgentName: () => baseAgentName,
|
|
266
|
+
canCoordinate: () => canCoordinate,
|
|
267
|
+
getCoordinatorEmployee: () => getCoordinatorEmployee,
|
|
268
|
+
getCoordinatorName: () => getCoordinatorName,
|
|
269
|
+
getEmployee: () => getEmployee,
|
|
270
|
+
getEmployeeByRole: () => getEmployeeByRole,
|
|
271
|
+
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
272
|
+
hasRole: () => hasRole,
|
|
273
|
+
hireEmployee: () => hireEmployee,
|
|
274
|
+
isCoordinatorName: () => isCoordinatorName,
|
|
275
|
+
isCoordinatorRole: () => isCoordinatorRole,
|
|
276
|
+
isMultiInstance: () => isMultiInstance,
|
|
277
|
+
loadEmployees: () => loadEmployees,
|
|
278
|
+
loadEmployeesSync: () => loadEmployeesSync,
|
|
279
|
+
normalizeRole: () => normalizeRole,
|
|
280
|
+
normalizeRosterCase: () => normalizeRosterCase,
|
|
281
|
+
registerBinSymlinks: () => registerBinSymlinks,
|
|
282
|
+
saveEmployees: () => saveEmployees,
|
|
283
|
+
validateEmployeeName: () => validateEmployeeName
|
|
284
|
+
});
|
|
115
285
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
116
|
-
import { existsSync as
|
|
286
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
117
287
|
import { execSync } from "child_process";
|
|
118
|
-
import
|
|
288
|
+
import path3 from "path";
|
|
119
289
|
import os2 from "os";
|
|
120
290
|
function normalizeRole(role) {
|
|
121
291
|
return (role ?? "").trim().toLowerCase();
|
|
@@ -133,29 +303,217 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
133
303
|
if (!agentName) return false;
|
|
134
304
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
135
305
|
}
|
|
306
|
+
function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
|
|
307
|
+
return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
|
|
308
|
+
}
|
|
309
|
+
function validateEmployeeName(name) {
|
|
310
|
+
if (!name) {
|
|
311
|
+
return { valid: false, error: "Name is required" };
|
|
312
|
+
}
|
|
313
|
+
if (name.length > 32) {
|
|
314
|
+
return { valid: false, error: "Name must be 32 characters or fewer" };
|
|
315
|
+
}
|
|
316
|
+
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
317
|
+
return {
|
|
318
|
+
valid: false,
|
|
319
|
+
error: "Name must start with a letter and contain only lowercase alphanumeric characters"
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
return { valid: true };
|
|
323
|
+
}
|
|
324
|
+
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
325
|
+
if (!existsSync4(employeesPath)) {
|
|
326
|
+
return [];
|
|
327
|
+
}
|
|
328
|
+
const raw = await readFile2(employeesPath, "utf-8");
|
|
329
|
+
try {
|
|
330
|
+
return JSON.parse(raw);
|
|
331
|
+
} catch {
|
|
332
|
+
return [];
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
336
|
+
await mkdir2(path3.dirname(employeesPath), { recursive: true });
|
|
337
|
+
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
338
|
+
}
|
|
136
339
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
137
|
-
if (!
|
|
340
|
+
if (!existsSync4(employeesPath)) return [];
|
|
138
341
|
try {
|
|
139
|
-
return JSON.parse(
|
|
342
|
+
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
140
343
|
} catch {
|
|
141
344
|
return [];
|
|
142
345
|
}
|
|
143
346
|
}
|
|
144
|
-
|
|
347
|
+
function getEmployee(employees, name) {
|
|
348
|
+
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
349
|
+
}
|
|
350
|
+
function getEmployeeByRole(employees, role) {
|
|
351
|
+
const lower = role.toLowerCase();
|
|
352
|
+
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
353
|
+
}
|
|
354
|
+
function getEmployeeNamesByRole(employees, role) {
|
|
355
|
+
const lower = role.toLowerCase();
|
|
356
|
+
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
357
|
+
}
|
|
358
|
+
function hasRole(agentName, role) {
|
|
359
|
+
const employees = loadEmployeesSync();
|
|
360
|
+
const emp = getEmployee(employees, agentName);
|
|
361
|
+
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
362
|
+
}
|
|
363
|
+
function baseAgentName(name, employees) {
|
|
364
|
+
const match = name.match(/^([a-zA-Z]+)\d+$/);
|
|
365
|
+
if (!match) return name;
|
|
366
|
+
const base = match[1];
|
|
367
|
+
const roster = employees ?? loadEmployeesSync();
|
|
368
|
+
if (getEmployee(roster, base)) return base;
|
|
369
|
+
return name;
|
|
370
|
+
}
|
|
371
|
+
function isMultiInstance(agentName, employees) {
|
|
372
|
+
const roster = employees ?? loadEmployeesSync();
|
|
373
|
+
const emp = getEmployee(roster, agentName);
|
|
374
|
+
if (!emp) return false;
|
|
375
|
+
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
376
|
+
}
|
|
377
|
+
function addEmployee(employees, employee) {
|
|
378
|
+
const normalized = { ...employee, name: employee.name.toLowerCase() };
|
|
379
|
+
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
380
|
+
throw new Error(`Employee '${normalized.name}' already exists`);
|
|
381
|
+
}
|
|
382
|
+
return [...employees, normalized];
|
|
383
|
+
}
|
|
384
|
+
function appendToCoordinatorTeam(employee) {
|
|
385
|
+
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
386
|
+
if (!coordinator) return;
|
|
387
|
+
const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
388
|
+
if (!existsSync4(idPath)) return;
|
|
389
|
+
const content = readFileSync3(idPath, "utf-8");
|
|
390
|
+
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
391
|
+
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
392
|
+
if (!teamMatch || teamMatch.index === void 0) return;
|
|
393
|
+
const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
|
|
394
|
+
const nextHeading = afterTeam.match(/\n## /);
|
|
395
|
+
const entry = `
|
|
396
|
+
**${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
|
|
397
|
+
`;
|
|
398
|
+
let updated;
|
|
399
|
+
if (nextHeading && nextHeading.index !== void 0) {
|
|
400
|
+
const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
|
|
401
|
+
updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
|
|
402
|
+
} else {
|
|
403
|
+
updated = content.trimEnd() + "\n" + entry;
|
|
404
|
+
}
|
|
405
|
+
writeFileSync2(idPath, updated, "utf-8");
|
|
406
|
+
}
|
|
407
|
+
function capitalize(s) {
|
|
408
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
409
|
+
}
|
|
410
|
+
async function hireEmployee(employee) {
|
|
411
|
+
const employees = await loadEmployees();
|
|
412
|
+
const updated = addEmployee(employees, employee);
|
|
413
|
+
await saveEmployees(updated);
|
|
414
|
+
try {
|
|
415
|
+
appendToCoordinatorTeam(employee);
|
|
416
|
+
} catch {
|
|
417
|
+
}
|
|
418
|
+
try {
|
|
419
|
+
const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
|
|
420
|
+
const config = loadAgentConfig2();
|
|
421
|
+
const name = employee.name.toLowerCase();
|
|
422
|
+
if (!config[name] && config["default"]) {
|
|
423
|
+
config[name] = { ...config["default"] };
|
|
424
|
+
saveAgentConfig2(config);
|
|
425
|
+
}
|
|
426
|
+
} catch {
|
|
427
|
+
}
|
|
428
|
+
return updated;
|
|
429
|
+
}
|
|
430
|
+
async function normalizeRosterCase(rosterPath) {
|
|
431
|
+
const employees = await loadEmployees(rosterPath);
|
|
432
|
+
let changed = false;
|
|
433
|
+
for (const emp of employees) {
|
|
434
|
+
if (emp.name !== emp.name.toLowerCase()) {
|
|
435
|
+
const oldName = emp.name;
|
|
436
|
+
emp.name = emp.name.toLowerCase();
|
|
437
|
+
changed = true;
|
|
438
|
+
try {
|
|
439
|
+
const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
|
|
440
|
+
const oldPath = path3.join(identityDir, `${oldName}.md`);
|
|
441
|
+
const newPath = path3.join(identityDir, `${emp.name}.md`);
|
|
442
|
+
if (existsSync4(oldPath) && !existsSync4(newPath)) {
|
|
443
|
+
renameSync2(oldPath, newPath);
|
|
444
|
+
} else if (existsSync4(oldPath) && oldPath !== newPath) {
|
|
445
|
+
const content = readFileSync3(oldPath, "utf-8");
|
|
446
|
+
writeFileSync2(newPath, content, "utf-8");
|
|
447
|
+
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
448
|
+
unlinkSync(oldPath);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
} catch {
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
if (changed) {
|
|
456
|
+
await saveEmployees(employees, rosterPath);
|
|
457
|
+
}
|
|
458
|
+
return changed;
|
|
459
|
+
}
|
|
460
|
+
function findExeBin() {
|
|
461
|
+
try {
|
|
462
|
+
return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
463
|
+
} catch {
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
function registerBinSymlinks(name) {
|
|
468
|
+
const created = [];
|
|
469
|
+
const skipped = [];
|
|
470
|
+
const errors = [];
|
|
471
|
+
const exeBinPath = findExeBin();
|
|
472
|
+
if (!exeBinPath) {
|
|
473
|
+
errors.push("Could not find 'exe-os' in PATH");
|
|
474
|
+
return { created, skipped, errors };
|
|
475
|
+
}
|
|
476
|
+
const binDir = path3.dirname(exeBinPath);
|
|
477
|
+
let target;
|
|
478
|
+
try {
|
|
479
|
+
target = readlinkSync(exeBinPath);
|
|
480
|
+
} catch {
|
|
481
|
+
errors.push("Could not read 'exe' symlink");
|
|
482
|
+
return { created, skipped, errors };
|
|
483
|
+
}
|
|
484
|
+
for (const suffix of ["", "-opencode"]) {
|
|
485
|
+
const linkName = `${name}${suffix}`;
|
|
486
|
+
const linkPath = path3.join(binDir, linkName);
|
|
487
|
+
if (existsSync4(linkPath)) {
|
|
488
|
+
skipped.push(linkName);
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
try {
|
|
492
|
+
symlinkSync(target, linkPath);
|
|
493
|
+
created.push(linkName);
|
|
494
|
+
} catch (err) {
|
|
495
|
+
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return { created, skipped, errors };
|
|
499
|
+
}
|
|
500
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
|
|
145
501
|
var init_employees = __esm({
|
|
146
502
|
"src/lib/employees.ts"() {
|
|
147
503
|
"use strict";
|
|
148
504
|
init_config();
|
|
149
|
-
EMPLOYEES_PATH =
|
|
505
|
+
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
150
506
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
151
507
|
COORDINATOR_ROLE = "COO";
|
|
152
|
-
|
|
508
|
+
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
509
|
+
IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
|
|
510
|
+
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
153
511
|
}
|
|
154
512
|
});
|
|
155
513
|
|
|
156
514
|
// src/lib/database-adapter.ts
|
|
157
515
|
import os3 from "os";
|
|
158
|
-
import
|
|
516
|
+
import path4 from "path";
|
|
159
517
|
import { createRequire } from "module";
|
|
160
518
|
import { pathToFileURL } from "url";
|
|
161
519
|
var BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES;
|
|
@@ -182,6 +540,7 @@ var init_database = __esm({
|
|
|
182
540
|
init_db_retry();
|
|
183
541
|
init_employees();
|
|
184
542
|
init_database_adapter();
|
|
543
|
+
init_memory();
|
|
185
544
|
}
|
|
186
545
|
});
|
|
187
546
|
|
|
@@ -189,13 +548,14 @@ var init_database = __esm({
|
|
|
189
548
|
import { randomUUID } from "crypto";
|
|
190
549
|
|
|
191
550
|
// src/lib/store.ts
|
|
192
|
-
|
|
551
|
+
init_memory();
|
|
193
552
|
init_database();
|
|
553
|
+
import { createHash } from "crypto";
|
|
194
554
|
|
|
195
555
|
// src/lib/keychain.ts
|
|
196
556
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
197
|
-
import { existsSync as
|
|
198
|
-
import
|
|
557
|
+
import { existsSync as existsSync5 } from "fs";
|
|
558
|
+
import path5 from "path";
|
|
199
559
|
import os4 from "os";
|
|
200
560
|
|
|
201
561
|
// src/lib/store.ts
|
|
@@ -263,7 +623,6 @@ async function selectUnconsolidated(client, limit = 200) {
|
|
|
263
623
|
sql: `SELECT id, agent_id, project_name, tool_name, raw_text, timestamp
|
|
264
624
|
FROM memories
|
|
265
625
|
WHERE consolidated = 0
|
|
266
|
-
AND timestamp >= datetime('now', '-7 days')
|
|
267
626
|
ORDER BY timestamp DESC
|
|
268
627
|
LIMIT ?`,
|
|
269
628
|
args: [limit]
|
|
@@ -318,11 +677,42 @@ function groupMemories(memories) {
|
|
|
318
677
|
}
|
|
319
678
|
return clusters;
|
|
320
679
|
}
|
|
321
|
-
|
|
680
|
+
var ROLE_PROMPTS = {
|
|
681
|
+
"COO": `Extract EXACTLY THREE types of insights:
|
|
682
|
+
1. KEY DECISIONS \u2014 choices that were made and why (at most 3)
|
|
683
|
+
2. RECURRING PATTERNS \u2014 actions or approaches that repeated (at most 3)
|
|
684
|
+
3. OPEN QUESTIONS \u2014 things that seem unresolved or risky (at most 2)`,
|
|
685
|
+
"CTO": `Extract EXACTLY THREE types of insights:
|
|
686
|
+
1. ARCHITECTURE DECISIONS \u2014 design choices, tradeoffs, delegation patterns (at most 3)
|
|
687
|
+
2. REVIEW OUTCOMES \u2014 what was approved, rejected, or flagged during code review (at most 3)
|
|
688
|
+
3. TECHNICAL DEBT \u2014 unresolved issues, deferred work, risks identified (at most 2)`,
|
|
689
|
+
"Principal Engineer": `Extract EXACTLY THREE types of insights:
|
|
690
|
+
1. CODE DECISIONS \u2014 implementation choices, patterns used, bugs fixed (at most 3)
|
|
691
|
+
2. FILE CHANGES \u2014 key files modified and why (at most 3)
|
|
692
|
+
3. BLOCKERS \u2014 things that slowed down or prevented progress (at most 2)`,
|
|
693
|
+
"Staff Code Reviewer": `Extract EXACTLY THREE types of insights:
|
|
694
|
+
1. AUDIT FINDINGS \u2014 issues found, severity, fix status (at most 3)
|
|
695
|
+
2. RECURRING ISSUES \u2014 patterns that keep appearing across reviews (at most 3)
|
|
696
|
+
3. QUALITY SIGNALS \u2014 areas of the codebase that are strong or weak (at most 2)`,
|
|
697
|
+
"CMO": `Extract EXACTLY THREE types of insights:
|
|
698
|
+
1. DESIGN DECISIONS \u2014 brand, layout, typography, or content choices made (at most 3)
|
|
699
|
+
2. CONTENT STRATEGY \u2014 messaging, SEO, platform-specific decisions (at most 3)
|
|
700
|
+
3. BRAND ALIGNMENT \u2014 consistency issues or guidelines established (at most 2)`,
|
|
701
|
+
"AI Product Lead": `Extract EXACTLY THREE types of insights:
|
|
702
|
+
1. RESEARCH FINDINGS \u2014 tools, repos, or models evaluated with conclusions (at most 3)
|
|
703
|
+
2. COMPETITIVE GAPS \u2014 features or patterns worth adopting from competitors (at most 3)
|
|
704
|
+
3. BUILD RECOMMENDATIONS \u2014 what to build, skip, or watch (at most 2)`
|
|
705
|
+
};
|
|
706
|
+
var DEFAULT_ROLE_PROMPT = `Extract EXACTLY THREE types of insights:
|
|
707
|
+
1. KEY DECISIONS \u2014 choices that were made and why (at most 3)
|
|
708
|
+
2. RECURRING PATTERNS \u2014 actions or approaches that repeated (at most 3)
|
|
709
|
+
3. OPEN QUESTIONS \u2014 things that seem unresolved or risky (at most 2)`;
|
|
710
|
+
function buildConsolidationPrompt(cluster, agentRole) {
|
|
322
711
|
const snippets = cluster.memories.map((m, i) => {
|
|
323
712
|
const text = m.raw_text.length > 200 ? m.raw_text.slice(0, 200) + "..." : m.raw_text;
|
|
324
713
|
return `${i + 1}. [${m.tool_name}] ${text}`;
|
|
325
714
|
}).join("\n");
|
|
715
|
+
const rolePrompt = agentRole && ROLE_PROMPTS[agentRole] || DEFAULT_ROLE_PROMPT;
|
|
326
716
|
return `You are reviewing a set of work memories from an AI coding agent.
|
|
327
717
|
These are raw tool call records from ${cluster.dateRange}.
|
|
328
718
|
|
|
@@ -331,19 +721,15 @@ Agent: ${cluster.agentId} | Project: ${cluster.projectName} | Date: ${cluster.da
|
|
|
331
721
|
MEMORIES:
|
|
332
722
|
${snippets}
|
|
333
723
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
1. KEY DECISIONS \u2014 choices that were made and why (at most 3)
|
|
337
|
-
2. RECURRING PATTERNS \u2014 actions or approaches that repeated (at most 3)
|
|
338
|
-
3. OPEN QUESTIONS \u2014 things that seem unresolved or risky (at most 2)
|
|
724
|
+
${rolePrompt}
|
|
339
725
|
|
|
340
726
|
Format each as a single sentence. Be specific \u2014 include file names,
|
|
341
727
|
function names, and concrete details. Skip if no insight for a category.`;
|
|
342
728
|
}
|
|
343
|
-
async function consolidateCluster(cluster, model) {
|
|
729
|
+
async function consolidateCluster(cluster, model, agentRole) {
|
|
344
730
|
const Anthropic = (await import("@anthropic-ai/sdk")).default;
|
|
345
731
|
const client = new Anthropic();
|
|
346
|
-
const prompt = buildConsolidationPrompt(cluster);
|
|
732
|
+
const prompt = buildConsolidationPrompt(cluster, agentRole);
|
|
347
733
|
const response = await client.messages.create({
|
|
348
734
|
model,
|
|
349
735
|
max_tokens: 300,
|
|
@@ -487,6 +873,15 @@ async function runConsolidation(client, options) {
|
|
|
487
873
|
if (memories.length < 20) {
|
|
488
874
|
return { clustersProcessed: 0, memoriesConsolidated: 0 };
|
|
489
875
|
}
|
|
876
|
+
let roleMap = {};
|
|
877
|
+
try {
|
|
878
|
+
const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
879
|
+
const employees = loadEmployeesSync2();
|
|
880
|
+
for (const e of employees) {
|
|
881
|
+
roleMap[e.name] = e.role;
|
|
882
|
+
}
|
|
883
|
+
} catch {
|
|
884
|
+
}
|
|
490
885
|
const clusters = groupMemories(memories);
|
|
491
886
|
let clustersProcessed = 0;
|
|
492
887
|
let memoriesConsolidated = 0;
|
|
@@ -495,21 +890,27 @@ async function runConsolidation(client, options) {
|
|
|
495
890
|
if (cluster.memories.length < 3) continue;
|
|
496
891
|
try {
|
|
497
892
|
const isCoordinator = isCoordinatorName(cluster.agentId);
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
893
|
+
const agentRole = roleMap[cluster.agentId];
|
|
894
|
+
const dedupCount = await dedupCluster(client, cluster, options.embedFn);
|
|
895
|
+
memoriesConsolidated += dedupCount;
|
|
896
|
+
const synthesis = await consolidateCluster(cluster, options.model, agentRole);
|
|
897
|
+
if (!synthesis.trim()) {
|
|
898
|
+
if (dedupCount > 0) clustersProcessed++;
|
|
899
|
+
continue;
|
|
900
|
+
}
|
|
901
|
+
const result = await storeConsolidation(client, cluster, synthesis, options.embedFn);
|
|
902
|
+
if (isCoordinator && options.wikiConfig) {
|
|
903
|
+
await pushToWiki(
|
|
904
|
+
{ ...result, projectName: cluster.projectName },
|
|
905
|
+
options.wikiConfig
|
|
906
|
+
).catch((err) => {
|
|
907
|
+
process.stderr.write(
|
|
908
|
+
`[consolidation] Wiki push error (non-fatal): ${err instanceof Error ? err.message : String(err)}
|
|
509
909
|
`
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
910
|
+
);
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
if (isCoordinator) {
|
|
513
914
|
const sourceIds = result.sourceIds;
|
|
514
915
|
if (sourceIds.length > 0) {
|
|
515
916
|
const placeholders = sourceIds.map(() => "?").join(",");
|
|
@@ -518,13 +919,9 @@ async function runConsolidation(client, options) {
|
|
|
518
919
|
args: sourceIds
|
|
519
920
|
});
|
|
520
921
|
}
|
|
521
|
-
} else {
|
|
522
|
-
const dedupCount = await dedupCluster(client, cluster, options.embedFn);
|
|
523
|
-
memoriesConsolidated += dedupCount;
|
|
524
|
-
if (dedupCount === 0) continue;
|
|
525
922
|
}
|
|
526
923
|
clustersProcessed++;
|
|
527
|
-
memoriesConsolidated +=
|
|
924
|
+
memoriesConsolidated += cluster.memories.length;
|
|
528
925
|
} catch (err) {
|
|
529
926
|
process.stderr.write(
|
|
530
927
|
`[consolidation] Cluster failed (${cluster.projectName}/${cluster.dateRange}): ${err instanceof Error ? err.message : String(err)}
|
|
@@ -600,8 +997,7 @@ async function isUserIdle(client, idleMinutes = 30) {
|
|
|
600
997
|
async function countUnconsolidated(client) {
|
|
601
998
|
const result = await client.execute({
|
|
602
999
|
sql: `SELECT COUNT(*) as cnt FROM memories
|
|
603
|
-
WHERE consolidated = 0
|
|
604
|
-
AND timestamp >= datetime('now', '-7 days')`,
|
|
1000
|
+
WHERE consolidated = 0`,
|
|
605
1001
|
args: []
|
|
606
1002
|
});
|
|
607
1003
|
return Number(result.rows[0]?.cnt ?? 0);
|
package/dist/lib/database.js
CHANGED
|
@@ -553,7 +553,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
553
553
|
});
|
|
554
554
|
if (response.error) {
|
|
555
555
|
const errMsg = String(response.error);
|
|
556
|
-
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
556
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed" || errMsg === "DB not initialized") {
|
|
557
557
|
process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
|
|
558
558
|
`);
|
|
559
559
|
return fallbackClient.execute(stmt);
|
|
@@ -578,7 +578,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
578
578
|
});
|
|
579
579
|
if (response.error) {
|
|
580
580
|
const errMsg = String(response.error);
|
|
581
|
-
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed") {
|
|
581
|
+
if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed" || errMsg === "DB not initialized") {
|
|
582
582
|
process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
|
|
583
583
|
`);
|
|
584
584
|
return fallbackClient.batch(stmts, mode);
|
|
@@ -654,9 +654,9 @@ var init_db_daemon_client = __esm({
|
|
|
654
654
|
import { createClient } from "@libsql/client";
|
|
655
655
|
|
|
656
656
|
// src/lib/db-retry.ts
|
|
657
|
-
var MAX_RETRIES =
|
|
658
|
-
var BASE_DELAY_MS =
|
|
659
|
-
var MAX_JITTER_MS =
|
|
657
|
+
var MAX_RETRIES = 5;
|
|
658
|
+
var BASE_DELAY_MS = 250;
|
|
659
|
+
var MAX_JITTER_MS = 400;
|
|
660
660
|
function isBusyError(err) {
|
|
661
661
|
if (err instanceof Error) {
|
|
662
662
|
const msg = err.message.toLowerCase();
|
|
@@ -1312,6 +1312,9 @@ async function createPrismaDbAdapter(fallbackClient) {
|
|
|
1312
1312
|
return adapter;
|
|
1313
1313
|
}
|
|
1314
1314
|
|
|
1315
|
+
// src/types/memory.ts
|
|
1316
|
+
var EMBEDDING_DIM = 1024;
|
|
1317
|
+
|
|
1315
1318
|
// src/lib/database.ts
|
|
1316
1319
|
var _client = null;
|
|
1317
1320
|
var _resilientClient = null;
|
|
@@ -1356,7 +1359,7 @@ async function initDatabase(config) {
|
|
|
1356
1359
|
});
|
|
1357
1360
|
}, 3e4);
|
|
1358
1361
|
_walCheckpointTimer.unref();
|
|
1359
|
-
if (process.env.DATABASE_URL) {
|
|
1362
|
+
if (process.env.DATABASE_URL && process.env.EXE_USE_POSTGRES === "1") {
|
|
1360
1363
|
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
1361
1364
|
}
|
|
1362
1365
|
}
|
|
@@ -1367,7 +1370,7 @@ function getClient() {
|
|
|
1367
1370
|
if (!_adapterClient) {
|
|
1368
1371
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1369
1372
|
}
|
|
1370
|
-
if (process.env.DATABASE_URL) {
|
|
1373
|
+
if (process.env.DATABASE_URL && process.env.EXE_USE_POSTGRES === "1") {
|
|
1371
1374
|
return _adapterClient;
|
|
1372
1375
|
}
|
|
1373
1376
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
@@ -1379,9 +1382,11 @@ function getClient() {
|
|
|
1379
1382
|
return _resilientClient;
|
|
1380
1383
|
}
|
|
1381
1384
|
async function initDaemonClient() {
|
|
1382
|
-
if (process.env.DATABASE_URL) return;
|
|
1385
|
+
if (process.env.DATABASE_URL && process.env.EXE_USE_POSTGRES === "1") return;
|
|
1383
1386
|
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1387
|
+
if (process.env.VITEST) return;
|
|
1384
1388
|
if (!_resilientClient) return;
|
|
1389
|
+
if (_daemonClient) return;
|
|
1385
1390
|
try {
|
|
1386
1391
|
const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
|
|
1387
1392
|
_daemonClient = await initDaemonDbClient2(_resilientClient);
|
|
@@ -1530,6 +1535,13 @@ async function ensureSchema() {
|
|
|
1530
1535
|
});
|
|
1531
1536
|
} catch {
|
|
1532
1537
|
}
|
|
1538
|
+
try {
|
|
1539
|
+
await client.execute({
|
|
1540
|
+
sql: `ALTER TABLE behaviors ADD COLUMN vector F32_BLOB(${EMBEDDING_DIM})`,
|
|
1541
|
+
args: []
|
|
1542
|
+
});
|
|
1543
|
+
} catch {
|
|
1544
|
+
}
|
|
1533
1545
|
try {
|
|
1534
1546
|
await client.execute({
|
|
1535
1547
|
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|