@askexenow/exe-os 0.8.83 → 0.8.85
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 +746 -595
- package/dist/bin/backfill-responses.js +745 -594
- package/dist/bin/backfill-vectors.js +312 -226
- package/dist/bin/cleanup-stale-review-tasks.js +97 -2
- package/dist/bin/cli.js +14350 -12518
- package/dist/bin/exe-agent.js +97 -88
- package/dist/bin/exe-assign.js +1003 -854
- package/dist/bin/exe-boot.js +1257 -320
- package/dist/bin/exe-call.js +10 -0
- package/dist/bin/exe-cloud.js +29 -6
- package/dist/bin/exe-dispatch.js +210 -34
- package/dist/bin/exe-doctor.js +403 -6
- package/dist/bin/exe-export-behaviors.js +175 -72
- package/dist/bin/exe-forget.js +97 -2
- package/dist/bin/exe-gateway.js +550 -171
- package/dist/bin/exe-healthcheck.js +1 -0
- package/dist/bin/exe-heartbeat.js +100 -5
- package/dist/bin/exe-kill.js +175 -72
- package/dist/bin/exe-launch-agent.js +189 -76
- package/dist/bin/exe-link.js +902 -80
- package/dist/bin/exe-new-employee.js +38 -8
- package/dist/bin/exe-pending-messages.js +96 -2
- package/dist/bin/exe-pending-notifications.js +97 -2
- package/dist/bin/exe-pending-reviews.js +98 -3
- package/dist/bin/exe-rename.js +564 -23
- package/dist/bin/exe-review.js +231 -73
- package/dist/bin/exe-search.js +989 -226
- package/dist/bin/exe-session-cleanup.js +4806 -1665
- package/dist/bin/exe-settings.js +20 -5
- package/dist/bin/exe-status.js +97 -2
- package/dist/bin/exe-team.js +97 -2
- package/dist/bin/git-sweep.js +899 -207
- package/dist/bin/graph-backfill.js +175 -72
- package/dist/bin/graph-export.js +175 -72
- package/dist/bin/install.js +38 -7
- package/dist/bin/list-providers.js +1 -0
- package/dist/bin/scan-tasks.js +904 -211
- package/dist/bin/setup.js +867 -268
- package/dist/bin/shard-migrate.js +175 -72
- package/dist/bin/update.js +1 -0
- package/dist/bin/wiki-sync.js +175 -72
- package/dist/gateway/index.js +548 -166
- package/dist/hooks/bug-report-worker.js +208 -23
- package/dist/hooks/commit-complete.js +897 -205
- package/dist/hooks/error-recall.js +988 -226
- package/dist/hooks/ingest-worker.js +1638 -1194
- package/dist/hooks/ingest.js +3 -0
- package/dist/hooks/instructions-loaded.js +707 -97
- package/dist/hooks/notification.js +699 -89
- package/dist/hooks/post-compact.js +714 -104
- package/dist/hooks/pre-compact.js +897 -205
- package/dist/hooks/pre-tool-use.js +742 -123
- package/dist/hooks/prompt-ingest-worker.js +242 -101
- package/dist/hooks/prompt-submit.js +995 -233
- package/dist/hooks/response-ingest-worker.js +242 -101
- package/dist/hooks/session-end.js +3941 -400
- package/dist/hooks/session-start.js +1001 -226
- package/dist/hooks/stop.js +725 -115
- package/dist/hooks/subagent-stop.js +714 -104
- package/dist/hooks/summary-worker.js +1964 -1330
- package/dist/index.js +1651 -1053
- package/dist/lib/cloud-sync.js +907 -86
- package/dist/lib/consolidation.js +2 -1
- package/dist/lib/database.js +642 -87
- package/dist/lib/db-daemon-client.js +503 -0
- package/dist/lib/device-registry.js +547 -7
- package/dist/lib/embedder.js +14 -28
- package/dist/lib/employee-templates.js +84 -74
- package/dist/lib/employees.js +9 -0
- package/dist/lib/exe-daemon-client.js +16 -29
- package/dist/lib/exe-daemon.js +1955 -922
- package/dist/lib/hybrid-search.js +988 -226
- package/dist/lib/identity.js +87 -67
- package/dist/lib/keychain.js +9 -1
- package/dist/lib/messaging.js +8 -1
- package/dist/lib/reminders.js +91 -74
- package/dist/lib/schedules.js +96 -2
- package/dist/lib/skill-learning.js +103 -85
- package/dist/lib/store.js +234 -73
- package/dist/lib/tasks.js +111 -22
- package/dist/lib/tmux-routing.js +120 -31
- package/dist/lib/token-spend.js +273 -0
- package/dist/lib/ws-client.js +11 -0
- package/dist/mcp/server.js +5222 -475
- package/dist/mcp/tools/complete-reminder.js +94 -77
- package/dist/mcp/tools/create-reminder.js +94 -77
- package/dist/mcp/tools/create-task.js +120 -22
- package/dist/mcp/tools/deactivate-behavior.js +95 -77
- package/dist/mcp/tools/list-reminders.js +94 -77
- package/dist/mcp/tools/list-tasks.js +31 -1
- package/dist/mcp/tools/send-message.js +8 -1
- package/dist/mcp/tools/update-task.js +39 -10
- package/dist/runtime/index.js +911 -219
- package/dist/tui/App.js +997 -295
- package/package.json +6 -1
|
@@ -264,6 +264,7 @@ __export(employees_exports, {
|
|
|
264
264
|
DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
265
265
|
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
266
266
|
addEmployee: () => addEmployee,
|
|
267
|
+
baseAgentName: () => baseAgentName,
|
|
267
268
|
canCoordinate: () => canCoordinate,
|
|
268
269
|
getCoordinatorEmployee: () => getCoordinatorEmployee,
|
|
269
270
|
getCoordinatorName: () => getCoordinatorName,
|
|
@@ -360,6 +361,14 @@ function hasRole(agentName, role) {
|
|
|
360
361
|
const emp = getEmployee(employees, agentName);
|
|
361
362
|
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
362
363
|
}
|
|
364
|
+
function baseAgentName(name, employees) {
|
|
365
|
+
const match = name.match(/^([a-zA-Z]+)\d+$/);
|
|
366
|
+
if (!match) return name;
|
|
367
|
+
const base = match[1];
|
|
368
|
+
const roster = employees ?? loadEmployeesSync();
|
|
369
|
+
if (getEmployee(roster, base)) return base;
|
|
370
|
+
return name;
|
|
371
|
+
}
|
|
363
372
|
function isMultiInstance(agentName, employees) {
|
|
364
373
|
const roster = employees ?? loadEmployeesSync();
|
|
365
374
|
const emp = getEmployee(roster, agentName);
|
|
@@ -476,6 +485,12 @@ function getClient() {
|
|
|
476
485
|
if (!_resilientClient) {
|
|
477
486
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
478
487
|
}
|
|
488
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
489
|
+
return _resilientClient;
|
|
490
|
+
}
|
|
491
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
492
|
+
return _daemonClient;
|
|
493
|
+
}
|
|
479
494
|
return _resilientClient;
|
|
480
495
|
}
|
|
481
496
|
function getRawClient() {
|
|
@@ -964,6 +979,12 @@ async function ensureSchema() {
|
|
|
964
979
|
} catch {
|
|
965
980
|
}
|
|
966
981
|
}
|
|
982
|
+
try {
|
|
983
|
+
await client.execute(
|
|
984
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
|
|
985
|
+
);
|
|
986
|
+
} catch {
|
|
987
|
+
}
|
|
967
988
|
await client.executeMultiple(`
|
|
968
989
|
CREATE TABLE IF NOT EXISTS entities (
|
|
969
990
|
id TEXT PRIMARY KEY,
|
|
@@ -1016,7 +1037,30 @@ async function ensureSchema() {
|
|
|
1016
1037
|
entity_id TEXT NOT NULL,
|
|
1017
1038
|
PRIMARY KEY (hyperedge_id, entity_id)
|
|
1018
1039
|
);
|
|
1040
|
+
|
|
1041
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
1042
|
+
name,
|
|
1043
|
+
content=entities,
|
|
1044
|
+
content_rowid=rowid
|
|
1045
|
+
);
|
|
1046
|
+
|
|
1047
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
|
|
1048
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1049
|
+
END;
|
|
1050
|
+
|
|
1051
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
|
|
1052
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1053
|
+
END;
|
|
1054
|
+
|
|
1055
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
|
|
1056
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1057
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1058
|
+
END;
|
|
1019
1059
|
`);
|
|
1060
|
+
try {
|
|
1061
|
+
await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
|
|
1062
|
+
} catch {
|
|
1063
|
+
}
|
|
1020
1064
|
await client.executeMultiple(`
|
|
1021
1065
|
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
1022
1066
|
alias TEXT NOT NULL PRIMARY KEY,
|
|
@@ -1197,6 +1241,33 @@ async function ensureSchema() {
|
|
|
1197
1241
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
1198
1242
|
ON conversations(channel_id);
|
|
1199
1243
|
`);
|
|
1244
|
+
await client.executeMultiple(`
|
|
1245
|
+
CREATE TABLE IF NOT EXISTS session_agent_map (
|
|
1246
|
+
session_uuid TEXT PRIMARY KEY,
|
|
1247
|
+
agent_id TEXT NOT NULL,
|
|
1248
|
+
session_name TEXT,
|
|
1249
|
+
task_id TEXT,
|
|
1250
|
+
project_name TEXT,
|
|
1251
|
+
started_at TEXT NOT NULL
|
|
1252
|
+
);
|
|
1253
|
+
|
|
1254
|
+
CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
|
|
1255
|
+
ON session_agent_map(agent_id);
|
|
1256
|
+
`);
|
|
1257
|
+
try {
|
|
1258
|
+
const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
|
|
1259
|
+
if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
|
|
1260
|
+
await client.execute({
|
|
1261
|
+
sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
|
|
1262
|
+
SELECT session_id, agent_id, '', MIN(timestamp)
|
|
1263
|
+
FROM memories
|
|
1264
|
+
WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
|
|
1265
|
+
GROUP BY session_id, agent_id`,
|
|
1266
|
+
args: []
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
} catch {
|
|
1270
|
+
}
|
|
1200
1271
|
try {
|
|
1201
1272
|
await client.execute({
|
|
1202
1273
|
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
@@ -1330,8 +1401,30 @@ async function ensureSchema() {
|
|
|
1330
1401
|
});
|
|
1331
1402
|
} catch {
|
|
1332
1403
|
}
|
|
1404
|
+
for (const col of [
|
|
1405
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
1406
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
1407
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
1408
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
1409
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
1410
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
1411
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
1412
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
1413
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
1414
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
1415
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
1416
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
1417
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
1418
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
1419
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
1420
|
+
]) {
|
|
1421
|
+
try {
|
|
1422
|
+
await client.execute(col);
|
|
1423
|
+
} catch {
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1333
1426
|
}
|
|
1334
|
-
var _client, _resilientClient, initTurso;
|
|
1427
|
+
var _client, _resilientClient, _daemonClient, initTurso;
|
|
1335
1428
|
var init_database = __esm({
|
|
1336
1429
|
"src/lib/database.ts"() {
|
|
1337
1430
|
"use strict";
|
|
@@ -1339,6 +1432,7 @@ var init_database = __esm({
|
|
|
1339
1432
|
init_employees();
|
|
1340
1433
|
_client = null;
|
|
1341
1434
|
_resilientClient = null;
|
|
1435
|
+
_daemonClient = null;
|
|
1342
1436
|
initTurso = initDatabase;
|
|
1343
1437
|
}
|
|
1344
1438
|
});
|
|
@@ -2945,7 +3039,7 @@ function notifyParentExe(sessionKey) {
|
|
|
2945
3039
|
return true;
|
|
2946
3040
|
}
|
|
2947
3041
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
2948
|
-
if (
|
|
3042
|
+
if (isCoordinatorName(employeeName)) {
|
|
2949
3043
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
2950
3044
|
}
|
|
2951
3045
|
try {
|
|
@@ -3278,6 +3372,7 @@ var init_task_scope = __esm({
|
|
|
3278
3372
|
// src/lib/tasks-crud.ts
|
|
3279
3373
|
import crypto3 from "crypto";
|
|
3280
3374
|
import path11 from "path";
|
|
3375
|
+
import os8 from "os";
|
|
3281
3376
|
import { execSync as execSync5 } from "child_process";
|
|
3282
3377
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
3283
3378
|
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
|
|
@@ -3321,6 +3416,35 @@ function extractParentFromContext(contextBody) {
|
|
|
3321
3416
|
function slugify(title) {
|
|
3322
3417
|
return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
3323
3418
|
}
|
|
3419
|
+
function buildKeywordIndex() {
|
|
3420
|
+
const idx = /* @__PURE__ */ new Map();
|
|
3421
|
+
for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
|
|
3422
|
+
for (const kw of keywords) {
|
|
3423
|
+
const existing = idx.get(kw) ?? [];
|
|
3424
|
+
existing.push(role);
|
|
3425
|
+
idx.set(kw, existing);
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
return idx;
|
|
3429
|
+
}
|
|
3430
|
+
function checkLaneAffinity(title, context, assigneeName) {
|
|
3431
|
+
const employees = loadEmployeesSync();
|
|
3432
|
+
const employee = employees.find((e) => e.name === assigneeName);
|
|
3433
|
+
if (!employee) return void 0;
|
|
3434
|
+
const assigneeRole = employee.role;
|
|
3435
|
+
const text = `${title} ${context}`.toLowerCase();
|
|
3436
|
+
const matchedRoles = /* @__PURE__ */ new Set();
|
|
3437
|
+
for (const [keyword, roles] of KEYWORD_INDEX) {
|
|
3438
|
+
if (text.includes(keyword)) {
|
|
3439
|
+
for (const role of roles) matchedRoles.add(role);
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
if (matchedRoles.size === 0) return void 0;
|
|
3443
|
+
if (matchedRoles.has(assigneeRole)) return void 0;
|
|
3444
|
+
if (assigneeRole === "COO") return void 0;
|
|
3445
|
+
const expectedRoles = Array.from(matchedRoles).join(" or ");
|
|
3446
|
+
return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
|
|
3447
|
+
}
|
|
3324
3448
|
async function resolveTask(client, identifier, scopeSession) {
|
|
3325
3449
|
const scope = sessionScopeFilter(scopeSession);
|
|
3326
3450
|
let result = await client.execute({
|
|
@@ -3370,7 +3494,14 @@ async function createTaskCore(input) {
|
|
|
3370
3494
|
const id = crypto3.randomUUID();
|
|
3371
3495
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3372
3496
|
const slug = slugify(input.title);
|
|
3373
|
-
|
|
3497
|
+
let earlySessionScope = null;
|
|
3498
|
+
try {
|
|
3499
|
+
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
3500
|
+
earlySessionScope = resolveExeSession2();
|
|
3501
|
+
} catch {
|
|
3502
|
+
}
|
|
3503
|
+
const scope = earlySessionScope ?? "default";
|
|
3504
|
+
const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
|
|
3374
3505
|
let blockedById = null;
|
|
3375
3506
|
const initialStatus = input.blockedBy ? "blocked" : "open";
|
|
3376
3507
|
if (input.blockedBy) {
|
|
@@ -3410,6 +3541,13 @@ async function createTaskCore(input) {
|
|
|
3410
3541
|
if (dupCheck.rows.length > 0) {
|
|
3411
3542
|
warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
|
|
3412
3543
|
}
|
|
3544
|
+
if (!process.env.DISABLE_LANE_AFFINITY) {
|
|
3545
|
+
const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
|
|
3546
|
+
if (laneWarning) {
|
|
3547
|
+
warning = warning ? `${warning}
|
|
3548
|
+
${laneWarning}` : laneWarning;
|
|
3549
|
+
}
|
|
3550
|
+
}
|
|
3413
3551
|
if (input.baseDir) {
|
|
3414
3552
|
try {
|
|
3415
3553
|
await mkdir4(path11.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
@@ -3420,12 +3558,7 @@ async function createTaskCore(input) {
|
|
|
3420
3558
|
}
|
|
3421
3559
|
}
|
|
3422
3560
|
const complexity = input.complexity ?? "standard";
|
|
3423
|
-
|
|
3424
|
-
try {
|
|
3425
|
-
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
3426
|
-
sessionScope = resolveExeSession2();
|
|
3427
|
-
} catch {
|
|
3428
|
-
}
|
|
3561
|
+
const sessionScope = earlySessionScope;
|
|
3429
3562
|
await client.execute({
|
|
3430
3563
|
sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, session_scope, created_at, updated_at)
|
|
3431
3564
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -3452,6 +3585,39 @@ async function createTaskCore(input) {
|
|
|
3452
3585
|
now
|
|
3453
3586
|
]
|
|
3454
3587
|
});
|
|
3588
|
+
if (input.baseDir) {
|
|
3589
|
+
try {
|
|
3590
|
+
const EXE_OS_DIR = path11.join(os8.homedir(), ".exe-os");
|
|
3591
|
+
const mdPath = path11.join(EXE_OS_DIR, taskFile);
|
|
3592
|
+
const mdDir = path11.dirname(mdPath);
|
|
3593
|
+
if (!existsSync11(mdDir)) await mkdir4(mdDir, { recursive: true });
|
|
3594
|
+
const reviewer = input.reviewer ?? input.assignedBy;
|
|
3595
|
+
const mdContent = `# ${input.title}
|
|
3596
|
+
|
|
3597
|
+
**ID:** ${id}
|
|
3598
|
+
**Status:** ${initialStatus}
|
|
3599
|
+
**Priority:** ${input.priority}
|
|
3600
|
+
**Assigned by:** ${input.assignedBy}
|
|
3601
|
+
**Assigned to:** ${input.assignedTo}
|
|
3602
|
+
**Project:** ${input.projectName}
|
|
3603
|
+
**Created:** ${now.split("T")[0]}${parentTaskId ? `
|
|
3604
|
+
**Parent task:** ${parentTaskId}` : ""}
|
|
3605
|
+
**Reviewer:** ${reviewer}
|
|
3606
|
+
|
|
3607
|
+
## Context
|
|
3608
|
+
|
|
3609
|
+
${input.context}
|
|
3610
|
+
|
|
3611
|
+
## MANDATORY: When done
|
|
3612
|
+
|
|
3613
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
3614
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
3615
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
3616
|
+
`;
|
|
3617
|
+
await writeFile4(mdPath, mdContent, "utf-8");
|
|
3618
|
+
} catch {
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3455
3621
|
return {
|
|
3456
3622
|
id,
|
|
3457
3623
|
title: input.title,
|
|
@@ -3644,7 +3810,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
3644
3810
|
return { row, taskFile, now, taskId };
|
|
3645
3811
|
}
|
|
3646
3812
|
}
|
|
3647
|
-
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId
|
|
3813
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
|
|
3648
3814
|
process.stderr.write(
|
|
3649
3815
|
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
3650
3816
|
`
|
|
@@ -3756,12 +3922,22 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
3756
3922
|
} catch {
|
|
3757
3923
|
}
|
|
3758
3924
|
}
|
|
3759
|
-
var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
3925
|
+
var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
3760
3926
|
var init_tasks_crud = __esm({
|
|
3761
3927
|
"src/lib/tasks-crud.ts"() {
|
|
3762
3928
|
"use strict";
|
|
3763
3929
|
init_database();
|
|
3764
3930
|
init_task_scope();
|
|
3931
|
+
init_employees();
|
|
3932
|
+
LANE_KEYWORDS = {
|
|
3933
|
+
CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
|
|
3934
|
+
CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
|
|
3935
|
+
"Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
|
|
3936
|
+
"Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
|
|
3937
|
+
"Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
|
|
3938
|
+
"AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
|
|
3939
|
+
};
|
|
3940
|
+
KEYWORD_INDEX = buildKeywordIndex();
|
|
3765
3941
|
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
3766
3942
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
3767
3943
|
}
|
|
@@ -3791,7 +3967,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
|
3791
3967
|
const result2 = await client.execute({
|
|
3792
3968
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
3793
3969
|
WHERE status = 'needs_review' AND updated_at > ?
|
|
3794
|
-
AND
|
|
3970
|
+
AND session_scope = ?`,
|
|
3795
3971
|
args: [sinceIso, sessionScope]
|
|
3796
3972
|
});
|
|
3797
3973
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
@@ -3809,7 +3985,7 @@ async function listPendingReviews(limit, sessionScope) {
|
|
|
3809
3985
|
const result2 = await client.execute({
|
|
3810
3986
|
sql: `SELECT title, assigned_to, project_name FROM tasks
|
|
3811
3987
|
WHERE status = 'needs_review'
|
|
3812
|
-
AND
|
|
3988
|
+
AND session_scope = ?
|
|
3813
3989
|
ORDER BY priority ASC, created_at DESC LIMIT ?`,
|
|
3814
3990
|
args: [sessionScope, limit]
|
|
3815
3991
|
});
|
|
@@ -3930,14 +4106,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3930
4106
|
if (parts.length >= 3 && parts[0] === "review") {
|
|
3931
4107
|
const agent = parts[1];
|
|
3932
4108
|
const slug = parts.slice(2).join("-");
|
|
3933
|
-
const
|
|
4109
|
+
const legacyTaskFile = `exe/${agent}/${slug}.md`;
|
|
3934
4110
|
const result = await client.execute({
|
|
3935
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
3936
|
-
args: [now,
|
|
4111
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
|
|
4112
|
+
args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
|
|
3937
4113
|
});
|
|
3938
4114
|
if (result.rowsAffected > 0) {
|
|
3939
4115
|
process.stderr.write(
|
|
3940
|
-
`[review-cleanup] Cascaded original task to done
|
|
4116
|
+
`[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
|
|
3941
4117
|
`
|
|
3942
4118
|
);
|
|
3943
4119
|
}
|
|
@@ -4119,7 +4295,7 @@ function findSessionForProject(projectName) {
|
|
|
4119
4295
|
const sessions = listSessions();
|
|
4120
4296
|
for (const s of sessions) {
|
|
4121
4297
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
4122
|
-
if (proj === projectName &&
|
|
4298
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
4123
4299
|
}
|
|
4124
4300
|
return null;
|
|
4125
4301
|
}
|
|
@@ -4165,7 +4341,7 @@ var init_session_scope = __esm({
|
|
|
4165
4341
|
|
|
4166
4342
|
// src/lib/tasks-notify.ts
|
|
4167
4343
|
async function dispatchTaskToEmployee(input) {
|
|
4168
|
-
if (
|
|
4344
|
+
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
4169
4345
|
let crossProject = false;
|
|
4170
4346
|
if (input.projectName) {
|
|
4171
4347
|
try {
|
|
@@ -4644,7 +4820,7 @@ async function updateTask(input) {
|
|
|
4644
4820
|
}
|
|
4645
4821
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
4646
4822
|
if (isTerminal) {
|
|
4647
|
-
const isCoordinator =
|
|
4823
|
+
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
4648
4824
|
if (!isCoordinator) {
|
|
4649
4825
|
notifyTaskDone();
|
|
4650
4826
|
}
|
|
@@ -4669,7 +4845,7 @@ async function updateTask(input) {
|
|
|
4669
4845
|
}
|
|
4670
4846
|
}
|
|
4671
4847
|
}
|
|
4672
|
-
if (input.status === "done" &&
|
|
4848
|
+
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4673
4849
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
4674
4850
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
4675
4851
|
taskId,
|
|
@@ -4685,7 +4861,7 @@ async function updateTask(input) {
|
|
|
4685
4861
|
});
|
|
4686
4862
|
}
|
|
4687
4863
|
let nextTask;
|
|
4688
|
-
if (isTerminal &&
|
|
4864
|
+
if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
|
|
4689
4865
|
try {
|
|
4690
4866
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
4691
4867
|
} catch {
|
|
@@ -4742,6 +4918,7 @@ var init_tasks = __esm({
|
|
|
4742
4918
|
});
|
|
4743
4919
|
|
|
4744
4920
|
// src/lib/store.ts
|
|
4921
|
+
import { createHash } from "crypto";
|
|
4745
4922
|
init_database();
|
|
4746
4923
|
|
|
4747
4924
|
// src/lib/keychain.ts
|
|
@@ -4777,12 +4954,20 @@ async function getMasterKey() {
|
|
|
4777
4954
|
}
|
|
4778
4955
|
const keyPath = getKeyPath();
|
|
4779
4956
|
if (!existsSync3(keyPath)) {
|
|
4957
|
+
process.stderr.write(
|
|
4958
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os3.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
4959
|
+
`
|
|
4960
|
+
);
|
|
4780
4961
|
return null;
|
|
4781
4962
|
}
|
|
4782
4963
|
try {
|
|
4783
4964
|
const content = await readFile3(keyPath, "utf-8");
|
|
4784
4965
|
return Buffer.from(content.trim(), "base64");
|
|
4785
|
-
} catch {
|
|
4966
|
+
} catch (err) {
|
|
4967
|
+
process.stderr.write(
|
|
4968
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
4969
|
+
`
|
|
4970
|
+
);
|
|
4786
4971
|
return null;
|
|
4787
4972
|
}
|
|
4788
4973
|
}
|