@askexenow/exe-os 0.9.112 → 0.9.113
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/README.md +9 -7
- package/dist/bin/agentic-ontology-backfill.js +54 -11
- package/dist/bin/agentic-reflection-backfill.js +29 -1
- package/dist/bin/agentic-semantic-label.js +29 -1
- package/dist/bin/backfill-conversations.js +53 -10
- package/dist/bin/backfill-responses.js +54 -11
- package/dist/bin/backfill-vectors.js +29 -1
- package/dist/bin/bulk-sync-postgres.js +55 -12
- package/dist/bin/cleanup-stale-review-tasks.js +75 -15
- package/dist/bin/cli.js +293 -76
- package/dist/bin/exe-agent-config.js +7 -1
- package/dist/bin/exe-agent.js +28 -2
- package/dist/bin/exe-assign.js +54 -11
- package/dist/bin/exe-boot.js +481 -147
- package/dist/bin/exe-call.js +45 -4
- package/dist/bin/exe-cloud.js +93 -15
- package/dist/bin/exe-dispatch.js +369 -24
- package/dist/bin/exe-doctor.js +53 -10
- package/dist/bin/exe-export-behaviors.js +54 -11
- package/dist/bin/exe-forget.js +54 -11
- package/dist/bin/exe-gateway.js +128 -23
- package/dist/bin/exe-heartbeat.js +75 -15
- package/dist/bin/exe-kill.js +54 -11
- package/dist/bin/exe-launch-agent.js +70 -12
- package/dist/bin/exe-new-employee.js +175 -7
- package/dist/bin/exe-pending-messages.js +75 -15
- package/dist/bin/exe-pending-notifications.js +75 -15
- package/dist/bin/exe-pending-reviews.js +75 -15
- package/dist/bin/exe-rename.js +54 -11
- package/dist/bin/exe-review.js +54 -11
- package/dist/bin/exe-search.js +54 -11
- package/dist/bin/exe-session-cleanup.js +491 -146
- package/dist/bin/exe-settings.js +10 -4
- package/dist/bin/exe-start-codex.js +524 -245
- package/dist/bin/exe-start-opencode.js +534 -165
- package/dist/bin/exe-status.js +75 -15
- package/dist/bin/exe-support.js +1 -1
- package/dist/bin/exe-team.js +54 -11
- package/dist/bin/git-sweep.js +369 -24
- package/dist/bin/graph-backfill.js +54 -11
- package/dist/bin/graph-export.js +54 -11
- package/dist/bin/install.js +62 -4
- package/dist/bin/intercom-check.js +491 -146
- package/dist/bin/pre-publish.js +13 -1
- package/dist/bin/scan-tasks.js +369 -24
- package/dist/bin/setup.js +91 -13
- package/dist/bin/shard-migrate.js +54 -11
- package/dist/bin/stack-update.js +1 -1
- package/dist/bin/update.js +3 -3
- package/dist/gateway/index.js +128 -23
- package/dist/hooks/bug-report-worker.js +128 -23
- package/dist/hooks/codex-stop-task-finalizer.js +512 -140
- package/dist/hooks/commit-complete.js +369 -24
- package/dist/hooks/error-recall.js +54 -11
- package/dist/hooks/ingest.js +4575 -252
- package/dist/hooks/instructions-loaded.js +54 -11
- package/dist/hooks/notification.js +54 -11
- package/dist/hooks/post-compact.js +75 -15
- package/dist/hooks/post-tool-combined.js +75 -15
- package/dist/hooks/pre-compact.js +449 -104
- package/dist/hooks/pre-tool-use.js +90 -15
- package/dist/hooks/prompt-submit.js +129 -24
- package/dist/hooks/session-end.js +451 -109
- package/dist/hooks/session-start.js +104 -16
- package/dist/hooks/stop.js +74 -14
- package/dist/hooks/subagent-stop.js +75 -15
- package/dist/hooks/summary-worker.js +73 -7
- package/dist/index.js +128 -23
- package/dist/lib/agent-config.js +16 -1
- package/dist/lib/cloud-sync.js +38 -1
- package/dist/lib/consolidation.js +16 -1
- package/dist/lib/database.js +16 -0
- package/dist/lib/db.js +16 -0
- package/dist/lib/device-registry.js +16 -0
- package/dist/lib/employee-templates.js +29 -3
- package/dist/lib/employees.js +16 -1
- package/dist/lib/exe-daemon.js +268 -42
- package/dist/lib/hybrid-search.js +54 -11
- package/dist/lib/license.js +3 -3
- package/dist/lib/messaging.js +21 -4
- package/dist/lib/schedules.js +29 -1
- package/dist/lib/skill-learning.js +458 -70
- package/dist/lib/status-brief.js +14 -1
- package/dist/lib/store.js +54 -11
- package/dist/lib/tasks.js +393 -91
- package/dist/lib/tmux-routing.js +316 -14
- package/dist/mcp/server.js +169 -30
- package/dist/mcp/tools/create-task.js +75 -13
- package/dist/mcp/tools/deactivate-behavior.js +33 -24
- package/dist/mcp/tools/list-tasks.js +21 -4
- package/dist/mcp/tools/send-message.js +21 -4
- package/dist/mcp/tools/update-task.js +390 -91
- package/dist/runtime/index.js +446 -101
- package/dist/tui/App.js +208 -54
- package/package.json +1 -1
package/dist/bin/exe-boot.js
CHANGED
|
@@ -331,11 +331,168 @@ var init_config = __esm({
|
|
|
331
331
|
}
|
|
332
332
|
});
|
|
333
333
|
|
|
334
|
+
// src/lib/runtime-table.ts
|
|
335
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
336
|
+
var init_runtime_table = __esm({
|
|
337
|
+
"src/lib/runtime-table.ts"() {
|
|
338
|
+
"use strict";
|
|
339
|
+
RUNTIME_TABLE = {
|
|
340
|
+
codex: {
|
|
341
|
+
binary: "codex",
|
|
342
|
+
launchMode: "interactive",
|
|
343
|
+
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
344
|
+
inlineFlag: "--no-alt-screen",
|
|
345
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
346
|
+
defaultModel: "gpt-5.5"
|
|
347
|
+
},
|
|
348
|
+
opencode: {
|
|
349
|
+
binary: "opencode",
|
|
350
|
+
launchMode: "exec",
|
|
351
|
+
autoApproveFlag: "--dangerously-skip-permissions",
|
|
352
|
+
inlineFlag: "",
|
|
353
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
354
|
+
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
DEFAULT_RUNTIME = "claude";
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// src/lib/agent-config.ts
|
|
362
|
+
var agent_config_exports = {};
|
|
363
|
+
__export(agent_config_exports, {
|
|
364
|
+
AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
|
|
365
|
+
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
366
|
+
KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
|
|
367
|
+
RUNTIME_LABELS: () => RUNTIME_LABELS,
|
|
368
|
+
clearAgentRuntime: () => clearAgentRuntime,
|
|
369
|
+
getAgentRuntime: () => getAgentRuntime,
|
|
370
|
+
loadAgentConfig: () => loadAgentConfig,
|
|
371
|
+
saveAgentConfig: () => saveAgentConfig,
|
|
372
|
+
setAgentMcps: () => setAgentMcps,
|
|
373
|
+
setAgentRuntime: () => setAgentRuntime
|
|
374
|
+
});
|
|
375
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
376
|
+
import path2 from "path";
|
|
377
|
+
function loadAgentConfig() {
|
|
378
|
+
if (!existsSync3(AGENT_CONFIG_PATH)) return {};
|
|
379
|
+
try {
|
|
380
|
+
return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
|
|
381
|
+
} catch {
|
|
382
|
+
return {};
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
function saveAgentConfig(config) {
|
|
386
|
+
const dir = path2.dirname(AGENT_CONFIG_PATH);
|
|
387
|
+
ensurePrivateDirSync(dir);
|
|
388
|
+
writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
389
|
+
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
390
|
+
}
|
|
391
|
+
function getAgentRuntime(agentId) {
|
|
392
|
+
const config = loadAgentConfig();
|
|
393
|
+
const entry = config[agentId];
|
|
394
|
+
if (entry) return entry;
|
|
395
|
+
const orgDefault = config["default"];
|
|
396
|
+
if (orgDefault) return orgDefault;
|
|
397
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
398
|
+
}
|
|
399
|
+
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
400
|
+
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
401
|
+
if (!knownModels) {
|
|
402
|
+
return {
|
|
403
|
+
ok: false,
|
|
404
|
+
error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
if (!knownModels.includes(model)) {
|
|
408
|
+
return {
|
|
409
|
+
ok: false,
|
|
410
|
+
error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
const config = loadAgentConfig();
|
|
414
|
+
const existing = config[agentId];
|
|
415
|
+
const entry = { runtime, model };
|
|
416
|
+
if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
|
|
417
|
+
if (mcps !== void 0) {
|
|
418
|
+
entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
419
|
+
} else if (existing?.mcps) {
|
|
420
|
+
entry.mcps = existing.mcps;
|
|
421
|
+
}
|
|
422
|
+
config[agentId] = entry;
|
|
423
|
+
saveAgentConfig(config);
|
|
424
|
+
return { ok: true };
|
|
425
|
+
}
|
|
426
|
+
function setAgentMcps(agentId, mcps) {
|
|
427
|
+
const config = loadAgentConfig();
|
|
428
|
+
const existing = config[agentId] ?? getAgentRuntime(agentId);
|
|
429
|
+
existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
430
|
+
config[agentId] = existing;
|
|
431
|
+
saveAgentConfig(config);
|
|
432
|
+
return { ok: true };
|
|
433
|
+
}
|
|
434
|
+
function clearAgentRuntime(agentId) {
|
|
435
|
+
const config = loadAgentConfig();
|
|
436
|
+
delete config[agentId];
|
|
437
|
+
saveAgentConfig(config);
|
|
438
|
+
}
|
|
439
|
+
var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
|
|
440
|
+
var init_agent_config = __esm({
|
|
441
|
+
"src/lib/agent-config.ts"() {
|
|
442
|
+
"use strict";
|
|
443
|
+
init_config();
|
|
444
|
+
init_runtime_table();
|
|
445
|
+
init_secure_files();
|
|
446
|
+
AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
|
|
447
|
+
KNOWN_RUNTIMES = {
|
|
448
|
+
claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
449
|
+
codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
|
|
450
|
+
opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
|
|
451
|
+
};
|
|
452
|
+
RUNTIME_LABELS = {
|
|
453
|
+
claude: "Claude Code (Anthropic)",
|
|
454
|
+
codex: "Codex (OpenAI)",
|
|
455
|
+
opencode: "OpenCode (open source)"
|
|
456
|
+
};
|
|
457
|
+
DEFAULT_MODELS = {
|
|
458
|
+
claude: "claude-opus-4.6",
|
|
459
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
460
|
+
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
|
|
334
465
|
// src/lib/employees.ts
|
|
466
|
+
var employees_exports = {};
|
|
467
|
+
__export(employees_exports, {
|
|
468
|
+
COORDINATOR_ROLE: () => COORDINATOR_ROLE,
|
|
469
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
470
|
+
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
471
|
+
addEmployee: () => addEmployee,
|
|
472
|
+
baseAgentName: () => baseAgentName,
|
|
473
|
+
canCoordinate: () => canCoordinate,
|
|
474
|
+
getCoordinatorEmployee: () => getCoordinatorEmployee,
|
|
475
|
+
getCoordinatorName: () => getCoordinatorName,
|
|
476
|
+
getEmployee: () => getEmployee,
|
|
477
|
+
getEmployeeByRole: () => getEmployeeByRole,
|
|
478
|
+
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
479
|
+
hasRole: () => hasRole,
|
|
480
|
+
hireEmployee: () => hireEmployee,
|
|
481
|
+
isCoordinatorName: () => isCoordinatorName,
|
|
482
|
+
isCoordinatorRole: () => isCoordinatorRole,
|
|
483
|
+
isMultiInstance: () => isMultiInstance,
|
|
484
|
+
loadEmployees: () => loadEmployees,
|
|
485
|
+
loadEmployeesSync: () => loadEmployeesSync,
|
|
486
|
+
normalizeRole: () => normalizeRole,
|
|
487
|
+
normalizeRosterCase: () => normalizeRosterCase,
|
|
488
|
+
registerBinSymlinks: () => registerBinSymlinks,
|
|
489
|
+
saveEmployees: () => saveEmployees,
|
|
490
|
+
validateEmployeeName: () => validateEmployeeName
|
|
491
|
+
});
|
|
335
492
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
336
|
-
import { existsSync as
|
|
493
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
337
494
|
import { execSync } from "child_process";
|
|
338
|
-
import
|
|
495
|
+
import path3 from "path";
|
|
339
496
|
import os2 from "os";
|
|
340
497
|
function normalizeRole(role) {
|
|
341
498
|
return (role ?? "").trim().toLowerCase();
|
|
@@ -353,8 +510,26 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
353
510
|
if (!agentName) return false;
|
|
354
511
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
355
512
|
}
|
|
513
|
+
function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
|
|
514
|
+
return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
|
|
515
|
+
}
|
|
516
|
+
function validateEmployeeName(name) {
|
|
517
|
+
if (!name) {
|
|
518
|
+
return { valid: false, error: "Name is required" };
|
|
519
|
+
}
|
|
520
|
+
if (name.length > 32) {
|
|
521
|
+
return { valid: false, error: "Name must be 32 characters or fewer" };
|
|
522
|
+
}
|
|
523
|
+
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
524
|
+
return {
|
|
525
|
+
valid: false,
|
|
526
|
+
error: "Name must start with a letter and contain only lowercase alphanumeric characters"
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
return { valid: true };
|
|
530
|
+
}
|
|
356
531
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
357
|
-
if (!
|
|
532
|
+
if (!existsSync4(employeesPath)) {
|
|
358
533
|
return [];
|
|
359
534
|
}
|
|
360
535
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -365,13 +540,13 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
365
540
|
}
|
|
366
541
|
}
|
|
367
542
|
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
368
|
-
await mkdir2(
|
|
543
|
+
await mkdir2(path3.dirname(employeesPath), { recursive: true });
|
|
369
544
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
370
545
|
}
|
|
371
546
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
372
|
-
if (!
|
|
547
|
+
if (!existsSync4(employeesPath)) return [];
|
|
373
548
|
try {
|
|
374
|
-
return JSON.parse(
|
|
549
|
+
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
375
550
|
} catch {
|
|
376
551
|
return [];
|
|
377
552
|
}
|
|
@@ -379,6 +554,19 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
|
379
554
|
function getEmployee(employees, name) {
|
|
380
555
|
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
381
556
|
}
|
|
557
|
+
function getEmployeeByRole(employees, role) {
|
|
558
|
+
const lower = role.toLowerCase();
|
|
559
|
+
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
560
|
+
}
|
|
561
|
+
function getEmployeeNamesByRole(employees, role) {
|
|
562
|
+
const lower = role.toLowerCase();
|
|
563
|
+
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
564
|
+
}
|
|
565
|
+
function hasRole(agentName, role) {
|
|
566
|
+
const employees = loadEmployeesSync();
|
|
567
|
+
const emp = getEmployee(employees, agentName);
|
|
568
|
+
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
569
|
+
}
|
|
382
570
|
function baseAgentName(name, employees) {
|
|
383
571
|
const match = name.match(/^([a-zA-Z]+)\d+$/);
|
|
384
572
|
if (!match) return name;
|
|
@@ -393,6 +581,90 @@ function isMultiInstance(agentName, employees) {
|
|
|
393
581
|
if (!emp) return false;
|
|
394
582
|
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
395
583
|
}
|
|
584
|
+
function addEmployee(employees, employee) {
|
|
585
|
+
const { systemPrompt: _legacyPrompt, ...rest } = employee;
|
|
586
|
+
const normalized = { ...rest, name: employee.name.toLowerCase() };
|
|
587
|
+
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
588
|
+
throw new Error(`Employee '${normalized.name}' already exists`);
|
|
589
|
+
}
|
|
590
|
+
return [...employees, normalized];
|
|
591
|
+
}
|
|
592
|
+
function appendToCoordinatorTeam(employee) {
|
|
593
|
+
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
594
|
+
if (!coordinator) return;
|
|
595
|
+
const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
596
|
+
if (!existsSync4(idPath)) return;
|
|
597
|
+
const content = readFileSync3(idPath, "utf-8");
|
|
598
|
+
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
599
|
+
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
600
|
+
if (!teamMatch || teamMatch.index === void 0) return;
|
|
601
|
+
const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
|
|
602
|
+
const nextHeading = afterTeam.match(/\n## /);
|
|
603
|
+
const entry = `
|
|
604
|
+
**${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
|
|
605
|
+
`;
|
|
606
|
+
let updated;
|
|
607
|
+
if (nextHeading && nextHeading.index !== void 0) {
|
|
608
|
+
const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
|
|
609
|
+
updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
|
|
610
|
+
} else {
|
|
611
|
+
updated = content.trimEnd() + "\n" + entry;
|
|
612
|
+
}
|
|
613
|
+
writeFileSync2(idPath, updated, "utf-8");
|
|
614
|
+
}
|
|
615
|
+
function capitalize(s) {
|
|
616
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
617
|
+
}
|
|
618
|
+
async function hireEmployee(employee) {
|
|
619
|
+
const employees = await loadEmployees();
|
|
620
|
+
const updated = addEmployee(employees, employee);
|
|
621
|
+
await saveEmployees(updated);
|
|
622
|
+
try {
|
|
623
|
+
appendToCoordinatorTeam(employee);
|
|
624
|
+
} catch {
|
|
625
|
+
}
|
|
626
|
+
try {
|
|
627
|
+
const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
|
|
628
|
+
const config = loadAgentConfig2();
|
|
629
|
+
const name = employee.name.toLowerCase();
|
|
630
|
+
if (!config[name] && config["default"]) {
|
|
631
|
+
config[name] = { ...config["default"] };
|
|
632
|
+
saveAgentConfig2(config);
|
|
633
|
+
}
|
|
634
|
+
} catch {
|
|
635
|
+
}
|
|
636
|
+
return updated;
|
|
637
|
+
}
|
|
638
|
+
async function normalizeRosterCase(rosterPath) {
|
|
639
|
+
const employees = await loadEmployees(rosterPath);
|
|
640
|
+
let changed = false;
|
|
641
|
+
for (const emp of employees) {
|
|
642
|
+
if (emp.name !== emp.name.toLowerCase()) {
|
|
643
|
+
const oldName = emp.name;
|
|
644
|
+
emp.name = emp.name.toLowerCase();
|
|
645
|
+
changed = true;
|
|
646
|
+
try {
|
|
647
|
+
const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
|
|
648
|
+
const oldPath = path3.join(identityDir, `${oldName}.md`);
|
|
649
|
+
const newPath = path3.join(identityDir, `${emp.name}.md`);
|
|
650
|
+
if (existsSync4(oldPath) && !existsSync4(newPath)) {
|
|
651
|
+
renameSync2(oldPath, newPath);
|
|
652
|
+
} else if (existsSync4(oldPath) && oldPath !== newPath) {
|
|
653
|
+
const content = readFileSync3(oldPath, "utf-8");
|
|
654
|
+
writeFileSync2(newPath, content, "utf-8");
|
|
655
|
+
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
656
|
+
unlinkSync(oldPath);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
} catch {
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
if (changed) {
|
|
664
|
+
await saveEmployees(employees, rosterPath);
|
|
665
|
+
}
|
|
666
|
+
return changed;
|
|
667
|
+
}
|
|
396
668
|
function findExeBin() {
|
|
397
669
|
try {
|
|
398
670
|
return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
@@ -409,7 +681,7 @@ function registerBinSymlinks(name) {
|
|
|
409
681
|
errors.push("Could not find 'exe-os' in PATH");
|
|
410
682
|
return { created, skipped, errors };
|
|
411
683
|
}
|
|
412
|
-
const binDir =
|
|
684
|
+
const binDir = path3.dirname(exeBinPath);
|
|
413
685
|
let target;
|
|
414
686
|
try {
|
|
415
687
|
target = readlinkSync(exeBinPath);
|
|
@@ -419,8 +691,8 @@ function registerBinSymlinks(name) {
|
|
|
419
691
|
}
|
|
420
692
|
for (const suffix of ["", "-opencode"]) {
|
|
421
693
|
const linkName = `${name}${suffix}`;
|
|
422
|
-
const linkPath =
|
|
423
|
-
if (
|
|
694
|
+
const linkPath = path3.join(binDir, linkName);
|
|
695
|
+
if (existsSync4(linkPath)) {
|
|
424
696
|
skipped.push(linkName);
|
|
425
697
|
continue;
|
|
426
698
|
}
|
|
@@ -433,16 +705,17 @@ function registerBinSymlinks(name) {
|
|
|
433
705
|
}
|
|
434
706
|
return { created, skipped, errors };
|
|
435
707
|
}
|
|
436
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
|
|
708
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
|
|
437
709
|
var init_employees = __esm({
|
|
438
710
|
"src/lib/employees.ts"() {
|
|
439
711
|
"use strict";
|
|
440
712
|
init_config();
|
|
441
|
-
EMPLOYEES_PATH =
|
|
713
|
+
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
442
714
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
443
715
|
COORDINATOR_ROLE = "COO";
|
|
444
716
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
445
|
-
IDENTITY_DIR =
|
|
717
|
+
IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
|
|
718
|
+
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
446
719
|
}
|
|
447
720
|
});
|
|
448
721
|
|
|
@@ -503,7 +776,7 @@ var init_db_retry = __esm({
|
|
|
503
776
|
|
|
504
777
|
// src/lib/database-adapter.ts
|
|
505
778
|
import os3 from "os";
|
|
506
|
-
import
|
|
779
|
+
import path4 from "path";
|
|
507
780
|
import { createRequire } from "module";
|
|
508
781
|
import { pathToFileURL } from "url";
|
|
509
782
|
function quotedIdentifier(identifier) {
|
|
@@ -814,8 +1087,8 @@ async function loadPrismaClient() {
|
|
|
814
1087
|
}
|
|
815
1088
|
return new PrismaClient2();
|
|
816
1089
|
}
|
|
817
|
-
const exeDbRoot = process.env.EXE_DB_ROOT ??
|
|
818
|
-
const requireFromExeDb = createRequire(
|
|
1090
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os3.homedir(), "exe-db");
|
|
1091
|
+
const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
|
|
819
1092
|
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
820
1093
|
const module = await import(pathToFileURL(prismaEntry).href);
|
|
821
1094
|
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
@@ -1096,8 +1369,8 @@ var init_memory = __esm({
|
|
|
1096
1369
|
|
|
1097
1370
|
// src/lib/daemon-auth.ts
|
|
1098
1371
|
import crypto from "crypto";
|
|
1099
|
-
import
|
|
1100
|
-
import { existsSync as
|
|
1372
|
+
import path5 from "path";
|
|
1373
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
1101
1374
|
function normalizeToken(token) {
|
|
1102
1375
|
if (!token) return null;
|
|
1103
1376
|
const trimmed = token.trim();
|
|
@@ -1105,8 +1378,8 @@ function normalizeToken(token) {
|
|
|
1105
1378
|
}
|
|
1106
1379
|
function readDaemonToken() {
|
|
1107
1380
|
try {
|
|
1108
|
-
if (!
|
|
1109
|
-
return normalizeToken(
|
|
1381
|
+
if (!existsSync5(DAEMON_TOKEN_PATH)) return null;
|
|
1382
|
+
return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
|
|
1110
1383
|
} catch {
|
|
1111
1384
|
return null;
|
|
1112
1385
|
}
|
|
@@ -1116,7 +1389,7 @@ function ensureDaemonToken(seed) {
|
|
|
1116
1389
|
if (existing) return existing;
|
|
1117
1390
|
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
1118
1391
|
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1119
|
-
|
|
1392
|
+
writeFileSync3(DAEMON_TOKEN_PATH, `${token}
|
|
1120
1393
|
`, "utf8");
|
|
1121
1394
|
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
1122
1395
|
return token;
|
|
@@ -1127,7 +1400,7 @@ var init_daemon_auth = __esm({
|
|
|
1127
1400
|
"use strict";
|
|
1128
1401
|
init_config();
|
|
1129
1402
|
init_secure_files();
|
|
1130
|
-
DAEMON_TOKEN_PATH =
|
|
1403
|
+
DAEMON_TOKEN_PATH = path5.join(EXE_AI_DIR, "exed.token");
|
|
1131
1404
|
}
|
|
1132
1405
|
});
|
|
1133
1406
|
|
|
@@ -1136,8 +1409,8 @@ import net from "net";
|
|
|
1136
1409
|
import os4 from "os";
|
|
1137
1410
|
import { spawn, execSync as execSync2 } from "child_process";
|
|
1138
1411
|
import { randomUUID } from "crypto";
|
|
1139
|
-
import { existsSync as
|
|
1140
|
-
import
|
|
1412
|
+
import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
|
|
1413
|
+
import path6 from "path";
|
|
1141
1414
|
import { fileURLToPath } from "url";
|
|
1142
1415
|
function handleData(chunk) {
|
|
1143
1416
|
_buffer += chunk.toString();
|
|
@@ -1173,9 +1446,9 @@ function isZombie(pid) {
|
|
|
1173
1446
|
}
|
|
1174
1447
|
}
|
|
1175
1448
|
function cleanupStaleFiles() {
|
|
1176
|
-
if (
|
|
1449
|
+
if (existsSync6(PID_PATH)) {
|
|
1177
1450
|
try {
|
|
1178
|
-
const pid = parseInt(
|
|
1451
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
1179
1452
|
if (pid > 0) {
|
|
1180
1453
|
try {
|
|
1181
1454
|
process.kill(pid, 0);
|
|
@@ -1200,11 +1473,11 @@ function cleanupStaleFiles() {
|
|
|
1200
1473
|
}
|
|
1201
1474
|
}
|
|
1202
1475
|
function findPackageRoot() {
|
|
1203
|
-
let dir =
|
|
1204
|
-
const { root } =
|
|
1476
|
+
let dir = path6.dirname(fileURLToPath(import.meta.url));
|
|
1477
|
+
const { root } = path6.parse(dir);
|
|
1205
1478
|
while (dir !== root) {
|
|
1206
|
-
if (
|
|
1207
|
-
dir =
|
|
1479
|
+
if (existsSync6(path6.join(dir, "package.json"))) return dir;
|
|
1480
|
+
dir = path6.dirname(dir);
|
|
1208
1481
|
}
|
|
1209
1482
|
return null;
|
|
1210
1483
|
}
|
|
@@ -1222,8 +1495,8 @@ function spawnDaemon() {
|
|
|
1222
1495
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1223
1496
|
return;
|
|
1224
1497
|
}
|
|
1225
|
-
const daemonPath =
|
|
1226
|
-
if (!
|
|
1498
|
+
const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1499
|
+
if (!existsSync6(daemonPath)) {
|
|
1227
1500
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1228
1501
|
`);
|
|
1229
1502
|
return;
|
|
@@ -1232,7 +1505,7 @@ function spawnDaemon() {
|
|
|
1232
1505
|
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
1233
1506
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1234
1507
|
`);
|
|
1235
|
-
const logPath =
|
|
1508
|
+
const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
|
|
1236
1509
|
let stderrFd = "ignore";
|
|
1237
1510
|
try {
|
|
1238
1511
|
stderrFd = openSync(logPath, "a");
|
|
@@ -1397,9 +1670,9 @@ function killAndRespawnDaemon() {
|
|
|
1397
1670
|
}
|
|
1398
1671
|
try {
|
|
1399
1672
|
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
1400
|
-
if (
|
|
1673
|
+
if (existsSync6(PID_PATH)) {
|
|
1401
1674
|
try {
|
|
1402
|
-
const pid = parseInt(
|
|
1675
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
1403
1676
|
if (pid > 0) {
|
|
1404
1677
|
try {
|
|
1405
1678
|
process.kill(pid, "SIGKILL");
|
|
@@ -1525,9 +1798,9 @@ var init_exe_daemon_client = __esm({
|
|
|
1525
1798
|
"use strict";
|
|
1526
1799
|
init_config();
|
|
1527
1800
|
init_daemon_auth();
|
|
1528
|
-
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ??
|
|
1529
|
-
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ??
|
|
1530
|
-
SPAWN_LOCK_PATH =
|
|
1801
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
|
|
1802
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
|
|
1803
|
+
SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1531
1804
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1532
1805
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
1533
1806
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -1760,7 +2033,7 @@ __export(database_exports, {
|
|
|
1760
2033
|
isInitialized: () => isInitialized,
|
|
1761
2034
|
setExternalClient: () => setExternalClient
|
|
1762
2035
|
});
|
|
1763
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
2036
|
+
import { chmodSync as chmodSync2, existsSync as existsSync7, statSync as statSync2, copyFileSync, unlinkSync as unlinkSync3, openSync as openSync2, closeSync as closeSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
1764
2037
|
import { createClient } from "@libsql/client";
|
|
1765
2038
|
import { homedir } from "os";
|
|
1766
2039
|
import { join } from "path";
|
|
@@ -1813,11 +2086,11 @@ function releaseDbLock() {
|
|
|
1813
2086
|
}
|
|
1814
2087
|
async function initDatabase(config) {
|
|
1815
2088
|
acquireDbLock();
|
|
1816
|
-
if (
|
|
2089
|
+
if (existsSync7(config.dbPath)) {
|
|
1817
2090
|
const dbStat = statSync2(config.dbPath);
|
|
1818
2091
|
if (dbStat.size === 0) {
|
|
1819
2092
|
const walPath = config.dbPath + "-wal";
|
|
1820
|
-
if (
|
|
2093
|
+
if (existsSync7(walPath) && statSync2(walPath).size > 0) {
|
|
1821
2094
|
const backupPath = config.dbPath + ".zeroed-" + Date.now();
|
|
1822
2095
|
copyFileSync(config.dbPath, backupPath);
|
|
1823
2096
|
unlinkSync3(config.dbPath);
|
|
@@ -3336,6 +3609,22 @@ async function ensureSchema() {
|
|
|
3336
3609
|
} catch (e) {
|
|
3337
3610
|
logCatchDebug("migration", e);
|
|
3338
3611
|
}
|
|
3612
|
+
try {
|
|
3613
|
+
await client.execute({
|
|
3614
|
+
sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
|
|
3615
|
+
args: []
|
|
3616
|
+
});
|
|
3617
|
+
} catch (e) {
|
|
3618
|
+
logCatchDebug("migration", e);
|
|
3619
|
+
}
|
|
3620
|
+
try {
|
|
3621
|
+
await client.execute({
|
|
3622
|
+
sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
|
|
3623
|
+
args: []
|
|
3624
|
+
});
|
|
3625
|
+
} catch (e) {
|
|
3626
|
+
logCatchDebug("migration", e);
|
|
3627
|
+
}
|
|
3339
3628
|
}
|
|
3340
3629
|
async function disposeDatabase() {
|
|
3341
3630
|
if (_walCheckpointTimer) {
|
|
@@ -3438,11 +3727,17 @@ var init_platform_procedures = __esm({
|
|
|
3438
3727
|
content: "Founder -> coordinator (the executive agent, internally routed as 'COO') -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the coordinator 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."
|
|
3439
3728
|
},
|
|
3440
3729
|
{
|
|
3441
|
-
title: "
|
|
3730
|
+
title: "Orchestration phase guidance \u2014 recommend, never trap",
|
|
3442
3731
|
domain: "workflow",
|
|
3443
3732
|
priority: "p1",
|
|
3444
3733
|
content: "New customers start best in Phase 1: founder \u2194 coordinator/Chief of Staff, building company context. Suggest Phase 2 executives when domain work repeats; suggest Phase 3 parallel execution only when review/permission gates are ready. This is guidance, not a blocker: users may jump phases anytime. Never overwrite their phase, role titles, identities, or custom org design."
|
|
3445
3734
|
},
|
|
3735
|
+
{
|
|
3736
|
+
title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
|
|
3737
|
+
domain: "identity",
|
|
3738
|
+
priority: "p0",
|
|
3739
|
+
content: "These procedures reference 'COO' as a shorthand for the coordinator role. This is an INTERNAL routing slot used by exe-os code (chain-of-command checks, dispatch logic, session detection). It is NOT your display title. Your actual title comes from your identity file's `title:` field \u2014 that is what you use externally: introductions, sign-offs, team comms, and any user-facing text. If your identity says `title: AI Chief of Staff`, you are the AI Chief of Staff. The routing slot stays `role: coo` for code compatibility \u2014 never rename it, but also never introduce yourself as 'COO' unless your identity file explicitly says so. The founder chose your title; respect it."
|
|
3740
|
+
},
|
|
3446
3741
|
{
|
|
3447
3742
|
title: "Single dispatch path \u2014 create_task only",
|
|
3448
3743
|
domain: "workflow",
|
|
@@ -3476,6 +3771,12 @@ var init_platform_procedures = __esm({
|
|
|
3476
3771
|
priority: "p0",
|
|
3477
3772
|
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."
|
|
3478
3773
|
},
|
|
3774
|
+
{
|
|
3775
|
+
title: "Destructive operations \u2014 mandatory reviewer gate",
|
|
3776
|
+
domain: "security",
|
|
3777
|
+
priority: "p0",
|
|
3778
|
+
content: "Before ANY destructive operation (delete, remove, overwrite, drop, reset, force-push, truncate), you MUST: (1) Have your full task spec accessible \u2014 if you cannot read it, STOP and report to your reviewer. Never improvise destructive actions. (2) Confirm with your reviewer (assigned_by or COO) before executing. (3) If the task spec explicitly authorizes the operation, proceed \u2014 but log it. Violation = immediate task failure. This applies to ALL agents regardless of role."
|
|
3779
|
+
},
|
|
3479
3780
|
{
|
|
3480
3781
|
title: "Customer patch triage \u2014 upstream bug vs customization",
|
|
3481
3782
|
domain: "support",
|
|
@@ -3760,15 +4061,15 @@ __export(keychain_exports, {
|
|
|
3760
4061
|
setMasterKey: () => setMasterKey
|
|
3761
4062
|
});
|
|
3762
4063
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
3763
|
-
import { existsSync as
|
|
4064
|
+
import { existsSync as existsSync8, statSync as statSync3 } from "fs";
|
|
3764
4065
|
import { execSync as execSync3 } from "child_process";
|
|
3765
|
-
import
|
|
4066
|
+
import path7 from "path";
|
|
3766
4067
|
import os5 from "os";
|
|
3767
4068
|
function getKeyDir() {
|
|
3768
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
4069
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path7.join(os5.homedir(), ".exe-os");
|
|
3769
4070
|
}
|
|
3770
4071
|
function getKeyPath() {
|
|
3771
|
-
return
|
|
4072
|
+
return path7.join(getKeyDir(), "master.key");
|
|
3772
4073
|
}
|
|
3773
4074
|
function nativeKeychainAllowed() {
|
|
3774
4075
|
return process.env.EXE_OS_DISABLE_NATIVE_KEYCHAIN !== "1";
|
|
@@ -3799,7 +4100,7 @@ function isRootOnlyTrustedServerKeyFile(keyPath) {
|
|
|
3799
4100
|
if (!st.isFile() || (st.mode & 63) !== 0) return false;
|
|
3800
4101
|
if (uid === 0) return true;
|
|
3801
4102
|
const exeOsDir = process.env.EXE_OS_DIR;
|
|
3802
|
-
return Boolean(exeOsDir &&
|
|
4103
|
+
return Boolean(exeOsDir && path7.resolve(keyPath).startsWith(path7.resolve(exeOsDir) + path7.sep));
|
|
3803
4104
|
} catch {
|
|
3804
4105
|
return false;
|
|
3805
4106
|
}
|
|
@@ -3996,7 +4297,7 @@ async function getMasterKey() {
|
|
|
3996
4297
|
}
|
|
3997
4298
|
}
|
|
3998
4299
|
const keyPath = getKeyPath();
|
|
3999
|
-
if (!
|
|
4300
|
+
if (!existsSync8(keyPath)) {
|
|
4000
4301
|
process.stderr.write(
|
|
4001
4302
|
`[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
4002
4303
|
`
|
|
@@ -4104,7 +4405,7 @@ async function getKeyStorageInfo() {
|
|
|
4104
4405
|
}
|
|
4105
4406
|
}
|
|
4106
4407
|
const keyPath = getKeyPath();
|
|
4107
|
-
if (!
|
|
4408
|
+
if (!existsSync8(keyPath)) {
|
|
4108
4409
|
return {
|
|
4109
4410
|
kind: "missing",
|
|
4110
4411
|
secure: false,
|
|
@@ -4200,7 +4501,7 @@ async function deleteMasterKey() {
|
|
|
4200
4501
|
}
|
|
4201
4502
|
}
|
|
4202
4503
|
const keyPath = getKeyPath();
|
|
4203
|
-
if (
|
|
4504
|
+
if (existsSync8(keyPath)) {
|
|
4204
4505
|
await unlink(keyPath);
|
|
4205
4506
|
}
|
|
4206
4507
|
}
|
|
@@ -4323,14 +4624,14 @@ __export(shard_manager_exports, {
|
|
|
4323
4624
|
listShards: () => listShards,
|
|
4324
4625
|
shardExists: () => shardExists
|
|
4325
4626
|
});
|
|
4326
|
-
import
|
|
4327
|
-
import { existsSync as
|
|
4627
|
+
import path8 from "path";
|
|
4628
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync3, readdirSync, renameSync as renameSync3, statSync as statSync4 } from "fs";
|
|
4328
4629
|
import { createClient as createClient2 } from "@libsql/client";
|
|
4329
4630
|
function initShardManager(encryptionKey) {
|
|
4330
4631
|
_encryptionKey = encryptionKey;
|
|
4331
4632
|
_keyValidated = false;
|
|
4332
4633
|
_keyValidationPromise = null;
|
|
4333
|
-
if (!
|
|
4634
|
+
if (!existsSync9(SHARDS_DIR)) {
|
|
4334
4635
|
mkdirSync3(SHARDS_DIR, { recursive: true });
|
|
4335
4636
|
}
|
|
4336
4637
|
const existingShards = readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db"));
|
|
@@ -4351,7 +4652,7 @@ async function validateEncryptionKey() {
|
|
|
4351
4652
|
return true;
|
|
4352
4653
|
}
|
|
4353
4654
|
for (const shardFile of existingShards.slice(0, 3)) {
|
|
4354
|
-
const dbPath =
|
|
4655
|
+
const dbPath = path8.join(SHARDS_DIR, shardFile);
|
|
4355
4656
|
const testClient = createClient2({ url: `file:${dbPath}`, encryptionKey: _encryptionKey });
|
|
4356
4657
|
try {
|
|
4357
4658
|
await testClient.execute("SELECT COUNT(*) FROM sqlite_schema");
|
|
@@ -4394,7 +4695,7 @@ function getShardClient(projectName) {
|
|
|
4394
4695
|
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
4395
4696
|
evictLRU();
|
|
4396
4697
|
}
|
|
4397
|
-
const dbPath =
|
|
4698
|
+
const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
|
|
4398
4699
|
const client = createClient2({
|
|
4399
4700
|
url: `file:${dbPath}`,
|
|
4400
4701
|
encryptionKey: _encryptionKey
|
|
@@ -4405,13 +4706,13 @@ function getShardClient(projectName) {
|
|
|
4405
4706
|
}
|
|
4406
4707
|
function shardExists(projectName) {
|
|
4407
4708
|
const safeName = safeShardName(projectName);
|
|
4408
|
-
return
|
|
4709
|
+
return existsSync9(path8.join(SHARDS_DIR, `${safeName}.db`));
|
|
4409
4710
|
}
|
|
4410
4711
|
function safeShardName(projectName) {
|
|
4411
4712
|
return projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
4412
4713
|
}
|
|
4413
4714
|
function listShards() {
|
|
4414
|
-
if (!
|
|
4715
|
+
if (!existsSync9(SHARDS_DIR)) return [];
|
|
4415
4716
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
4416
4717
|
}
|
|
4417
4718
|
async function auditShardHealth(options = {}) {
|
|
@@ -4423,7 +4724,7 @@ async function auditShardHealth(options = {}) {
|
|
|
4423
4724
|
const names = listShards();
|
|
4424
4725
|
const shards = [];
|
|
4425
4726
|
for (const name of names) {
|
|
4426
|
-
const dbPath =
|
|
4727
|
+
const dbPath = path8.join(SHARDS_DIR, `${name}.db`);
|
|
4427
4728
|
const stat = statSync4(dbPath);
|
|
4428
4729
|
const item = {
|
|
4429
4730
|
name,
|
|
@@ -4458,7 +4759,7 @@ async function auditShardHealth(options = {}) {
|
|
|
4458
4759
|
_shards.delete(name);
|
|
4459
4760
|
_shardLastAccess.delete(name);
|
|
4460
4761
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4461
|
-
const archivedPath =
|
|
4762
|
+
const archivedPath = path8.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
|
|
4462
4763
|
renameSync3(dbPath, archivedPath);
|
|
4463
4764
|
item.archivedPath = archivedPath;
|
|
4464
4765
|
}
|
|
@@ -4686,11 +4987,11 @@ async function getReadyShardClient(projectName) {
|
|
|
4686
4987
|
client.close();
|
|
4687
4988
|
_shards.delete(safeName);
|
|
4688
4989
|
_shardLastAccess.delete(safeName);
|
|
4689
|
-
const dbPath =
|
|
4690
|
-
if (
|
|
4990
|
+
const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
|
|
4991
|
+
if (existsSync9(dbPath)) {
|
|
4691
4992
|
const stat = statSync4(dbPath);
|
|
4692
4993
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4693
|
-
const archivedPath =
|
|
4994
|
+
const archivedPath = path8.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
|
|
4694
4995
|
renameSync3(dbPath, archivedPath);
|
|
4695
4996
|
process.stderr.write(
|
|
4696
4997
|
`[shard-manager] Archived unreadable shard ${safeName}: ${archivedPath} (${stat.size} bytes, mtime ${stat.mtime.toISOString()})
|
|
@@ -4758,7 +5059,7 @@ var init_shard_manager = __esm({
|
|
|
4758
5059
|
"src/lib/shard-manager.ts"() {
|
|
4759
5060
|
"use strict";
|
|
4760
5061
|
init_config();
|
|
4761
|
-
SHARDS_DIR =
|
|
5062
|
+
SHARDS_DIR = path8.join(EXE_AI_DIR, "shards");
|
|
4762
5063
|
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
4763
5064
|
MAX_OPEN_SHARDS = 10;
|
|
4764
5065
|
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
@@ -4887,13 +5188,13 @@ __export(session_registry_exports, {
|
|
|
4887
5188
|
refreshSessionProject: () => refreshSessionProject,
|
|
4888
5189
|
registerSession: () => registerSession
|
|
4889
5190
|
});
|
|
4890
|
-
import { readFileSync as
|
|
5191
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync10 } from "fs";
|
|
4891
5192
|
import { execSync as execSync4 } from "child_process";
|
|
4892
|
-
import
|
|
5193
|
+
import path9 from "path";
|
|
4893
5194
|
import os6 from "os";
|
|
4894
5195
|
function registerSession(entry) {
|
|
4895
|
-
const dir =
|
|
4896
|
-
if (!
|
|
5196
|
+
const dir = path9.dirname(REGISTRY_PATH);
|
|
5197
|
+
if (!existsSync10(dir)) {
|
|
4897
5198
|
mkdirSync4(dir, { recursive: true });
|
|
4898
5199
|
}
|
|
4899
5200
|
const sessions = listSessions();
|
|
@@ -4903,7 +5204,7 @@ function registerSession(entry) {
|
|
|
4903
5204
|
} else {
|
|
4904
5205
|
sessions.push(entry);
|
|
4905
5206
|
}
|
|
4906
|
-
|
|
5207
|
+
writeFileSync4(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
4907
5208
|
}
|
|
4908
5209
|
function refreshSessionProject(windowName, projectDir) {
|
|
4909
5210
|
const sessions = listSessions();
|
|
@@ -4911,13 +5212,13 @@ function refreshSessionProject(windowName, projectDir) {
|
|
|
4911
5212
|
if (!entry || entry.projectDir === projectDir) return;
|
|
4912
5213
|
entry.projectDir = projectDir;
|
|
4913
5214
|
try {
|
|
4914
|
-
|
|
5215
|
+
writeFileSync4(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
4915
5216
|
} catch {
|
|
4916
5217
|
}
|
|
4917
5218
|
}
|
|
4918
5219
|
function listSessions() {
|
|
4919
5220
|
try {
|
|
4920
|
-
const raw =
|
|
5221
|
+
const raw = readFileSync6(REGISTRY_PATH, "utf8");
|
|
4921
5222
|
return JSON.parse(raw);
|
|
4922
5223
|
} catch {
|
|
4923
5224
|
return [];
|
|
@@ -4938,7 +5239,7 @@ function pruneStaleSessions() {
|
|
|
4938
5239
|
const alive = sessions.filter((s) => liveSet.has(s.windowName));
|
|
4939
5240
|
const pruned = sessions.length - alive.length;
|
|
4940
5241
|
if (pruned > 0) {
|
|
4941
|
-
|
|
5242
|
+
writeFileSync4(REGISTRY_PATH, JSON.stringify(alive, null, 2));
|
|
4942
5243
|
}
|
|
4943
5244
|
return pruned;
|
|
4944
5245
|
}
|
|
@@ -4946,7 +5247,7 @@ var REGISTRY_PATH;
|
|
|
4946
5247
|
var init_session_registry = __esm({
|
|
4947
5248
|
"src/lib/session-registry.ts"() {
|
|
4948
5249
|
"use strict";
|
|
4949
|
-
REGISTRY_PATH =
|
|
5250
|
+
REGISTRY_PATH = path9.join(os6.homedir(), ".exe-os", "session-registry.json");
|
|
4950
5251
|
}
|
|
4951
5252
|
});
|
|
4952
5253
|
|
|
@@ -5208,68 +5509,6 @@ var init_provider_table = __esm({
|
|
|
5208
5509
|
}
|
|
5209
5510
|
});
|
|
5210
5511
|
|
|
5211
|
-
// src/lib/runtime-table.ts
|
|
5212
|
-
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
5213
|
-
var init_runtime_table = __esm({
|
|
5214
|
-
"src/lib/runtime-table.ts"() {
|
|
5215
|
-
"use strict";
|
|
5216
|
-
RUNTIME_TABLE = {
|
|
5217
|
-
codex: {
|
|
5218
|
-
binary: "codex",
|
|
5219
|
-
launchMode: "interactive",
|
|
5220
|
-
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
5221
|
-
inlineFlag: "--no-alt-screen",
|
|
5222
|
-
apiKeyEnv: "OPENAI_API_KEY",
|
|
5223
|
-
defaultModel: "gpt-5.5"
|
|
5224
|
-
},
|
|
5225
|
-
opencode: {
|
|
5226
|
-
binary: "opencode",
|
|
5227
|
-
launchMode: "exec",
|
|
5228
|
-
autoApproveFlag: "--dangerously-skip-permissions",
|
|
5229
|
-
inlineFlag: "",
|
|
5230
|
-
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
5231
|
-
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
5232
|
-
}
|
|
5233
|
-
};
|
|
5234
|
-
DEFAULT_RUNTIME = "claude";
|
|
5235
|
-
}
|
|
5236
|
-
});
|
|
5237
|
-
|
|
5238
|
-
// src/lib/agent-config.ts
|
|
5239
|
-
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync10 } from "fs";
|
|
5240
|
-
import path9 from "path";
|
|
5241
|
-
function loadAgentConfig() {
|
|
5242
|
-
if (!existsSync10(AGENT_CONFIG_PATH)) return {};
|
|
5243
|
-
try {
|
|
5244
|
-
return JSON.parse(readFileSync6(AGENT_CONFIG_PATH, "utf-8"));
|
|
5245
|
-
} catch {
|
|
5246
|
-
return {};
|
|
5247
|
-
}
|
|
5248
|
-
}
|
|
5249
|
-
function getAgentRuntime(agentId) {
|
|
5250
|
-
const config = loadAgentConfig();
|
|
5251
|
-
const entry = config[agentId];
|
|
5252
|
-
if (entry) return entry;
|
|
5253
|
-
const orgDefault = config["default"];
|
|
5254
|
-
if (orgDefault) return orgDefault;
|
|
5255
|
-
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
5256
|
-
}
|
|
5257
|
-
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
5258
|
-
var init_agent_config = __esm({
|
|
5259
|
-
"src/lib/agent-config.ts"() {
|
|
5260
|
-
"use strict";
|
|
5261
|
-
init_config();
|
|
5262
|
-
init_runtime_table();
|
|
5263
|
-
init_secure_files();
|
|
5264
|
-
AGENT_CONFIG_PATH = path9.join(EXE_AI_DIR, "agent-config.json");
|
|
5265
|
-
DEFAULT_MODELS = {
|
|
5266
|
-
claude: "claude-opus-4.6",
|
|
5267
|
-
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
5268
|
-
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
5269
|
-
};
|
|
5270
|
-
}
|
|
5271
|
-
});
|
|
5272
|
-
|
|
5273
5512
|
// src/lib/intercom-queue.ts
|
|
5274
5513
|
var intercom_queue_exports = {};
|
|
5275
5514
|
__export(intercom_queue_exports, {
|
|
@@ -5764,7 +6003,7 @@ async function assertVpsLicense(opts) {
|
|
|
5764
6003
|
}
|
|
5765
6004
|
if (!transientFailure) {
|
|
5766
6005
|
throw new Error(
|
|
5767
|
-
"License validation failed: unknown backend state. Restore network connectivity to https://askexe.com
|
|
6006
|
+
"License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
|
|
5768
6007
|
);
|
|
5769
6008
|
}
|
|
5770
6009
|
const fresh = await getCachedLicense();
|
|
@@ -5801,7 +6040,7 @@ async function assertVpsLicense(opts) {
|
|
|
5801
6040
|
} catch {
|
|
5802
6041
|
}
|
|
5803
6042
|
throw new Error(
|
|
5804
|
-
`License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://askexe.com
|
|
6043
|
+
`License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://cloud.askexe.com and retry. This VPS image refuses to boot after the offline grace window.`
|
|
5805
6044
|
);
|
|
5806
6045
|
}
|
|
5807
6046
|
function startLicenseRevalidation(intervalMs = 36e5) {
|
|
@@ -5833,7 +6072,7 @@ var init_license = __esm({
|
|
|
5833
6072
|
LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
|
|
5834
6073
|
CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
|
|
5835
6074
|
DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
|
|
5836
|
-
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com
|
|
6075
|
+
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
|
|
5837
6076
|
RETRY_DELAY_MS = 500;
|
|
5838
6077
|
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
5839
6078
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
@@ -6490,6 +6729,19 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
6490
6729
|
args: [identifier, ...scope.args]
|
|
6491
6730
|
});
|
|
6492
6731
|
if (result.rows.length === 1) return result.rows[0];
|
|
6732
|
+
if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
|
|
6733
|
+
result = await client.execute({
|
|
6734
|
+
sql: `SELECT * FROM tasks WHERE id LIKE ?`,
|
|
6735
|
+
args: [`${identifier}%`]
|
|
6736
|
+
});
|
|
6737
|
+
if (result.rows.length === 1) return result.rows[0];
|
|
6738
|
+
if (result.rows.length > 1) {
|
|
6739
|
+
const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
|
|
6740
|
+
throw new Error(
|
|
6741
|
+
`Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
|
|
6742
|
+
);
|
|
6743
|
+
}
|
|
6744
|
+
}
|
|
6493
6745
|
result = await client.execute({
|
|
6494
6746
|
sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
|
|
6495
6747
|
args: [`%${identifier}%`, ...scope.args]
|
|
@@ -7344,12 +7596,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
7344
7596
|
WHERE blocked_by = ? AND status = 'blocked'`,
|
|
7345
7597
|
args: [now, taskId]
|
|
7346
7598
|
});
|
|
7347
|
-
if (
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
|
|
7599
|
+
if (unblocked.rowsAffected === 0) return;
|
|
7600
|
+
const ubScope = sessionScopeFilter();
|
|
7601
|
+
const unblockedRows = await client.execute({
|
|
7602
|
+
sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
|
|
7603
|
+
args: [now, ...ubScope.args]
|
|
7604
|
+
});
|
|
7605
|
+
if (baseDir) {
|
|
7353
7606
|
for (const ur of unblockedRows.rows) {
|
|
7354
7607
|
try {
|
|
7355
7608
|
const ubFile = path18.join(baseDir, String(ur.task_file));
|
|
@@ -7361,6 +7614,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
7361
7614
|
}
|
|
7362
7615
|
}
|
|
7363
7616
|
}
|
|
7617
|
+
if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
|
|
7618
|
+
try {
|
|
7619
|
+
const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
7620
|
+
const dispatched = /* @__PURE__ */ new Set();
|
|
7621
|
+
for (const ur of unblockedRows.rows) {
|
|
7622
|
+
const assignee = String(ur.assigned_to);
|
|
7623
|
+
if (dispatched.has(assignee)) continue;
|
|
7624
|
+
dispatched.add(assignee);
|
|
7625
|
+
queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
|
|
7626
|
+
}
|
|
7627
|
+
} catch {
|
|
7628
|
+
}
|
|
7629
|
+
}
|
|
7364
7630
|
}
|
|
7365
7631
|
async function findNextTask(assignedTo) {
|
|
7366
7632
|
const client = getClient();
|
|
@@ -7570,6 +7836,15 @@ var init_embedder = __esm({
|
|
|
7570
7836
|
// src/lib/behaviors.ts
|
|
7571
7837
|
import crypto5 from "crypto";
|
|
7572
7838
|
async function storeBehavior(opts) {
|
|
7839
|
+
try {
|
|
7840
|
+
const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
7841
|
+
const roster = loadEmployeesSync2();
|
|
7842
|
+
if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
|
|
7843
|
+
throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
|
|
7844
|
+
}
|
|
7845
|
+
} catch (e) {
|
|
7846
|
+
if (e instanceof Error && e.message.includes("not found in roster")) throw e;
|
|
7847
|
+
}
|
|
7573
7848
|
const client = getClient();
|
|
7574
7849
|
const id = crypto5.randomUUID();
|
|
7575
7850
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -8023,6 +8298,12 @@ async function updateTask(input) {
|
|
|
8023
8298
|
}
|
|
8024
8299
|
}
|
|
8025
8300
|
}
|
|
8301
|
+
if (input.status === "cancelled") {
|
|
8302
|
+
try {
|
|
8303
|
+
await cascadeUnblock(taskId, input.baseDir, now);
|
|
8304
|
+
} catch {
|
|
8305
|
+
}
|
|
8306
|
+
}
|
|
8026
8307
|
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
8027
8308
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
8028
8309
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
@@ -8554,11 +8835,12 @@ function getDispatchedBy(sessionKey) {
|
|
|
8554
8835
|
}
|
|
8555
8836
|
}
|
|
8556
8837
|
function resolveExeSession() {
|
|
8838
|
+
if (process.env.EXE_SESSION_NAME) {
|
|
8839
|
+
const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
8840
|
+
if (fromEnv) return fromEnv;
|
|
8841
|
+
}
|
|
8557
8842
|
const mySession = getMySession();
|
|
8558
8843
|
if (!mySession) {
|
|
8559
|
-
if (process.env.EXE_SESSION_NAME) {
|
|
8560
|
-
return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
8561
|
-
}
|
|
8562
8844
|
return null;
|
|
8563
8845
|
}
|
|
8564
8846
|
const fromSessionName = extractRootExe(mySession);
|
|
@@ -8573,6 +8855,10 @@ function resolveExeSession() {
|
|
|
8573
8855
|
`[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
|
|
8574
8856
|
`
|
|
8575
8857
|
);
|
|
8858
|
+
try {
|
|
8859
|
+
registerParentExe(key, fromSessionName);
|
|
8860
|
+
} catch {
|
|
8861
|
+
}
|
|
8576
8862
|
candidate = fromSessionName;
|
|
8577
8863
|
} else {
|
|
8578
8864
|
candidate = fromCache;
|
|
@@ -10261,6 +10547,27 @@ async function cloudSync(config) {
|
|
|
10261
10547
|
if (stmts.length > 0) await client.batch(stmts, "write");
|
|
10262
10548
|
pulled = pullResult.records.length;
|
|
10263
10549
|
} else {
|
|
10550
|
+
try {
|
|
10551
|
+
const incomingIds = pullResult.records.map((r) => sqlSafe(r.id));
|
|
10552
|
+
if (incomingIds.length > 0) {
|
|
10553
|
+
const ph = incomingIds.map(() => "?").join(",");
|
|
10554
|
+
const existing = await client.execute({
|
|
10555
|
+
sql: `SELECT id, version, timestamp FROM memories WHERE id IN (${ph})`,
|
|
10556
|
+
args: incomingIds
|
|
10557
|
+
});
|
|
10558
|
+
const localMap = new Map(existing.rows.map((r) => [String(r.id), r]));
|
|
10559
|
+
for (const rec of pullResult.records) {
|
|
10560
|
+
const local = localMap.get(String(rec.id));
|
|
10561
|
+
if (local && Number(local.version) > 0 && Number(local.version) !== Number(rec.version ?? 0)) {
|
|
10562
|
+
process.stderr.write(
|
|
10563
|
+
`[cloud-sync] CONFLICT: memory ${String(rec.id).slice(0, 8)} \u2014 local v${local.version} vs remote v${rec.version ?? 0}. Remote wins (LWW).
|
|
10564
|
+
`
|
|
10565
|
+
);
|
|
10566
|
+
}
|
|
10567
|
+
}
|
|
10568
|
+
}
|
|
10569
|
+
} catch {
|
|
10570
|
+
}
|
|
10264
10571
|
const stmts = pullResult.records.map((rec) => ({
|
|
10265
10572
|
sql: `INSERT OR REPLACE INTO memories
|
|
10266
10573
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
@@ -11608,9 +11915,23 @@ var PROCEDURES_MARKER = "EXE OS \u2014 VISION AND NON-NEGOTIABLE PRINCIPLES";
|
|
|
11608
11915
|
function getSessionPrompt(storedPrompt) {
|
|
11609
11916
|
const markerIndex = storedPrompt.indexOf(PROCEDURES_MARKER);
|
|
11610
11917
|
const withoutProcedures = markerIndex >= 0 ? storedPrompt.slice(0, markerIndex).trimEnd() : storedPrompt;
|
|
11918
|
+
let titlePrefix = "";
|
|
11919
|
+
const frontmatterMatch = withoutProcedures.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
11920
|
+
if (frontmatterMatch) {
|
|
11921
|
+
const titleMatch = frontmatterMatch[1].match(/^title:\s*(.+)$/m);
|
|
11922
|
+
const roleMatch = frontmatterMatch[1].match(/^role:\s*(.+)$/m);
|
|
11923
|
+
if (titleMatch) {
|
|
11924
|
+
const title = titleMatch[1].trim();
|
|
11925
|
+
const role = roleMatch ? roleMatch[1].trim() : "";
|
|
11926
|
+
if (title && role && title.toLowerCase() !== role.toLowerCase()) {
|
|
11927
|
+
titlePrefix = `## Your Identity
|
|
11928
|
+
You are **${title}** (specialist). `;
|
|
11929
|
+
}
|
|
11930
|
+
}
|
|
11931
|
+
}
|
|
11611
11932
|
const rolePrompt = withoutProcedures.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").replace(/<!--[\s\S]*?-->/g, "").trimStart();
|
|
11612
11933
|
const globalBlock = getGlobalProceduresBlock();
|
|
11613
|
-
return `${globalBlock}${rolePrompt}
|
|
11934
|
+
return `${globalBlock}${titlePrefix}${rolePrompt}
|
|
11614
11935
|
${BASE_OPERATING_PROCEDURES}`;
|
|
11615
11936
|
}
|
|
11616
11937
|
|
|
@@ -11817,7 +12138,20 @@ function buildActionRequired(data) {
|
|
|
11817
12138
|
const blockedTasks = data.globalTasks.filter((t) => t.status === "blocked");
|
|
11818
12139
|
if (blockedTasks.length > 0) {
|
|
11819
12140
|
hasIssues = true;
|
|
11820
|
-
|
|
12141
|
+
const ESCALATION_MS = 48 * 60 * 60 * 1e3;
|
|
12142
|
+
const stale = blockedTasks.filter((t) => {
|
|
12143
|
+
if (!t.updatedAt) return false;
|
|
12144
|
+
return Date.now() - new Date(t.updatedAt).getTime() > ESCALATION_MS;
|
|
12145
|
+
});
|
|
12146
|
+
if (stale.length > 0) {
|
|
12147
|
+
lines.push(` \u{1F534} ESCALATION: ${stale.length} task${stale.length === 1 ? "" : "s"} blocked >48h:`);
|
|
12148
|
+
for (const t of stale) {
|
|
12149
|
+
const ageH = Math.round((Date.now() - new Date(t.updatedAt).getTime()) / 36e5);
|
|
12150
|
+
lines.push(` - "${t.title}" (${t.assignedTo}) \u2014 blocked ${ageH}h`);
|
|
12151
|
+
}
|
|
12152
|
+
} else {
|
|
12153
|
+
lines.push(` \u{1F534} ${blockedTasks.length} blocked task${blockedTasks.length === 1 ? "" : "s"} \u2014 needs your decision`);
|
|
12154
|
+
}
|
|
11821
12155
|
}
|
|
11822
12156
|
if (data.flaggedIssues.length > 0) {
|
|
11823
12157
|
hasIssues = true;
|