@askexenow/exe-os 0.8.80 → 0.8.82
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 +359 -267
- package/dist/bin/backfill-responses.js +357 -265
- package/dist/bin/backfill-vectors.js +339 -264
- package/dist/bin/cleanup-stale-review-tasks.js +315 -256
- package/dist/bin/cli.js +494 -240
- package/dist/bin/exe-agent.js +141 -46
- package/dist/bin/exe-assign.js +151 -63
- package/dist/bin/exe-boot.js +294 -115
- package/dist/bin/exe-call.js +76 -51
- package/dist/bin/exe-cloud.js +58 -45
- package/dist/bin/exe-dispatch.js +434 -277
- package/dist/bin/exe-doctor.js +317 -246
- package/dist/bin/exe-export-behaviors.js +328 -248
- package/dist/bin/exe-forget.js +314 -231
- package/dist/bin/exe-gateway.js +2676 -1402
- package/dist/bin/exe-heartbeat.js +329 -264
- package/dist/bin/exe-kill.js +324 -244
- package/dist/bin/exe-launch-agent.js +574 -463
- package/dist/bin/exe-link.js +1055 -95
- package/dist/bin/exe-new-employee.js +49 -54
- package/dist/bin/exe-pending-messages.js +310 -253
- package/dist/bin/exe-pending-notifications.js +299 -228
- package/dist/bin/exe-pending-reviews.js +314 -245
- package/dist/bin/exe-rename.js +259 -195
- package/dist/bin/exe-review.js +140 -64
- package/dist/bin/exe-search.js +543 -356
- package/dist/bin/exe-session-cleanup.js +463 -382
- package/dist/bin/exe-settings.js +129 -99
- package/dist/bin/exe-start.sh +6 -6
- package/dist/bin/exe-status.js +95 -36
- package/dist/bin/exe-team.js +116 -51
- package/dist/bin/git-sweep.js +482 -307
- package/dist/bin/graph-backfill.js +357 -245
- package/dist/bin/graph-export.js +324 -244
- package/dist/bin/install.js +33 -10
- package/dist/bin/scan-tasks.js +481 -307
- package/dist/bin/setup.js +1147 -140
- package/dist/bin/shard-migrate.js +321 -241
- package/dist/bin/update.js +1 -7
- package/dist/bin/wiki-sync.js +318 -238
- package/dist/gateway/index.js +2656 -1383
- package/dist/hooks/bug-report-worker.js +641 -472
- package/dist/hooks/commit-complete.js +482 -307
- package/dist/hooks/error-recall.js +363 -135
- package/dist/hooks/exe-heartbeat-hook.js +97 -27
- package/dist/hooks/ingest-worker.js +584 -397
- package/dist/hooks/ingest.js +123 -58
- package/dist/hooks/instructions-loaded.js +212 -82
- package/dist/hooks/notification.js +200 -70
- package/dist/hooks/post-compact.js +199 -81
- package/dist/hooks/pre-compact.js +352 -140
- package/dist/hooks/pre-tool-use.js +416 -278
- package/dist/hooks/prompt-ingest-worker.js +376 -299
- package/dist/hooks/prompt-submit.js +414 -188
- package/dist/hooks/response-ingest-worker.js +408 -338
- package/dist/hooks/session-end.js +209 -83
- package/dist/hooks/session-start.js +382 -158
- package/dist/hooks/stop.js +209 -83
- package/dist/hooks/subagent-stop.js +209 -85
- package/dist/hooks/summary-worker.js +606 -510
- package/dist/index.js +2133 -855
- package/dist/lib/cloud-sync.js +1175 -184
- package/dist/lib/config.js +1 -9
- package/dist/lib/consolidation.js +71 -34
- package/dist/lib/database.js +166 -14
- package/dist/lib/device-registry.js +189 -117
- package/dist/lib/embedder.js +6 -10
- package/dist/lib/employee-templates.js +134 -39
- package/dist/lib/employees.js +30 -7
- package/dist/lib/exe-daemon-client.js +5 -7
- package/dist/lib/exe-daemon.js +514 -152
- package/dist/lib/hybrid-search.js +543 -356
- package/dist/lib/identity-templates.js +15 -15
- package/dist/lib/identity.js +19 -15
- package/dist/lib/license.js +1 -7
- package/dist/lib/messaging.js +157 -135
- package/dist/lib/reminders.js +97 -0
- package/dist/lib/schedules.js +302 -231
- package/dist/lib/skill-learning.js +33 -27
- package/dist/lib/status-brief.js +11 -14
- package/dist/lib/store.js +326 -237
- package/dist/lib/task-router.js +105 -1
- package/dist/lib/tasks.js +233 -116
- package/dist/lib/tmux-routing.js +173 -56
- package/dist/lib/ws-client.js +13 -3
- package/dist/mcp/server.js +2009 -1015
- package/dist/mcp/tools/complete-reminder.js +97 -0
- package/dist/mcp/tools/create-reminder.js +97 -0
- package/dist/mcp/tools/create-task.js +426 -262
- package/dist/mcp/tools/deactivate-behavior.js +119 -44
- package/dist/mcp/tools/list-reminders.js +97 -0
- package/dist/mcp/tools/list-tasks.js +56 -57
- package/dist/mcp/tools/send-message.js +206 -143
- package/dist/mcp/tools/update-task.js +259 -85
- package/dist/runtime/index.js +495 -316
- package/dist/tui/App.js +1128 -919
- package/package.json +2 -10
- package/src/commands/exe/afk.md +8 -8
- package/src/commands/exe/assign.md +1 -1
- package/src/commands/exe/build-adv.md +1 -1
- package/src/commands/exe/call.md +10 -10
- package/src/commands/exe/employee-heartbeat.md +9 -6
- package/src/commands/exe/heartbeat.md +5 -5
- package/src/commands/exe/intercom.md +26 -15
- package/src/commands/exe/launch.md +2 -2
- package/src/commands/exe/new-employee.md +1 -1
- package/src/commands/exe/review.md +2 -2
- package/src/commands/exe/schedule.md +1 -1
- package/src/commands/exe/sessions.md +2 -2
- package/src/commands/exe.md +22 -20
|
@@ -58,7 +58,7 @@ function wrapWithRetry(client) {
|
|
|
58
58
|
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
59
59
|
}
|
|
60
60
|
if (prop === "batch") {
|
|
61
|
-
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
61
|
+
return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
|
|
62
62
|
}
|
|
63
63
|
return Reflect.get(target, prop, receiver);
|
|
64
64
|
}
|
|
@@ -74,6 +74,387 @@ var init_db_retry = __esm({
|
|
|
74
74
|
}
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
+
// src/lib/config.ts
|
|
78
|
+
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
79
|
+
import { readFileSync, existsSync, renameSync } from "fs";
|
|
80
|
+
import path from "path";
|
|
81
|
+
import os from "os";
|
|
82
|
+
function resolveDataDir() {
|
|
83
|
+
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
84
|
+
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
85
|
+
const newDir = path.join(os.homedir(), ".exe-os");
|
|
86
|
+
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
87
|
+
if (!existsSync(newDir) && existsSync(legacyDir)) {
|
|
88
|
+
try {
|
|
89
|
+
renameSync(legacyDir, newDir);
|
|
90
|
+
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
91
|
+
`);
|
|
92
|
+
} catch {
|
|
93
|
+
return legacyDir;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return newDir;
|
|
97
|
+
}
|
|
98
|
+
function migrateLegacyConfig(raw) {
|
|
99
|
+
if ("r2" in raw) {
|
|
100
|
+
process.stderr.write(
|
|
101
|
+
"[exe-os] Warning: config.json contains deprecated 'r2' field from v1.0. R2 sync has been replaced in v1.1. The 'r2' field will be ignored.\n"
|
|
102
|
+
);
|
|
103
|
+
delete raw.r2;
|
|
104
|
+
}
|
|
105
|
+
if ("syncIntervalMs" in raw) {
|
|
106
|
+
delete raw.syncIntervalMs;
|
|
107
|
+
}
|
|
108
|
+
return raw;
|
|
109
|
+
}
|
|
110
|
+
function migrateConfig(raw) {
|
|
111
|
+
const fromVersion = typeof raw.config_version === "number" ? raw.config_version : 0;
|
|
112
|
+
let currentVersion = fromVersion;
|
|
113
|
+
let migrated = false;
|
|
114
|
+
if (currentVersion > CURRENT_CONFIG_VERSION) {
|
|
115
|
+
return { config: raw, migrated: false, fromVersion };
|
|
116
|
+
}
|
|
117
|
+
for (const migration of CONFIG_MIGRATIONS) {
|
|
118
|
+
if (currentVersion === migration.from && migration.to <= CURRENT_CONFIG_VERSION) {
|
|
119
|
+
raw = migration.migrate(raw);
|
|
120
|
+
currentVersion = migration.to;
|
|
121
|
+
migrated = true;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return { config: raw, migrated, fromVersion };
|
|
125
|
+
}
|
|
126
|
+
function normalizeScalingRoadmap(raw) {
|
|
127
|
+
const defaultAuto = DEFAULT_CONFIG.scalingRoadmap.rerankerAutoTrigger;
|
|
128
|
+
const userRoadmap = raw.scalingRoadmap ?? {};
|
|
129
|
+
const userAuto = userRoadmap.rerankerAutoTrigger ?? {};
|
|
130
|
+
if (userAuto.enabled === void 0 && raw.rerankerEnabled !== void 0) {
|
|
131
|
+
userAuto.enabled = raw.rerankerEnabled;
|
|
132
|
+
}
|
|
133
|
+
raw.scalingRoadmap = {
|
|
134
|
+
...userRoadmap,
|
|
135
|
+
rerankerAutoTrigger: { ...defaultAuto, ...userAuto }
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function normalizeSessionLifecycle(raw) {
|
|
139
|
+
const defaultSL = DEFAULT_CONFIG.sessionLifecycle;
|
|
140
|
+
const userSL = raw.sessionLifecycle ?? {};
|
|
141
|
+
raw.sessionLifecycle = { ...defaultSL, ...userSL };
|
|
142
|
+
}
|
|
143
|
+
function normalizeAutoUpdate(raw) {
|
|
144
|
+
const defaultAU = DEFAULT_CONFIG.autoUpdate;
|
|
145
|
+
const userAU = raw.autoUpdate ?? {};
|
|
146
|
+
raw.autoUpdate = { ...defaultAU, ...userAU };
|
|
147
|
+
}
|
|
148
|
+
async function loadConfig() {
|
|
149
|
+
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
150
|
+
await mkdir(dir, { recursive: true });
|
|
151
|
+
const configPath = path.join(dir, "config.json");
|
|
152
|
+
if (!existsSync(configPath)) {
|
|
153
|
+
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
154
|
+
}
|
|
155
|
+
const raw = await readFile(configPath, "utf-8");
|
|
156
|
+
try {
|
|
157
|
+
let parsed = JSON.parse(raw);
|
|
158
|
+
parsed = migrateLegacyConfig(parsed);
|
|
159
|
+
const { config: migratedCfg, migrated, fromVersion } = migrateConfig(parsed);
|
|
160
|
+
if (migrated) {
|
|
161
|
+
process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
|
|
162
|
+
`);
|
|
163
|
+
try {
|
|
164
|
+
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
165
|
+
} catch {
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
normalizeScalingRoadmap(migratedCfg);
|
|
169
|
+
normalizeSessionLifecycle(migratedCfg);
|
|
170
|
+
normalizeAutoUpdate(migratedCfg);
|
|
171
|
+
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
172
|
+
if (config.dbPath.startsWith("~")) {
|
|
173
|
+
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
174
|
+
}
|
|
175
|
+
return config;
|
|
176
|
+
} catch {
|
|
177
|
+
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
|
|
181
|
+
var init_config = __esm({
|
|
182
|
+
"src/lib/config.ts"() {
|
|
183
|
+
"use strict";
|
|
184
|
+
EXE_AI_DIR = resolveDataDir();
|
|
185
|
+
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
186
|
+
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
187
|
+
CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
|
|
188
|
+
LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
|
|
189
|
+
CURRENT_CONFIG_VERSION = 1;
|
|
190
|
+
DEFAULT_CONFIG = {
|
|
191
|
+
config_version: CURRENT_CONFIG_VERSION,
|
|
192
|
+
dbPath: DB_PATH,
|
|
193
|
+
modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
|
|
194
|
+
embeddingDim: 1024,
|
|
195
|
+
batchSize: 20,
|
|
196
|
+
flushIntervalMs: 1e4,
|
|
197
|
+
autoIngestion: true,
|
|
198
|
+
autoRetrieval: true,
|
|
199
|
+
searchMode: "hybrid",
|
|
200
|
+
hookSearchMode: "hybrid",
|
|
201
|
+
fileGrepEnabled: true,
|
|
202
|
+
splashEffect: true,
|
|
203
|
+
consolidationEnabled: true,
|
|
204
|
+
consolidationIntervalMs: 6 * 60 * 60 * 1e3,
|
|
205
|
+
consolidationModel: "claude-haiku-4-5-20251001",
|
|
206
|
+
consolidationMaxCallsPerRun: 20,
|
|
207
|
+
selfQueryRouter: true,
|
|
208
|
+
selfQueryModel: "claude-haiku-4-5-20251001",
|
|
209
|
+
rerankerEnabled: true,
|
|
210
|
+
scalingRoadmap: {
|
|
211
|
+
rerankerAutoTrigger: {
|
|
212
|
+
enabled: true,
|
|
213
|
+
broadQueryMinCardinality: 5e4,
|
|
214
|
+
fetchTopK: 150,
|
|
215
|
+
returnTopK: 5
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
graphRagEnabled: true,
|
|
219
|
+
wikiEnabled: false,
|
|
220
|
+
wikiUrl: "",
|
|
221
|
+
wikiApiKey: "",
|
|
222
|
+
wikiSyncIntervalMs: 30 * 60 * 1e3,
|
|
223
|
+
wikiWorkspaceMapping: {},
|
|
224
|
+
wikiAutoUpdate: true,
|
|
225
|
+
wikiAutoUpdateThreshold: 0.5,
|
|
226
|
+
wikiAutoUpdateCreateNew: true,
|
|
227
|
+
skillLearning: true,
|
|
228
|
+
skillThreshold: 3,
|
|
229
|
+
skillModel: "claude-haiku-4-5-20251001",
|
|
230
|
+
exeHeartbeat: {
|
|
231
|
+
enabled: true,
|
|
232
|
+
intervalSeconds: 60,
|
|
233
|
+
staleInProgressThresholdHours: 2
|
|
234
|
+
},
|
|
235
|
+
sessionLifecycle: {
|
|
236
|
+
idleKillEnabled: true,
|
|
237
|
+
idleKillTicksRequired: 3,
|
|
238
|
+
idleKillIntercomAckWindowMs: 1e4,
|
|
239
|
+
maxAutoInstances: 10
|
|
240
|
+
},
|
|
241
|
+
autoUpdate: {
|
|
242
|
+
checkOnBoot: true,
|
|
243
|
+
autoInstall: false,
|
|
244
|
+
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
CONFIG_MIGRATIONS = [
|
|
248
|
+
{
|
|
249
|
+
from: 0,
|
|
250
|
+
to: 1,
|
|
251
|
+
migrate: (cfg) => {
|
|
252
|
+
cfg.config_version = 1;
|
|
253
|
+
return cfg;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
];
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// src/lib/employees.ts
|
|
261
|
+
var employees_exports = {};
|
|
262
|
+
__export(employees_exports, {
|
|
263
|
+
COORDINATOR_ROLE: () => COORDINATOR_ROLE,
|
|
264
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
265
|
+
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
266
|
+
addEmployee: () => addEmployee,
|
|
267
|
+
canCoordinate: () => canCoordinate,
|
|
268
|
+
getCoordinatorEmployee: () => getCoordinatorEmployee,
|
|
269
|
+
getCoordinatorName: () => getCoordinatorName,
|
|
270
|
+
getEmployee: () => getEmployee,
|
|
271
|
+
getEmployeeByRole: () => getEmployeeByRole,
|
|
272
|
+
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
273
|
+
hasRole: () => hasRole,
|
|
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
|
+
});
|
|
285
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
286
|
+
import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
287
|
+
import { execSync } from "child_process";
|
|
288
|
+
import path2 from "path";
|
|
289
|
+
import os2 from "os";
|
|
290
|
+
function normalizeRole(role) {
|
|
291
|
+
return (role ?? "").trim().toLowerCase();
|
|
292
|
+
}
|
|
293
|
+
function isCoordinatorRole(role) {
|
|
294
|
+
return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
|
|
295
|
+
}
|
|
296
|
+
function getCoordinatorEmployee(employees) {
|
|
297
|
+
return employees.find((e) => isCoordinatorRole(e.role));
|
|
298
|
+
}
|
|
299
|
+
function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
300
|
+
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
301
|
+
}
|
|
302
|
+
function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
303
|
+
if (!agentName) return false;
|
|
304
|
+
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
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 (!existsSync2(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(path2.dirname(employeesPath), { recursive: true });
|
|
337
|
+
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
338
|
+
}
|
|
339
|
+
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
340
|
+
if (!existsSync2(employeesPath)) return [];
|
|
341
|
+
try {
|
|
342
|
+
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
343
|
+
} catch {
|
|
344
|
+
return [];
|
|
345
|
+
}
|
|
346
|
+
}
|
|
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 isMultiInstance(agentName, employees) {
|
|
364
|
+
const roster = employees ?? loadEmployeesSync();
|
|
365
|
+
const emp = getEmployee(roster, agentName);
|
|
366
|
+
if (!emp) return false;
|
|
367
|
+
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
368
|
+
}
|
|
369
|
+
function addEmployee(employees, employee) {
|
|
370
|
+
const normalized = { ...employee, name: employee.name.toLowerCase() };
|
|
371
|
+
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
372
|
+
throw new Error(`Employee '${normalized.name}' already exists`);
|
|
373
|
+
}
|
|
374
|
+
return [...employees, normalized];
|
|
375
|
+
}
|
|
376
|
+
async function normalizeRosterCase(rosterPath) {
|
|
377
|
+
const employees = await loadEmployees(rosterPath);
|
|
378
|
+
let changed = false;
|
|
379
|
+
for (const emp of employees) {
|
|
380
|
+
if (emp.name !== emp.name.toLowerCase()) {
|
|
381
|
+
const oldName = emp.name;
|
|
382
|
+
emp.name = emp.name.toLowerCase();
|
|
383
|
+
changed = true;
|
|
384
|
+
try {
|
|
385
|
+
const identityDir = path2.join(os2.homedir(), ".exe-os", "identity");
|
|
386
|
+
const oldPath = path2.join(identityDir, `${oldName}.md`);
|
|
387
|
+
const newPath = path2.join(identityDir, `${emp.name}.md`);
|
|
388
|
+
if (existsSync2(oldPath) && !existsSync2(newPath)) {
|
|
389
|
+
renameSync2(oldPath, newPath);
|
|
390
|
+
} else if (existsSync2(oldPath) && oldPath !== newPath) {
|
|
391
|
+
const content = readFileSync2(oldPath, "utf-8");
|
|
392
|
+
writeFileSync(newPath, content, "utf-8");
|
|
393
|
+
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
394
|
+
unlinkSync(oldPath);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
} catch {
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (changed) {
|
|
402
|
+
await saveEmployees(employees, rosterPath);
|
|
403
|
+
}
|
|
404
|
+
return changed;
|
|
405
|
+
}
|
|
406
|
+
function findExeBin() {
|
|
407
|
+
try {
|
|
408
|
+
return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
409
|
+
} catch {
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
function registerBinSymlinks(name) {
|
|
414
|
+
const created = [];
|
|
415
|
+
const skipped = [];
|
|
416
|
+
const errors = [];
|
|
417
|
+
const exeBinPath = findExeBin();
|
|
418
|
+
if (!exeBinPath) {
|
|
419
|
+
errors.push("Could not find 'exe-os' in PATH");
|
|
420
|
+
return { created, skipped, errors };
|
|
421
|
+
}
|
|
422
|
+
const binDir = path2.dirname(exeBinPath);
|
|
423
|
+
let target;
|
|
424
|
+
try {
|
|
425
|
+
target = readlinkSync(exeBinPath);
|
|
426
|
+
} catch {
|
|
427
|
+
errors.push("Could not read 'exe' symlink");
|
|
428
|
+
return { created, skipped, errors };
|
|
429
|
+
}
|
|
430
|
+
for (const suffix of ["", "-opencode"]) {
|
|
431
|
+
const linkName = `${name}${suffix}`;
|
|
432
|
+
const linkPath = path2.join(binDir, linkName);
|
|
433
|
+
if (existsSync2(linkPath)) {
|
|
434
|
+
skipped.push(linkName);
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
try {
|
|
438
|
+
symlinkSync(target, linkPath);
|
|
439
|
+
created.push(linkName);
|
|
440
|
+
} catch (err) {
|
|
441
|
+
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return { created, skipped, errors };
|
|
445
|
+
}
|
|
446
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
447
|
+
var init_employees = __esm({
|
|
448
|
+
"src/lib/employees.ts"() {
|
|
449
|
+
"use strict";
|
|
450
|
+
init_config();
|
|
451
|
+
EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
452
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
453
|
+
COORDINATOR_ROLE = "COO";
|
|
454
|
+
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
|
|
77
458
|
// src/lib/database.ts
|
|
78
459
|
import { createClient } from "@libsql/client";
|
|
79
460
|
async function initDatabase(config) {
|
|
@@ -207,22 +588,24 @@ async function ensureSchema() {
|
|
|
207
588
|
ON behaviors(agent_id, active);
|
|
208
589
|
`);
|
|
209
590
|
try {
|
|
591
|
+
const coordinatorName = getCoordinatorName();
|
|
210
592
|
const existing = await client.execute({
|
|
211
|
-
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id =
|
|
212
|
-
args: []
|
|
593
|
+
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
|
|
594
|
+
args: [coordinatorName]
|
|
213
595
|
});
|
|
214
596
|
if (Number(existing.rows[0]?.cnt) === 0) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
597
|
+
const seededAt = "2026-03-25T00:00:00Z";
|
|
598
|
+
for (const [domain, content] of [
|
|
599
|
+
["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
|
|
600
|
+
["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
|
|
601
|
+
["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
|
|
602
|
+
]) {
|
|
603
|
+
await client.execute({
|
|
604
|
+
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
605
|
+
VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
|
|
606
|
+
args: [coordinatorName, domain, content, seededAt, seededAt]
|
|
607
|
+
});
|
|
608
|
+
}
|
|
226
609
|
}
|
|
227
610
|
} catch {
|
|
228
611
|
}
|
|
@@ -911,207 +1294,52 @@ async function ensureSchema() {
|
|
|
911
1294
|
]) {
|
|
912
1295
|
try {
|
|
913
1296
|
await client.execute(col);
|
|
914
|
-
} catch {
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
return raw;
|
|
961
|
-
}
|
|
962
|
-
function migrateConfig(raw) {
|
|
963
|
-
const fromVersion = typeof raw.config_version === "number" ? raw.config_version : 0;
|
|
964
|
-
let currentVersion = fromVersion;
|
|
965
|
-
let migrated = false;
|
|
966
|
-
if (currentVersion > CURRENT_CONFIG_VERSION) {
|
|
967
|
-
return { config: raw, migrated: false, fromVersion };
|
|
968
|
-
}
|
|
969
|
-
for (const migration of CONFIG_MIGRATIONS) {
|
|
970
|
-
if (currentVersion === migration.from && migration.to <= CURRENT_CONFIG_VERSION) {
|
|
971
|
-
raw = migration.migrate(raw);
|
|
972
|
-
currentVersion = migration.to;
|
|
973
|
-
migrated = true;
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
return { config: raw, migrated, fromVersion };
|
|
977
|
-
}
|
|
978
|
-
function normalizeScalingRoadmap(raw) {
|
|
979
|
-
const defaultAuto = DEFAULT_CONFIG.scalingRoadmap.rerankerAutoTrigger;
|
|
980
|
-
const userRoadmap = raw.scalingRoadmap ?? {};
|
|
981
|
-
const userAuto = userRoadmap.rerankerAutoTrigger ?? {};
|
|
982
|
-
if (userAuto.enabled === void 0 && raw.rerankerEnabled !== void 0) {
|
|
983
|
-
userAuto.enabled = raw.rerankerEnabled;
|
|
984
|
-
}
|
|
985
|
-
raw.scalingRoadmap = {
|
|
986
|
-
...userRoadmap,
|
|
987
|
-
rerankerAutoTrigger: { ...defaultAuto, ...userAuto }
|
|
988
|
-
};
|
|
989
|
-
}
|
|
990
|
-
function normalizeSessionLifecycle(raw) {
|
|
991
|
-
const defaultSL = DEFAULT_CONFIG.sessionLifecycle;
|
|
992
|
-
const userSL = raw.sessionLifecycle ?? {};
|
|
993
|
-
raw.sessionLifecycle = { ...defaultSL, ...userSL };
|
|
994
|
-
}
|
|
995
|
-
function normalizeAutoUpdate(raw) {
|
|
996
|
-
const defaultAU = DEFAULT_CONFIG.autoUpdate;
|
|
997
|
-
const userAU = raw.autoUpdate ?? {};
|
|
998
|
-
raw.autoUpdate = { ...defaultAU, ...userAU };
|
|
999
|
-
}
|
|
1000
|
-
async function loadConfig() {
|
|
1001
|
-
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
1002
|
-
await mkdir2(dir, { recursive: true });
|
|
1003
|
-
const configPath = path2.join(dir, "config.json");
|
|
1004
|
-
if (!existsSync2(configPath)) {
|
|
1005
|
-
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
1006
|
-
}
|
|
1007
|
-
const raw = await readFile2(configPath, "utf-8");
|
|
1008
|
-
try {
|
|
1009
|
-
let parsed = JSON.parse(raw);
|
|
1010
|
-
parsed = migrateLegacyConfig(parsed);
|
|
1011
|
-
const { config: migratedCfg, migrated, fromVersion } = migrateConfig(parsed);
|
|
1012
|
-
if (migrated) {
|
|
1013
|
-
process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
|
|
1014
|
-
`);
|
|
1015
|
-
try {
|
|
1016
|
-
await writeFile2(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
1017
|
-
} catch {
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
normalizeScalingRoadmap(migratedCfg);
|
|
1021
|
-
normalizeSessionLifecycle(migratedCfg);
|
|
1022
|
-
normalizeAutoUpdate(migratedCfg);
|
|
1023
|
-
const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
1024
|
-
if (config.dbPath.startsWith("~")) {
|
|
1025
|
-
config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
|
|
1026
|
-
}
|
|
1027
|
-
return config;
|
|
1028
|
-
} catch {
|
|
1029
|
-
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
|
|
1033
|
-
var init_config = __esm({
|
|
1034
|
-
"src/lib/config.ts"() {
|
|
1035
|
-
"use strict";
|
|
1036
|
-
EXE_AI_DIR = resolveDataDir();
|
|
1037
|
-
DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
|
|
1038
|
-
MODELS_DIR = path2.join(EXE_AI_DIR, "models");
|
|
1039
|
-
CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
|
|
1040
|
-
LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
|
|
1041
|
-
CURRENT_CONFIG_VERSION = 1;
|
|
1042
|
-
DEFAULT_CONFIG = {
|
|
1043
|
-
config_version: CURRENT_CONFIG_VERSION,
|
|
1044
|
-
dbPath: DB_PATH,
|
|
1045
|
-
modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
|
|
1046
|
-
embeddingDim: 1024,
|
|
1047
|
-
batchSize: 20,
|
|
1048
|
-
flushIntervalMs: 1e4,
|
|
1049
|
-
autoIngestion: true,
|
|
1050
|
-
autoRetrieval: true,
|
|
1051
|
-
searchMode: "hybrid",
|
|
1052
|
-
hookSearchMode: "hybrid",
|
|
1053
|
-
fileGrepEnabled: true,
|
|
1054
|
-
splashEffect: true,
|
|
1055
|
-
consolidationEnabled: true,
|
|
1056
|
-
consolidationIntervalMs: 6 * 60 * 60 * 1e3,
|
|
1057
|
-
consolidationModel: "claude-haiku-4-5-20251001",
|
|
1058
|
-
consolidationMaxCallsPerRun: 20,
|
|
1059
|
-
selfQueryRouter: true,
|
|
1060
|
-
selfQueryModel: "claude-haiku-4-5-20251001",
|
|
1061
|
-
rerankerEnabled: true,
|
|
1062
|
-
scalingRoadmap: {
|
|
1063
|
-
rerankerAutoTrigger: {
|
|
1064
|
-
enabled: true,
|
|
1065
|
-
broadQueryMinCardinality: 5e4,
|
|
1066
|
-
fetchTopK: 150,
|
|
1067
|
-
returnTopK: 5
|
|
1068
|
-
}
|
|
1069
|
-
},
|
|
1070
|
-
graphRagEnabled: true,
|
|
1071
|
-
wikiEnabled: false,
|
|
1072
|
-
wikiUrl: "",
|
|
1073
|
-
wikiApiKey: "",
|
|
1074
|
-
wikiSyncIntervalMs: 30 * 60 * 1e3,
|
|
1075
|
-
wikiWorkspaceMapping: {
|
|
1076
|
-
exe: "Executive",
|
|
1077
|
-
yoshi: "Engineering",
|
|
1078
|
-
mari: "Marketing",
|
|
1079
|
-
tom: "Engineering",
|
|
1080
|
-
sasha: "Production"
|
|
1081
|
-
},
|
|
1082
|
-
wikiAutoUpdate: true,
|
|
1083
|
-
wikiAutoUpdateThreshold: 0.5,
|
|
1084
|
-
wikiAutoUpdateCreateNew: true,
|
|
1085
|
-
skillLearning: true,
|
|
1086
|
-
skillThreshold: 3,
|
|
1087
|
-
skillModel: "claude-haiku-4-5-20251001",
|
|
1088
|
-
exeHeartbeat: {
|
|
1089
|
-
enabled: true,
|
|
1090
|
-
intervalSeconds: 60,
|
|
1091
|
-
staleInProgressThresholdHours: 2
|
|
1092
|
-
},
|
|
1093
|
-
sessionLifecycle: {
|
|
1094
|
-
idleKillEnabled: true,
|
|
1095
|
-
idleKillTicksRequired: 3,
|
|
1096
|
-
idleKillIntercomAckWindowMs: 1e4,
|
|
1097
|
-
maxAutoInstances: 10
|
|
1098
|
-
},
|
|
1099
|
-
autoUpdate: {
|
|
1100
|
-
checkOnBoot: true,
|
|
1101
|
-
autoInstall: false,
|
|
1102
|
-
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
1103
|
-
}
|
|
1104
|
-
};
|
|
1105
|
-
CONFIG_MIGRATIONS = [
|
|
1106
|
-
{
|
|
1107
|
-
from: 0,
|
|
1108
|
-
to: 1,
|
|
1109
|
-
migrate: (cfg) => {
|
|
1110
|
-
cfg.config_version = 1;
|
|
1111
|
-
return cfg;
|
|
1112
|
-
}
|
|
1113
|
-
}
|
|
1114
|
-
];
|
|
1297
|
+
} catch {
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
try {
|
|
1301
|
+
await client.execute({
|
|
1302
|
+
sql: `ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0`,
|
|
1303
|
+
args: []
|
|
1304
|
+
});
|
|
1305
|
+
} catch {
|
|
1306
|
+
}
|
|
1307
|
+
try {
|
|
1308
|
+
await client.execute(
|
|
1309
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_draft ON memories(draft) WHERE draft = 1`
|
|
1310
|
+
);
|
|
1311
|
+
} catch {
|
|
1312
|
+
}
|
|
1313
|
+
try {
|
|
1314
|
+
await client.execute({
|
|
1315
|
+
sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
|
|
1316
|
+
args: []
|
|
1317
|
+
});
|
|
1318
|
+
} catch {
|
|
1319
|
+
}
|
|
1320
|
+
try {
|
|
1321
|
+
await client.execute(
|
|
1322
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
|
|
1323
|
+
);
|
|
1324
|
+
} catch {
|
|
1325
|
+
}
|
|
1326
|
+
try {
|
|
1327
|
+
await client.execute({
|
|
1328
|
+
sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
|
|
1329
|
+
args: []
|
|
1330
|
+
});
|
|
1331
|
+
} catch {
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
var _client, _resilientClient, initTurso;
|
|
1335
|
+
var init_database = __esm({
|
|
1336
|
+
"src/lib/database.ts"() {
|
|
1337
|
+
"use strict";
|
|
1338
|
+
init_db_retry();
|
|
1339
|
+
init_employees();
|
|
1340
|
+
_client = null;
|
|
1341
|
+
_resilientClient = null;
|
|
1342
|
+
initTurso = initDatabase;
|
|
1115
1343
|
}
|
|
1116
1344
|
});
|
|
1117
1345
|
|
|
@@ -1183,12 +1411,12 @@ __export(shard_manager_exports, {
|
|
|
1183
1411
|
listShards: () => listShards,
|
|
1184
1412
|
shardExists: () => shardExists
|
|
1185
1413
|
});
|
|
1186
|
-
import
|
|
1187
|
-
import { existsSync as
|
|
1414
|
+
import path4 from "path";
|
|
1415
|
+
import { existsSync as existsSync4, mkdirSync, readdirSync } from "fs";
|
|
1188
1416
|
import { createClient as createClient2 } from "@libsql/client";
|
|
1189
1417
|
function initShardManager(encryptionKey) {
|
|
1190
1418
|
_encryptionKey = encryptionKey;
|
|
1191
|
-
if (!
|
|
1419
|
+
if (!existsSync4(SHARDS_DIR)) {
|
|
1192
1420
|
mkdirSync(SHARDS_DIR, { recursive: true });
|
|
1193
1421
|
}
|
|
1194
1422
|
_shardingEnabled = true;
|
|
@@ -1209,7 +1437,7 @@ function getShardClient(projectName) {
|
|
|
1209
1437
|
}
|
|
1210
1438
|
const cached = _shards.get(safeName);
|
|
1211
1439
|
if (cached) return cached;
|
|
1212
|
-
const dbPath =
|
|
1440
|
+
const dbPath = path4.join(SHARDS_DIR, `${safeName}.db`);
|
|
1213
1441
|
const client = createClient2({
|
|
1214
1442
|
url: `file:${dbPath}`,
|
|
1215
1443
|
encryptionKey: _encryptionKey
|
|
@@ -1219,10 +1447,10 @@ function getShardClient(projectName) {
|
|
|
1219
1447
|
}
|
|
1220
1448
|
function shardExists(projectName) {
|
|
1221
1449
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1222
|
-
return
|
|
1450
|
+
return existsSync4(path4.join(SHARDS_DIR, `${safeName}.db`));
|
|
1223
1451
|
}
|
|
1224
1452
|
function listShards() {
|
|
1225
|
-
if (!
|
|
1453
|
+
if (!existsSync4(SHARDS_DIR)) return [];
|
|
1226
1454
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
1227
1455
|
}
|
|
1228
1456
|
async function ensureShardSchema(client) {
|
|
@@ -1292,7 +1520,11 @@ async function ensureShardSchema(client) {
|
|
|
1292
1520
|
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
1293
1521
|
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
|
|
1294
1522
|
"ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
|
|
1295
|
-
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
|
|
1523
|
+
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
|
|
1524
|
+
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
1525
|
+
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
1526
|
+
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
1527
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
1296
1528
|
]) {
|
|
1297
1529
|
try {
|
|
1298
1530
|
await client.execute(col);
|
|
@@ -1404,7 +1636,7 @@ var init_shard_manager = __esm({
|
|
|
1404
1636
|
"src/lib/shard-manager.ts"() {
|
|
1405
1637
|
"use strict";
|
|
1406
1638
|
init_config();
|
|
1407
|
-
SHARDS_DIR =
|
|
1639
|
+
SHARDS_DIR = path4.join(EXE_AI_DIR, "shards");
|
|
1408
1640
|
_shards = /* @__PURE__ */ new Map();
|
|
1409
1641
|
_encryptionKey = null;
|
|
1410
1642
|
_shardingEnabled = false;
|
|
@@ -1422,26 +1654,26 @@ var init_platform_procedures = __esm({
|
|
|
1422
1654
|
title: "What is exe-os \u2014 the operating model every agent must understand",
|
|
1423
1655
|
domain: "architecture",
|
|
1424
1656
|
priority: "p0",
|
|
1425
|
-
content: "Exe OS is an AI employee operating system. A founder runs 5-10 AI agents as a real org: COO
|
|
1657
|
+
content: "Exe OS is an AI employee operating system. A founder runs 5-10 AI agents as a real org: COO, CTO, CMO, engineers, and content production specialists. Each agent has identity, expertise, and experience layers \u2014 persistent memory that makes them better over time. All data is local-first, E2EE, owned by the user. The MCP server is the ONLY data interface \u2014 never access the DB directly."
|
|
1426
1658
|
},
|
|
1427
1659
|
{
|
|
1428
1660
|
title: "Mode 1 \u2014 how exe-os runs inside Claude Code",
|
|
1429
1661
|
domain: "architecture",
|
|
1430
1662
|
priority: "p0",
|
|
1431
|
-
content: "Mode 1: exe-os runs AS hooks + MCP + skills inside Claude Code. The founder opens CC
|
|
1663
|
+
content: "Mode 1: exe-os runs AS hooks + MCP + skills inside Claude Code. The founder opens CC and boots the COO. The COO manages employees in tmux sessions. Each coordinator session is a separate CC window/project. Employees run in their own tmux panes via create_task auto-spawn. The founder talks to the COO; the COO orchestrates the team. CC is the shell, exe-os is the brain."
|
|
1432
1664
|
},
|
|
1433
1665
|
{
|
|
1434
|
-
title: "Sessions explained \u2014
|
|
1666
|
+
title: "Sessions explained \u2014 coordinator session names and projects",
|
|
1435
1667
|
domain: "architecture",
|
|
1436
1668
|
priority: "p0",
|
|
1437
|
-
content: "Each
|
|
1669
|
+
content: "Each coordinator session is an isolated project session. One might be exe-os development, another might be exe-wiki. Each session spawns its own employees using {employee}-{coordinatorSession}. Sessions share the same memory DB but tasks are scoped to the session that created them. A founder can run multiple projects simultaneously. Sessions never interfere with each other."
|
|
1438
1670
|
},
|
|
1439
1671
|
// --- Hierarchy and dispatch ---
|
|
1440
1672
|
{
|
|
1441
1673
|
title: "Chain of command \u2014 who talks to whom",
|
|
1442
1674
|
domain: "workflow",
|
|
1443
1675
|
priority: "p0",
|
|
1444
|
-
content: "Founder
|
|
1676
|
+
content: "Founder -> COO -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the COO does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
|
|
1445
1677
|
},
|
|
1446
1678
|
{
|
|
1447
1679
|
title: "Single dispatch path \u2014 create_task only",
|
|
@@ -1451,30 +1683,30 @@ var init_platform_procedures = __esm({
|
|
|
1451
1683
|
},
|
|
1452
1684
|
// --- Session isolation ---
|
|
1453
1685
|
{
|
|
1454
|
-
title: "Session scoping \u2014 stay in your
|
|
1686
|
+
title: "Session scoping \u2014 stay in your coordinator boundary",
|
|
1455
1687
|
domain: "security",
|
|
1456
1688
|
priority: "p0",
|
|
1457
|
-
content: "Session scoping is mandatory. Managers dispatch to workers within their own
|
|
1689
|
+
content: "Session scoping is mandatory. Managers dispatch to workers within their own coordinator session ONLY. Employee sessions use {employee}-{coordinatorSession}. Cross-session dispatch is blocked by the system. Verify session names before dispatch. Tasks are scoped to the creating coordinator session."
|
|
1458
1690
|
},
|
|
1459
1691
|
{
|
|
1460
1692
|
title: "Session isolation \u2014 never touch another session's work",
|
|
1461
1693
|
domain: "workflow",
|
|
1462
1694
|
priority: "p0",
|
|
1463
|
-
content:
|
|
1695
|
+
content: "Sessions are isolated. A coordinator session owns ONLY tasks it dispatched. (1) Never close/update/cancel tasks from another coordinator session. (2) Never review work from a different session \u2014 report that it belongs to another session and skip. (3) Ignore other sessions' items in list_tasks results. (4) Employees inherit session: employee sessions work ONLY on their parent coordinator session's tasks. Cross-session work is a system violation."
|
|
1464
1696
|
},
|
|
1465
1697
|
// --- Engineering: session scoping in code ---
|
|
1466
1698
|
{
|
|
1467
1699
|
title: "Three-dimensional scoping \u2014 session, project, role \u2014 enforced in every query",
|
|
1468
1700
|
domain: "architecture",
|
|
1469
1701
|
priority: "p0",
|
|
1470
|
-
content: "Every DB query, notification, review count, and task operation MUST be scoped on 3 dimensions: (1) Session \u2014 filter by session_scope matching current
|
|
1702
|
+
content: "Every DB query, notification, review count, and task operation MUST be scoped on 3 dimensions: (1) Session \u2014 filter by session_scope matching the current coordinator session. (2) Project \u2014 filter by project_name. (3) Role \u2014 agents only see data at their hierarchy level. When writing ANY function that touches tasks, reviews, messages, or notifications: always accept a sessionScope parameter and pass it to the SQL WHERE clause. Unscoped queries are bugs. Test by running 2+ coordinator sessions simultaneously."
|
|
1471
1703
|
},
|
|
1472
1704
|
// --- Hard constraints ---
|
|
1473
1705
|
{
|
|
1474
1706
|
title: "What you CANNOT do in exe-os \u2014 hard constraints",
|
|
1475
1707
|
domain: "security",
|
|
1476
1708
|
priority: "p0",
|
|
1477
|
-
content: "NEVER: (1) Access the database directly \u2014 it's SQLCipher encrypted, always fails. Use MCP tools only. (2) Manually spawn tmux sessions \u2014 create_task handles it. (3) Run git checkout main \u2014 agents work in worktrees. (4) Modify another agent's in-progress task. (5) Push to remote \u2014
|
|
1709
|
+
content: "NEVER: (1) Access the database directly \u2014 it's SQLCipher encrypted, always fails. Use MCP tools only. (2) Manually spawn tmux sessions \u2014 create_task handles it. (3) Run git checkout main \u2014 agents work in worktrees. (4) Modify another agent's in-progress task. (5) Push to remote \u2014 the COO reviews and pushes. (6) Skip update_task(done) \u2014 it's the ONLY way your work gets reviewed. (7) Run git init."
|
|
1478
1710
|
},
|
|
1479
1711
|
// --- Operations ---
|
|
1480
1712
|
{
|
|
@@ -1594,13 +1826,13 @@ ${p.content}`).join("\n\n");
|
|
|
1594
1826
|
|
|
1595
1827
|
// src/lib/notifications.ts
|
|
1596
1828
|
import crypto from "crypto";
|
|
1597
|
-
import
|
|
1598
|
-
import
|
|
1829
|
+
import path5 from "path";
|
|
1830
|
+
import os4 from "os";
|
|
1599
1831
|
import {
|
|
1600
|
-
readFileSync as
|
|
1832
|
+
readFileSync as readFileSync3,
|
|
1601
1833
|
readdirSync as readdirSync2,
|
|
1602
|
-
unlinkSync,
|
|
1603
|
-
existsSync as
|
|
1834
|
+
unlinkSync as unlinkSync2,
|
|
1835
|
+
existsSync as existsSync5,
|
|
1604
1836
|
rmdirSync
|
|
1605
1837
|
} from "fs";
|
|
1606
1838
|
async function writeNotification(notification) {
|
|
@@ -1645,12 +1877,12 @@ var init_notifications = __esm({
|
|
|
1645
1877
|
});
|
|
1646
1878
|
|
|
1647
1879
|
// src/lib/session-registry.ts
|
|
1648
|
-
import { readFileSync as
|
|
1649
|
-
import
|
|
1650
|
-
import
|
|
1880
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync6 } from "fs";
|
|
1881
|
+
import path6 from "path";
|
|
1882
|
+
import os5 from "os";
|
|
1651
1883
|
function registerSession(entry) {
|
|
1652
|
-
const dir =
|
|
1653
|
-
if (!
|
|
1884
|
+
const dir = path6.dirname(REGISTRY_PATH);
|
|
1885
|
+
if (!existsSync6(dir)) {
|
|
1654
1886
|
mkdirSync2(dir, { recursive: true });
|
|
1655
1887
|
}
|
|
1656
1888
|
const sessions = listSessions();
|
|
@@ -1660,11 +1892,11 @@ function registerSession(entry) {
|
|
|
1660
1892
|
} else {
|
|
1661
1893
|
sessions.push(entry);
|
|
1662
1894
|
}
|
|
1663
|
-
|
|
1895
|
+
writeFileSync2(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
1664
1896
|
}
|
|
1665
1897
|
function listSessions() {
|
|
1666
1898
|
try {
|
|
1667
|
-
const raw =
|
|
1899
|
+
const raw = readFileSync4(REGISTRY_PATH, "utf8");
|
|
1668
1900
|
return JSON.parse(raw);
|
|
1669
1901
|
} catch {
|
|
1670
1902
|
return [];
|
|
@@ -1674,18 +1906,18 @@ var REGISTRY_PATH;
|
|
|
1674
1906
|
var init_session_registry = __esm({
|
|
1675
1907
|
"src/lib/session-registry.ts"() {
|
|
1676
1908
|
"use strict";
|
|
1677
|
-
REGISTRY_PATH =
|
|
1909
|
+
REGISTRY_PATH = path6.join(os5.homedir(), ".exe-os", "session-registry.json");
|
|
1678
1910
|
}
|
|
1679
1911
|
});
|
|
1680
1912
|
|
|
1681
1913
|
// src/lib/session-key.ts
|
|
1682
|
-
import { execSync } from "child_process";
|
|
1914
|
+
import { execSync as execSync2 } from "child_process";
|
|
1683
1915
|
function getSessionKey() {
|
|
1684
1916
|
if (_cached) return _cached;
|
|
1685
1917
|
let pid = process.ppid;
|
|
1686
1918
|
for (let i = 0; i < 10; i++) {
|
|
1687
1919
|
try {
|
|
1688
|
-
const info =
|
|
1920
|
+
const info = execSync2(`ps -p ${pid} -o ppid=,comm=`, {
|
|
1689
1921
|
encoding: "utf8",
|
|
1690
1922
|
timeout: 2e3
|
|
1691
1923
|
}).trim();
|
|
@@ -1821,14 +2053,14 @@ var init_transport = __esm({
|
|
|
1821
2053
|
});
|
|
1822
2054
|
|
|
1823
2055
|
// src/lib/cc-agent-support.ts
|
|
1824
|
-
import { execSync as
|
|
2056
|
+
import { execSync as execSync3 } from "child_process";
|
|
1825
2057
|
function _resetCcAgentSupportCache() {
|
|
1826
2058
|
_cachedSupport = null;
|
|
1827
2059
|
}
|
|
1828
2060
|
function claudeSupportsAgentFlag() {
|
|
1829
2061
|
if (_cachedSupport !== null) return _cachedSupport;
|
|
1830
2062
|
try {
|
|
1831
|
-
const helpOutput =
|
|
2063
|
+
const helpOutput = execSync3("claude --help 2>&1", {
|
|
1832
2064
|
encoding: "utf-8",
|
|
1833
2065
|
timeout: 5e3
|
|
1834
2066
|
});
|
|
@@ -1894,17 +2126,17 @@ var init_provider_table = __esm({
|
|
|
1894
2126
|
});
|
|
1895
2127
|
|
|
1896
2128
|
// src/lib/intercom-queue.ts
|
|
1897
|
-
import { readFileSync as
|
|
1898
|
-
import
|
|
1899
|
-
import
|
|
2129
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
|
|
2130
|
+
import path7 from "path";
|
|
2131
|
+
import os6 from "os";
|
|
1900
2132
|
function ensureDir() {
|
|
1901
|
-
const dir =
|
|
1902
|
-
if (!
|
|
2133
|
+
const dir = path7.dirname(QUEUE_PATH);
|
|
2134
|
+
if (!existsSync7(dir)) mkdirSync3(dir, { recursive: true });
|
|
1903
2135
|
}
|
|
1904
2136
|
function readQueue() {
|
|
1905
2137
|
try {
|
|
1906
|
-
if (!
|
|
1907
|
-
return JSON.parse(
|
|
2138
|
+
if (!existsSync7(QUEUE_PATH)) return [];
|
|
2139
|
+
return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
|
|
1908
2140
|
} catch {
|
|
1909
2141
|
return [];
|
|
1910
2142
|
}
|
|
@@ -1912,8 +2144,8 @@ function readQueue() {
|
|
|
1912
2144
|
function writeQueue(queue) {
|
|
1913
2145
|
ensureDir();
|
|
1914
2146
|
const tmp = `${QUEUE_PATH}.tmp`;
|
|
1915
|
-
|
|
1916
|
-
|
|
2147
|
+
writeFileSync3(tmp, JSON.stringify(queue, null, 2));
|
|
2148
|
+
renameSync3(tmp, QUEUE_PATH);
|
|
1917
2149
|
}
|
|
1918
2150
|
function queueIntercom(targetSession, reason) {
|
|
1919
2151
|
const queue = readQueue();
|
|
@@ -1936,178 +2168,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
|
1936
2168
|
var init_intercom_queue = __esm({
|
|
1937
2169
|
"src/lib/intercom-queue.ts"() {
|
|
1938
2170
|
"use strict";
|
|
1939
|
-
QUEUE_PATH =
|
|
2171
|
+
QUEUE_PATH = path7.join(os6.homedir(), ".exe-os", "intercom-queue.json");
|
|
1940
2172
|
TTL_MS = 60 * 60 * 1e3;
|
|
1941
|
-
INTERCOM_LOG =
|
|
1942
|
-
}
|
|
1943
|
-
});
|
|
1944
|
-
|
|
1945
|
-
// src/lib/employees.ts
|
|
1946
|
-
var employees_exports = {};
|
|
1947
|
-
__export(employees_exports, {
|
|
1948
|
-
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
1949
|
-
addEmployee: () => addEmployee,
|
|
1950
|
-
getEmployee: () => getEmployee,
|
|
1951
|
-
getEmployeeByRole: () => getEmployeeByRole,
|
|
1952
|
-
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
1953
|
-
hasRole: () => hasRole,
|
|
1954
|
-
isMultiInstance: () => isMultiInstance,
|
|
1955
|
-
loadEmployees: () => loadEmployees,
|
|
1956
|
-
loadEmployeesSync: () => loadEmployeesSync,
|
|
1957
|
-
normalizeRosterCase: () => normalizeRosterCase,
|
|
1958
|
-
registerBinSymlinks: () => registerBinSymlinks,
|
|
1959
|
-
saveEmployees: () => saveEmployees,
|
|
1960
|
-
validateEmployeeName: () => validateEmployeeName
|
|
1961
|
-
});
|
|
1962
|
-
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
1963
|
-
import { existsSync as existsSync7, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
1964
|
-
import { execSync as execSync3 } from "child_process";
|
|
1965
|
-
import path7 from "path";
|
|
1966
|
-
import os6 from "os";
|
|
1967
|
-
function validateEmployeeName(name) {
|
|
1968
|
-
if (!name) {
|
|
1969
|
-
return { valid: false, error: "Name is required" };
|
|
1970
|
-
}
|
|
1971
|
-
if (name.length > 32) {
|
|
1972
|
-
return { valid: false, error: "Name must be 32 characters or fewer" };
|
|
1973
|
-
}
|
|
1974
|
-
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
1975
|
-
return {
|
|
1976
|
-
valid: false,
|
|
1977
|
-
error: "Name must start with a letter and contain only lowercase alphanumeric characters"
|
|
1978
|
-
};
|
|
1979
|
-
}
|
|
1980
|
-
return { valid: true };
|
|
1981
|
-
}
|
|
1982
|
-
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
1983
|
-
if (!existsSync7(employeesPath)) {
|
|
1984
|
-
return [];
|
|
1985
|
-
}
|
|
1986
|
-
const raw = await readFile3(employeesPath, "utf-8");
|
|
1987
|
-
try {
|
|
1988
|
-
return JSON.parse(raw);
|
|
1989
|
-
} catch {
|
|
1990
|
-
return [];
|
|
1991
|
-
}
|
|
1992
|
-
}
|
|
1993
|
-
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
1994
|
-
await mkdir3(path7.dirname(employeesPath), { recursive: true });
|
|
1995
|
-
await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
1996
|
-
}
|
|
1997
|
-
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
1998
|
-
if (!existsSync7(employeesPath)) return [];
|
|
1999
|
-
try {
|
|
2000
|
-
return JSON.parse(readFileSync5(employeesPath, "utf-8"));
|
|
2001
|
-
} catch {
|
|
2002
|
-
return [];
|
|
2003
|
-
}
|
|
2004
|
-
}
|
|
2005
|
-
function getEmployee(employees, name) {
|
|
2006
|
-
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
2007
|
-
}
|
|
2008
|
-
function getEmployeeByRole(employees, role) {
|
|
2009
|
-
const lower = role.toLowerCase();
|
|
2010
|
-
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
2011
|
-
}
|
|
2012
|
-
function getEmployeeNamesByRole(employees, role) {
|
|
2013
|
-
const lower = role.toLowerCase();
|
|
2014
|
-
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
2015
|
-
}
|
|
2016
|
-
function hasRole(agentName, role) {
|
|
2017
|
-
const employees = loadEmployeesSync();
|
|
2018
|
-
const emp = getEmployee(employees, agentName);
|
|
2019
|
-
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
2020
|
-
}
|
|
2021
|
-
function isMultiInstance(agentName, employees) {
|
|
2022
|
-
const roster = employees ?? loadEmployeesSync();
|
|
2023
|
-
const emp = getEmployee(roster, agentName);
|
|
2024
|
-
if (!emp) return false;
|
|
2025
|
-
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
2026
|
-
}
|
|
2027
|
-
function addEmployee(employees, employee) {
|
|
2028
|
-
const normalized = { ...employee, name: employee.name.toLowerCase() };
|
|
2029
|
-
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
2030
|
-
throw new Error(`Employee '${normalized.name}' already exists`);
|
|
2031
|
-
}
|
|
2032
|
-
return [...employees, normalized];
|
|
2033
|
-
}
|
|
2034
|
-
async function normalizeRosterCase(rosterPath) {
|
|
2035
|
-
const employees = await loadEmployees(rosterPath);
|
|
2036
|
-
let changed = false;
|
|
2037
|
-
for (const emp of employees) {
|
|
2038
|
-
if (emp.name !== emp.name.toLowerCase()) {
|
|
2039
|
-
const oldName = emp.name;
|
|
2040
|
-
emp.name = emp.name.toLowerCase();
|
|
2041
|
-
changed = true;
|
|
2042
|
-
try {
|
|
2043
|
-
const identityDir = path7.join(os6.homedir(), ".exe-os", "identity");
|
|
2044
|
-
const oldPath = path7.join(identityDir, `${oldName}.md`);
|
|
2045
|
-
const newPath = path7.join(identityDir, `${emp.name}.md`);
|
|
2046
|
-
if (existsSync7(oldPath) && !existsSync7(newPath)) {
|
|
2047
|
-
renameSync3(oldPath, newPath);
|
|
2048
|
-
} else if (existsSync7(oldPath) && oldPath !== newPath) {
|
|
2049
|
-
const content = readFileSync5(oldPath, "utf-8");
|
|
2050
|
-
writeFileSync3(newPath, content, "utf-8");
|
|
2051
|
-
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
2052
|
-
unlinkSync2(oldPath);
|
|
2053
|
-
}
|
|
2054
|
-
}
|
|
2055
|
-
} catch {
|
|
2056
|
-
}
|
|
2057
|
-
}
|
|
2058
|
-
}
|
|
2059
|
-
if (changed) {
|
|
2060
|
-
await saveEmployees(employees, rosterPath);
|
|
2061
|
-
}
|
|
2062
|
-
return changed;
|
|
2063
|
-
}
|
|
2064
|
-
function findExeBin() {
|
|
2065
|
-
try {
|
|
2066
|
-
return execSync3(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
2067
|
-
} catch {
|
|
2068
|
-
return null;
|
|
2069
|
-
}
|
|
2070
|
-
}
|
|
2071
|
-
function registerBinSymlinks(name) {
|
|
2072
|
-
const created = [];
|
|
2073
|
-
const skipped = [];
|
|
2074
|
-
const errors = [];
|
|
2075
|
-
const exeBinPath = findExeBin();
|
|
2076
|
-
if (!exeBinPath) {
|
|
2077
|
-
errors.push("Could not find 'exe-os' in PATH");
|
|
2078
|
-
return { created, skipped, errors };
|
|
2079
|
-
}
|
|
2080
|
-
const binDir = path7.dirname(exeBinPath);
|
|
2081
|
-
let target;
|
|
2082
|
-
try {
|
|
2083
|
-
target = readlinkSync(exeBinPath);
|
|
2084
|
-
} catch {
|
|
2085
|
-
errors.push("Could not read 'exe' symlink");
|
|
2086
|
-
return { created, skipped, errors };
|
|
2087
|
-
}
|
|
2088
|
-
for (const suffix of ["", "-opencode"]) {
|
|
2089
|
-
const linkName = `${name}${suffix}`;
|
|
2090
|
-
const linkPath = path7.join(binDir, linkName);
|
|
2091
|
-
if (existsSync7(linkPath)) {
|
|
2092
|
-
skipped.push(linkName);
|
|
2093
|
-
continue;
|
|
2094
|
-
}
|
|
2095
|
-
try {
|
|
2096
|
-
symlinkSync(target, linkPath);
|
|
2097
|
-
created.push(linkName);
|
|
2098
|
-
} catch (err) {
|
|
2099
|
-
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2100
|
-
}
|
|
2101
|
-
}
|
|
2102
|
-
return { created, skipped, errors };
|
|
2103
|
-
}
|
|
2104
|
-
var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
|
|
2105
|
-
var init_employees = __esm({
|
|
2106
|
-
"src/lib/employees.ts"() {
|
|
2107
|
-
"use strict";
|
|
2108
|
-
init_config();
|
|
2109
|
-
EMPLOYEES_PATH = path7.join(EXE_AI_DIR, "exe-employees.json");
|
|
2110
|
-
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
2173
|
+
INTERCOM_LOG = path7.join(os6.homedir(), ".exe-os", "intercom.log");
|
|
2111
2174
|
}
|
|
2112
2175
|
});
|
|
2113
2176
|
|
|
@@ -2322,7 +2385,7 @@ function _resetLastRelaunchCache() {
|
|
|
2322
2385
|
}
|
|
2323
2386
|
async function lastResumeCreatedAtMs(agentId) {
|
|
2324
2387
|
const client = getClient();
|
|
2325
|
-
const cmScope = sessionScopeFilter();
|
|
2388
|
+
const cmScope = sessionScopeFilter(null);
|
|
2326
2389
|
const result = await client.execute({
|
|
2327
2390
|
sql: `SELECT MAX(created_at) AS last_created_at
|
|
2328
2391
|
FROM tasks
|
|
@@ -2347,7 +2410,7 @@ async function createOrRefreshResumeTask(agentId, projectDir, openTasks) {
|
|
|
2347
2410
|
const client = getClient();
|
|
2348
2411
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2349
2412
|
const context = buildResumeContext(agentId, openTasks);
|
|
2350
|
-
const rdScope = sessionScopeFilter();
|
|
2413
|
+
const rdScope = sessionScopeFilter(null);
|
|
2351
2414
|
const existing = await client.execute({
|
|
2352
2415
|
sql: `SELECT id FROM tasks
|
|
2353
2416
|
WHERE assigned_to = ?
|
|
@@ -2381,7 +2444,7 @@ async function pollCapacityDead() {
|
|
|
2381
2444
|
const transport = getTransport();
|
|
2382
2445
|
const relaunched = [];
|
|
2383
2446
|
const registered = listSessions().filter(
|
|
2384
|
-
(s) => s.agentId !== "exe"
|
|
2447
|
+
(s) => s.agentId !== "exe" && !isCoordinatorName(s.agentId)
|
|
2385
2448
|
);
|
|
2386
2449
|
if (registered.length === 0) return [];
|
|
2387
2450
|
let liveSessions;
|
|
@@ -2441,7 +2504,7 @@ async function pollCapacityDead() {
|
|
|
2441
2504
|
reason: "capacity"
|
|
2442
2505
|
});
|
|
2443
2506
|
const client = getClient();
|
|
2444
|
-
const rlScope = sessionScopeFilter();
|
|
2507
|
+
const rlScope = sessionScopeFilter(null);
|
|
2445
2508
|
const openTasks = await client.execute({
|
|
2446
2509
|
sql: `SELECT id, title, priority, task_file, status
|
|
2447
2510
|
FROM tasks
|
|
@@ -2495,6 +2558,7 @@ var init_capacity_monitor = __esm({
|
|
|
2495
2558
|
init_session_kill_telemetry();
|
|
2496
2559
|
init_tmux_routing();
|
|
2497
2560
|
init_task_scope();
|
|
2561
|
+
init_employees();
|
|
2498
2562
|
CAPACITY_PATTERNS = [
|
|
2499
2563
|
/conversation is too long/i,
|
|
2500
2564
|
/maximum context length/i,
|
|
@@ -2644,7 +2708,7 @@ function employeeSessionName(employee, exeSession, instance) {
|
|
|
2644
2708
|
exeSession = root;
|
|
2645
2709
|
} else {
|
|
2646
2710
|
throw new Error(
|
|
2647
|
-
`Invalid
|
|
2711
|
+
`Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
|
|
2648
2712
|
);
|
|
2649
2713
|
}
|
|
2650
2714
|
}
|
|
@@ -2664,8 +2728,10 @@ function parseParentExe(sessionName, agentId) {
|
|
|
2664
2728
|
return match?.[1] ?? null;
|
|
2665
2729
|
}
|
|
2666
2730
|
function extractRootExe(name) {
|
|
2667
|
-
|
|
2668
|
-
|
|
2731
|
+
if (!name) return null;
|
|
2732
|
+
if (!name.includes("-")) return name;
|
|
2733
|
+
const parts = name.split("-").filter(Boolean);
|
|
2734
|
+
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
2669
2735
|
}
|
|
2670
2736
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
2671
2737
|
if (!existsSync10(SESSION_CACHE)) {
|
|
@@ -2810,12 +2876,14 @@ function isSessionBusy(sessionName) {
|
|
|
2810
2876
|
return state === "thinking" || state === "tool";
|
|
2811
2877
|
}
|
|
2812
2878
|
function isExeSession(sessionName) {
|
|
2813
|
-
|
|
2879
|
+
const matchesBaseWithInstance = (baseName) => sessionName === baseName || sessionName.startsWith(baseName) && /^\d+$/.test(sessionName.slice(baseName.length));
|
|
2880
|
+
const coordinatorName = getCoordinatorName();
|
|
2881
|
+
return matchesBaseWithInstance(coordinatorName) || matchesBaseWithInstance("exe");
|
|
2814
2882
|
}
|
|
2815
2883
|
function sendIntercom(targetSession) {
|
|
2816
2884
|
const transport = getTransport();
|
|
2817
2885
|
if (isExeSession(targetSession)) {
|
|
2818
|
-
logIntercom(`
|
|
2886
|
+
logIntercom(`SKIP_COORDINATOR \u2192 ${targetSession} (coordinator sessions use prompt-submit hook)`);
|
|
2819
2887
|
return "skipped_exe";
|
|
2820
2888
|
}
|
|
2821
2889
|
if (isDebounced(targetSession)) {
|
|
@@ -2867,7 +2935,7 @@ function notifyParentExe(sessionKey) {
|
|
|
2867
2935
|
if (result === "failed") {
|
|
2868
2936
|
const rootExe = resolveExeSession();
|
|
2869
2937
|
if (rootExe && rootExe !== target) {
|
|
2870
|
-
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root
|
|
2938
|
+
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root coordinator session ${rootExe}
|
|
2871
2939
|
`);
|
|
2872
2940
|
const fallback = sendIntercom(rootExe);
|
|
2873
2941
|
return fallback !== "failed";
|
|
@@ -2877,8 +2945,8 @@ function notifyParentExe(sessionKey) {
|
|
|
2877
2945
|
return true;
|
|
2878
2946
|
}
|
|
2879
2947
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
2880
|
-
if (employeeName === "exe") {
|
|
2881
|
-
return { status: "failed", sessionName: "", error: "
|
|
2948
|
+
if (employeeName === "exe" || isCoordinatorName(employeeName)) {
|
|
2949
|
+
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
2882
2950
|
}
|
|
2883
2951
|
try {
|
|
2884
2952
|
assertEmployeeLimitSync();
|
|
@@ -2887,8 +2955,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2887
2955
|
return { status: "failed", sessionName: "", error: err.message };
|
|
2888
2956
|
}
|
|
2889
2957
|
}
|
|
2890
|
-
if (
|
|
2891
|
-
const bare = employeeName.
|
|
2958
|
+
if (employeeName.includes("-")) {
|
|
2959
|
+
const bare = employeeName.split("-")[0].replace(/\d+$/, "");
|
|
2892
2960
|
return {
|
|
2893
2961
|
status: "failed",
|
|
2894
2962
|
sessionName: "",
|
|
@@ -2907,7 +2975,7 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2907
2975
|
return {
|
|
2908
2976
|
status: "failed",
|
|
2909
2977
|
sessionName: "",
|
|
2910
|
-
error: `Invalid
|
|
2978
|
+
error: `Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
|
|
2911
2979
|
};
|
|
2912
2980
|
}
|
|
2913
2981
|
}
|
|
@@ -3064,8 +3132,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3064
3132
|
const ctxContent = [
|
|
3065
3133
|
`## Session Context`,
|
|
3066
3134
|
`You are running in tmux session: ${sessionName}.`,
|
|
3067
|
-
`Your parent
|
|
3068
|
-
`Your employees (if any) use the -${exeSession} suffix
|
|
3135
|
+
`Your parent coordinator session is ${exeSession}.`,
|
|
3136
|
+
`Your employees (if any) use the -${exeSession} suffix.`
|
|
3069
3137
|
].join("\n");
|
|
3070
3138
|
writeFileSync5(ctxFile, ctxContent);
|
|
3071
3139
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
@@ -3169,6 +3237,7 @@ var init_tmux_routing = __esm({
|
|
|
3169
3237
|
init_provider_table();
|
|
3170
3238
|
init_intercom_queue();
|
|
3171
3239
|
init_plan_limits();
|
|
3240
|
+
init_employees();
|
|
3172
3241
|
SPAWN_LOCK_DIR = path10.join(os7.homedir(), ".exe-os", "spawn-locks");
|
|
3173
3242
|
SESSION_CACHE = path10.join(os7.homedir(), ".exe-os", "session-cache");
|
|
3174
3243
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
@@ -3451,6 +3520,36 @@ async function listTasks(input) {
|
|
|
3451
3520
|
tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
|
|
3452
3521
|
}));
|
|
3453
3522
|
}
|
|
3523
|
+
function isTmuxSessionAlive(identifier) {
|
|
3524
|
+
if (!identifier || identifier === "unknown") return true;
|
|
3525
|
+
try {
|
|
3526
|
+
if (identifier.startsWith("%")) {
|
|
3527
|
+
const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
|
|
3528
|
+
timeout: 2e3,
|
|
3529
|
+
encoding: "utf8",
|
|
3530
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3531
|
+
});
|
|
3532
|
+
return output.split("\n").some((l) => l.trim() === identifier);
|
|
3533
|
+
} else {
|
|
3534
|
+
execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
3535
|
+
timeout: 2e3,
|
|
3536
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3537
|
+
});
|
|
3538
|
+
return true;
|
|
3539
|
+
}
|
|
3540
|
+
} catch {
|
|
3541
|
+
if (identifier.startsWith("%")) return true;
|
|
3542
|
+
try {
|
|
3543
|
+
execSync5("tmux list-sessions", {
|
|
3544
|
+
timeout: 2e3,
|
|
3545
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3546
|
+
});
|
|
3547
|
+
return false;
|
|
3548
|
+
} catch {
|
|
3549
|
+
return true;
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3454
3553
|
function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
3455
3554
|
if (!taskContext) return null;
|
|
3456
3555
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
@@ -3513,13 +3612,59 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
3513
3612
|
});
|
|
3514
3613
|
if (claim.rowsAffected === 0) {
|
|
3515
3614
|
const current = await client.execute({
|
|
3516
|
-
sql: "SELECT status, assigned_tmux FROM tasks WHERE id = ?",
|
|
3615
|
+
sql: "SELECT status, assigned_tmux, assigned_by FROM tasks WHERE id = ?",
|
|
3517
3616
|
args: [taskId]
|
|
3518
3617
|
});
|
|
3519
3618
|
const cur = current.rows[0];
|
|
3520
|
-
const
|
|
3521
|
-
const
|
|
3522
|
-
|
|
3619
|
+
const curStatus = cur?.status ?? "unknown";
|
|
3620
|
+
const claimedBySession = cur?.assigned_tmux ?? "";
|
|
3621
|
+
const assignedBy = cur?.assigned_by ?? "";
|
|
3622
|
+
if (curStatus === "in_progress" && claimedBySession && !isTmuxSessionAlive(claimedBySession)) {
|
|
3623
|
+
process.stderr.write(
|
|
3624
|
+
`[tasks] Auto-releasing dead claim on ${taskId} (was ${claimedBySession})
|
|
3625
|
+
`
|
|
3626
|
+
);
|
|
3627
|
+
await client.execute({
|
|
3628
|
+
sql: "UPDATE tasks SET status = 'open', assigned_tmux = NULL, updated_at = ? WHERE id = ?",
|
|
3629
|
+
args: [now, taskId]
|
|
3630
|
+
});
|
|
3631
|
+
const retried = await client.execute({
|
|
3632
|
+
sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ? AND status = 'open'`,
|
|
3633
|
+
args: [tmuxSession, now, taskId]
|
|
3634
|
+
});
|
|
3635
|
+
if (retried.rowsAffected > 0) {
|
|
3636
|
+
try {
|
|
3637
|
+
await writeCheckpoint({
|
|
3638
|
+
taskId,
|
|
3639
|
+
step: "reclaimed_dead_session",
|
|
3640
|
+
contextSummary: `Task reclaimed after dead session ${claimedBySession} released.`
|
|
3641
|
+
});
|
|
3642
|
+
} catch {
|
|
3643
|
+
}
|
|
3644
|
+
return { row, taskFile, now, taskId };
|
|
3645
|
+
}
|
|
3646
|
+
}
|
|
3647
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId === "exe")) {
|
|
3648
|
+
process.stderr.write(
|
|
3649
|
+
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
3650
|
+
`
|
|
3651
|
+
);
|
|
3652
|
+
await client.execute({
|
|
3653
|
+
sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ?`,
|
|
3654
|
+
args: [tmuxSession, now, taskId]
|
|
3655
|
+
});
|
|
3656
|
+
try {
|
|
3657
|
+
await writeCheckpoint({
|
|
3658
|
+
taskId,
|
|
3659
|
+
step: "assigner_override",
|
|
3660
|
+
contextSummary: `Task force-reclaimed by assigner ${input.callerAgentId}.`
|
|
3661
|
+
});
|
|
3662
|
+
} catch {
|
|
3663
|
+
}
|
|
3664
|
+
return { row, taskFile, now, taskId };
|
|
3665
|
+
}
|
|
3666
|
+
const claimedBy = claimedBySession ? ` (claimed by ${claimedBySession})` : "";
|
|
3667
|
+
throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${curStatus}${claimedBy}`);
|
|
3523
3668
|
}
|
|
3524
3669
|
try {
|
|
3525
3670
|
await writeCheckpoint({
|
|
@@ -3617,7 +3762,7 @@ var init_tasks_crud = __esm({
|
|
|
3617
3762
|
"use strict";
|
|
3618
3763
|
init_database();
|
|
3619
3764
|
init_task_scope();
|
|
3620
|
-
DELEGATION_KEYWORDS = /parallel|delegate|wave|
|
|
3765
|
+
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
3621
3766
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
3622
3767
|
}
|
|
3623
3768
|
});
|
|
@@ -3974,7 +4119,7 @@ function findSessionForProject(projectName) {
|
|
|
3974
4119
|
const sessions = listSessions();
|
|
3975
4120
|
for (const s of sessions) {
|
|
3976
4121
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
3977
|
-
if (proj === projectName && s.agentId === "exe") return s;
|
|
4122
|
+
if (proj === projectName && (s.agentId === "exe" || isCoordinatorName(s.agentId))) return s;
|
|
3978
4123
|
}
|
|
3979
4124
|
return null;
|
|
3980
4125
|
}
|
|
@@ -4014,12 +4159,13 @@ var init_session_scope = __esm({
|
|
|
4014
4159
|
init_session_registry();
|
|
4015
4160
|
init_project_name();
|
|
4016
4161
|
init_tmux_routing();
|
|
4162
|
+
init_employees();
|
|
4017
4163
|
}
|
|
4018
4164
|
});
|
|
4019
4165
|
|
|
4020
4166
|
// src/lib/tasks-notify.ts
|
|
4021
4167
|
async function dispatchTaskToEmployee(input) {
|
|
4022
|
-
if (input.assignedTo === "exe") return { dispatched: "skipped" };
|
|
4168
|
+
if (input.assignedTo === "exe" || isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
4023
4169
|
let crossProject = false;
|
|
4024
4170
|
if (input.projectName) {
|
|
4025
4171
|
try {
|
|
@@ -4462,6 +4608,24 @@ async function updateTask(input) {
|
|
|
4462
4608
|
});
|
|
4463
4609
|
} catch {
|
|
4464
4610
|
}
|
|
4611
|
+
const assignedAgent = String(row.assigned_to);
|
|
4612
|
+
if (!isCoordinatorName(assignedAgent)) {
|
|
4613
|
+
try {
|
|
4614
|
+
const draftClient = getClient();
|
|
4615
|
+
if (input.status === "done") {
|
|
4616
|
+
await draftClient.execute({
|
|
4617
|
+
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
4618
|
+
args: [assignedAgent]
|
|
4619
|
+
});
|
|
4620
|
+
} else if (input.status === "cancelled") {
|
|
4621
|
+
await draftClient.execute({
|
|
4622
|
+
sql: `DELETE FROM memories WHERE agent_id = ? AND draft = 1`,
|
|
4623
|
+
args: [assignedAgent]
|
|
4624
|
+
});
|
|
4625
|
+
}
|
|
4626
|
+
} catch {
|
|
4627
|
+
}
|
|
4628
|
+
}
|
|
4465
4629
|
try {
|
|
4466
4630
|
const client = getClient();
|
|
4467
4631
|
const cascaded = await client.execute({
|
|
@@ -4480,8 +4644,8 @@ async function updateTask(input) {
|
|
|
4480
4644
|
}
|
|
4481
4645
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
4482
4646
|
if (isTerminal) {
|
|
4483
|
-
const
|
|
4484
|
-
if (!
|
|
4647
|
+
const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
|
|
4648
|
+
if (!isCoordinator) {
|
|
4485
4649
|
notifyTaskDone();
|
|
4486
4650
|
}
|
|
4487
4651
|
await markTaskNotificationsRead(taskFile);
|
|
@@ -4505,7 +4669,7 @@ async function updateTask(input) {
|
|
|
4505
4669
|
}
|
|
4506
4670
|
}
|
|
4507
4671
|
}
|
|
4508
|
-
if (input.status === "done" && String(row.assigned_to) !== "exe" && !process.env.VITEST) {
|
|
4672
|
+
if (input.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4509
4673
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
4510
4674
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
4511
4675
|
taskId,
|
|
@@ -4521,7 +4685,7 @@ async function updateTask(input) {
|
|
|
4521
4685
|
});
|
|
4522
4686
|
}
|
|
4523
4687
|
let nextTask;
|
|
4524
|
-
if (isTerminal && String(row.assigned_to) !== "exe") {
|
|
4688
|
+
if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
|
|
4525
4689
|
try {
|
|
4526
4690
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
4527
4691
|
} catch {
|
|
@@ -4548,12 +4712,14 @@ async function updateTask(input) {
|
|
|
4548
4712
|
async function deleteTask(taskId, baseDir) {
|
|
4549
4713
|
const client = getClient();
|
|
4550
4714
|
const { taskFile, assignedTo, assignedBy, taskSlug } = await deleteTaskCore(taskId, baseDir);
|
|
4551
|
-
const
|
|
4715
|
+
const coordinatorName = getCoordinatorName();
|
|
4716
|
+
const reviewer = assignedBy || coordinatorName;
|
|
4552
4717
|
const reviewSlug = `review-${assignedTo}-${taskSlug}`;
|
|
4553
4718
|
const reviewFile = `exe/${reviewer}/${reviewSlug}.md`;
|
|
4719
|
+
const legacyReviewFile = `exe/${coordinatorName}/${reviewSlug}.md`;
|
|
4554
4720
|
await client.execute({
|
|
4555
|
-
sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ?",
|
|
4556
|
-
args: [reviewFile, `exe/exe/${reviewSlug}.md`]
|
|
4721
|
+
sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ? OR task_file = ?",
|
|
4722
|
+
args: [reviewFile, legacyReviewFile, `exe/exe/${reviewSlug}.md`]
|
|
4557
4723
|
});
|
|
4558
4724
|
await markAsReadByTaskFile(taskFile);
|
|
4559
4725
|
await markAsReadByTaskFile(reviewFile);
|
|
@@ -4565,6 +4731,7 @@ var init_tasks = __esm({
|
|
|
4565
4731
|
init_config();
|
|
4566
4732
|
init_notifications();
|
|
4567
4733
|
init_state_bus();
|
|
4734
|
+
init_employees();
|
|
4568
4735
|
init_tasks_crud();
|
|
4569
4736
|
init_tasks_review();
|
|
4570
4737
|
init_tasks_crud();
|
|
@@ -4578,17 +4745,17 @@ var init_tasks = __esm({
|
|
|
4578
4745
|
init_database();
|
|
4579
4746
|
|
|
4580
4747
|
// src/lib/keychain.ts
|
|
4581
|
-
import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
|
|
4582
|
-
import { existsSync } from "fs";
|
|
4583
|
-
import
|
|
4584
|
-
import
|
|
4748
|
+
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
4749
|
+
import { existsSync as existsSync3 } from "fs";
|
|
4750
|
+
import path3 from "path";
|
|
4751
|
+
import os3 from "os";
|
|
4585
4752
|
var SERVICE = "exe-mem";
|
|
4586
4753
|
var ACCOUNT = "master-key";
|
|
4587
4754
|
function getKeyDir() {
|
|
4588
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
4755
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(os3.homedir(), ".exe-os");
|
|
4589
4756
|
}
|
|
4590
4757
|
function getKeyPath() {
|
|
4591
|
-
return
|
|
4758
|
+
return path3.join(getKeyDir(), "master.key");
|
|
4592
4759
|
}
|
|
4593
4760
|
async function tryKeytar() {
|
|
4594
4761
|
try {
|
|
@@ -4609,11 +4776,11 @@ async function getMasterKey() {
|
|
|
4609
4776
|
}
|
|
4610
4777
|
}
|
|
4611
4778
|
const keyPath = getKeyPath();
|
|
4612
|
-
if (!
|
|
4779
|
+
if (!existsSync3(keyPath)) {
|
|
4613
4780
|
return null;
|
|
4614
4781
|
}
|
|
4615
4782
|
try {
|
|
4616
|
-
const content = await
|
|
4783
|
+
const content = await readFile3(keyPath, "utf-8");
|
|
4617
4784
|
return Buffer.from(content.trim(), "base64");
|
|
4618
4785
|
} catch {
|
|
4619
4786
|
return null;
|
|
@@ -4714,8 +4881,10 @@ async function main() {
|
|
|
4714
4881
|
await initStore();
|
|
4715
4882
|
const fpPrefix = fingerprint.slice(0, 8);
|
|
4716
4883
|
const client = getClient();
|
|
4717
|
-
const { loadEmployeesSync: loadEmployeesSync2, getEmployeeByRole: getEmployeeByRole2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
4718
|
-
const
|
|
4884
|
+
const { loadEmployeesSync: loadEmployeesSync2, getEmployeeByRole: getEmployeeByRole2, getCoordinatorName: getCoordinatorName2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
4885
|
+
const employees = loadEmployeesSync2();
|
|
4886
|
+
const coordinatorName = getCoordinatorName2(employees);
|
|
4887
|
+
const ctoName = getEmployeeByRole2(employees, "CTO")?.name ?? coordinatorName;
|
|
4719
4888
|
const existing = await client.execute({
|
|
4720
4889
|
sql: `SELECT id FROM tasks
|
|
4721
4890
|
WHERE assigned_to = ?
|
|
@@ -4766,7 +4935,7 @@ async function main() {
|
|
|
4766
4935
|
context,
|
|
4767
4936
|
baseDir: process.cwd(),
|
|
4768
4937
|
skipDispatch: true,
|
|
4769
|
-
reviewer:
|
|
4938
|
+
reviewer: coordinatorName
|
|
4770
4939
|
});
|
|
4771
4940
|
process.stderr.write(`[bug-report-worker] Created auto-bug task for ${toolName}: ${errorSummary}
|
|
4772
4941
|
`);
|