@askexenow/exe-os 0.9.7 → 0.9.9
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 +953 -105
- package/dist/bin/backfill-responses.js +952 -104
- package/dist/bin/backfill-vectors.js +956 -108
- package/dist/bin/cleanup-stale-review-tasks.js +802 -58
- package/dist/bin/cli.js +2292 -1070
- package/dist/bin/exe-agent-config.js +157 -101
- package/dist/bin/exe-agent.js +55 -29
- package/dist/bin/exe-assign.js +940 -92
- package/dist/bin/exe-boot.js +1424 -442
- package/dist/bin/exe-call.js +240 -141
- package/dist/bin/exe-cloud.js +198 -70
- package/dist/bin/exe-dispatch.js +951 -192
- package/dist/bin/exe-doctor.js +791 -51
- package/dist/bin/exe-export-behaviors.js +790 -42
- package/dist/bin/exe-forget.js +771 -31
- package/dist/bin/exe-gateway.js +1592 -521
- package/dist/bin/exe-heartbeat.js +850 -109
- package/dist/bin/exe-kill.js +783 -35
- package/dist/bin/exe-launch-agent.js +1030 -107
- package/dist/bin/exe-link.js +916 -110
- package/dist/bin/exe-new-employee.js +526 -217
- package/dist/bin/exe-pending-messages.js +1046 -62
- package/dist/bin/exe-pending-notifications.js +1318 -111
- package/dist/bin/exe-pending-reviews.js +1040 -72
- package/dist/bin/exe-rename.js +772 -59
- package/dist/bin/exe-review.js +772 -32
- package/dist/bin/exe-search.js +982 -128
- package/dist/bin/exe-session-cleanup.js +1180 -306
- package/dist/bin/exe-settings.js +185 -105
- package/dist/bin/exe-start-codex.js +886 -132
- package/dist/bin/exe-start-opencode.js +873 -119
- package/dist/bin/exe-status.js +803 -59
- package/dist/bin/exe-team.js +772 -32
- package/dist/bin/git-sweep.js +1046 -223
- package/dist/bin/graph-backfill.js +779 -31
- package/dist/bin/graph-export.js +785 -37
- package/dist/bin/install.js +632 -200
- package/dist/bin/scan-tasks.js +1055 -232
- package/dist/bin/setup.js +1419 -320
- package/dist/bin/shard-migrate.js +783 -35
- package/dist/bin/update.js +138 -49
- package/dist/bin/wiki-sync.js +782 -34
- package/dist/gateway/index.js +1444 -449
- package/dist/hooks/bug-report-worker.js +1141 -269
- package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
- package/dist/hooks/commit-complete.js +1044 -221
- package/dist/hooks/error-recall.js +989 -135
- package/dist/hooks/exe-heartbeat-hook.js +99 -75
- package/dist/hooks/ingest-worker.js +4176 -3226
- package/dist/hooks/ingest.js +920 -168
- package/dist/hooks/instructions-loaded.js +874 -70
- package/dist/hooks/notification.js +860 -56
- package/dist/hooks/post-compact.js +881 -73
- package/dist/hooks/pre-compact.js +1050 -227
- package/dist/hooks/pre-tool-use.js +1084 -159
- package/dist/hooks/prompt-ingest-worker.js +1089 -164
- package/dist/hooks/prompt-submit.js +1469 -515
- package/dist/hooks/response-ingest-worker.js +1104 -179
- package/dist/hooks/session-end.js +1085 -251
- package/dist/hooks/session-start.js +1241 -231
- package/dist/hooks/stop.js +935 -109
- package/dist/hooks/subagent-stop.js +881 -73
- package/dist/hooks/summary-worker.js +1323 -307
- package/dist/index.js +1449 -452
- package/dist/lib/agent-config.js +28 -6
- package/dist/lib/cloud-sync.js +909 -115
- package/dist/lib/config.js +30 -10
- package/dist/lib/consolidation.js +42 -9
- package/dist/lib/database.js +739 -33
- package/dist/lib/db-daemon-client.js +73 -19
- package/dist/lib/db.js +2359 -0
- package/dist/lib/device-registry.js +760 -47
- package/dist/lib/embedder.js +201 -73
- package/dist/lib/employee-templates.js +30 -4
- package/dist/lib/employees.js +290 -86
- package/dist/lib/exe-daemon-client.js +187 -83
- package/dist/lib/exe-daemon.js +1696 -616
- package/dist/lib/hybrid-search.js +982 -128
- package/dist/lib/identity.js +43 -13
- package/dist/lib/license.js +133 -48
- package/dist/lib/messaging.js +167 -80
- package/dist/lib/reminders.js +35 -5
- package/dist/lib/schedules.js +772 -32
- package/dist/lib/skill-learning.js +54 -7
- package/dist/lib/store.js +779 -31
- package/dist/lib/task-router.js +94 -73
- package/dist/lib/tasks.js +298 -225
- package/dist/lib/tmux-routing.js +246 -172
- package/dist/lib/token-spend.js +52 -14
- package/dist/mcp/server.js +2893 -850
- package/dist/mcp/tools/complete-reminder.js +35 -5
- package/dist/mcp/tools/create-reminder.js +35 -5
- package/dist/mcp/tools/create-task.js +507 -323
- package/dist/mcp/tools/deactivate-behavior.js +40 -10
- package/dist/mcp/tools/list-reminders.js +35 -5
- package/dist/mcp/tools/list-tasks.js +277 -104
- package/dist/mcp/tools/send-message.js +129 -56
- package/dist/mcp/tools/update-task.js +1864 -188
- package/dist/runtime/index.js +1083 -259
- package/dist/tui/App.js +1501 -434
- package/package.json +3 -2
package/dist/lib/tasks.js
CHANGED
|
@@ -32,9 +32,34 @@ var init_db_retry = __esm({
|
|
|
32
32
|
}
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
+
// src/lib/secure-files.ts
|
|
36
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
37
|
+
import { chmod, mkdir } from "fs/promises";
|
|
38
|
+
async function ensurePrivateDir(dirPath) {
|
|
39
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
40
|
+
try {
|
|
41
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function enforcePrivateFile(filePath) {
|
|
46
|
+
try {
|
|
47
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
52
|
+
var init_secure_files = __esm({
|
|
53
|
+
"src/lib/secure-files.ts"() {
|
|
54
|
+
"use strict";
|
|
55
|
+
PRIVATE_DIR_MODE = 448;
|
|
56
|
+
PRIVATE_FILE_MODE = 384;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
35
60
|
// src/lib/config.ts
|
|
36
|
-
import { readFile, writeFile
|
|
37
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
61
|
+
import { readFile, writeFile } from "fs/promises";
|
|
62
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
38
63
|
import path from "path";
|
|
39
64
|
import os from "os";
|
|
40
65
|
function resolveDataDir() {
|
|
@@ -42,7 +67,7 @@ function resolveDataDir() {
|
|
|
42
67
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
43
68
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
44
69
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
45
|
-
if (!
|
|
70
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
46
71
|
try {
|
|
47
72
|
renameSync(legacyDir, newDir);
|
|
48
73
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -105,9 +130,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
105
130
|
}
|
|
106
131
|
async function loadConfig() {
|
|
107
132
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
108
|
-
await
|
|
133
|
+
await ensurePrivateDir(dir);
|
|
109
134
|
const configPath = path.join(dir, "config.json");
|
|
110
|
-
if (!
|
|
135
|
+
if (!existsSync2(configPath)) {
|
|
111
136
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
112
137
|
}
|
|
113
138
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -120,6 +145,7 @@ async function loadConfig() {
|
|
|
120
145
|
`);
|
|
121
146
|
try {
|
|
122
147
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
148
|
+
await enforcePrivateFile(configPath);
|
|
123
149
|
} catch {
|
|
124
150
|
}
|
|
125
151
|
}
|
|
@@ -139,6 +165,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
139
165
|
var init_config = __esm({
|
|
140
166
|
"src/lib/config.ts"() {
|
|
141
167
|
"use strict";
|
|
168
|
+
init_secure_files();
|
|
142
169
|
EXE_AI_DIR = resolveDataDir();
|
|
143
170
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
144
171
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -217,7 +244,7 @@ var init_config = __esm({
|
|
|
217
244
|
|
|
218
245
|
// src/lib/employees.ts
|
|
219
246
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
220
|
-
import { existsSync as
|
|
247
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
221
248
|
import { execSync } from "child_process";
|
|
222
249
|
import path2 from "path";
|
|
223
250
|
import os2 from "os";
|
|
@@ -238,7 +265,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
238
265
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
239
266
|
}
|
|
240
267
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
241
|
-
if (!
|
|
268
|
+
if (!existsSync3(employeesPath)) return [];
|
|
242
269
|
try {
|
|
243
270
|
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
244
271
|
} catch {
|
|
@@ -262,7 +289,7 @@ function isMultiInstance(agentName, employees) {
|
|
|
262
289
|
if (!emp) return false;
|
|
263
290
|
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
264
291
|
}
|
|
265
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
292
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
|
|
266
293
|
var init_employees = __esm({
|
|
267
294
|
"src/lib/employees.ts"() {
|
|
268
295
|
"use strict";
|
|
@@ -271,15 +298,40 @@ var init_employees = __esm({
|
|
|
271
298
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
272
299
|
COORDINATOR_ROLE = "COO";
|
|
273
300
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
301
|
+
IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// src/lib/database-adapter.ts
|
|
306
|
+
import os3 from "os";
|
|
307
|
+
import path3 from "path";
|
|
308
|
+
import { createRequire } from "module";
|
|
309
|
+
import { pathToFileURL } from "url";
|
|
310
|
+
var BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES;
|
|
311
|
+
var init_database_adapter = __esm({
|
|
312
|
+
"src/lib/database-adapter.ts"() {
|
|
313
|
+
"use strict";
|
|
314
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
315
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
316
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
317
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
318
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
319
|
+
};
|
|
320
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
321
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
322
|
+
);
|
|
274
323
|
}
|
|
275
324
|
});
|
|
276
325
|
|
|
277
326
|
// src/lib/database.ts
|
|
278
327
|
import { createClient } from "@libsql/client";
|
|
279
328
|
function getClient() {
|
|
280
|
-
if (!
|
|
329
|
+
if (!_adapterClient) {
|
|
281
330
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
282
331
|
}
|
|
332
|
+
if (process.env.DATABASE_URL) {
|
|
333
|
+
return _adapterClient;
|
|
334
|
+
}
|
|
283
335
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
284
336
|
return _resilientClient;
|
|
285
337
|
}
|
|
@@ -288,132 +340,27 @@ function getClient() {
|
|
|
288
340
|
}
|
|
289
341
|
return _resilientClient;
|
|
290
342
|
}
|
|
291
|
-
var _resilientClient, _daemonClient;
|
|
343
|
+
var _resilientClient, _daemonClient, _adapterClient;
|
|
292
344
|
var init_database = __esm({
|
|
293
345
|
"src/lib/database.ts"() {
|
|
294
346
|
"use strict";
|
|
295
347
|
init_db_retry();
|
|
296
348
|
init_employees();
|
|
349
|
+
init_database_adapter();
|
|
297
350
|
_resilientClient = null;
|
|
298
351
|
_daemonClient = null;
|
|
299
|
-
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
// src/lib/notifications.ts
|
|
303
|
-
import crypto from "crypto";
|
|
304
|
-
import path3 from "path";
|
|
305
|
-
import os3 from "os";
|
|
306
|
-
import {
|
|
307
|
-
readFileSync as readFileSync3,
|
|
308
|
-
readdirSync,
|
|
309
|
-
unlinkSync as unlinkSync2,
|
|
310
|
-
existsSync as existsSync3,
|
|
311
|
-
rmdirSync
|
|
312
|
-
} from "fs";
|
|
313
|
-
async function writeNotification(notification) {
|
|
314
|
-
try {
|
|
315
|
-
const client = getClient();
|
|
316
|
-
const id = crypto.randomUUID();
|
|
317
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
318
|
-
await client.execute({
|
|
319
|
-
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
|
|
320
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
321
|
-
args: [
|
|
322
|
-
id,
|
|
323
|
-
notification.agentId,
|
|
324
|
-
notification.agentRole,
|
|
325
|
-
notification.event,
|
|
326
|
-
notification.project,
|
|
327
|
-
notification.summary,
|
|
328
|
-
notification.taskFile ?? null,
|
|
329
|
-
now
|
|
330
|
-
]
|
|
331
|
-
});
|
|
332
|
-
} catch (err) {
|
|
333
|
-
process.stderr.write(`[notifications] WRITE FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
334
|
-
`);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
async function markAsReadByTaskFile(taskFile) {
|
|
338
|
-
try {
|
|
339
|
-
const client = getClient();
|
|
340
|
-
await client.execute({
|
|
341
|
-
sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
|
|
342
|
-
args: [taskFile]
|
|
343
|
-
});
|
|
344
|
-
} catch {
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
var init_notifications = __esm({
|
|
348
|
-
"src/lib/notifications.ts"() {
|
|
349
|
-
"use strict";
|
|
350
|
-
init_database();
|
|
351
|
-
}
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
// src/lib/state-bus.ts
|
|
355
|
-
var StateBus, orgBus;
|
|
356
|
-
var init_state_bus = __esm({
|
|
357
|
-
"src/lib/state-bus.ts"() {
|
|
358
|
-
"use strict";
|
|
359
|
-
StateBus = class {
|
|
360
|
-
handlers = /* @__PURE__ */ new Map();
|
|
361
|
-
globalHandlers = /* @__PURE__ */ new Set();
|
|
362
|
-
/** Emit an event to all subscribers */
|
|
363
|
-
emit(event) {
|
|
364
|
-
const typeHandlers = this.handlers.get(event.type);
|
|
365
|
-
if (typeHandlers) {
|
|
366
|
-
for (const handler of typeHandlers) {
|
|
367
|
-
try {
|
|
368
|
-
handler(event);
|
|
369
|
-
} catch {
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
for (const handler of this.globalHandlers) {
|
|
374
|
-
try {
|
|
375
|
-
handler(event);
|
|
376
|
-
} catch {
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
/** Subscribe to a specific event type */
|
|
381
|
-
on(type, handler) {
|
|
382
|
-
if (!this.handlers.has(type)) {
|
|
383
|
-
this.handlers.set(type, /* @__PURE__ */ new Set());
|
|
384
|
-
}
|
|
385
|
-
this.handlers.get(type).add(handler);
|
|
386
|
-
}
|
|
387
|
-
/** Subscribe to ALL events */
|
|
388
|
-
onAny(handler) {
|
|
389
|
-
this.globalHandlers.add(handler);
|
|
390
|
-
}
|
|
391
|
-
/** Unsubscribe from a specific event type */
|
|
392
|
-
off(type, handler) {
|
|
393
|
-
this.handlers.get(type)?.delete(handler);
|
|
394
|
-
}
|
|
395
|
-
/** Unsubscribe from ALL events */
|
|
396
|
-
offAny(handler) {
|
|
397
|
-
this.globalHandlers.delete(handler);
|
|
398
|
-
}
|
|
399
|
-
/** Remove all listeners */
|
|
400
|
-
clear() {
|
|
401
|
-
this.handlers.clear();
|
|
402
|
-
this.globalHandlers.clear();
|
|
403
|
-
}
|
|
404
|
-
};
|
|
405
|
-
orgBus = new StateBus();
|
|
352
|
+
_adapterClient = null;
|
|
406
353
|
}
|
|
407
354
|
});
|
|
408
355
|
|
|
409
356
|
// src/lib/session-registry.ts
|
|
410
|
-
import { readFileSync as
|
|
357
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
|
|
411
358
|
import path4 from "path";
|
|
412
359
|
import os4 from "os";
|
|
413
360
|
function registerSession(entry) {
|
|
414
361
|
const dir = path4.dirname(REGISTRY_PATH);
|
|
415
362
|
if (!existsSync4(dir)) {
|
|
416
|
-
|
|
363
|
+
mkdirSync2(dir, { recursive: true });
|
|
417
364
|
}
|
|
418
365
|
const sessions = listSessions();
|
|
419
366
|
const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
|
|
@@ -426,7 +373,7 @@ function registerSession(entry) {
|
|
|
426
373
|
}
|
|
427
374
|
function listSessions() {
|
|
428
375
|
try {
|
|
429
|
-
const raw =
|
|
376
|
+
const raw = readFileSync3(REGISTRY_PATH, "utf8");
|
|
430
377
|
return JSON.parse(raw);
|
|
431
378
|
} catch {
|
|
432
379
|
return [];
|
|
@@ -716,12 +663,12 @@ var init_runtime_table = __esm({
|
|
|
716
663
|
});
|
|
717
664
|
|
|
718
665
|
// src/lib/agent-config.ts
|
|
719
|
-
import { readFileSync as
|
|
666
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5 } from "fs";
|
|
720
667
|
import path5 from "path";
|
|
721
668
|
function loadAgentConfig() {
|
|
722
669
|
if (!existsSync5(AGENT_CONFIG_PATH)) return {};
|
|
723
670
|
try {
|
|
724
|
-
return JSON.parse(
|
|
671
|
+
return JSON.parse(readFileSync4(AGENT_CONFIG_PATH, "utf-8"));
|
|
725
672
|
} catch {
|
|
726
673
|
return {};
|
|
727
674
|
}
|
|
@@ -740,6 +687,7 @@ var init_agent_config = __esm({
|
|
|
740
687
|
"use strict";
|
|
741
688
|
init_config();
|
|
742
689
|
init_runtime_table();
|
|
690
|
+
init_secure_files();
|
|
743
691
|
AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
|
|
744
692
|
DEFAULT_MODELS = {
|
|
745
693
|
claude: "claude-opus-4",
|
|
@@ -758,7 +706,7 @@ __export(intercom_queue_exports, {
|
|
|
758
706
|
queueIntercom: () => queueIntercom,
|
|
759
707
|
readQueue: () => readQueue
|
|
760
708
|
});
|
|
761
|
-
import { readFileSync as
|
|
709
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
762
710
|
import path6 from "path";
|
|
763
711
|
import os5 from "os";
|
|
764
712
|
function ensureDir() {
|
|
@@ -768,7 +716,7 @@ function ensureDir() {
|
|
|
768
716
|
function readQueue() {
|
|
769
717
|
try {
|
|
770
718
|
if (!existsSync6(QUEUE_PATH)) return [];
|
|
771
|
-
return JSON.parse(
|
|
719
|
+
return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
|
|
772
720
|
} catch {
|
|
773
721
|
return [];
|
|
774
722
|
}
|
|
@@ -876,8 +824,11 @@ var init_intercom_queue = __esm({
|
|
|
876
824
|
});
|
|
877
825
|
|
|
878
826
|
// src/lib/license.ts
|
|
879
|
-
import { readFileSync as
|
|
827
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
|
|
880
828
|
import { randomUUID } from "crypto";
|
|
829
|
+
import { createRequire as createRequire2 } from "module";
|
|
830
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
831
|
+
import os6 from "os";
|
|
881
832
|
import path7 from "path";
|
|
882
833
|
import { jwtVerify, importSPKI } from "jose";
|
|
883
834
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
@@ -899,12 +850,12 @@ var init_license = __esm({
|
|
|
899
850
|
});
|
|
900
851
|
|
|
901
852
|
// src/lib/plan-limits.ts
|
|
902
|
-
import { readFileSync as
|
|
853
|
+
import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
|
|
903
854
|
import path8 from "path";
|
|
904
855
|
function getLicenseSync() {
|
|
905
856
|
try {
|
|
906
857
|
if (!existsSync8(CACHE_PATH2)) return freeLicense();
|
|
907
|
-
const raw = JSON.parse(
|
|
858
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
|
|
908
859
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
909
860
|
const parts = raw.token.split(".");
|
|
910
861
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -943,7 +894,7 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
943
894
|
let count = 0;
|
|
944
895
|
try {
|
|
945
896
|
if (existsSync8(filePath)) {
|
|
946
|
-
const raw =
|
|
897
|
+
const raw = readFileSync7(filePath, "utf8");
|
|
947
898
|
const employees = JSON.parse(raw);
|
|
948
899
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
949
900
|
}
|
|
@@ -977,7 +928,7 @@ var init_plan_limits = __esm({
|
|
|
977
928
|
});
|
|
978
929
|
|
|
979
930
|
// src/lib/session-kill-telemetry.ts
|
|
980
|
-
import
|
|
931
|
+
import crypto from "crypto";
|
|
981
932
|
async function recordSessionKill(input) {
|
|
982
933
|
try {
|
|
983
934
|
const client = getClient();
|
|
@@ -987,7 +938,7 @@ async function recordSessionKill(input) {
|
|
|
987
938
|
ticks_idle, estimated_tokens_saved)
|
|
988
939
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
989
940
|
args: [
|
|
990
|
-
|
|
941
|
+
crypto.randomUUID(),
|
|
991
942
|
input.sessionName,
|
|
992
943
|
input.agentId,
|
|
993
944
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1310,6 +1261,7 @@ __export(tmux_routing_exports, {
|
|
|
1310
1261
|
isEmployeeAlive: () => isEmployeeAlive,
|
|
1311
1262
|
isExeSession: () => isExeSession,
|
|
1312
1263
|
isSessionBusy: () => isSessionBusy,
|
|
1264
|
+
notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
|
|
1313
1265
|
notifyParentExe: () => notifyParentExe,
|
|
1314
1266
|
parseParentExe: () => parseParentExe,
|
|
1315
1267
|
registerParentExe: () => registerParentExe,
|
|
@@ -1320,11 +1272,11 @@ __export(tmux_routing_exports, {
|
|
|
1320
1272
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
1321
1273
|
});
|
|
1322
1274
|
import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
|
|
1323
|
-
import { readFileSync as
|
|
1275
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync9, appendFileSync, readdirSync } from "fs";
|
|
1324
1276
|
import path9 from "path";
|
|
1325
|
-
import
|
|
1277
|
+
import os7 from "os";
|
|
1326
1278
|
import { fileURLToPath } from "url";
|
|
1327
|
-
import { unlinkSync as
|
|
1279
|
+
import { unlinkSync as unlinkSync2 } from "fs";
|
|
1328
1280
|
function spawnLockPath(sessionName) {
|
|
1329
1281
|
return path9.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
1330
1282
|
}
|
|
@@ -1343,7 +1295,7 @@ function acquireSpawnLock(sessionName) {
|
|
|
1343
1295
|
const lockFile = spawnLockPath(sessionName);
|
|
1344
1296
|
if (existsSync9(lockFile)) {
|
|
1345
1297
|
try {
|
|
1346
|
-
const lock = JSON.parse(
|
|
1298
|
+
const lock = JSON.parse(readFileSync8(lockFile, "utf8"));
|
|
1347
1299
|
const age = Date.now() - lock.timestamp;
|
|
1348
1300
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
1349
1301
|
return false;
|
|
@@ -1356,7 +1308,7 @@ function acquireSpawnLock(sessionName) {
|
|
|
1356
1308
|
}
|
|
1357
1309
|
function releaseSpawnLock(sessionName) {
|
|
1358
1310
|
try {
|
|
1359
|
-
|
|
1311
|
+
unlinkSync2(spawnLockPath(sessionName));
|
|
1360
1312
|
} catch {
|
|
1361
1313
|
}
|
|
1362
1314
|
}
|
|
@@ -1448,7 +1400,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
1448
1400
|
}
|
|
1449
1401
|
function getParentExe(sessionKey) {
|
|
1450
1402
|
try {
|
|
1451
|
-
const data = JSON.parse(
|
|
1403
|
+
const data = JSON.parse(readFileSync8(path9.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
1452
1404
|
return data.parentExe || null;
|
|
1453
1405
|
} catch {
|
|
1454
1406
|
return null;
|
|
@@ -1456,7 +1408,7 @@ function getParentExe(sessionKey) {
|
|
|
1456
1408
|
}
|
|
1457
1409
|
function getDispatchedBy(sessionKey) {
|
|
1458
1410
|
try {
|
|
1459
|
-
const data = JSON.parse(
|
|
1411
|
+
const data = JSON.parse(readFileSync8(
|
|
1460
1412
|
path9.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
1461
1413
|
"utf8"
|
|
1462
1414
|
));
|
|
@@ -1528,7 +1480,7 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
1528
1480
|
function readDebounceState() {
|
|
1529
1481
|
try {
|
|
1530
1482
|
if (!existsSync9(DEBOUNCE_FILE)) return {};
|
|
1531
|
-
const raw = JSON.parse(
|
|
1483
|
+
const raw = JSON.parse(readFileSync8(DEBOUNCE_FILE, "utf8"));
|
|
1532
1484
|
const state = {};
|
|
1533
1485
|
for (const [key, val] of Object.entries(raw)) {
|
|
1534
1486
|
if (typeof val === "number") {
|
|
@@ -1655,7 +1607,7 @@ function sendIntercom(targetSession) {
|
|
|
1655
1607
|
const agent = baseAgentName(rawAgent);
|
|
1656
1608
|
const taskDir = path9.join(process.cwd(), "exe", agent);
|
|
1657
1609
|
if (existsSync9(taskDir)) {
|
|
1658
|
-
const files =
|
|
1610
|
+
const files = readdirSync(taskDir).filter(
|
|
1659
1611
|
(f) => f.endsWith(".md") && f !== "DONE.txt"
|
|
1660
1612
|
);
|
|
1661
1613
|
if (files.length === 0) {
|
|
@@ -1714,6 +1666,21 @@ function notifyParentExe(sessionKey) {
|
|
|
1714
1666
|
}
|
|
1715
1667
|
return true;
|
|
1716
1668
|
}
|
|
1669
|
+
function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
|
|
1670
|
+
const transport = getTransport();
|
|
1671
|
+
try {
|
|
1672
|
+
const sessions = transport.listSessions();
|
|
1673
|
+
if (!sessions.includes(coordinatorSession)) return false;
|
|
1674
|
+
execSync4(
|
|
1675
|
+
`tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
|
|
1676
|
+
{ timeout: 3e3 }
|
|
1677
|
+
);
|
|
1678
|
+
logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
|
|
1679
|
+
return true;
|
|
1680
|
+
} catch {
|
|
1681
|
+
return false;
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1717
1684
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
1718
1685
|
if (isCoordinatorName(employeeName)) {
|
|
1719
1686
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
@@ -1787,7 +1754,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1787
1754
|
const transport = getTransport();
|
|
1788
1755
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
1789
1756
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
1790
|
-
const logDir = path9.join(
|
|
1757
|
+
const logDir = path9.join(os7.homedir(), ".exe-os", "session-logs");
|
|
1791
1758
|
const logFile = path9.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
1792
1759
|
if (!existsSync9(logDir)) {
|
|
1793
1760
|
mkdirSync5(logDir, { recursive: true });
|
|
@@ -1803,10 +1770,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1803
1770
|
} catch {
|
|
1804
1771
|
}
|
|
1805
1772
|
try {
|
|
1806
|
-
const claudeJsonPath = path9.join(
|
|
1773
|
+
const claudeJsonPath = path9.join(os7.homedir(), ".claude.json");
|
|
1807
1774
|
let claudeJson = {};
|
|
1808
1775
|
try {
|
|
1809
|
-
claudeJson = JSON.parse(
|
|
1776
|
+
claudeJson = JSON.parse(readFileSync8(claudeJsonPath, "utf8"));
|
|
1810
1777
|
} catch {
|
|
1811
1778
|
}
|
|
1812
1779
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -1818,13 +1785,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1818
1785
|
} catch {
|
|
1819
1786
|
}
|
|
1820
1787
|
try {
|
|
1821
|
-
const settingsDir = path9.join(
|
|
1788
|
+
const settingsDir = path9.join(os7.homedir(), ".claude", "projects");
|
|
1822
1789
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
1823
1790
|
const projSettingsDir = path9.join(settingsDir, normalizedKey);
|
|
1824
1791
|
const settingsPath = path9.join(projSettingsDir, "settings.json");
|
|
1825
1792
|
let settings = {};
|
|
1826
1793
|
try {
|
|
1827
|
-
settings = JSON.parse(
|
|
1794
|
+
settings = JSON.parse(readFileSync8(settingsPath, "utf8"));
|
|
1828
1795
|
} catch {
|
|
1829
1796
|
}
|
|
1830
1797
|
const perms = settings.permissions ?? {};
|
|
@@ -1869,7 +1836,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1869
1836
|
let legacyFallbackWarned = false;
|
|
1870
1837
|
if (!useExeAgent && !useBinSymlink) {
|
|
1871
1838
|
const identityPath = path9.join(
|
|
1872
|
-
|
|
1839
|
+
os7.homedir(),
|
|
1873
1840
|
".exe-os",
|
|
1874
1841
|
"identity",
|
|
1875
1842
|
`${employeeName}.md`
|
|
@@ -1899,7 +1866,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1899
1866
|
}
|
|
1900
1867
|
let sessionContextFlag = "";
|
|
1901
1868
|
try {
|
|
1902
|
-
const ctxDir = path9.join(
|
|
1869
|
+
const ctxDir = path9.join(os7.homedir(), ".exe-os", "session-cache");
|
|
1903
1870
|
mkdirSync5(ctxDir, { recursive: true });
|
|
1904
1871
|
const ctxFile = path9.join(ctxDir, `session-context-${sessionName}.md`);
|
|
1905
1872
|
const ctxContent = [
|
|
@@ -2060,14 +2027,14 @@ var init_tmux_routing = __esm({
|
|
|
2060
2027
|
init_intercom_queue();
|
|
2061
2028
|
init_plan_limits();
|
|
2062
2029
|
init_employees();
|
|
2063
|
-
SPAWN_LOCK_DIR = path9.join(
|
|
2064
|
-
SESSION_CACHE = path9.join(
|
|
2030
|
+
SPAWN_LOCK_DIR = path9.join(os7.homedir(), ".exe-os", "spawn-locks");
|
|
2031
|
+
SESSION_CACHE = path9.join(os7.homedir(), ".exe-os", "session-cache");
|
|
2065
2032
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
2066
2033
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
2067
2034
|
VERIFY_PANE_LINES = 200;
|
|
2068
2035
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
2069
2036
|
CODEX_DEBOUNCE_MS = 12e4;
|
|
2070
|
-
INTERCOM_LOG2 = path9.join(
|
|
2037
|
+
INTERCOM_LOG2 = path9.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
2071
2038
|
DEBOUNCE_FILE = path9.join(SESSION_CACHE, "intercom-debounce.json");
|
|
2072
2039
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
2073
2040
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
|
|
@@ -2091,6 +2058,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
|
|
|
2091
2058
|
args: [scope]
|
|
2092
2059
|
};
|
|
2093
2060
|
}
|
|
2061
|
+
function strictSessionScopeFilter(sessionScope, tableAlias) {
|
|
2062
|
+
const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
|
|
2063
|
+
if (!scope) return { sql: "", args: [] };
|
|
2064
|
+
const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
|
|
2065
|
+
return {
|
|
2066
|
+
sql: ` AND ${col} = ?`,
|
|
2067
|
+
args: [scope]
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
2094
2070
|
var init_task_scope = __esm({
|
|
2095
2071
|
"src/lib/task-scope.ts"() {
|
|
2096
2072
|
"use strict";
|
|
@@ -2098,13 +2074,125 @@ var init_task_scope = __esm({
|
|
|
2098
2074
|
}
|
|
2099
2075
|
});
|
|
2100
2076
|
|
|
2077
|
+
// src/lib/notifications.ts
|
|
2078
|
+
import crypto2 from "crypto";
|
|
2079
|
+
import path10 from "path";
|
|
2080
|
+
import os8 from "os";
|
|
2081
|
+
import {
|
|
2082
|
+
readFileSync as readFileSync9,
|
|
2083
|
+
readdirSync as readdirSync2,
|
|
2084
|
+
unlinkSync as unlinkSync3,
|
|
2085
|
+
existsSync as existsSync10,
|
|
2086
|
+
rmdirSync
|
|
2087
|
+
} from "fs";
|
|
2088
|
+
async function writeNotification(notification) {
|
|
2089
|
+
try {
|
|
2090
|
+
const client = getClient();
|
|
2091
|
+
const id = crypto2.randomUUID();
|
|
2092
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2093
|
+
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
2094
|
+
await client.execute({
|
|
2095
|
+
sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
|
|
2096
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
|
|
2097
|
+
args: [
|
|
2098
|
+
id,
|
|
2099
|
+
notification.agentId,
|
|
2100
|
+
notification.agentRole,
|
|
2101
|
+
notification.event,
|
|
2102
|
+
notification.project,
|
|
2103
|
+
notification.summary,
|
|
2104
|
+
notification.taskFile ?? null,
|
|
2105
|
+
sessionScope,
|
|
2106
|
+
now
|
|
2107
|
+
]
|
|
2108
|
+
});
|
|
2109
|
+
} catch (err) {
|
|
2110
|
+
process.stderr.write(`[notifications] WRITE FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
2111
|
+
`);
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
async function markAsReadByTaskFile(taskFile, sessionScope) {
|
|
2115
|
+
try {
|
|
2116
|
+
const client = getClient();
|
|
2117
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
2118
|
+
await client.execute({
|
|
2119
|
+
sql: `UPDATE notifications SET read = 1
|
|
2120
|
+
WHERE task_file = ? AND read = 0${scope.sql}`,
|
|
2121
|
+
args: [taskFile, ...scope.args]
|
|
2122
|
+
});
|
|
2123
|
+
} catch {
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
var init_notifications = __esm({
|
|
2127
|
+
"src/lib/notifications.ts"() {
|
|
2128
|
+
"use strict";
|
|
2129
|
+
init_database();
|
|
2130
|
+
init_task_scope();
|
|
2131
|
+
}
|
|
2132
|
+
});
|
|
2133
|
+
|
|
2134
|
+
// src/lib/state-bus.ts
|
|
2135
|
+
var StateBus, orgBus;
|
|
2136
|
+
var init_state_bus = __esm({
|
|
2137
|
+
"src/lib/state-bus.ts"() {
|
|
2138
|
+
"use strict";
|
|
2139
|
+
StateBus = class {
|
|
2140
|
+
handlers = /* @__PURE__ */ new Map();
|
|
2141
|
+
globalHandlers = /* @__PURE__ */ new Set();
|
|
2142
|
+
/** Emit an event to all subscribers */
|
|
2143
|
+
emit(event) {
|
|
2144
|
+
const typeHandlers = this.handlers.get(event.type);
|
|
2145
|
+
if (typeHandlers) {
|
|
2146
|
+
for (const handler of typeHandlers) {
|
|
2147
|
+
try {
|
|
2148
|
+
handler(event);
|
|
2149
|
+
} catch {
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
for (const handler of this.globalHandlers) {
|
|
2154
|
+
try {
|
|
2155
|
+
handler(event);
|
|
2156
|
+
} catch {
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
/** Subscribe to a specific event type */
|
|
2161
|
+
on(type, handler) {
|
|
2162
|
+
if (!this.handlers.has(type)) {
|
|
2163
|
+
this.handlers.set(type, /* @__PURE__ */ new Set());
|
|
2164
|
+
}
|
|
2165
|
+
this.handlers.get(type).add(handler);
|
|
2166
|
+
}
|
|
2167
|
+
/** Subscribe to ALL events */
|
|
2168
|
+
onAny(handler) {
|
|
2169
|
+
this.globalHandlers.add(handler);
|
|
2170
|
+
}
|
|
2171
|
+
/** Unsubscribe from a specific event type */
|
|
2172
|
+
off(type, handler) {
|
|
2173
|
+
this.handlers.get(type)?.delete(handler);
|
|
2174
|
+
}
|
|
2175
|
+
/** Unsubscribe from ALL events */
|
|
2176
|
+
offAny(handler) {
|
|
2177
|
+
this.globalHandlers.delete(handler);
|
|
2178
|
+
}
|
|
2179
|
+
/** Remove all listeners */
|
|
2180
|
+
clear() {
|
|
2181
|
+
this.handlers.clear();
|
|
2182
|
+
this.globalHandlers.clear();
|
|
2183
|
+
}
|
|
2184
|
+
};
|
|
2185
|
+
orgBus = new StateBus();
|
|
2186
|
+
}
|
|
2187
|
+
});
|
|
2188
|
+
|
|
2101
2189
|
// src/lib/tasks-crud.ts
|
|
2102
2190
|
import crypto3 from "crypto";
|
|
2103
|
-
import
|
|
2104
|
-
import
|
|
2191
|
+
import path11 from "path";
|
|
2192
|
+
import os9 from "os";
|
|
2105
2193
|
import { execSync as execSync5 } from "child_process";
|
|
2106
2194
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
2107
|
-
import { existsSync as
|
|
2195
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
2108
2196
|
async function writeCheckpoint(input) {
|
|
2109
2197
|
const client = getClient();
|
|
2110
2198
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -2279,8 +2367,8 @@ ${laneWarning}` : laneWarning;
|
|
|
2279
2367
|
}
|
|
2280
2368
|
if (input.baseDir) {
|
|
2281
2369
|
try {
|
|
2282
|
-
await mkdir3(
|
|
2283
|
-
await mkdir3(
|
|
2370
|
+
await mkdir3(path11.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
2371
|
+
await mkdir3(path11.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
2284
2372
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
2285
2373
|
await ensureGitignoreExe(input.baseDir);
|
|
2286
2374
|
} catch {
|
|
@@ -2316,13 +2404,19 @@ ${laneWarning}` : laneWarning;
|
|
|
2316
2404
|
});
|
|
2317
2405
|
if (input.baseDir) {
|
|
2318
2406
|
try {
|
|
2319
|
-
const EXE_OS_DIR =
|
|
2320
|
-
const mdPath =
|
|
2321
|
-
const mdDir =
|
|
2322
|
-
if (!
|
|
2407
|
+
const EXE_OS_DIR = path11.join(os9.homedir(), ".exe-os");
|
|
2408
|
+
const mdPath = path11.join(EXE_OS_DIR, taskFile);
|
|
2409
|
+
const mdDir = path11.dirname(mdPath);
|
|
2410
|
+
if (!existsSync11(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2323
2411
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
2324
2412
|
const mdContent = `# ${input.title}
|
|
2325
2413
|
|
|
2414
|
+
## MANDATORY: When done
|
|
2415
|
+
|
|
2416
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
2417
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2418
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2419
|
+
|
|
2326
2420
|
**ID:** ${id}
|
|
2327
2421
|
**Status:** ${initialStatus}
|
|
2328
2422
|
**Priority:** ${input.priority}
|
|
@@ -2336,12 +2430,6 @@ ${laneWarning}` : laneWarning;
|
|
|
2336
2430
|
## Context
|
|
2337
2431
|
|
|
2338
2432
|
${input.context}
|
|
2339
|
-
|
|
2340
|
-
## MANDATORY: When done
|
|
2341
|
-
|
|
2342
|
-
You MUST call update_task with status "done" and a result summary when finished.
|
|
2343
|
-
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2344
|
-
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2345
2433
|
`;
|
|
2346
2434
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2347
2435
|
} catch (err) {
|
|
@@ -2590,7 +2678,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2590
2678
|
await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
2591
2679
|
} catch {
|
|
2592
2680
|
}
|
|
2593
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
2681
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
2594
2682
|
try {
|
|
2595
2683
|
const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
2596
2684
|
clearQueueForAgent2(String(row.assigned_to));
|
|
@@ -2619,9 +2707,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
2619
2707
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
2620
2708
|
}
|
|
2621
2709
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
2622
|
-
const archPath =
|
|
2710
|
+
const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
2623
2711
|
try {
|
|
2624
|
-
if (
|
|
2712
|
+
if (existsSync11(archPath)) return;
|
|
2625
2713
|
const template = [
|
|
2626
2714
|
`# ${projectName} \u2014 System Architecture`,
|
|
2627
2715
|
"",
|
|
@@ -2654,9 +2742,9 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
2654
2742
|
}
|
|
2655
2743
|
}
|
|
2656
2744
|
async function ensureGitignoreExe(baseDir) {
|
|
2657
|
-
const gitignorePath =
|
|
2745
|
+
const gitignorePath = path11.join(baseDir, ".gitignore");
|
|
2658
2746
|
try {
|
|
2659
|
-
if (
|
|
2747
|
+
if (existsSync11(gitignorePath)) {
|
|
2660
2748
|
const content = readFileSync10(gitignorePath, "utf-8");
|
|
2661
2749
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
2662
2750
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
@@ -2688,58 +2776,42 @@ var init_tasks_crud = __esm({
|
|
|
2688
2776
|
});
|
|
2689
2777
|
|
|
2690
2778
|
// src/lib/tasks-review.ts
|
|
2691
|
-
import
|
|
2692
|
-
import { existsSync as
|
|
2779
|
+
import path12 from "path";
|
|
2780
|
+
import { existsSync as existsSync12, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
|
|
2693
2781
|
async function countPendingReviews(sessionScope) {
|
|
2694
2782
|
const client = getClient();
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
args: [sessionScope]
|
|
2699
|
-
});
|
|
2700
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
2701
|
-
}
|
|
2783
|
+
const scope = strictSessionScopeFilter(
|
|
2784
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
2785
|
+
);
|
|
2702
2786
|
const result = await client.execute({
|
|
2703
|
-
sql:
|
|
2704
|
-
|
|
2787
|
+
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2788
|
+
WHERE status = 'needs_review'${scope.sql}`,
|
|
2789
|
+
args: [...scope.args]
|
|
2705
2790
|
});
|
|
2706
2791
|
return Number(result.rows[0]?.cnt) || 0;
|
|
2707
2792
|
}
|
|
2708
2793
|
async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
2709
2794
|
const client = getClient();
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
WHERE status = 'needs_review' AND updated_at > ?
|
|
2714
|
-
AND session_scope = ?`,
|
|
2715
|
-
args: [sinceIso, sessionScope]
|
|
2716
|
-
});
|
|
2717
|
-
return Number(result2.rows[0]?.cnt) || 0;
|
|
2718
|
-
}
|
|
2795
|
+
const scope = strictSessionScopeFilter(
|
|
2796
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
2797
|
+
);
|
|
2719
2798
|
const result = await client.execute({
|
|
2720
2799
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2721
|
-
WHERE status = 'needs_review' AND updated_at >
|
|
2722
|
-
args: [sinceIso]
|
|
2800
|
+
WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
|
|
2801
|
+
args: [sinceIso, ...scope.args]
|
|
2723
2802
|
});
|
|
2724
2803
|
return Number(result.rows[0]?.cnt) || 0;
|
|
2725
2804
|
}
|
|
2726
2805
|
async function listPendingReviews(limit, sessionScope) {
|
|
2727
2806
|
const client = getClient();
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
WHERE status = 'needs_review'
|
|
2732
|
-
AND session_scope = ?
|
|
2733
|
-
ORDER BY updated_at ASC LIMIT ?`,
|
|
2734
|
-
args: [sessionScope, limit]
|
|
2735
|
-
});
|
|
2736
|
-
return result2.rows;
|
|
2737
|
-
}
|
|
2807
|
+
const scope = strictSessionScopeFilter(
|
|
2808
|
+
sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
|
|
2809
|
+
);
|
|
2738
2810
|
const result = await client.execute({
|
|
2739
2811
|
sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
|
|
2740
|
-
WHERE status = 'needs_review'
|
|
2812
|
+
WHERE status = 'needs_review'${scope.sql}
|
|
2741
2813
|
ORDER BY updated_at ASC LIMIT ?`,
|
|
2742
|
-
args: [limit]
|
|
2814
|
+
args: [...scope.args, limit]
|
|
2743
2815
|
});
|
|
2744
2816
|
return result.rows;
|
|
2745
2817
|
}
|
|
@@ -2751,7 +2823,7 @@ async function cleanupOrphanedReviews() {
|
|
|
2751
2823
|
WHERE status IN ('open', 'needs_review', 'in_progress')
|
|
2752
2824
|
AND assigned_by = 'system'
|
|
2753
2825
|
AND title LIKE 'Review:%'
|
|
2754
|
-
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
|
|
2826
|
+
AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
|
|
2755
2827
|
args: [now]
|
|
2756
2828
|
});
|
|
2757
2829
|
const r1b = await client.execute({
|
|
@@ -2870,11 +2942,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2870
2942
|
);
|
|
2871
2943
|
}
|
|
2872
2944
|
try {
|
|
2873
|
-
const cacheDir =
|
|
2874
|
-
if (
|
|
2945
|
+
const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
|
|
2946
|
+
if (existsSync12(cacheDir)) {
|
|
2875
2947
|
for (const f of readdirSync3(cacheDir)) {
|
|
2876
2948
|
if (f.startsWith("review-notified-")) {
|
|
2877
|
-
unlinkSync4(
|
|
2949
|
+
unlinkSync4(path12.join(cacheDir, f));
|
|
2878
2950
|
}
|
|
2879
2951
|
}
|
|
2880
2952
|
}
|
|
@@ -2891,11 +2963,12 @@ var init_tasks_review = __esm({
|
|
|
2891
2963
|
init_tmux_routing();
|
|
2892
2964
|
init_session_key();
|
|
2893
2965
|
init_state_bus();
|
|
2966
|
+
init_task_scope();
|
|
2894
2967
|
}
|
|
2895
2968
|
});
|
|
2896
2969
|
|
|
2897
2970
|
// src/lib/tasks-chain.ts
|
|
2898
|
-
import
|
|
2971
|
+
import path13 from "path";
|
|
2899
2972
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
2900
2973
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
2901
2974
|
const client = getClient();
|
|
@@ -2912,7 +2985,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
2912
2985
|
});
|
|
2913
2986
|
for (const ur of unblockedRows.rows) {
|
|
2914
2987
|
try {
|
|
2915
|
-
const ubFile =
|
|
2988
|
+
const ubFile = path13.join(baseDir, String(ur.task_file));
|
|
2916
2989
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
2917
2990
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
2918
2991
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -2947,7 +3020,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
|
|
|
2947
3020
|
const scScope = sessionScopeFilter();
|
|
2948
3021
|
const remaining = await client.execute({
|
|
2949
3022
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2950
|
-
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
|
|
3023
|
+
WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
|
|
2951
3024
|
args: [parentTaskId, ...scScope.args]
|
|
2952
3025
|
});
|
|
2953
3026
|
const cnt = Number(remaining.rows[0]?.cnt ?? 1);
|
|
@@ -2981,7 +3054,7 @@ var init_tasks_chain = __esm({
|
|
|
2981
3054
|
|
|
2982
3055
|
// src/lib/project-name.ts
|
|
2983
3056
|
import { execSync as execSync6 } from "child_process";
|
|
2984
|
-
import
|
|
3057
|
+
import path14 from "path";
|
|
2985
3058
|
function getProjectName(cwd) {
|
|
2986
3059
|
const dir = cwd ?? process.cwd();
|
|
2987
3060
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -2994,7 +3067,7 @@ function getProjectName(cwd) {
|
|
|
2994
3067
|
timeout: 2e3,
|
|
2995
3068
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2996
3069
|
}).trim();
|
|
2997
|
-
repoRoot =
|
|
3070
|
+
repoRoot = path14.dirname(gitCommonDir);
|
|
2998
3071
|
} catch {
|
|
2999
3072
|
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
3000
3073
|
cwd: dir,
|
|
@@ -3003,11 +3076,11 @@ function getProjectName(cwd) {
|
|
|
3003
3076
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3004
3077
|
}).trim();
|
|
3005
3078
|
}
|
|
3006
|
-
_cached2 =
|
|
3079
|
+
_cached2 = path14.basename(repoRoot);
|
|
3007
3080
|
_cachedCwd = dir;
|
|
3008
3081
|
return _cached2;
|
|
3009
3082
|
} catch {
|
|
3010
|
-
_cached2 =
|
|
3083
|
+
_cached2 = path14.basename(dir);
|
|
3011
3084
|
_cachedCwd = dir;
|
|
3012
3085
|
return _cached2;
|
|
3013
3086
|
}
|
|
@@ -3480,7 +3553,7 @@ __export(tasks_exports, {
|
|
|
3480
3553
|
updateTaskStatus: () => updateTaskStatus,
|
|
3481
3554
|
writeCheckpoint: () => writeCheckpoint
|
|
3482
3555
|
});
|
|
3483
|
-
import
|
|
3556
|
+
import path15 from "path";
|
|
3484
3557
|
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, unlinkSync as unlinkSync5 } from "fs";
|
|
3485
3558
|
async function createTask(input) {
|
|
3486
3559
|
const result = await createTaskCore(input);
|
|
@@ -3500,12 +3573,12 @@ async function updateTask(input) {
|
|
|
3500
3573
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
3501
3574
|
try {
|
|
3502
3575
|
const agent = String(row.assigned_to);
|
|
3503
|
-
const cacheDir =
|
|
3504
|
-
const cachePath =
|
|
3576
|
+
const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
|
|
3577
|
+
const cachePath = path15.join(cacheDir, `current-task-${agent}.json`);
|
|
3505
3578
|
if (input.status === "in_progress") {
|
|
3506
3579
|
mkdirSync6(cacheDir, { recursive: true });
|
|
3507
3580
|
writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
3508
|
-
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
3581
|
+
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
|
|
3509
3582
|
try {
|
|
3510
3583
|
unlinkSync5(cachePath);
|
|
3511
3584
|
} catch {
|
|
@@ -3513,10 +3586,10 @@ async function updateTask(input) {
|
|
|
3513
3586
|
}
|
|
3514
3587
|
} catch {
|
|
3515
3588
|
}
|
|
3516
|
-
if (input.status === "done") {
|
|
3589
|
+
if (input.status === "done" || input.status === "closed") {
|
|
3517
3590
|
await cleanupReviewFile(row, taskFile, input.baseDir);
|
|
3518
3591
|
}
|
|
3519
|
-
if (input.status === "done" || input.status === "cancelled") {
|
|
3592
|
+
if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
|
|
3520
3593
|
try {
|
|
3521
3594
|
const client = getClient();
|
|
3522
3595
|
const taskTitle = String(row.title);
|
|
@@ -3532,7 +3605,7 @@ async function updateTask(input) {
|
|
|
3532
3605
|
if (!isCoordinatorName(assignedAgent)) {
|
|
3533
3606
|
try {
|
|
3534
3607
|
const draftClient = getClient();
|
|
3535
|
-
if (input.status === "done") {
|
|
3608
|
+
if (input.status === "done" || input.status === "closed") {
|
|
3536
3609
|
await draftClient.execute({
|
|
3537
3610
|
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
3538
3611
|
args: [assignedAgent]
|
|
@@ -3549,7 +3622,7 @@ async function updateTask(input) {
|
|
|
3549
3622
|
try {
|
|
3550
3623
|
const client = getClient();
|
|
3551
3624
|
const cascaded = await client.execute({
|
|
3552
|
-
sql: `UPDATE tasks SET status = '
|
|
3625
|
+
sql: `UPDATE tasks SET status = 'closed', updated_at = ?
|
|
3553
3626
|
WHERE parent_task_id = ? AND status = 'needs_review'`,
|
|
3554
3627
|
args: [now, taskId]
|
|
3555
3628
|
});
|
|
@@ -3562,14 +3635,14 @@ async function updateTask(input) {
|
|
|
3562
3635
|
} catch {
|
|
3563
3636
|
}
|
|
3564
3637
|
}
|
|
3565
|
-
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
3638
|
+
const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
|
|
3566
3639
|
if (isTerminal) {
|
|
3567
3640
|
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
3568
3641
|
if (!isCoordinator) {
|
|
3569
3642
|
notifyTaskDone();
|
|
3570
3643
|
}
|
|
3571
3644
|
await markTaskNotificationsRead(taskFile);
|
|
3572
|
-
if (input.status === "done") {
|
|
3645
|
+
if (input.status === "done" || input.status === "closed") {
|
|
3573
3646
|
try {
|
|
3574
3647
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
3575
3648
|
} catch {
|
|
@@ -3589,7 +3662,7 @@ async function updateTask(input) {
|
|
|
3589
3662
|
}
|
|
3590
3663
|
}
|
|
3591
3664
|
}
|
|
3592
|
-
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3665
|
+
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3593
3666
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3594
3667
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3595
3668
|
taskId,
|