@askexenow/exe-os 0.8.21 → 0.8.22

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.
@@ -446,11 +446,110 @@ var init_shard_manager = __esm({
446
446
  }
447
447
  });
448
448
 
449
+ // src/lib/employees.ts
450
+ var employees_exports = {};
451
+ __export(employees_exports, {
452
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
453
+ addEmployee: () => addEmployee,
454
+ getEmployee: () => getEmployee,
455
+ loadEmployees: () => loadEmployees,
456
+ registerBinSymlinks: () => registerBinSymlinks,
457
+ saveEmployees: () => saveEmployees,
458
+ validateEmployeeName: () => validateEmployeeName
459
+ });
460
+ import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
461
+ import { existsSync as existsSync5, symlinkSync, readlinkSync } from "fs";
462
+ import { execSync } from "child_process";
463
+ import path5 from "path";
464
+ function validateEmployeeName(name) {
465
+ if (!name) {
466
+ return { valid: false, error: "Name is required" };
467
+ }
468
+ if (name.length > 32) {
469
+ return { valid: false, error: "Name must be 32 characters or fewer" };
470
+ }
471
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
472
+ return {
473
+ valid: false,
474
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
475
+ };
476
+ }
477
+ return { valid: true };
478
+ }
479
+ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
480
+ if (!existsSync5(employeesPath)) {
481
+ return [];
482
+ }
483
+ const raw = await readFile3(employeesPath, "utf-8");
484
+ try {
485
+ return JSON.parse(raw);
486
+ } catch {
487
+ return [];
488
+ }
489
+ }
490
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
491
+ await mkdir3(path5.dirname(employeesPath), { recursive: true });
492
+ await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
493
+ }
494
+ function getEmployee(employees, name) {
495
+ return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
496
+ }
497
+ function addEmployee(employees, employee) {
498
+ const normalized = { ...employee, name: employee.name.toLowerCase() };
499
+ if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
500
+ throw new Error(`Employee '${normalized.name}' already exists`);
501
+ }
502
+ return [...employees, normalized];
503
+ }
504
+ function registerBinSymlinks(name) {
505
+ const created = [];
506
+ const skipped = [];
507
+ const errors = [];
508
+ let exeBinPath;
509
+ try {
510
+ exeBinPath = execSync("which exe", { encoding: "utf-8" }).trim();
511
+ } catch {
512
+ errors.push("Could not find 'exe' in PATH");
513
+ return { created, skipped, errors };
514
+ }
515
+ const binDir = path5.dirname(exeBinPath);
516
+ let target;
517
+ try {
518
+ target = readlinkSync(exeBinPath);
519
+ } catch {
520
+ errors.push("Could not read 'exe' symlink");
521
+ return { created, skipped, errors };
522
+ }
523
+ for (const suffix of ["", "-opencode"]) {
524
+ const linkName = `${name}${suffix}`;
525
+ const linkPath = path5.join(binDir, linkName);
526
+ if (existsSync5(linkPath)) {
527
+ skipped.push(linkName);
528
+ continue;
529
+ }
530
+ try {
531
+ symlinkSync(target, linkPath);
532
+ created.push(linkName);
533
+ } catch (err) {
534
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
535
+ }
536
+ }
537
+ return { created, skipped, errors };
538
+ }
539
+ var EMPLOYEES_PATH;
540
+ var init_employees = __esm({
541
+ "src/lib/employees.ts"() {
542
+ "use strict";
543
+ init_config();
544
+ EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
545
+ }
546
+ });
547
+
449
548
  // src/bin/backfill-conversations.ts
450
549
  import crypto2 from "crypto";
451
550
  import { createReadStream } from "fs";
452
551
  import { readdir, stat } from "fs/promises";
453
- import path5 from "path";
552
+ import path6 from "path";
454
553
  import { createInterface } from "readline";
455
554
  import { homedir } from "os";
456
555
 
@@ -1876,7 +1975,7 @@ function isMainModule(importMetaUrl) {
1876
1975
  // src/bin/backfill-conversations.ts
1877
1976
  process.env.EXE_EMBED_PRIORITY = "low";
1878
1977
  async function findJsonlFiles(cutoffMs) {
1879
- const projectsDir = path5.join(homedir(), ".claude", "projects");
1978
+ const projectsDir = path6.join(homedir(), ".claude", "projects");
1880
1979
  const files = [];
1881
1980
  async function walk(dir) {
1882
1981
  let entries;
@@ -1886,7 +1985,7 @@ async function findJsonlFiles(cutoffMs) {
1886
1985
  return;
1887
1986
  }
1888
1987
  for (const entry of entries) {
1889
- const full = path5.join(dir, entry.name);
1988
+ const full = path6.join(dir, entry.name);
1890
1989
  if (entry.isDirectory()) {
1891
1990
  await walk(full);
1892
1991
  } else if (entry.name.endsWith(".jsonl")) {
@@ -1902,9 +2001,9 @@ async function findJsonlFiles(cutoffMs) {
1902
2001
  return files;
1903
2002
  }
1904
2003
  function projectNameFromPath(filePath) {
1905
- const projectsDir = path5.join(homedir(), ".claude", "projects");
1906
- const relative = path5.relative(projectsDir, filePath);
1907
- const projectDir = relative.split(path5.sep)[0] ?? relative;
2004
+ const projectsDir = path6.join(homedir(), ".claude", "projects");
2005
+ const relative = path6.relative(projectsDir, filePath);
2006
+ const projectDir = relative.split(path6.sep)[0] ?? relative;
1908
2007
  const homeEncoded = homedir().replaceAll("/", "-");
1909
2008
  if (projectDir.startsWith(homeEncoded + "-")) {
1910
2009
  return projectDir.slice(homeEncoded.length + 1);
@@ -1967,14 +2066,14 @@ async function extractConversationPairs(filePath, projectFilter) {
1967
2066
  } else if (entry.type === "assistant" && entry.message?.content) {
1968
2067
  const assistantText = extractAssistantText(entry.message.content);
1969
2068
  if (!assistantText.trim()) continue;
1970
- const resolvedProject = fileCwd ? path5.basename(fileCwd) : fallbackProject;
2069
+ const resolvedProject = fileCwd ? path6.basename(fileCwd) : fallbackProject;
1971
2070
  const userText = pendingUser?.text ?? "";
1972
2071
  const timestamp = entry.timestamp ?? pendingUser?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
1973
2072
  pairs.push({
1974
2073
  userText,
1975
2074
  assistantText,
1976
2075
  timestamp,
1977
- sessionId: fileSessionId || path5.basename(filePath, ".jsonl"),
2076
+ sessionId: fileSessionId || path6.basename(filePath, ".jsonl"),
1978
2077
  project: resolvedProject,
1979
2078
  cwd: fileCwd || fallbackProject
1980
2079
  });
@@ -2013,7 +2112,7 @@ async function loadExistingHashes(cutoffIso) {
2013
2112
  }
2014
2113
  var MAX_CONTENT_LENGTH = 8e3;
2015
2114
  var MIN_ASSISTANT_LENGTH = 50;
2016
- async function storePair(pair, daemonConnected, stats) {
2115
+ async function storePair(pair, daemonConnected, stats, agentId) {
2017
2116
  const client = getClient();
2018
2117
  const id = crypto2.randomUUID();
2019
2118
  const userTrunc = pair.userText.length > MAX_CONTENT_LENGTH ? pair.userText.slice(0, MAX_CONTENT_LENGTH) : pair.userText;
@@ -2062,8 +2161,8 @@ async function storePair(pair, daemonConnected, stats) {
2062
2161
  }
2063
2162
  await writeMemory({
2064
2163
  id: crypto2.randomUUID(),
2065
- agent_id: "backfill",
2066
- agent_role: "system",
2164
+ agent_id: agentId,
2165
+ agent_role: "coo",
2067
2166
  session_id: pair.sessionId,
2068
2167
  timestamp: pair.timestamp,
2069
2168
  tool_name: "ConversationBackfill",
@@ -2086,7 +2185,15 @@ async function backfillConversations(options) {
2086
2185
  };
2087
2186
  const cutoffMs = Date.now() - options.days * 24 * 60 * 60 * 1e3;
2088
2187
  const cutoffIso = new Date(cutoffMs).toISOString();
2089
- process.stderr.write(`[backfill-conversations] Scanning last ${options.days} days...
2188
+ let cooAgentId = "exe";
2189
+ try {
2190
+ const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
2191
+ const employees = await loadEmployees2();
2192
+ const coo = employees.find((e) => e.role === "COO");
2193
+ if (coo) cooAgentId = coo.name;
2194
+ } catch {
2195
+ }
2196
+ process.stderr.write(`[backfill-conversations] Scanning last ${options.days} days (agent: ${cooAgentId})...
2090
2197
  `);
2091
2198
  if (!options.dryRun) {
2092
2199
  process.stderr.write("[backfill-conversations] Initializing store...\n");
@@ -2123,7 +2230,7 @@ async function backfillConversations(options) {
2123
2230
  }
2124
2231
  seenHashes.add(hash);
2125
2232
  if (!options.dryRun) {
2126
- await storePair(pair, daemonConnected, stats);
2233
+ await storePair(pair, daemonConnected, stats, cooAgentId);
2127
2234
  } else {
2128
2235
  stats.conversationsStored++;
2129
2236
  stats.memoriesStored++;
package/dist/bin/cli.js CHANGED
@@ -3085,7 +3085,7 @@ async function loadExistingHashes(cutoffIso) {
3085
3085
  }
3086
3086
  return hashes;
3087
3087
  }
3088
- async function storePair(pair, daemonConnected, stats) {
3088
+ async function storePair(pair, daemonConnected, stats, agentId) {
3089
3089
  const client = getClient();
3090
3090
  const id = crypto2.randomUUID();
3091
3091
  const userTrunc = pair.userText.length > MAX_CONTENT_LENGTH ? pair.userText.slice(0, MAX_CONTENT_LENGTH) : pair.userText;
@@ -3134,8 +3134,8 @@ async function storePair(pair, daemonConnected, stats) {
3134
3134
  }
3135
3135
  await writeMemory({
3136
3136
  id: crypto2.randomUUID(),
3137
- agent_id: "backfill",
3138
- agent_role: "system",
3137
+ agent_id: agentId,
3138
+ agent_role: "coo",
3139
3139
  session_id: pair.sessionId,
3140
3140
  timestamp: pair.timestamp,
3141
3141
  tool_name: "ConversationBackfill",
@@ -3158,7 +3158,15 @@ async function backfillConversations(options) {
3158
3158
  };
3159
3159
  const cutoffMs = Date.now() - options.days * 24 * 60 * 60 * 1e3;
3160
3160
  const cutoffIso = new Date(cutoffMs).toISOString();
3161
- process.stderr.write(`[backfill-conversations] Scanning last ${options.days} days...
3161
+ let cooAgentId = "exe";
3162
+ try {
3163
+ const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
3164
+ const employees = await loadEmployees2();
3165
+ const coo = employees.find((e) => e.role === "COO");
3166
+ if (coo) cooAgentId = coo.name;
3167
+ } catch {
3168
+ }
3169
+ process.stderr.write(`[backfill-conversations] Scanning last ${options.days} days (agent: ${cooAgentId})...
3162
3170
  `);
3163
3171
  if (!options.dryRun) {
3164
3172
  process.stderr.write("[backfill-conversations] Initializing store...\n");
@@ -3195,7 +3203,7 @@ async function backfillConversations(options) {
3195
3203
  }
3196
3204
  seenHashes.add(hash);
3197
3205
  if (!options.dryRun) {
3198
- await storePair(pair, daemonConnected, stats);
3206
+ await storePair(pair, daemonConnected, stats, cooAgentId);
3199
3207
  } else {
3200
3208
  stats.conversationsStored++;
3201
3209
  stats.memoriesStored++;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askexenow/exe-os",
3
- "version": "0.8.21",
3
+ "version": "0.8.22",
4
4
  "description": "AI employee operating system — persistent memory, task management, and multi-agent coordination for Claude Code.",
5
5
  "license": "CC-BY-NC-4.0",
6
6
  "type": "module",