@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
package/dist/runtime/index.js
CHANGED
|
@@ -25,6 +25,240 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
25
25
|
};
|
|
26
26
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
27
|
|
|
28
|
+
// src/lib/config.ts
|
|
29
|
+
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
30
|
+
import { readFileSync, existsSync, renameSync } from "fs";
|
|
31
|
+
import path2 from "path";
|
|
32
|
+
import os2 from "os";
|
|
33
|
+
function resolveDataDir() {
|
|
34
|
+
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
35
|
+
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
36
|
+
const newDir = path2.join(os2.homedir(), ".exe-os");
|
|
37
|
+
const legacyDir = path2.join(os2.homedir(), ".exe-mem");
|
|
38
|
+
if (!existsSync(newDir) && existsSync(legacyDir)) {
|
|
39
|
+
try {
|
|
40
|
+
renameSync(legacyDir, newDir);
|
|
41
|
+
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
42
|
+
`);
|
|
43
|
+
} catch {
|
|
44
|
+
return legacyDir;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return newDir;
|
|
48
|
+
}
|
|
49
|
+
function migrateLegacyConfig(raw) {
|
|
50
|
+
if ("r2" in raw) {
|
|
51
|
+
process.stderr.write(
|
|
52
|
+
"[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"
|
|
53
|
+
);
|
|
54
|
+
delete raw.r2;
|
|
55
|
+
}
|
|
56
|
+
if ("syncIntervalMs" in raw) {
|
|
57
|
+
delete raw.syncIntervalMs;
|
|
58
|
+
}
|
|
59
|
+
return raw;
|
|
60
|
+
}
|
|
61
|
+
function migrateConfig(raw) {
|
|
62
|
+
const fromVersion = typeof raw.config_version === "number" ? raw.config_version : 0;
|
|
63
|
+
let currentVersion = fromVersion;
|
|
64
|
+
let migrated = false;
|
|
65
|
+
if (currentVersion > CURRENT_CONFIG_VERSION) {
|
|
66
|
+
return { config: raw, migrated: false, fromVersion };
|
|
67
|
+
}
|
|
68
|
+
for (const migration of CONFIG_MIGRATIONS) {
|
|
69
|
+
if (currentVersion === migration.from && migration.to <= CURRENT_CONFIG_VERSION) {
|
|
70
|
+
raw = migration.migrate(raw);
|
|
71
|
+
currentVersion = migration.to;
|
|
72
|
+
migrated = true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return { config: raw, migrated, fromVersion };
|
|
76
|
+
}
|
|
77
|
+
function normalizeScalingRoadmap(raw) {
|
|
78
|
+
const defaultAuto = DEFAULT_CONFIG.scalingRoadmap.rerankerAutoTrigger;
|
|
79
|
+
const userRoadmap = raw.scalingRoadmap ?? {};
|
|
80
|
+
const userAuto = userRoadmap.rerankerAutoTrigger ?? {};
|
|
81
|
+
if (userAuto.enabled === void 0 && raw.rerankerEnabled !== void 0) {
|
|
82
|
+
userAuto.enabled = raw.rerankerEnabled;
|
|
83
|
+
}
|
|
84
|
+
raw.scalingRoadmap = {
|
|
85
|
+
...userRoadmap,
|
|
86
|
+
rerankerAutoTrigger: { ...defaultAuto, ...userAuto }
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function normalizeSessionLifecycle(raw) {
|
|
90
|
+
const defaultSL = DEFAULT_CONFIG.sessionLifecycle;
|
|
91
|
+
const userSL = raw.sessionLifecycle ?? {};
|
|
92
|
+
raw.sessionLifecycle = { ...defaultSL, ...userSL };
|
|
93
|
+
}
|
|
94
|
+
function normalizeAutoUpdate(raw) {
|
|
95
|
+
const defaultAU = DEFAULT_CONFIG.autoUpdate;
|
|
96
|
+
const userAU = raw.autoUpdate ?? {};
|
|
97
|
+
raw.autoUpdate = { ...defaultAU, ...userAU };
|
|
98
|
+
}
|
|
99
|
+
async function loadConfig() {
|
|
100
|
+
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
101
|
+
await mkdir(dir, { recursive: true });
|
|
102
|
+
const configPath = path2.join(dir, "config.json");
|
|
103
|
+
if (!existsSync(configPath)) {
|
|
104
|
+
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
105
|
+
}
|
|
106
|
+
const raw = await readFile(configPath, "utf-8");
|
|
107
|
+
try {
|
|
108
|
+
let parsed = JSON.parse(raw);
|
|
109
|
+
parsed = migrateLegacyConfig(parsed);
|
|
110
|
+
const { config: migratedCfg, migrated, fromVersion } = migrateConfig(parsed);
|
|
111
|
+
if (migrated) {
|
|
112
|
+
process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
|
|
113
|
+
`);
|
|
114
|
+
try {
|
|
115
|
+
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
116
|
+
} catch {
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
normalizeScalingRoadmap(migratedCfg);
|
|
120
|
+
normalizeSessionLifecycle(migratedCfg);
|
|
121
|
+
normalizeAutoUpdate(migratedCfg);
|
|
122
|
+
const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
123
|
+
if (config.dbPath.startsWith("~")) {
|
|
124
|
+
config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
|
|
125
|
+
}
|
|
126
|
+
return config;
|
|
127
|
+
} catch {
|
|
128
|
+
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
|
|
132
|
+
var init_config = __esm({
|
|
133
|
+
"src/lib/config.ts"() {
|
|
134
|
+
"use strict";
|
|
135
|
+
EXE_AI_DIR = resolveDataDir();
|
|
136
|
+
DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
|
|
137
|
+
MODELS_DIR = path2.join(EXE_AI_DIR, "models");
|
|
138
|
+
CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
|
|
139
|
+
LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
|
|
140
|
+
CURRENT_CONFIG_VERSION = 1;
|
|
141
|
+
DEFAULT_CONFIG = {
|
|
142
|
+
config_version: CURRENT_CONFIG_VERSION,
|
|
143
|
+
dbPath: DB_PATH,
|
|
144
|
+
modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
|
|
145
|
+
embeddingDim: 1024,
|
|
146
|
+
batchSize: 20,
|
|
147
|
+
flushIntervalMs: 1e4,
|
|
148
|
+
autoIngestion: true,
|
|
149
|
+
autoRetrieval: true,
|
|
150
|
+
searchMode: "hybrid",
|
|
151
|
+
hookSearchMode: "hybrid",
|
|
152
|
+
fileGrepEnabled: true,
|
|
153
|
+
splashEffect: true,
|
|
154
|
+
consolidationEnabled: true,
|
|
155
|
+
consolidationIntervalMs: 6 * 60 * 60 * 1e3,
|
|
156
|
+
consolidationModel: "claude-haiku-4-5-20251001",
|
|
157
|
+
consolidationMaxCallsPerRun: 20,
|
|
158
|
+
selfQueryRouter: true,
|
|
159
|
+
selfQueryModel: "claude-haiku-4-5-20251001",
|
|
160
|
+
rerankerEnabled: true,
|
|
161
|
+
scalingRoadmap: {
|
|
162
|
+
rerankerAutoTrigger: {
|
|
163
|
+
enabled: true,
|
|
164
|
+
broadQueryMinCardinality: 5e4,
|
|
165
|
+
fetchTopK: 150,
|
|
166
|
+
returnTopK: 5
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
graphRagEnabled: true,
|
|
170
|
+
wikiEnabled: false,
|
|
171
|
+
wikiUrl: "",
|
|
172
|
+
wikiApiKey: "",
|
|
173
|
+
wikiSyncIntervalMs: 30 * 60 * 1e3,
|
|
174
|
+
wikiWorkspaceMapping: {},
|
|
175
|
+
wikiAutoUpdate: true,
|
|
176
|
+
wikiAutoUpdateThreshold: 0.5,
|
|
177
|
+
wikiAutoUpdateCreateNew: true,
|
|
178
|
+
skillLearning: true,
|
|
179
|
+
skillThreshold: 3,
|
|
180
|
+
skillModel: "claude-haiku-4-5-20251001",
|
|
181
|
+
exeHeartbeat: {
|
|
182
|
+
enabled: true,
|
|
183
|
+
intervalSeconds: 60,
|
|
184
|
+
staleInProgressThresholdHours: 2
|
|
185
|
+
},
|
|
186
|
+
sessionLifecycle: {
|
|
187
|
+
idleKillEnabled: true,
|
|
188
|
+
idleKillTicksRequired: 3,
|
|
189
|
+
idleKillIntercomAckWindowMs: 1e4,
|
|
190
|
+
maxAutoInstances: 10
|
|
191
|
+
},
|
|
192
|
+
autoUpdate: {
|
|
193
|
+
checkOnBoot: true,
|
|
194
|
+
autoInstall: false,
|
|
195
|
+
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
CONFIG_MIGRATIONS = [
|
|
199
|
+
{
|
|
200
|
+
from: 0,
|
|
201
|
+
to: 1,
|
|
202
|
+
migrate: (cfg) => {
|
|
203
|
+
cfg.config_version = 1;
|
|
204
|
+
return cfg;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
];
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// src/lib/employees.ts
|
|
212
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
213
|
+
import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
214
|
+
import { execSync } from "child_process";
|
|
215
|
+
import path3 from "path";
|
|
216
|
+
import os3 from "os";
|
|
217
|
+
function normalizeRole(role) {
|
|
218
|
+
return (role ?? "").trim().toLowerCase();
|
|
219
|
+
}
|
|
220
|
+
function isCoordinatorRole(role) {
|
|
221
|
+
return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
|
|
222
|
+
}
|
|
223
|
+
function getCoordinatorEmployee(employees) {
|
|
224
|
+
return employees.find((e) => isCoordinatorRole(e.role));
|
|
225
|
+
}
|
|
226
|
+
function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
227
|
+
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
228
|
+
}
|
|
229
|
+
function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
230
|
+
if (!agentName) return false;
|
|
231
|
+
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
232
|
+
}
|
|
233
|
+
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
234
|
+
if (!existsSync2(employeesPath)) return [];
|
|
235
|
+
try {
|
|
236
|
+
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
237
|
+
} catch {
|
|
238
|
+
return [];
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function getEmployee(employees, name) {
|
|
242
|
+
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
243
|
+
}
|
|
244
|
+
function isMultiInstance(agentName, employees) {
|
|
245
|
+
const roster = employees ?? loadEmployeesSync();
|
|
246
|
+
const emp = getEmployee(roster, agentName);
|
|
247
|
+
if (!emp) return false;
|
|
248
|
+
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
249
|
+
}
|
|
250
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
251
|
+
var init_employees = __esm({
|
|
252
|
+
"src/lib/employees.ts"() {
|
|
253
|
+
"use strict";
|
|
254
|
+
init_config();
|
|
255
|
+
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
256
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
257
|
+
COORDINATOR_ROLE = "COO";
|
|
258
|
+
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
28
262
|
// src/lib/session-registry.ts
|
|
29
263
|
var session_registry_exports = {};
|
|
30
264
|
__export(session_registry_exports, {
|
|
@@ -32,13 +266,13 @@ __export(session_registry_exports, {
|
|
|
32
266
|
pruneStaleSessions: () => pruneStaleSessions,
|
|
33
267
|
registerSession: () => registerSession
|
|
34
268
|
});
|
|
35
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
36
|
-
import { execSync } from "child_process";
|
|
37
|
-
import
|
|
38
|
-
import
|
|
269
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync3 } from "fs";
|
|
270
|
+
import { execSync as execSync2 } from "child_process";
|
|
271
|
+
import path4 from "path";
|
|
272
|
+
import os4 from "os";
|
|
39
273
|
function registerSession(entry) {
|
|
40
|
-
const dir =
|
|
41
|
-
if (!
|
|
274
|
+
const dir = path4.dirname(REGISTRY_PATH);
|
|
275
|
+
if (!existsSync3(dir)) {
|
|
42
276
|
mkdirSync(dir, { recursive: true });
|
|
43
277
|
}
|
|
44
278
|
const sessions = listSessions();
|
|
@@ -48,11 +282,11 @@ function registerSession(entry) {
|
|
|
48
282
|
} else {
|
|
49
283
|
sessions.push(entry);
|
|
50
284
|
}
|
|
51
|
-
|
|
285
|
+
writeFileSync2(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
52
286
|
}
|
|
53
287
|
function listSessions() {
|
|
54
288
|
try {
|
|
55
|
-
const raw =
|
|
289
|
+
const raw = readFileSync3(REGISTRY_PATH, "utf8");
|
|
56
290
|
return JSON.parse(raw);
|
|
57
291
|
} catch {
|
|
58
292
|
return [];
|
|
@@ -63,7 +297,7 @@ function pruneStaleSessions() {
|
|
|
63
297
|
if (sessions.length === 0) return 0;
|
|
64
298
|
let liveSessions = [];
|
|
65
299
|
try {
|
|
66
|
-
liveSessions =
|
|
300
|
+
liveSessions = execSync2("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
|
|
67
301
|
encoding: "utf8"
|
|
68
302
|
}).trim().split("\n").filter(Boolean);
|
|
69
303
|
} catch {
|
|
@@ -73,7 +307,7 @@ function pruneStaleSessions() {
|
|
|
73
307
|
const alive = sessions.filter((s) => liveSet.has(s.windowName));
|
|
74
308
|
const pruned = sessions.length - alive.length;
|
|
75
309
|
if (pruned > 0) {
|
|
76
|
-
|
|
310
|
+
writeFileSync2(REGISTRY_PATH, JSON.stringify(alive, null, 2));
|
|
77
311
|
}
|
|
78
312
|
return pruned;
|
|
79
313
|
}
|
|
@@ -81,18 +315,18 @@ var REGISTRY_PATH;
|
|
|
81
315
|
var init_session_registry = __esm({
|
|
82
316
|
"src/lib/session-registry.ts"() {
|
|
83
317
|
"use strict";
|
|
84
|
-
REGISTRY_PATH =
|
|
318
|
+
REGISTRY_PATH = path4.join(os4.homedir(), ".exe-os", "session-registry.json");
|
|
85
319
|
}
|
|
86
320
|
});
|
|
87
321
|
|
|
88
322
|
// src/lib/session-key.ts
|
|
89
|
-
import { execSync as
|
|
323
|
+
import { execSync as execSync3 } from "child_process";
|
|
90
324
|
function getSessionKey() {
|
|
91
325
|
if (_cached) return _cached;
|
|
92
326
|
let pid = process.ppid;
|
|
93
327
|
for (let i = 0; i < 10; i++) {
|
|
94
328
|
try {
|
|
95
|
-
const info =
|
|
329
|
+
const info = execSync3(`ps -p ${pid} -o ppid=,comm=`, {
|
|
96
330
|
encoding: "utf8",
|
|
97
331
|
timeout: 2e3
|
|
98
332
|
}).trim();
|
|
@@ -228,14 +462,14 @@ var init_transport = __esm({
|
|
|
228
462
|
});
|
|
229
463
|
|
|
230
464
|
// src/lib/cc-agent-support.ts
|
|
231
|
-
import { execSync as
|
|
465
|
+
import { execSync as execSync4 } from "child_process";
|
|
232
466
|
function _resetCcAgentSupportCache() {
|
|
233
467
|
_cachedSupport = null;
|
|
234
468
|
}
|
|
235
469
|
function claudeSupportsAgentFlag() {
|
|
236
470
|
if (_cachedSupport !== null) return _cachedSupport;
|
|
237
471
|
try {
|
|
238
|
-
const helpOutput =
|
|
472
|
+
const helpOutput = execSync4("claude --help 2>&1", {
|
|
239
473
|
encoding: "utf-8",
|
|
240
474
|
timeout: 5e3
|
|
241
475
|
});
|
|
@@ -311,17 +545,17 @@ var init_provider_table = __esm({
|
|
|
311
545
|
});
|
|
312
546
|
|
|
313
547
|
// src/lib/intercom-queue.ts
|
|
314
|
-
import { readFileSync as
|
|
315
|
-
import
|
|
316
|
-
import
|
|
548
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
|
|
549
|
+
import path5 from "path";
|
|
550
|
+
import os5 from "os";
|
|
317
551
|
function ensureDir() {
|
|
318
|
-
const dir =
|
|
319
|
-
if (!
|
|
552
|
+
const dir = path5.dirname(QUEUE_PATH);
|
|
553
|
+
if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
|
|
320
554
|
}
|
|
321
555
|
function readQueue() {
|
|
322
556
|
try {
|
|
323
|
-
if (!
|
|
324
|
-
return JSON.parse(
|
|
557
|
+
if (!existsSync4(QUEUE_PATH)) return [];
|
|
558
|
+
return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
|
|
325
559
|
} catch {
|
|
326
560
|
return [];
|
|
327
561
|
}
|
|
@@ -329,8 +563,8 @@ function readQueue() {
|
|
|
329
563
|
function writeQueue(queue) {
|
|
330
564
|
ensureDir();
|
|
331
565
|
const tmp = `${QUEUE_PATH}.tmp`;
|
|
332
|
-
|
|
333
|
-
|
|
566
|
+
writeFileSync3(tmp, JSON.stringify(queue, null, 2));
|
|
567
|
+
renameSync3(tmp, QUEUE_PATH);
|
|
334
568
|
}
|
|
335
569
|
function queueIntercom(targetSession, reason) {
|
|
336
570
|
const queue = readQueue();
|
|
@@ -353,9 +587,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
|
353
587
|
var init_intercom_queue = __esm({
|
|
354
588
|
"src/lib/intercom-queue.ts"() {
|
|
355
589
|
"use strict";
|
|
356
|
-
QUEUE_PATH =
|
|
590
|
+
QUEUE_PATH = path5.join(os5.homedir(), ".exe-os", "intercom-queue.json");
|
|
357
591
|
TTL_MS = 60 * 60 * 1e3;
|
|
358
|
-
INTERCOM_LOG =
|
|
592
|
+
INTERCOM_LOG = path5.join(os5.homedir(), ".exe-os", "intercom.log");
|
|
359
593
|
}
|
|
360
594
|
});
|
|
361
595
|
|
|
@@ -398,7 +632,7 @@ function wrapWithRetry(client) {
|
|
|
398
632
|
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
399
633
|
}
|
|
400
634
|
if (prop === "batch") {
|
|
401
|
-
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
635
|
+
return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
|
|
402
636
|
}
|
|
403
637
|
return Reflect.get(target, prop, receiver);
|
|
404
638
|
}
|
|
@@ -561,22 +795,24 @@ async function ensureSchema() {
|
|
|
561
795
|
ON behaviors(agent_id, active);
|
|
562
796
|
`);
|
|
563
797
|
try {
|
|
798
|
+
const coordinatorName = getCoordinatorName();
|
|
564
799
|
const existing = await client.execute({
|
|
565
|
-
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id =
|
|
566
|
-
args: []
|
|
800
|
+
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
|
|
801
|
+
args: [coordinatorName]
|
|
567
802
|
});
|
|
568
803
|
if (Number(existing.rows[0]?.cnt) === 0) {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
804
|
+
const seededAt = "2026-03-25T00:00:00Z";
|
|
805
|
+
for (const [domain, content] of [
|
|
806
|
+
["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
|
|
807
|
+
["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
|
|
808
|
+
["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
|
|
809
|
+
]) {
|
|
810
|
+
await client.execute({
|
|
811
|
+
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
812
|
+
VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
|
|
813
|
+
args: [coordinatorName, domain, content, seededAt, seededAt]
|
|
814
|
+
});
|
|
815
|
+
}
|
|
580
816
|
}
|
|
581
817
|
} catch {
|
|
582
818
|
}
|
|
@@ -1268,6 +1504,39 @@ async function ensureSchema() {
|
|
|
1268
1504
|
} catch {
|
|
1269
1505
|
}
|
|
1270
1506
|
}
|
|
1507
|
+
try {
|
|
1508
|
+
await client.execute({
|
|
1509
|
+
sql: `ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0`,
|
|
1510
|
+
args: []
|
|
1511
|
+
});
|
|
1512
|
+
} catch {
|
|
1513
|
+
}
|
|
1514
|
+
try {
|
|
1515
|
+
await client.execute(
|
|
1516
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_draft ON memories(draft) WHERE draft = 1`
|
|
1517
|
+
);
|
|
1518
|
+
} catch {
|
|
1519
|
+
}
|
|
1520
|
+
try {
|
|
1521
|
+
await client.execute({
|
|
1522
|
+
sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
|
|
1523
|
+
args: []
|
|
1524
|
+
});
|
|
1525
|
+
} catch {
|
|
1526
|
+
}
|
|
1527
|
+
try {
|
|
1528
|
+
await client.execute(
|
|
1529
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
|
|
1530
|
+
);
|
|
1531
|
+
} catch {
|
|
1532
|
+
}
|
|
1533
|
+
try {
|
|
1534
|
+
await client.execute({
|
|
1535
|
+
sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
|
|
1536
|
+
args: []
|
|
1537
|
+
});
|
|
1538
|
+
} catch {
|
|
1539
|
+
}
|
|
1271
1540
|
}
|
|
1272
1541
|
async function disposeDatabase() {
|
|
1273
1542
|
if (_client) {
|
|
@@ -1281,6 +1550,7 @@ var init_database = __esm({
|
|
|
1281
1550
|
"src/lib/database.ts"() {
|
|
1282
1551
|
"use strict";
|
|
1283
1552
|
init_db_retry();
|
|
1553
|
+
init_employees();
|
|
1284
1554
|
_client = null;
|
|
1285
1555
|
_resilientClient = null;
|
|
1286
1556
|
initTurso = initDatabase;
|
|
@@ -1288,228 +1558,6 @@ var init_database = __esm({
|
|
|
1288
1558
|
}
|
|
1289
1559
|
});
|
|
1290
1560
|
|
|
1291
|
-
// src/lib/config.ts
|
|
1292
|
-
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
1293
|
-
import { readFileSync as readFileSync3, existsSync as existsSync3, renameSync as renameSync2 } from "fs";
|
|
1294
|
-
import path4 from "path";
|
|
1295
|
-
import os4 from "os";
|
|
1296
|
-
function resolveDataDir() {
|
|
1297
|
-
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
1298
|
-
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
1299
|
-
const newDir = path4.join(os4.homedir(), ".exe-os");
|
|
1300
|
-
const legacyDir = path4.join(os4.homedir(), ".exe-mem");
|
|
1301
|
-
if (!existsSync3(newDir) && existsSync3(legacyDir)) {
|
|
1302
|
-
try {
|
|
1303
|
-
renameSync2(legacyDir, newDir);
|
|
1304
|
-
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
1305
|
-
`);
|
|
1306
|
-
} catch {
|
|
1307
|
-
return legacyDir;
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
return newDir;
|
|
1311
|
-
}
|
|
1312
|
-
function migrateLegacyConfig(raw) {
|
|
1313
|
-
if ("r2" in raw) {
|
|
1314
|
-
process.stderr.write(
|
|
1315
|
-
"[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"
|
|
1316
|
-
);
|
|
1317
|
-
delete raw.r2;
|
|
1318
|
-
}
|
|
1319
|
-
if ("syncIntervalMs" in raw) {
|
|
1320
|
-
delete raw.syncIntervalMs;
|
|
1321
|
-
}
|
|
1322
|
-
return raw;
|
|
1323
|
-
}
|
|
1324
|
-
function migrateConfig(raw) {
|
|
1325
|
-
const fromVersion = typeof raw.config_version === "number" ? raw.config_version : 0;
|
|
1326
|
-
let currentVersion = fromVersion;
|
|
1327
|
-
let migrated = false;
|
|
1328
|
-
if (currentVersion > CURRENT_CONFIG_VERSION) {
|
|
1329
|
-
return { config: raw, migrated: false, fromVersion };
|
|
1330
|
-
}
|
|
1331
|
-
for (const migration of CONFIG_MIGRATIONS) {
|
|
1332
|
-
if (currentVersion === migration.from && migration.to <= CURRENT_CONFIG_VERSION) {
|
|
1333
|
-
raw = migration.migrate(raw);
|
|
1334
|
-
currentVersion = migration.to;
|
|
1335
|
-
migrated = true;
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
return { config: raw, migrated, fromVersion };
|
|
1339
|
-
}
|
|
1340
|
-
function normalizeScalingRoadmap(raw) {
|
|
1341
|
-
const defaultAuto = DEFAULT_CONFIG.scalingRoadmap.rerankerAutoTrigger;
|
|
1342
|
-
const userRoadmap = raw.scalingRoadmap ?? {};
|
|
1343
|
-
const userAuto = userRoadmap.rerankerAutoTrigger ?? {};
|
|
1344
|
-
if (userAuto.enabled === void 0 && raw.rerankerEnabled !== void 0) {
|
|
1345
|
-
userAuto.enabled = raw.rerankerEnabled;
|
|
1346
|
-
}
|
|
1347
|
-
raw.scalingRoadmap = {
|
|
1348
|
-
...userRoadmap,
|
|
1349
|
-
rerankerAutoTrigger: { ...defaultAuto, ...userAuto }
|
|
1350
|
-
};
|
|
1351
|
-
}
|
|
1352
|
-
function normalizeSessionLifecycle(raw) {
|
|
1353
|
-
const defaultSL = DEFAULT_CONFIG.sessionLifecycle;
|
|
1354
|
-
const userSL = raw.sessionLifecycle ?? {};
|
|
1355
|
-
raw.sessionLifecycle = { ...defaultSL, ...userSL };
|
|
1356
|
-
}
|
|
1357
|
-
function normalizeAutoUpdate(raw) {
|
|
1358
|
-
const defaultAU = DEFAULT_CONFIG.autoUpdate;
|
|
1359
|
-
const userAU = raw.autoUpdate ?? {};
|
|
1360
|
-
raw.autoUpdate = { ...defaultAU, ...userAU };
|
|
1361
|
-
}
|
|
1362
|
-
async function loadConfig() {
|
|
1363
|
-
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
1364
|
-
await mkdir(dir, { recursive: true });
|
|
1365
|
-
const configPath = path4.join(dir, "config.json");
|
|
1366
|
-
if (!existsSync3(configPath)) {
|
|
1367
|
-
return { ...DEFAULT_CONFIG, dbPath: path4.join(dir, "memories.db") };
|
|
1368
|
-
}
|
|
1369
|
-
const raw = await readFile(configPath, "utf-8");
|
|
1370
|
-
try {
|
|
1371
|
-
let parsed = JSON.parse(raw);
|
|
1372
|
-
parsed = migrateLegacyConfig(parsed);
|
|
1373
|
-
const { config: migratedCfg, migrated, fromVersion } = migrateConfig(parsed);
|
|
1374
|
-
if (migrated) {
|
|
1375
|
-
process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
|
|
1376
|
-
`);
|
|
1377
|
-
try {
|
|
1378
|
-
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
1379
|
-
} catch {
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
normalizeScalingRoadmap(migratedCfg);
|
|
1383
|
-
normalizeSessionLifecycle(migratedCfg);
|
|
1384
|
-
normalizeAutoUpdate(migratedCfg);
|
|
1385
|
-
const config = { ...DEFAULT_CONFIG, dbPath: path4.join(dir, "memories.db"), ...migratedCfg };
|
|
1386
|
-
if (config.dbPath.startsWith("~")) {
|
|
1387
|
-
config.dbPath = config.dbPath.replace(/^~/, os4.homedir());
|
|
1388
|
-
}
|
|
1389
|
-
return config;
|
|
1390
|
-
} catch {
|
|
1391
|
-
return { ...DEFAULT_CONFIG, dbPath: path4.join(dir, "memories.db") };
|
|
1392
|
-
}
|
|
1393
|
-
}
|
|
1394
|
-
var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
|
|
1395
|
-
var init_config = __esm({
|
|
1396
|
-
"src/lib/config.ts"() {
|
|
1397
|
-
"use strict";
|
|
1398
|
-
EXE_AI_DIR = resolveDataDir();
|
|
1399
|
-
DB_PATH = path4.join(EXE_AI_DIR, "memories.db");
|
|
1400
|
-
MODELS_DIR = path4.join(EXE_AI_DIR, "models");
|
|
1401
|
-
CONFIG_PATH = path4.join(EXE_AI_DIR, "config.json");
|
|
1402
|
-
LEGACY_LANCE_PATH = path4.join(EXE_AI_DIR, "local.lance");
|
|
1403
|
-
CURRENT_CONFIG_VERSION = 1;
|
|
1404
|
-
DEFAULT_CONFIG = {
|
|
1405
|
-
config_version: CURRENT_CONFIG_VERSION,
|
|
1406
|
-
dbPath: DB_PATH,
|
|
1407
|
-
modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
|
|
1408
|
-
embeddingDim: 1024,
|
|
1409
|
-
batchSize: 20,
|
|
1410
|
-
flushIntervalMs: 1e4,
|
|
1411
|
-
autoIngestion: true,
|
|
1412
|
-
autoRetrieval: true,
|
|
1413
|
-
searchMode: "hybrid",
|
|
1414
|
-
hookSearchMode: "hybrid",
|
|
1415
|
-
fileGrepEnabled: true,
|
|
1416
|
-
splashEffect: true,
|
|
1417
|
-
consolidationEnabled: true,
|
|
1418
|
-
consolidationIntervalMs: 6 * 60 * 60 * 1e3,
|
|
1419
|
-
consolidationModel: "claude-haiku-4-5-20251001",
|
|
1420
|
-
consolidationMaxCallsPerRun: 20,
|
|
1421
|
-
selfQueryRouter: true,
|
|
1422
|
-
selfQueryModel: "claude-haiku-4-5-20251001",
|
|
1423
|
-
rerankerEnabled: true,
|
|
1424
|
-
scalingRoadmap: {
|
|
1425
|
-
rerankerAutoTrigger: {
|
|
1426
|
-
enabled: true,
|
|
1427
|
-
broadQueryMinCardinality: 5e4,
|
|
1428
|
-
fetchTopK: 150,
|
|
1429
|
-
returnTopK: 5
|
|
1430
|
-
}
|
|
1431
|
-
},
|
|
1432
|
-
graphRagEnabled: true,
|
|
1433
|
-
wikiEnabled: false,
|
|
1434
|
-
wikiUrl: "",
|
|
1435
|
-
wikiApiKey: "",
|
|
1436
|
-
wikiSyncIntervalMs: 30 * 60 * 1e3,
|
|
1437
|
-
wikiWorkspaceMapping: {
|
|
1438
|
-
exe: "Executive",
|
|
1439
|
-
yoshi: "Engineering",
|
|
1440
|
-
mari: "Marketing",
|
|
1441
|
-
tom: "Engineering",
|
|
1442
|
-
sasha: "Production"
|
|
1443
|
-
},
|
|
1444
|
-
wikiAutoUpdate: true,
|
|
1445
|
-
wikiAutoUpdateThreshold: 0.5,
|
|
1446
|
-
wikiAutoUpdateCreateNew: true,
|
|
1447
|
-
skillLearning: true,
|
|
1448
|
-
skillThreshold: 3,
|
|
1449
|
-
skillModel: "claude-haiku-4-5-20251001",
|
|
1450
|
-
exeHeartbeat: {
|
|
1451
|
-
enabled: true,
|
|
1452
|
-
intervalSeconds: 60,
|
|
1453
|
-
staleInProgressThresholdHours: 2
|
|
1454
|
-
},
|
|
1455
|
-
sessionLifecycle: {
|
|
1456
|
-
idleKillEnabled: true,
|
|
1457
|
-
idleKillTicksRequired: 3,
|
|
1458
|
-
idleKillIntercomAckWindowMs: 1e4,
|
|
1459
|
-
maxAutoInstances: 10
|
|
1460
|
-
},
|
|
1461
|
-
autoUpdate: {
|
|
1462
|
-
checkOnBoot: true,
|
|
1463
|
-
autoInstall: false,
|
|
1464
|
-
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
1465
|
-
}
|
|
1466
|
-
};
|
|
1467
|
-
CONFIG_MIGRATIONS = [
|
|
1468
|
-
{
|
|
1469
|
-
from: 0,
|
|
1470
|
-
to: 1,
|
|
1471
|
-
migrate: (cfg) => {
|
|
1472
|
-
cfg.config_version = 1;
|
|
1473
|
-
return cfg;
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
];
|
|
1477
|
-
}
|
|
1478
|
-
});
|
|
1479
|
-
|
|
1480
|
-
// src/lib/employees.ts
|
|
1481
|
-
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
1482
|
-
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
1483
|
-
import { execSync as execSync4 } from "child_process";
|
|
1484
|
-
import path5 from "path";
|
|
1485
|
-
import os5 from "os";
|
|
1486
|
-
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
1487
|
-
if (!existsSync4(employeesPath)) return [];
|
|
1488
|
-
try {
|
|
1489
|
-
return JSON.parse(readFileSync4(employeesPath, "utf-8"));
|
|
1490
|
-
} catch {
|
|
1491
|
-
return [];
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
function getEmployee(employees, name) {
|
|
1495
|
-
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
1496
|
-
}
|
|
1497
|
-
function isMultiInstance(agentName, employees) {
|
|
1498
|
-
const roster = employees ?? loadEmployeesSync();
|
|
1499
|
-
const emp = getEmployee(roster, agentName);
|
|
1500
|
-
if (!emp) return false;
|
|
1501
|
-
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
1502
|
-
}
|
|
1503
|
-
var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
|
|
1504
|
-
var init_employees = __esm({
|
|
1505
|
-
"src/lib/employees.ts"() {
|
|
1506
|
-
"use strict";
|
|
1507
|
-
init_config();
|
|
1508
|
-
EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
|
|
1509
|
-
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
1510
|
-
}
|
|
1511
|
-
});
|
|
1512
|
-
|
|
1513
1561
|
// src/lib/license.ts
|
|
1514
1562
|
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
1515
1563
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
@@ -2026,6 +2074,36 @@ async function listTasks(input) {
|
|
|
2026
2074
|
tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
|
|
2027
2075
|
}));
|
|
2028
2076
|
}
|
|
2077
|
+
function isTmuxSessionAlive(identifier) {
|
|
2078
|
+
if (!identifier || identifier === "unknown") return true;
|
|
2079
|
+
try {
|
|
2080
|
+
if (identifier.startsWith("%")) {
|
|
2081
|
+
const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
|
|
2082
|
+
timeout: 2e3,
|
|
2083
|
+
encoding: "utf8",
|
|
2084
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2085
|
+
});
|
|
2086
|
+
return output.split("\n").some((l) => l.trim() === identifier);
|
|
2087
|
+
} else {
|
|
2088
|
+
execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
2089
|
+
timeout: 2e3,
|
|
2090
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2091
|
+
});
|
|
2092
|
+
return true;
|
|
2093
|
+
}
|
|
2094
|
+
} catch {
|
|
2095
|
+
if (identifier.startsWith("%")) return true;
|
|
2096
|
+
try {
|
|
2097
|
+
execSync5("tmux list-sessions", {
|
|
2098
|
+
timeout: 2e3,
|
|
2099
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2100
|
+
});
|
|
2101
|
+
return false;
|
|
2102
|
+
} catch {
|
|
2103
|
+
return true;
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2029
2107
|
function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
2030
2108
|
if (!taskContext) return null;
|
|
2031
2109
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
@@ -2088,13 +2166,59 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2088
2166
|
});
|
|
2089
2167
|
if (claim.rowsAffected === 0) {
|
|
2090
2168
|
const current = await client.execute({
|
|
2091
|
-
sql: "SELECT status, assigned_tmux FROM tasks WHERE id = ?",
|
|
2169
|
+
sql: "SELECT status, assigned_tmux, assigned_by FROM tasks WHERE id = ?",
|
|
2092
2170
|
args: [taskId]
|
|
2093
2171
|
});
|
|
2094
2172
|
const cur = current.rows[0];
|
|
2095
|
-
const
|
|
2096
|
-
const
|
|
2097
|
-
|
|
2173
|
+
const curStatus = cur?.status ?? "unknown";
|
|
2174
|
+
const claimedBySession = cur?.assigned_tmux ?? "";
|
|
2175
|
+
const assignedBy = cur?.assigned_by ?? "";
|
|
2176
|
+
if (curStatus === "in_progress" && claimedBySession && !isTmuxSessionAlive(claimedBySession)) {
|
|
2177
|
+
process.stderr.write(
|
|
2178
|
+
`[tasks] Auto-releasing dead claim on ${taskId} (was ${claimedBySession})
|
|
2179
|
+
`
|
|
2180
|
+
);
|
|
2181
|
+
await client.execute({
|
|
2182
|
+
sql: "UPDATE tasks SET status = 'open', assigned_tmux = NULL, updated_at = ? WHERE id = ?",
|
|
2183
|
+
args: [now, taskId]
|
|
2184
|
+
});
|
|
2185
|
+
const retried = await client.execute({
|
|
2186
|
+
sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ? AND status = 'open'`,
|
|
2187
|
+
args: [tmuxSession, now, taskId]
|
|
2188
|
+
});
|
|
2189
|
+
if (retried.rowsAffected > 0) {
|
|
2190
|
+
try {
|
|
2191
|
+
await writeCheckpoint({
|
|
2192
|
+
taskId,
|
|
2193
|
+
step: "reclaimed_dead_session",
|
|
2194
|
+
contextSummary: `Task reclaimed after dead session ${claimedBySession} released.`
|
|
2195
|
+
});
|
|
2196
|
+
} catch {
|
|
2197
|
+
}
|
|
2198
|
+
return { row, taskFile, now, taskId };
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId === "exe")) {
|
|
2202
|
+
process.stderr.write(
|
|
2203
|
+
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
2204
|
+
`
|
|
2205
|
+
);
|
|
2206
|
+
await client.execute({
|
|
2207
|
+
sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ?`,
|
|
2208
|
+
args: [tmuxSession, now, taskId]
|
|
2209
|
+
});
|
|
2210
|
+
try {
|
|
2211
|
+
await writeCheckpoint({
|
|
2212
|
+
taskId,
|
|
2213
|
+
step: "assigner_override",
|
|
2214
|
+
contextSummary: `Task force-reclaimed by assigner ${input.callerAgentId}.`
|
|
2215
|
+
});
|
|
2216
|
+
} catch {
|
|
2217
|
+
}
|
|
2218
|
+
return { row, taskFile, now, taskId };
|
|
2219
|
+
}
|
|
2220
|
+
const claimedBy = claimedBySession ? ` (claimed by ${claimedBySession})` : "";
|
|
2221
|
+
throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${curStatus}${claimedBy}`);
|
|
2098
2222
|
}
|
|
2099
2223
|
try {
|
|
2100
2224
|
await writeCheckpoint({
|
|
@@ -2192,7 +2316,7 @@ var init_tasks_crud = __esm({
|
|
|
2192
2316
|
"use strict";
|
|
2193
2317
|
init_database();
|
|
2194
2318
|
init_task_scope();
|
|
2195
|
-
DELEGATION_KEYWORDS = /parallel|delegate|wave|
|
|
2319
|
+
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
2196
2320
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
2197
2321
|
}
|
|
2198
2322
|
});
|
|
@@ -2549,7 +2673,7 @@ function findSessionForProject(projectName) {
|
|
|
2549
2673
|
const sessions = listSessions();
|
|
2550
2674
|
for (const s of sessions) {
|
|
2551
2675
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
2552
|
-
if (proj === projectName && s.agentId === "exe") return s;
|
|
2676
|
+
if (proj === projectName && (s.agentId === "exe" || isCoordinatorName(s.agentId))) return s;
|
|
2553
2677
|
}
|
|
2554
2678
|
return null;
|
|
2555
2679
|
}
|
|
@@ -2589,12 +2713,13 @@ var init_session_scope = __esm({
|
|
|
2589
2713
|
init_session_registry();
|
|
2590
2714
|
init_project_name();
|
|
2591
2715
|
init_tmux_routing();
|
|
2716
|
+
init_employees();
|
|
2592
2717
|
}
|
|
2593
2718
|
});
|
|
2594
2719
|
|
|
2595
2720
|
// src/lib/tasks-notify.ts
|
|
2596
2721
|
async function dispatchTaskToEmployee(input) {
|
|
2597
|
-
if (input.assignedTo === "exe") return { dispatched: "skipped" };
|
|
2722
|
+
if (input.assignedTo === "exe" || isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
2598
2723
|
let crossProject = false;
|
|
2599
2724
|
if (input.projectName) {
|
|
2600
2725
|
try {
|
|
@@ -3099,6 +3224,24 @@ async function updateTask(input) {
|
|
|
3099
3224
|
});
|
|
3100
3225
|
} catch {
|
|
3101
3226
|
}
|
|
3227
|
+
const assignedAgent = String(row.assigned_to);
|
|
3228
|
+
if (!isCoordinatorName(assignedAgent)) {
|
|
3229
|
+
try {
|
|
3230
|
+
const draftClient = getClient();
|
|
3231
|
+
if (input.status === "done") {
|
|
3232
|
+
await draftClient.execute({
|
|
3233
|
+
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
3234
|
+
args: [assignedAgent]
|
|
3235
|
+
});
|
|
3236
|
+
} else if (input.status === "cancelled") {
|
|
3237
|
+
await draftClient.execute({
|
|
3238
|
+
sql: `DELETE FROM memories WHERE agent_id = ? AND draft = 1`,
|
|
3239
|
+
args: [assignedAgent]
|
|
3240
|
+
});
|
|
3241
|
+
}
|
|
3242
|
+
} catch {
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3102
3245
|
try {
|
|
3103
3246
|
const client = getClient();
|
|
3104
3247
|
const cascaded = await client.execute({
|
|
@@ -3117,8 +3260,8 @@ async function updateTask(input) {
|
|
|
3117
3260
|
}
|
|
3118
3261
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
3119
3262
|
if (isTerminal) {
|
|
3120
|
-
const
|
|
3121
|
-
if (!
|
|
3263
|
+
const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
|
|
3264
|
+
if (!isCoordinator) {
|
|
3122
3265
|
notifyTaskDone();
|
|
3123
3266
|
}
|
|
3124
3267
|
await markTaskNotificationsRead(taskFile);
|
|
@@ -3142,7 +3285,7 @@ async function updateTask(input) {
|
|
|
3142
3285
|
}
|
|
3143
3286
|
}
|
|
3144
3287
|
}
|
|
3145
|
-
if (input.status === "done" && String(row.assigned_to) !== "exe" && !process.env.VITEST) {
|
|
3288
|
+
if (input.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3146
3289
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3147
3290
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3148
3291
|
taskId,
|
|
@@ -3158,7 +3301,7 @@ async function updateTask(input) {
|
|
|
3158
3301
|
});
|
|
3159
3302
|
}
|
|
3160
3303
|
let nextTask;
|
|
3161
|
-
if (isTerminal && String(row.assigned_to) !== "exe") {
|
|
3304
|
+
if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
|
|
3162
3305
|
try {
|
|
3163
3306
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
3164
3307
|
} catch {
|
|
@@ -3185,12 +3328,14 @@ async function updateTask(input) {
|
|
|
3185
3328
|
async function deleteTask(taskId, baseDir) {
|
|
3186
3329
|
const client = getClient();
|
|
3187
3330
|
const { taskFile, assignedTo, assignedBy, taskSlug } = await deleteTaskCore(taskId, baseDir);
|
|
3188
|
-
const
|
|
3331
|
+
const coordinatorName = getCoordinatorName();
|
|
3332
|
+
const reviewer = assignedBy || coordinatorName;
|
|
3189
3333
|
const reviewSlug = `review-${assignedTo}-${taskSlug}`;
|
|
3190
3334
|
const reviewFile = `exe/${reviewer}/${reviewSlug}.md`;
|
|
3335
|
+
const legacyReviewFile = `exe/${coordinatorName}/${reviewSlug}.md`;
|
|
3191
3336
|
await client.execute({
|
|
3192
|
-
sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ?",
|
|
3193
|
-
args: [reviewFile, `exe/exe/${reviewSlug}.md`]
|
|
3337
|
+
sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ? OR task_file = ?",
|
|
3338
|
+
args: [reviewFile, legacyReviewFile, `exe/exe/${reviewSlug}.md`]
|
|
3194
3339
|
});
|
|
3195
3340
|
await markAsReadByTaskFile(taskFile);
|
|
3196
3341
|
await markAsReadByTaskFile(reviewFile);
|
|
@@ -3202,6 +3347,7 @@ var init_tasks = __esm({
|
|
|
3202
3347
|
init_config();
|
|
3203
3348
|
init_notifications();
|
|
3204
3349
|
init_state_bus();
|
|
3350
|
+
init_employees();
|
|
3205
3351
|
init_tasks_crud();
|
|
3206
3352
|
init_tasks_review();
|
|
3207
3353
|
init_tasks_crud();
|
|
@@ -3287,7 +3433,7 @@ function _resetLastRelaunchCache() {
|
|
|
3287
3433
|
}
|
|
3288
3434
|
async function lastResumeCreatedAtMs(agentId) {
|
|
3289
3435
|
const client = getClient();
|
|
3290
|
-
const cmScope = sessionScopeFilter();
|
|
3436
|
+
const cmScope = sessionScopeFilter(null);
|
|
3291
3437
|
const result = await client.execute({
|
|
3292
3438
|
sql: `SELECT MAX(created_at) AS last_created_at
|
|
3293
3439
|
FROM tasks
|
|
@@ -3312,7 +3458,7 @@ async function createOrRefreshResumeTask(agentId, projectDir, openTasks) {
|
|
|
3312
3458
|
const client = getClient();
|
|
3313
3459
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3314
3460
|
const context = buildResumeContext(agentId, openTasks);
|
|
3315
|
-
const rdScope = sessionScopeFilter();
|
|
3461
|
+
const rdScope = sessionScopeFilter(null);
|
|
3316
3462
|
const existing = await client.execute({
|
|
3317
3463
|
sql: `SELECT id FROM tasks
|
|
3318
3464
|
WHERE assigned_to = ?
|
|
@@ -3346,7 +3492,7 @@ async function pollCapacityDead() {
|
|
|
3346
3492
|
const transport = getTransport();
|
|
3347
3493
|
const relaunched = [];
|
|
3348
3494
|
const registered = listSessions().filter(
|
|
3349
|
-
(s) => s.agentId !== "exe"
|
|
3495
|
+
(s) => s.agentId !== "exe" && !isCoordinatorName(s.agentId)
|
|
3350
3496
|
);
|
|
3351
3497
|
if (registered.length === 0) return [];
|
|
3352
3498
|
let liveSessions;
|
|
@@ -3406,7 +3552,7 @@ async function pollCapacityDead() {
|
|
|
3406
3552
|
reason: "capacity"
|
|
3407
3553
|
});
|
|
3408
3554
|
const client = getClient();
|
|
3409
|
-
const rlScope = sessionScopeFilter();
|
|
3555
|
+
const rlScope = sessionScopeFilter(null);
|
|
3410
3556
|
const openTasks = await client.execute({
|
|
3411
3557
|
sql: `SELECT id, title, priority, task_file, status
|
|
3412
3558
|
FROM tasks
|
|
@@ -3460,6 +3606,7 @@ var init_capacity_monitor = __esm({
|
|
|
3460
3606
|
init_session_kill_telemetry();
|
|
3461
3607
|
init_tmux_routing();
|
|
3462
3608
|
init_task_scope();
|
|
3609
|
+
init_employees();
|
|
3463
3610
|
CAPACITY_PATTERNS = [
|
|
3464
3611
|
/conversation is too long/i,
|
|
3465
3612
|
/maximum context length/i,
|
|
@@ -3609,7 +3756,7 @@ function employeeSessionName(employee, exeSession, instance) {
|
|
|
3609
3756
|
exeSession = root;
|
|
3610
3757
|
} else {
|
|
3611
3758
|
throw new Error(
|
|
3612
|
-
`Invalid
|
|
3759
|
+
`Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
|
|
3613
3760
|
);
|
|
3614
3761
|
}
|
|
3615
3762
|
}
|
|
@@ -3629,8 +3776,10 @@ function parseParentExe(sessionName, agentId) {
|
|
|
3629
3776
|
return match?.[1] ?? null;
|
|
3630
3777
|
}
|
|
3631
3778
|
function extractRootExe(name) {
|
|
3632
|
-
|
|
3633
|
-
|
|
3779
|
+
if (!name) return null;
|
|
3780
|
+
if (!name.includes("-")) return name;
|
|
3781
|
+
const parts = name.split("-").filter(Boolean);
|
|
3782
|
+
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
3634
3783
|
}
|
|
3635
3784
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
3636
3785
|
if (!existsSync10(SESSION_CACHE)) {
|
|
@@ -3775,12 +3924,14 @@ function isSessionBusy(sessionName) {
|
|
|
3775
3924
|
return state === "thinking" || state === "tool";
|
|
3776
3925
|
}
|
|
3777
3926
|
function isExeSession(sessionName) {
|
|
3778
|
-
|
|
3927
|
+
const matchesBaseWithInstance = (baseName) => sessionName === baseName || sessionName.startsWith(baseName) && /^\d+$/.test(sessionName.slice(baseName.length));
|
|
3928
|
+
const coordinatorName = getCoordinatorName();
|
|
3929
|
+
return matchesBaseWithInstance(coordinatorName) || matchesBaseWithInstance("exe");
|
|
3779
3930
|
}
|
|
3780
3931
|
function sendIntercom(targetSession) {
|
|
3781
3932
|
const transport = getTransport();
|
|
3782
3933
|
if (isExeSession(targetSession)) {
|
|
3783
|
-
logIntercom(`
|
|
3934
|
+
logIntercom(`SKIP_COORDINATOR \u2192 ${targetSession} (coordinator sessions use prompt-submit hook)`);
|
|
3784
3935
|
return "skipped_exe";
|
|
3785
3936
|
}
|
|
3786
3937
|
if (isDebounced(targetSession)) {
|
|
@@ -3832,7 +3983,7 @@ function notifyParentExe(sessionKey) {
|
|
|
3832
3983
|
if (result === "failed") {
|
|
3833
3984
|
const rootExe = resolveExeSession();
|
|
3834
3985
|
if (rootExe && rootExe !== target) {
|
|
3835
|
-
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root
|
|
3986
|
+
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root coordinator session ${rootExe}
|
|
3836
3987
|
`);
|
|
3837
3988
|
const fallback = sendIntercom(rootExe);
|
|
3838
3989
|
return fallback !== "failed";
|
|
@@ -3842,8 +3993,8 @@ function notifyParentExe(sessionKey) {
|
|
|
3842
3993
|
return true;
|
|
3843
3994
|
}
|
|
3844
3995
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
3845
|
-
if (employeeName === "exe") {
|
|
3846
|
-
return { status: "failed", sessionName: "", error: "
|
|
3996
|
+
if (employeeName === "exe" || isCoordinatorName(employeeName)) {
|
|
3997
|
+
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
3847
3998
|
}
|
|
3848
3999
|
try {
|
|
3849
4000
|
assertEmployeeLimitSync();
|
|
@@ -3852,8 +4003,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3852
4003
|
return { status: "failed", sessionName: "", error: err.message };
|
|
3853
4004
|
}
|
|
3854
4005
|
}
|
|
3855
|
-
if (
|
|
3856
|
-
const bare = employeeName.
|
|
4006
|
+
if (employeeName.includes("-")) {
|
|
4007
|
+
const bare = employeeName.split("-")[0].replace(/\d+$/, "");
|
|
3857
4008
|
return {
|
|
3858
4009
|
status: "failed",
|
|
3859
4010
|
sessionName: "",
|
|
@@ -3872,7 +4023,7 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3872
4023
|
return {
|
|
3873
4024
|
status: "failed",
|
|
3874
4025
|
sessionName: "",
|
|
3875
|
-
error: `Invalid
|
|
4026
|
+
error: `Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
|
|
3876
4027
|
};
|
|
3877
4028
|
}
|
|
3878
4029
|
}
|
|
@@ -4029,8 +4180,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4029
4180
|
const ctxContent = [
|
|
4030
4181
|
`## Session Context`,
|
|
4031
4182
|
`You are running in tmux session: ${sessionName}.`,
|
|
4032
|
-
`Your parent
|
|
4033
|
-
`Your employees (if any) use the -${exeSession} suffix
|
|
4183
|
+
`Your parent coordinator session is ${exeSession}.`,
|
|
4184
|
+
`Your employees (if any) use the -${exeSession} suffix.`
|
|
4034
4185
|
].join("\n");
|
|
4035
4186
|
writeFileSync6(ctxFile, ctxContent);
|
|
4036
4187
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
@@ -4134,6 +4285,7 @@ var init_tmux_routing = __esm({
|
|
|
4134
4285
|
init_provider_table();
|
|
4135
4286
|
init_intercom_queue();
|
|
4136
4287
|
init_plan_limits();
|
|
4288
|
+
init_employees();
|
|
4137
4289
|
SPAWN_LOCK_DIR = path14.join(os7.homedir(), ".exe-os", "spawn-locks");
|
|
4138
4290
|
SESSION_CACHE = path14.join(os7.homedir(), ".exe-os", "session-cache");
|
|
4139
4291
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
@@ -4327,7 +4479,11 @@ async function ensureShardSchema(client) {
|
|
|
4327
4479
|
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
4328
4480
|
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
|
|
4329
4481
|
"ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
|
|
4330
|
-
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
|
|
4482
|
+
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
|
|
4483
|
+
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
4484
|
+
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
4485
|
+
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
4486
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
4331
4487
|
]) {
|
|
4332
4488
|
try {
|
|
4333
4489
|
await client.execute(col);
|
|
@@ -4457,26 +4613,26 @@ var init_platform_procedures = __esm({
|
|
|
4457
4613
|
title: "What is exe-os \u2014 the operating model every agent must understand",
|
|
4458
4614
|
domain: "architecture",
|
|
4459
4615
|
priority: "p0",
|
|
4460
|
-
content: "Exe OS is an AI employee operating system. A founder runs 5-10 AI agents as a real org: COO
|
|
4616
|
+
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."
|
|
4461
4617
|
},
|
|
4462
4618
|
{
|
|
4463
4619
|
title: "Mode 1 \u2014 how exe-os runs inside Claude Code",
|
|
4464
4620
|
domain: "architecture",
|
|
4465
4621
|
priority: "p0",
|
|
4466
|
-
content: "Mode 1: exe-os runs AS hooks + MCP + skills inside Claude Code. The founder opens CC
|
|
4622
|
+
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."
|
|
4467
4623
|
},
|
|
4468
4624
|
{
|
|
4469
|
-
title: "Sessions explained \u2014
|
|
4625
|
+
title: "Sessions explained \u2014 coordinator session names and projects",
|
|
4470
4626
|
domain: "architecture",
|
|
4471
4627
|
priority: "p0",
|
|
4472
|
-
content: "Each
|
|
4628
|
+
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."
|
|
4473
4629
|
},
|
|
4474
4630
|
// --- Hierarchy and dispatch ---
|
|
4475
4631
|
{
|
|
4476
4632
|
title: "Chain of command \u2014 who talks to whom",
|
|
4477
4633
|
domain: "workflow",
|
|
4478
4634
|
priority: "p0",
|
|
4479
|
-
content: "Founder
|
|
4635
|
+
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."
|
|
4480
4636
|
},
|
|
4481
4637
|
{
|
|
4482
4638
|
title: "Single dispatch path \u2014 create_task only",
|
|
@@ -4486,30 +4642,30 @@ var init_platform_procedures = __esm({
|
|
|
4486
4642
|
},
|
|
4487
4643
|
// --- Session isolation ---
|
|
4488
4644
|
{
|
|
4489
|
-
title: "Session scoping \u2014 stay in your
|
|
4645
|
+
title: "Session scoping \u2014 stay in your coordinator boundary",
|
|
4490
4646
|
domain: "security",
|
|
4491
4647
|
priority: "p0",
|
|
4492
|
-
content: "Session scoping is mandatory. Managers dispatch to workers within their own
|
|
4648
|
+
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."
|
|
4493
4649
|
},
|
|
4494
4650
|
{
|
|
4495
4651
|
title: "Session isolation \u2014 never touch another session's work",
|
|
4496
4652
|
domain: "workflow",
|
|
4497
4653
|
priority: "p0",
|
|
4498
|
-
content:
|
|
4654
|
+
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."
|
|
4499
4655
|
},
|
|
4500
4656
|
// --- Engineering: session scoping in code ---
|
|
4501
4657
|
{
|
|
4502
4658
|
title: "Three-dimensional scoping \u2014 session, project, role \u2014 enforced in every query",
|
|
4503
4659
|
domain: "architecture",
|
|
4504
4660
|
priority: "p0",
|
|
4505
|
-
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
|
|
4661
|
+
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."
|
|
4506
4662
|
},
|
|
4507
4663
|
// --- Hard constraints ---
|
|
4508
4664
|
{
|
|
4509
4665
|
title: "What you CANNOT do in exe-os \u2014 hard constraints",
|
|
4510
4666
|
domain: "security",
|
|
4511
4667
|
priority: "p0",
|
|
4512
|
-
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
|
|
4668
|
+
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."
|
|
4513
4669
|
},
|
|
4514
4670
|
// --- Operations ---
|
|
4515
4671
|
{
|
|
@@ -4749,7 +4905,10 @@ async function writeMemory(record) {
|
|
|
4749
4905
|
source_path: record.source_path ?? null,
|
|
4750
4906
|
source_type: record.source_type ?? null,
|
|
4751
4907
|
tier: record.tier ?? classifyTier(record),
|
|
4752
|
-
supersedes_id: record.supersedes_id ?? null
|
|
4908
|
+
supersedes_id: record.supersedes_id ?? null,
|
|
4909
|
+
draft: record.draft ? 1 : 0,
|
|
4910
|
+
memory_type: record.memory_type ?? "raw",
|
|
4911
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
4753
4912
|
};
|
|
4754
4913
|
_pendingRecords.push(dbRow);
|
|
4755
4914
|
orgBus.emit({
|
|
@@ -4804,6 +4963,9 @@ async function flushBatch() {
|
|
|
4804
4963
|
const sourceType = row.source_type ?? null;
|
|
4805
4964
|
const tier = row.tier ?? 3;
|
|
4806
4965
|
const supersedesId = row.supersedes_id ?? null;
|
|
4966
|
+
const draft = row.draft ? 1 : 0;
|
|
4967
|
+
const memoryType = row.memory_type ?? "raw";
|
|
4968
|
+
const trajectory = row.trajectory ?? null;
|
|
4807
4969
|
return {
|
|
4808
4970
|
sql: hasVector ? `INSERT OR IGNORE INTO memories
|
|
4809
4971
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
@@ -4811,15 +4973,15 @@ async function flushBatch() {
|
|
|
4811
4973
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
4812
4974
|
confidence, last_accessed,
|
|
4813
4975
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
4814
|
-
source_path, source_type, tier, supersedes_id)
|
|
4815
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
|
|
4976
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
|
|
4977
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
|
|
4816
4978
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
4817
4979
|
tool_name, project_name,
|
|
4818
4980
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
4819
4981
|
confidence, last_accessed,
|
|
4820
4982
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
4821
|
-
source_path, source_type, tier, supersedes_id)
|
|
4822
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4983
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
|
|
4984
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4823
4985
|
args: hasVector ? [
|
|
4824
4986
|
row.id,
|
|
4825
4987
|
row.agent_id,
|
|
@@ -4845,7 +5007,10 @@ async function flushBatch() {
|
|
|
4845
5007
|
sourcePath,
|
|
4846
5008
|
sourceType,
|
|
4847
5009
|
tier,
|
|
4848
|
-
supersedesId
|
|
5010
|
+
supersedesId,
|
|
5011
|
+
draft,
|
|
5012
|
+
memoryType,
|
|
5013
|
+
trajectory
|
|
4849
5014
|
] : [
|
|
4850
5015
|
row.id,
|
|
4851
5016
|
row.agent_id,
|
|
@@ -4870,7 +5035,10 @@ async function flushBatch() {
|
|
|
4870
5035
|
sourcePath,
|
|
4871
5036
|
sourceType,
|
|
4872
5037
|
tier,
|
|
4873
|
-
supersedesId
|
|
5038
|
+
supersedesId,
|
|
5039
|
+
draft,
|
|
5040
|
+
memoryType,
|
|
5041
|
+
trajectory
|
|
4874
5042
|
]
|
|
4875
5043
|
};
|
|
4876
5044
|
};
|
|
@@ -4939,6 +5107,8 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
4939
5107
|
const limit = options?.limit ?? 10;
|
|
4940
5108
|
const statusFilter = options?.includeArchived ? "" : `
|
|
4941
5109
|
AND COALESCE(status, 'active') = 'active'`;
|
|
5110
|
+
const draftFilter = options?.includeDrafts ? "" : `
|
|
5111
|
+
AND (draft = 0 OR draft IS NULL)`;
|
|
4942
5112
|
let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
|
|
4943
5113
|
tool_name, project_name,
|
|
4944
5114
|
has_error, raw_text, vector, importance, status,
|
|
@@ -4948,7 +5118,7 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
4948
5118
|
source_path, source_type
|
|
4949
5119
|
FROM memories
|
|
4950
5120
|
WHERE agent_id = ?
|
|
4951
|
-
AND vector IS NOT NULL${statusFilter}
|
|
5121
|
+
AND vector IS NOT NULL${statusFilter}${draftFilter}
|
|
4952
5122
|
AND COALESCE(confidence, 0.7) >= 0.3`;
|
|
4953
5123
|
const args = [agentId];
|
|
4954
5124
|
const scope = buildWikiScopeFilter(options, "");
|
|
@@ -4970,6 +5140,10 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
4970
5140
|
sql += ` AND timestamp >= ?`;
|
|
4971
5141
|
args.push(options.since);
|
|
4972
5142
|
}
|
|
5143
|
+
if (options?.memoryType) {
|
|
5144
|
+
sql += ` AND memory_type = ?`;
|
|
5145
|
+
args.push(options.memoryType);
|
|
5146
|
+
}
|
|
4973
5147
|
sql += ` ORDER BY vector_distance_cos(vector, vector32(?))`;
|
|
4974
5148
|
args.push(vectorToBlob(queryVector));
|
|
4975
5149
|
sql += ` LIMIT ?`;
|
|
@@ -6643,7 +6817,11 @@ function composeHooks(...pipelines) {
|
|
|
6643
6817
|
};
|
|
6644
6818
|
}
|
|
6645
6819
|
|
|
6820
|
+
// src/runtime/orchestrator.ts
|
|
6821
|
+
init_employees();
|
|
6822
|
+
|
|
6646
6823
|
// src/lib/task-router.ts
|
|
6824
|
+
init_employees();
|
|
6647
6825
|
import { randomUUID } from "crypto";
|
|
6648
6826
|
var DEFAULT_BLOOM_CONFIG = {
|
|
6649
6827
|
complexityToTier: {
|
|
@@ -6701,7 +6879,7 @@ async function scoreEmployee(taskVector, agentId, searchFn) {
|
|
|
6701
6879
|
return { agentId, score: results.length / 5 };
|
|
6702
6880
|
}
|
|
6703
6881
|
async function routeTask(taskDescription, employees, embedFn, searchFn, options) {
|
|
6704
|
-
let specialists = employees.filter((e) => e.
|
|
6882
|
+
let specialists = employees.filter((e) => !isCoordinatorRole(e.role));
|
|
6705
6883
|
if (specialists.length === 0) {
|
|
6706
6884
|
throw new Error(
|
|
6707
6885
|
"No specialist employees available. Create one with /exe-new-employee."
|
|
@@ -6778,7 +6956,7 @@ ${task.context}`,
|
|
|
6778
6956
|
await createTaskCore({
|
|
6779
6957
|
title: task.title,
|
|
6780
6958
|
assignedTo: targetEmployee.name,
|
|
6781
|
-
assignedBy:
|
|
6959
|
+
assignedBy: getCoordinatorName(),
|
|
6782
6960
|
projectName: task.projectName,
|
|
6783
6961
|
priority: task.priority,
|
|
6784
6962
|
context: task.context,
|
|
@@ -6883,15 +7061,16 @@ ${task.context}`,
|
|
|
6883
7061
|
const client = getClient2();
|
|
6884
7062
|
const autoApproved = [];
|
|
6885
7063
|
const needsReview = [];
|
|
7064
|
+
const coordinatorName = getCoordinatorName();
|
|
6886
7065
|
const rScope = sessionScopeFilter();
|
|
6887
7066
|
const reviews = await client.execute({
|
|
6888
7067
|
sql: `SELECT id, title, assigned_to, priority, result, task_file FROM tasks
|
|
6889
|
-
WHERE assigned_to = 'exe'
|
|
7068
|
+
WHERE (assigned_to = ? OR assigned_to = 'exe')
|
|
6890
7069
|
AND status IN ('open', 'in_progress')
|
|
6891
7070
|
AND task_file LIKE '%review-%'${rScope.sql}
|
|
6892
7071
|
ORDER BY priority ASC
|
|
6893
7072
|
LIMIT 20`,
|
|
6894
|
-
args: [...rScope.args]
|
|
7073
|
+
args: [coordinatorName, ...rScope.args]
|
|
6895
7074
|
});
|
|
6896
7075
|
for (const row of reviews.rows) {
|
|
6897
7076
|
const item = {
|