@askexenow/exe-os 0.8.41 → 0.8.43

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.
Files changed (76) hide show
  1. package/dist/bin/backfill-conversations.js +805 -642
  2. package/dist/bin/backfill-responses.js +804 -641
  3. package/dist/bin/backfill-vectors.js +791 -634
  4. package/dist/bin/cleanup-stale-review-tasks.js +788 -631
  5. package/dist/bin/cli.js +1345 -660
  6. package/dist/bin/exe-agent.js +20 -1
  7. package/dist/bin/exe-assign.js +1503 -1343
  8. package/dist/bin/exe-boot.js +2518 -1798
  9. package/dist/bin/exe-call.js +39 -1
  10. package/dist/bin/exe-cloud.js +15 -1
  11. package/dist/bin/exe-dispatch.js +39 -2
  12. package/dist/bin/exe-doctor.js +790 -633
  13. package/dist/bin/exe-export-behaviors.js +792 -637
  14. package/dist/bin/exe-forget.js +145 -0
  15. package/dist/bin/exe-gateway.js +2500 -1877
  16. package/dist/bin/exe-heartbeat.js +147 -1
  17. package/dist/bin/exe-kill.js +795 -640
  18. package/dist/bin/exe-launch-agent.js +2168 -2008
  19. package/dist/bin/exe-link.js +28 -2
  20. package/dist/bin/exe-new-employee.js +25 -3
  21. package/dist/bin/exe-pending-messages.js +146 -1
  22. package/dist/bin/exe-pending-notifications.js +788 -631
  23. package/dist/bin/exe-pending-reviews.js +147 -1
  24. package/dist/bin/exe-rename.js +23 -0
  25. package/dist/bin/exe-review.js +490 -327
  26. package/dist/bin/exe-search.js +154 -3
  27. package/dist/bin/exe-session-cleanup.js +2466 -413
  28. package/dist/bin/exe-status.js +474 -317
  29. package/dist/bin/exe-team.js +474 -317
  30. package/dist/bin/git-sweep.js +2690 -150
  31. package/dist/bin/graph-backfill.js +794 -637
  32. package/dist/bin/graph-export.js +798 -641
  33. package/dist/bin/scan-tasks.js +2951 -44
  34. package/dist/bin/setup.js +62 -26
  35. package/dist/bin/shard-migrate.js +792 -637
  36. package/dist/bin/wiki-sync.js +794 -637
  37. package/dist/gateway/index.js +2504 -1895
  38. package/dist/hooks/bug-report-worker.js +2118 -576
  39. package/dist/hooks/commit-complete.js +2689 -149
  40. package/dist/hooks/error-recall.js +154 -3
  41. package/dist/hooks/ingest-worker.js +1439 -815
  42. package/dist/hooks/instructions-loaded.js +151 -0
  43. package/dist/hooks/notification.js +153 -2
  44. package/dist/hooks/post-compact.js +164 -0
  45. package/dist/hooks/pre-compact.js +3073 -101
  46. package/dist/hooks/pre-tool-use.js +151 -0
  47. package/dist/hooks/prompt-ingest-worker.js +1714 -1537
  48. package/dist/hooks/prompt-submit.js +2658 -1113
  49. package/dist/hooks/response-ingest-worker.js +170 -6
  50. package/dist/hooks/session-end.js +153 -2
  51. package/dist/hooks/session-start.js +154 -3
  52. package/dist/hooks/stop.js +151 -0
  53. package/dist/hooks/subagent-stop.js +151 -0
  54. package/dist/hooks/summary-worker.js +179 -7
  55. package/dist/index.js +278 -100
  56. package/dist/lib/cloud-sync.js +28 -2
  57. package/dist/lib/consolidation.js +69 -2
  58. package/dist/lib/database.js +19 -0
  59. package/dist/lib/device-registry.js +19 -0
  60. package/dist/lib/employee-templates.js +20 -1
  61. package/dist/lib/exe-daemon.js +236 -16
  62. package/dist/lib/hybrid-search.js +154 -3
  63. package/dist/lib/license.js +15 -1
  64. package/dist/lib/messaging.js +39 -2
  65. package/dist/lib/schedules.js +792 -637
  66. package/dist/lib/store.js +796 -636
  67. package/dist/lib/tasks.js +1614 -1091
  68. package/dist/lib/tmux-routing.js +149 -9
  69. package/dist/mcp/server.js +1825 -1138
  70. package/dist/mcp/tools/create-task.js +2280 -828
  71. package/dist/mcp/tools/list-tasks.js +2788 -159
  72. package/dist/mcp/tools/send-message.js +39 -2
  73. package/dist/mcp/tools/update-task.js +64 -0
  74. package/dist/runtime/index.js +235 -67
  75. package/dist/tui/App.js +1452 -644
  76. package/package.json +3 -2
package/dist/tui/App.js CHANGED
@@ -39,6 +39,73 @@ var init_devtools = __esm({
39
39
  }
40
40
  });
41
41
 
42
+ // src/lib/telemetry.ts
43
+ var telemetry_exports = {};
44
+ __export(telemetry_exports, {
45
+ instrumentServer: () => instrumentServer,
46
+ withTrace: () => withTrace
47
+ });
48
+ async function ensureInit() {
49
+ if (initialized || !ENABLED) return;
50
+ initialized = true;
51
+ try {
52
+ const { NodeSDK } = await import("@opentelemetry/sdk-node");
53
+ const { ConsoleSpanExporter } = await import("@opentelemetry/sdk-trace-base");
54
+ const sdk = new NodeSDK({
55
+ serviceName: "exe-os",
56
+ traceExporter: new ConsoleSpanExporter()
57
+ });
58
+ sdk.start();
59
+ process.stderr.write("[exe-os] OpenTelemetry tracing enabled\n");
60
+ } catch (err) {
61
+ process.stderr.write(
62
+ `[exe-os] OpenTelemetry init failed: ${err instanceof Error ? err.message : String(err)}
63
+ `
64
+ );
65
+ }
66
+ }
67
+ async function withTrace(toolName, fn) {
68
+ if (!ENABLED) return fn();
69
+ await ensureInit();
70
+ const { trace, SpanStatusCode } = await import("@opentelemetry/api");
71
+ const tracer = trace.getTracer("exe-os", "1.0.0");
72
+ return tracer.startActiveSpan(`mcp.tool.${toolName}`, async (span) => {
73
+ span.setAttribute("mcp.tool.name", toolName);
74
+ try {
75
+ const result = await fn();
76
+ span.setStatus({ code: SpanStatusCode.OK });
77
+ return result;
78
+ } catch (err) {
79
+ span.setStatus({
80
+ code: SpanStatusCode.ERROR,
81
+ message: err instanceof Error ? err.message : String(err)
82
+ });
83
+ span.recordException(
84
+ err instanceof Error ? err : new Error(String(err))
85
+ );
86
+ throw err;
87
+ } finally {
88
+ span.end();
89
+ }
90
+ });
91
+ }
92
+ function instrumentServer(server) {
93
+ if (!ENABLED) return;
94
+ const original = server.registerTool.bind(server);
95
+ server.registerTool = (name, config, handler) => {
96
+ const traced = async (...args) => withTrace(name, () => handler(...args));
97
+ return original(name, config, traced);
98
+ };
99
+ }
100
+ var ENABLED, initialized;
101
+ var init_telemetry = __esm({
102
+ "src/lib/telemetry.ts"() {
103
+ "use strict";
104
+ ENABLED = process.env.EXE_TELEMETRY === "1";
105
+ initialized = false;
106
+ }
107
+ });
108
+
42
109
  // src/lib/db-retry.ts
43
110
  function isBusyError(err) {
44
111
  if (err instanceof Error) {
@@ -346,6 +413,13 @@ async function ensureSchema() {
346
413
  });
347
414
  } catch {
348
415
  }
416
+ try {
417
+ await client.execute({
418
+ sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
419
+ args: []
420
+ });
421
+ } catch {
422
+ }
349
423
  try {
350
424
  await client.execute({
351
425
  sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
@@ -792,6 +866,18 @@ async function ensureSchema() {
792
866
  CREATE INDEX IF NOT EXISTS idx_session_kills_agent
793
867
  ON session_kills(agent_id);
794
868
  `);
869
+ await client.execute(`
870
+ CREATE TABLE IF NOT EXISTS global_procedures (
871
+ id TEXT PRIMARY KEY,
872
+ title TEXT NOT NULL,
873
+ content TEXT NOT NULL,
874
+ priority TEXT NOT NULL DEFAULT 'p0',
875
+ domain TEXT,
876
+ active INTEGER NOT NULL DEFAULT 1,
877
+ created_at TEXT NOT NULL,
878
+ updated_at TEXT NOT NULL
879
+ )
880
+ `);
795
881
  await client.executeMultiple(`
796
882
  CREATE TABLE IF NOT EXISTS conversations (
797
883
  id TEXT PRIMARY KEY,
@@ -1357,7 +1443,21 @@ function getCacheAgeMs() {
1357
1443
  }
1358
1444
  }
1359
1445
  async function checkLicense() {
1360
- const key = loadLicense();
1446
+ let key = loadLicense();
1447
+ if (!key) {
1448
+ try {
1449
+ const configPath = path2.join(EXE_AI_DIR, "config.json");
1450
+ if (existsSync3(configPath)) {
1451
+ const raw = JSON.parse(readFileSync3(configPath, "utf8"));
1452
+ const cloud = raw.cloud;
1453
+ if (cloud?.apiKey) {
1454
+ key = cloud.apiKey;
1455
+ saveLicense(key);
1456
+ }
1457
+ }
1458
+ } catch {
1459
+ }
1460
+ }
1361
1461
  if (!key) return FREE_LICENSE;
1362
1462
  const cached = await getCachedLicense();
1363
1463
  if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
@@ -2711,6 +2811,71 @@ var init_agent_loop = __esm({
2711
2811
  }
2712
2812
  });
2713
2813
 
2814
+ // src/lib/global-procedures.ts
2815
+ var global_procedures_exports = {};
2816
+ __export(global_procedures_exports, {
2817
+ deactivateGlobalProcedure: () => deactivateGlobalProcedure,
2818
+ getGlobalProceduresBlock: () => getGlobalProceduresBlock,
2819
+ loadGlobalProcedures: () => loadGlobalProcedures,
2820
+ storeGlobalProcedure: () => storeGlobalProcedure
2821
+ });
2822
+ import { randomUUID as randomUUID2 } from "crypto";
2823
+ async function loadGlobalProcedures() {
2824
+ const client = getClient();
2825
+ const result = await client.execute({
2826
+ sql: "SELECT * FROM global_procedures WHERE active = 1 ORDER BY priority ASC, created_at ASC",
2827
+ args: []
2828
+ });
2829
+ const procedures = result.rows;
2830
+ if (procedures.length > 0) {
2831
+ _cache = procedures.map((p) => `### ${p.title}
2832
+ ${p.content}`).join("\n\n");
2833
+ } else {
2834
+ _cache = "";
2835
+ }
2836
+ _cacheLoaded = true;
2837
+ return procedures;
2838
+ }
2839
+ function getGlobalProceduresBlock() {
2840
+ if (!_cacheLoaded) return "";
2841
+ if (!_cache) return "";
2842
+ return `## Organization-Wide Procedures (MANDATORY \u2014 supersedes all other rules)
2843
+
2844
+ ${_cache}
2845
+ `;
2846
+ }
2847
+ async function storeGlobalProcedure(input) {
2848
+ const id = randomUUID2();
2849
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2850
+ const client = getClient();
2851
+ await client.execute({
2852
+ sql: `INSERT INTO global_procedures (id, title, content, priority, domain, active, created_at, updated_at)
2853
+ VALUES (?, ?, ?, ?, ?, 1, ?, ?)`,
2854
+ args: [id, input.title, input.content, input.priority ?? "p0", input.domain ?? null, now, now]
2855
+ });
2856
+ await loadGlobalProcedures();
2857
+ return id;
2858
+ }
2859
+ async function deactivateGlobalProcedure(id) {
2860
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2861
+ const client = getClient();
2862
+ const result = await client.execute({
2863
+ sql: "UPDATE global_procedures SET active = 0, updated_at = ? WHERE id = ?",
2864
+ args: [now, id]
2865
+ });
2866
+ await loadGlobalProcedures();
2867
+ return result.rowsAffected > 0;
2868
+ }
2869
+ var _cache, _cacheLoaded;
2870
+ var init_global_procedures = __esm({
2871
+ "src/lib/global-procedures.ts"() {
2872
+ "use strict";
2873
+ init_database();
2874
+ _cache = "";
2875
+ _cacheLoaded = false;
2876
+ }
2877
+ });
2878
+
2714
2879
  // src/gateway/providers/anthropic.ts
2715
2880
  import Anthropic from "@anthropic-ai/sdk";
2716
2881
  var AnthropicProvider;
@@ -2815,7 +2980,7 @@ var init_anthropic = __esm({
2815
2980
 
2816
2981
  // src/gateway/providers/openai-compat.ts
2817
2982
  import OpenAI from "openai";
2818
- import { randomUUID as randomUUID2 } from "crypto";
2983
+ import { randomUUID as randomUUID3 } from "crypto";
2819
2984
  var OpenAICompatProvider;
2820
2985
  var init_openai_compat = __esm({
2821
2986
  "src/gateway/providers/openai-compat.ts"() {
@@ -2932,7 +3097,7 @@ var init_openai_compat = __esm({
2932
3097
  }
2933
3098
  content.push({
2934
3099
  type: "tool_use",
2935
- id: call.id ?? randomUUID2(),
3100
+ id: call.id ?? randomUUID3(),
2936
3101
  name: fn.name,
2937
3102
  input
2938
3103
  });
@@ -4017,7 +4182,7 @@ var init_employees = __esm({
4017
4182
  });
4018
4183
 
4019
4184
  // src/lib/task-router.ts
4020
- import { randomUUID as randomUUID3 } from "crypto";
4185
+ import { randomUUID as randomUUID4 } from "crypto";
4021
4186
  function resolveBloomRouting(complexity, config = DEFAULT_BLOOM_CONFIG) {
4022
4187
  const tier = config.complexityToTier[complexity];
4023
4188
  const rule = config.tierRules[tier];
@@ -4549,6 +4714,61 @@ var init_session_kill_telemetry = __esm({
4549
4714
  }
4550
4715
  });
4551
4716
 
4717
+ // src/lib/state-bus.ts
4718
+ var StateBus, orgBus;
4719
+ var init_state_bus = __esm({
4720
+ "src/lib/state-bus.ts"() {
4721
+ "use strict";
4722
+ StateBus = class {
4723
+ handlers = /* @__PURE__ */ new Map();
4724
+ globalHandlers = /* @__PURE__ */ new Set();
4725
+ /** Emit an event to all subscribers */
4726
+ emit(event) {
4727
+ const typeHandlers = this.handlers.get(event.type);
4728
+ if (typeHandlers) {
4729
+ for (const handler of typeHandlers) {
4730
+ try {
4731
+ handler(event);
4732
+ } catch {
4733
+ }
4734
+ }
4735
+ }
4736
+ for (const handler of this.globalHandlers) {
4737
+ try {
4738
+ handler(event);
4739
+ } catch {
4740
+ }
4741
+ }
4742
+ }
4743
+ /** Subscribe to a specific event type */
4744
+ on(type, handler) {
4745
+ if (!this.handlers.has(type)) {
4746
+ this.handlers.set(type, /* @__PURE__ */ new Set());
4747
+ }
4748
+ this.handlers.get(type).add(handler);
4749
+ }
4750
+ /** Subscribe to ALL events */
4751
+ onAny(handler) {
4752
+ this.globalHandlers.add(handler);
4753
+ }
4754
+ /** Unsubscribe from a specific event type */
4755
+ off(type, handler) {
4756
+ this.handlers.get(type)?.delete(handler);
4757
+ }
4758
+ /** Unsubscribe from ALL events */
4759
+ offAny(handler) {
4760
+ this.globalHandlers.delete(handler);
4761
+ }
4762
+ /** Remove all listeners */
4763
+ clear() {
4764
+ this.handlers.clear();
4765
+ this.globalHandlers.clear();
4766
+ }
4767
+ };
4768
+ orgBus = new StateBus();
4769
+ }
4770
+ });
4771
+
4552
4772
  // src/lib/tasks-crud.ts
4553
4773
  import crypto3 from "crypto";
4554
4774
  import path15 from "path";
@@ -4692,9 +4912,15 @@ async function createTaskCore(input) {
4692
4912
  }
4693
4913
  }
4694
4914
  const complexity = input.complexity ?? "standard";
4915
+ let sessionScope = null;
4916
+ try {
4917
+ const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
4918
+ sessionScope = resolveExeSession2();
4919
+ } catch {
4920
+ }
4695
4921
  await client.execute({
4696
- 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, created_at, updated_at)
4697
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4922
+ 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)
4923
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4698
4924
  args: [
4699
4925
  id,
4700
4926
  input.title,
@@ -4713,6 +4939,7 @@ async function createTaskCore(input) {
4713
4939
  input.budgetFallbackModel ?? null,
4714
4940
  0,
4715
4941
  null,
4942
+ sessionScope,
4716
4943
  now,
4717
4944
  now
4718
4945
  ]
@@ -4757,6 +4984,15 @@ async function listTasks(input) {
4757
4984
  conditions.push("priority = ?");
4758
4985
  args.push(input.priority);
4759
4986
  }
4987
+ try {
4988
+ const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
4989
+ const session = resolveExeSession2();
4990
+ if (session) {
4991
+ conditions.push("(session_scope IS NULL OR session_scope = ?)");
4992
+ args.push(session);
4993
+ }
4994
+ } catch {
4995
+ }
4760
4996
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
4761
4997
  const result = await client.execute({
4762
4998
  sql: `SELECT * FROM tasks ${where} ORDER BY CASE status WHEN 'blocked' THEN 0 WHEN 'in_progress' THEN 1 WHEN 'open' THEN 2 ELSE 3 END, priority ASC, created_at DESC LIMIT 1000`,
@@ -5121,6 +5357,7 @@ var init_tasks_review = __esm({
5121
5357
  init_tasks_crud();
5122
5358
  init_tmux_routing();
5123
5359
  init_session_key();
5360
+ init_state_bus();
5124
5361
  }
5125
5362
  });
5126
5363
 
@@ -5285,13 +5522,12 @@ function assertSessionScope(actionType, targetProject) {
5285
5522
  };
5286
5523
  }
5287
5524
  process.stderr.write(
5288
- `[session-scope] Cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
5525
+ `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
5289
5526
  `
5290
5527
  );
5291
5528
  return {
5292
- allowed: true,
5293
- // v1: warn-only, don't block
5294
- reason: "cross_session_granted",
5529
+ allowed: false,
5530
+ reason: "cross_session_denied",
5295
5531
  currentProject,
5296
5532
  targetProject,
5297
5533
  targetSession: findSessionForProject(targetProject)?.windowName
@@ -5317,8 +5553,9 @@ async function dispatchTaskToEmployee(input) {
5317
5553
  try {
5318
5554
  const { assertSessionScope: assertSessionScope2 } = (init_session_scope(), __toCommonJS(session_scope_exports));
5319
5555
  const check = assertSessionScope2("dispatch_task", input.projectName);
5320
- if (check.reason === "cross_session_granted") {
5556
+ if (check.reason === "cross_session_denied") {
5321
5557
  crossProject = true;
5558
+ return { dispatched: "skipped", crossProject: true };
5322
5559
  }
5323
5560
  } catch {
5324
5561
  }
@@ -5781,6 +6018,13 @@ async function updateTask(input) {
5781
6018
  await cascadeUnblock(taskId, input.baseDir, now);
5782
6019
  } catch {
5783
6020
  }
6021
+ orgBus.emit({
6022
+ type: "task_completed",
6023
+ taskId,
6024
+ employee: String(row.assigned_to),
6025
+ result: input.result ?? "",
6026
+ timestamp: now
6027
+ });
5784
6028
  if (row.parent_task_id) {
5785
6029
  try {
5786
6030
  await checkSubtaskCompletion(String(row.parent_task_id), String(row.project_name));
@@ -5848,6 +6092,7 @@ var init_tasks = __esm({
5848
6092
  init_database();
5849
6093
  init_config();
5850
6094
  init_notifications();
6095
+ init_state_bus();
5851
6096
  init_tasks_crud();
5852
6097
  init_tasks_review();
5853
6098
  init_tasks_crud();
@@ -6238,8 +6483,28 @@ function getMySession() {
6238
6483
  return getTransport().getMySession();
6239
6484
  }
6240
6485
  function employeeSessionName(employee, exeSession, instance) {
6486
+ if (!/^exe\d+$/.test(exeSession)) {
6487
+ const root = extractRootExe(exeSession);
6488
+ if (root) {
6489
+ process.stderr.write(
6490
+ `[tmux-routing] WARN: exeSession="${exeSession}" is not a root exe session, using "${root}" instead
6491
+ `
6492
+ );
6493
+ exeSession = root;
6494
+ } else {
6495
+ throw new Error(
6496
+ `Invalid exeSession "${exeSession}" \u2014 must be a root exe session (e.g., "exe1"), not an agent session`
6497
+ );
6498
+ }
6499
+ }
6241
6500
  const suffix = instance != null && instance > 0 ? String(instance) : "";
6242
- return `${employee}${suffix}-${exeSession}`;
6501
+ const name = `${employee}${suffix}-${exeSession}`;
6502
+ if (!VALID_SESSION_NAME.test(name)) {
6503
+ throw new Error(
6504
+ `Invalid session name "${name}" \u2014 must match {agent}-exe{N} or {agent}{instance}-exe{N}`
6505
+ );
6506
+ }
6507
+ return name;
6243
6508
  }
6244
6509
  function parseParentExe(sessionName, agentId) {
6245
6510
  const escaped = agentId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -6479,6 +6744,22 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
6479
6744
  error: `Error: pass employee name ('${bare}'), not session name ('${employeeName}')`
6480
6745
  };
6481
6746
  }
6747
+ if (!/^exe\d+$/.test(exeSession)) {
6748
+ const root = extractRootExe(exeSession);
6749
+ if (root) {
6750
+ process.stderr.write(
6751
+ `[ensureEmployee] WARN: caller passed exeSession="${exeSession}" (not a root exe). Auto-correcting to "${root}".
6752
+ `
6753
+ );
6754
+ exeSession = root;
6755
+ } else {
6756
+ return {
6757
+ status: "failed",
6758
+ sessionName: "",
6759
+ error: `Invalid exeSession "${exeSession}" \u2014 must be a root exe session (e.g., "exe1")`
6760
+ };
6761
+ }
6762
+ }
6482
6763
  let effectiveInstance = opts?.instance;
6483
6764
  if (effectiveInstance === void 0 && opts?.autoInstance) {
6484
6765
  const free = findFreeInstance(
@@ -6725,7 +7006,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6725
7006
  releaseSpawnLock(sessionName);
6726
7007
  return { sessionName };
6727
7008
  }
6728
- var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
7009
+ var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VALID_SESSION_NAME, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
6729
7010
  var init_tmux_routing = __esm({
6730
7011
  "src/lib/tmux-routing.ts"() {
6731
7012
  "use strict";
@@ -6740,6 +7021,7 @@ var init_tmux_routing = __esm({
6740
7021
  SPAWN_LOCK_DIR = path20.join(os6.homedir(), ".exe-os", "spawn-locks");
6741
7022
  SESSION_CACHE = path20.join(os6.homedir(), ".exe-os", "session-cache");
6742
7023
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
7024
+ VALID_SESSION_NAME = /^[a-z]+-exe\d+$|^[a-z]+\d+-exe\d+$/;
6743
7025
  VERIFY_PANE_LINES = 200;
6744
7026
  INTERCOM_DEBOUNCE_MS = 3e4;
6745
7027
  INTERCOM_LOG2 = path20.join(os6.homedir(), ".exe-os", "intercom.log");
@@ -7908,6 +8190,11 @@ async function initStore(options) {
7908
8190
  "version-query"
7909
8191
  );
7910
8192
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
8193
+ try {
8194
+ const { loadGlobalProcedures: loadGlobalProcedures2 } = await Promise.resolve().then(() => (init_global_procedures(), global_procedures_exports));
8195
+ await loadGlobalProcedures2();
8196
+ } catch {
8197
+ }
7911
8198
  }
7912
8199
  function classifyTier(record) {
7913
8200
  if (record.tool_name === "commit_to_long_term_memory" && (record.importance ?? 0) >= 8) return 1;
@@ -7949,6 +8236,12 @@ async function writeMemory(record) {
7949
8236
  supersedes_id: record.supersedes_id ?? null
7950
8237
  };
7951
8238
  _pendingRecords.push(dbRow);
8239
+ orgBus.emit({
8240
+ type: "memory_stored",
8241
+ agentId: record.agent_id,
8242
+ project: record.project_name,
8243
+ timestamp: record.timestamp
8244
+ });
7952
8245
  const MAX_PENDING = 1e3;
7953
8246
  if (_pendingRecords.length > MAX_PENDING) {
7954
8247
  const dropped = _pendingRecords.length - MAX_PENDING;
@@ -8294,6 +8587,7 @@ var init_store = __esm({
8294
8587
  init_database();
8295
8588
  init_keychain();
8296
8589
  init_config();
8590
+ init_state_bus();
8297
8591
  INIT_MAX_RETRIES = 3;
8298
8592
  INIT_RETRY_DELAY_MS = 1e3;
8299
8593
  _pendingRecords = [];
@@ -8306,7 +8600,7 @@ var init_store = __esm({
8306
8600
  });
8307
8601
 
8308
8602
  // src/tui/App.tsx
8309
- import { useState as useState15, useEffect as useEffect17, useCallback as useCallback7 } from "react";
8603
+ import { useState as useState16, useEffect as useEffect18, useCallback as useCallback8 } from "react";
8310
8604
 
8311
8605
  // src/tui/ink/render.js
8312
8606
  import { Stream } from "stream";
@@ -14206,61 +14500,64 @@ function Footer() {
14206
14500
  return () => clearInterval(timer);
14207
14501
  }, []);
14208
14502
  async function loadFooterData() {
14209
- try {
14210
- const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
14211
- const client = getClient2();
14212
- if (client) {
14213
- const result = await client.execute("SELECT COUNT(*) as cnt FROM memories");
14214
- setMemoryCount(Number(result.rows[0]?.cnt ?? 0));
14503
+ const { withTrace: withTrace2 } = await Promise.resolve().then(() => (init_telemetry(), telemetry_exports));
14504
+ return withTrace2("tui.footer.loadData", async () => {
14505
+ try {
14506
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
14507
+ const client = getClient2();
14508
+ if (client) {
14509
+ const result = await client.execute("SELECT COUNT(*) as cnt FROM memories");
14510
+ setMemoryCount(Number(result.rows[0]?.cnt ?? 0));
14511
+ }
14512
+ } catch {
14215
14513
  }
14216
- } catch {
14217
- }
14218
- try {
14219
- const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
14220
- const client = getClient2();
14221
- if (client) {
14222
- const result = await client.execute(
14223
- "SELECT COUNT(*) as cnt FROM tasks WHERE status IN ('open','in_progress')"
14224
- );
14225
- setTaskCount(Number(result.rows[0]?.cnt ?? 0));
14514
+ try {
14515
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
14516
+ const client = getClient2();
14517
+ if (client) {
14518
+ const result = await client.execute(
14519
+ "SELECT COUNT(*) as cnt FROM tasks WHERE status IN ('open','in_progress')"
14520
+ );
14521
+ setTaskCount(Number(result.rows[0]?.cnt ?? 0));
14522
+ }
14523
+ } catch {
14226
14524
  }
14227
- } catch {
14228
- }
14229
- try {
14230
- const { existsSync: existsSync14 } = await import("fs");
14231
- const { join } = await import("path");
14232
- const home = process.env.HOME ?? "";
14233
- const pidPath = join(home, ".exe-os", "exed.pid");
14234
- setDaemon(existsSync14(pidPath) ? "running" : "stopped");
14235
- } catch {
14236
- setDaemon("unknown");
14237
- }
14238
- try {
14239
- const { checkLicense: checkLicense2 } = await Promise.resolve().then(() => (init_license(), license_exports));
14240
- const license = await checkLicense2();
14241
- setPlan(license.plan);
14242
- } catch {
14243
- setPlan("free");
14244
- }
14245
- try {
14246
- const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
14247
- if (inTmux2()) {
14248
- const allSessions = listTmuxSessions2();
14249
- setSessions(allSessions.length);
14250
- if (!currentSession) {
14251
- try {
14252
- const { execSync: execSync10 } = await import("child_process");
14253
- const name = execSync10("tmux display-message -p '#{session_name}' 2>/dev/null", {
14254
- encoding: "utf8",
14255
- timeout: 2e3
14256
- }).trim();
14257
- if (name) setCurrentSession(name);
14258
- } catch {
14525
+ try {
14526
+ const { existsSync: existsSync14 } = await import("fs");
14527
+ const { join } = await import("path");
14528
+ const home = process.env.HOME ?? "";
14529
+ const pidPath = join(home, ".exe-os", "exed.pid");
14530
+ setDaemon(existsSync14(pidPath) ? "running" : "stopped");
14531
+ } catch {
14532
+ setDaemon("unknown");
14533
+ }
14534
+ try {
14535
+ const { checkLicense: checkLicense2 } = await Promise.resolve().then(() => (init_license(), license_exports));
14536
+ const license = await checkLicense2();
14537
+ setPlan(license.plan);
14538
+ } catch {
14539
+ setPlan("free");
14540
+ }
14541
+ try {
14542
+ const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
14543
+ if (inTmux2()) {
14544
+ const allSessions = listTmuxSessions2();
14545
+ setSessions(allSessions.length);
14546
+ if (!currentSession) {
14547
+ try {
14548
+ const { execSync: execSync10 } = await import("child_process");
14549
+ const name = execSync10("tmux display-message -p '#{session_name}' 2>/dev/null", {
14550
+ encoding: "utf8",
14551
+ timeout: 2e3
14552
+ }).trim();
14553
+ if (name) setCurrentSession(name);
14554
+ } catch {
14555
+ }
14259
14556
  }
14260
14557
  }
14558
+ } catch {
14261
14559
  }
14262
- } catch {
14263
- }
14560
+ });
14264
14561
  }
14265
14562
  return /* @__PURE__ */ jsxs3(Box_default, { flexDirection: "column", children: [
14266
14563
  /* @__PURE__ */ jsx5(Text, { color: "#3D3660", children: "\u2500".repeat(process.stdout.columns || 120) }),
@@ -14301,6 +14598,8 @@ function Footer() {
14301
14598
  ] }),
14302
14599
  /* @__PURE__ */ jsx5(Text, { color: "#6B4C9A", children: "1-7 navigate" }),
14303
14600
  /* @__PURE__ */ jsx5(Text, { color: "#3D3660", children: "\u2502" }),
14601
+ /* @__PURE__ */ jsx5(Text, { color: "#6B4C9A", children: "d debug" }),
14602
+ /* @__PURE__ */ jsx5(Text, { color: "#3D3660", children: "\u2502" }),
14304
14603
  /* @__PURE__ */ jsx5(Text, { color: "#6B4C9A", children: "q quit" })
14305
14604
  ] })
14306
14605
  ] })
@@ -14921,6 +15220,147 @@ function handleEvent(event, addMessage) {
14921
15220
  }
14922
15221
  }
14923
15222
 
15223
+ // src/lib/employee-templates.ts
15224
+ init_global_procedures();
15225
+ var BASE_OPERATING_PROCEDURES = `
15226
+ EXE OS \u2014 VISION AND NON-NEGOTIABLE PRINCIPLES (above all work):
15227
+
15228
+ Product: "Hire the team you couldn't afford." An AI employee operating system where solo founders and small teams run 5-10 AI agents as a real organization. Three-layer cognition (identity/expertise/experience). Five runtime modes (CC Raw \u2192 TUI \u2192 Desktop). Local-first with E2EE cloud sync.
15229
+
15230
+ ICP (who we build for):
15231
+ - Solopreneurs, SMB founders, creators with institutional IP
15232
+ - Bootstrapped small e-commerce / fitness creators / influencers
15233
+ - NOT VC-backed startups \u2014 intentionally excluded
15234
+
15235
+ Crown jewels (load-bearing for all three business paths \u2014 never compromise):
15236
+ - Memory sovereignty (user owns everything, E2EE, local-first)
15237
+ - Three-layer cognition (identity/expertise/experience)
15238
+ - MCP contract boundary (surfaces consume memory OS via MCP only \u2014 never direct DB access, never bundled code)
15239
+ - AGPL network boundary for public forks (e.g., exe-crm)
15240
+
15241
+ Three business-model paths (every product decision must serve these):
15242
+ 1. B2C direct \u2014 solopreneurs run their own instance (active, current default)
15243
+ 2. Agency white-label \u2014 distributors rebrand for their clients (deferred, but branding must be config-driven)
15244
+ 3. Creator franchise (Mike pattern) \u2014 creators inject institutional IP into agent identity+expertise+experience layers, sell scoped access to subscribers (v2+ moat, requires memory export scoping)
15245
+
15246
+ Ethos:
15247
+ - Bootstrapped, profitable, forever. Not a VC-raise.
15248
+ - Founder zero-ego. Distributors and customers are the loudest voice.
15249
+ - Crypto values: big companies should not own consumer/SMB AI.
15250
+
15251
+ STOP AND REDIRECT: Any decision that compromises memory sovereignty, 3-layer cognition, MCP boundary, or AGPL boundary kills all three business paths. Surface the conflict to exe before proceeding.
15252
+
15253
+ Always reference .planning/ARCHITECTURE.md and .planning/PROJECT.md as source of truth for all architectural and product decisions.
15254
+
15255
+ OPERATING PROCEDURES (mandatory for all employees):
15256
+
15257
+ You report to the COO. All work flows through exe. These procedures are non-negotiable.
15258
+
15259
+ 1. BEFORE starting work:
15260
+ - Read exe/ARCHITECTURE.md (if it exists). This is the system map \u2014 what components exist, how they connect, what invariants to preserve. Understand the architecture before changing anything.
15261
+ - Check YOUR task folder ONLY: Read exe/<your-name>/ for assigned tasks
15262
+ - NEVER read, write, or modify files in another employee's folder. Those are their tasks, not yours. Use ask_team_memory() if you need context from a colleague.
15263
+ - If you have open tasks, work on the highest priority one first
15264
+ - Ensure exe/output/ exists (mkdir -p exe/output). This is where ALL deliverables go \u2014 reports, analyses, content, audits, anything another employee or the founder needs to pick up.
15265
+ - Update task status to "in_progress" when starting (use update_task MCP tool)
15266
+ - recall_my_memory \u2014 check what you've done before in this project. What patterns, decisions, context exist?
15267
+ - Read the relevant files. Understand what exists before changing anything.
15268
+
15269
+ 2. BEFORE marking done \u2014 CHECKPOINT (mandatory, never skip):
15270
+ - Run the tests. If they fail, fix them before reporting done.
15271
+ - Run typecheck if TypeScript. Zero errors.
15272
+ - Verify the change actually works \u2014 run it, check the output, prove it.
15273
+ - If you can't verify, say so explicitly: "Couldn't verify because X."
15274
+
15275
+ 3. AFTER completing work \u2014 update_task(done) IMMEDIATELY (the ONE critical action):
15276
+ Calling update_task with status "done" is the single action that must ALWAYS happen.
15277
+ Call it FIRST \u2014 before commit, before report, before anything else. If you do nothing else, do this.
15278
+ - Use update_task MCP tool with status "done" and your result summary
15279
+ - Include what was done, decisions made, and any issues
15280
+ - If you're stuck, looping, confused, or running low on context \u2014 update_task(done) with whatever partial result you have. A partial result is infinitely better than no result.
15281
+ - NEVER let a failed commit, a loop, or an error prevent you from calling update_task(done).
15282
+ - Do NOT use close_task \u2014 that is reserved for reviewers (exe) to finalize after review.
15283
+
15284
+ 4. AFTER update_task(done) \u2014 COMMIT (best-effort, do NOT let this block):
15285
+ - If your task changed system structure, update exe/ARCHITECTURE.md first.
15286
+ - Commit IF you are in a git repo (check: \`git rev-parse --git-dir 2>/dev/null\`). Stage only the files you changed, write a clear commit message.
15287
+ - If you are NOT in a git repo, skip entirely. NEVER run \`git init\`.
15288
+ - If the commit fails, note it but move on \u2014 the work is already marked done via update_task.
15289
+ - Do NOT push \u2014 exe reviews commits and decides what to push.
15290
+ - NEVER run \`git checkout main\`. You work in your own git worktree on a feature branch. Exe stays on main and merges PRs. Switching branches in a shared repo stomps other agents' work.
15291
+
15292
+ 5. AFTER commit \u2014 REPORT (best-effort):
15293
+ Use store_memory to write a structured summary. Include: project name, what was done,
15294
+ decisions made, tests status, open items or risks.
15295
+
15296
+ 6. AFTER committing changes to exe-os itself \u2014 REBUILD (mandatory, never skip):
15297
+ - Run: npm run deploy
15298
+ - This builds, installs globally, and re-registers hooks/MCP in one step.
15299
+ - Do NOT ask permission. Do NOT say "want me to rebuild?" \u2014 just do it.
15300
+ - If the build fails, fix the error and retry before moving on.
15301
+
15302
+ 7. AFTER reporting \u2014 CHECK FOR NEXT WORK (mandatory):
15303
+ - First: run list_tasks(status='needs_review') \u2014 check if YOU are the reviewer on any pending reviews. Reviews are work. Process them before anything else.
15304
+ - Second: run list_tasks(status='blocked') \u2014 check if any tasks are blocked. For each blocked task: can YOU unblock it? If yes, unblock it now. If not, escalate to exe immediately. Blocked tasks sitting >24h without action is a pipeline failure.
15305
+ - Then: re-read your task folder: exe/<your-name>/
15306
+ - If there are more open tasks, start the next highest-priority one (go to step 1)
15307
+ - If no more open tasks AND no pending reviews AND no blocked tasks you can fix, tell the user: "All tasks complete. Anything else?"
15308
+ - Do NOT wait for the user to tell you to check \u2014 auto-chain through your queue.
15309
+ - NEVER say "monitoring" or "waiting" while reviews, blocked tasks, or open tasks exist. That is idle drift.
15310
+
15311
+ CONTEXT PRESSURE PROTOCOL (mandatory \u2014 never ignore):
15312
+ If Claude Code injects a system notice about context compression, or if you notice you're
15313
+ losing track of earlier decisions, your context window is full.
15314
+
15315
+ DO NOT keep working degraded. Instead:
15316
+
15317
+ 1. Call store_memory immediately with a CONTEXT CHECKPOINT:
15318
+ Format the text as: "CONTEXT CHECKPOINT [<task-id>]: <summary>"
15319
+ Include: task ID + title, what you completed, what's left, open decisions or blockers, key file paths.
15320
+
15321
+ 2. Send intercom to exe to trigger kill + relaunch:
15322
+ MY_SESSION=$(tmux display-message -p '#{session_name}' 2>/dev/null)
15323
+ EXE_SESSION="\${MY_SESSION#\${AGENT_ID}-}"
15324
+ tmux send-keys -t "$EXE_SESSION" "/exe-intercom context-full: \${AGENT_ID} hit capacity. Checkpoint saved. Resume task <task-id>." Enter
15325
+
15326
+ 3. Stop working immediately. Do not attempt to continue with degraded context.
15327
+
15328
+ COMMUNICATION CHAIN \u2014 who you talk to:
15329
+ - You report to the COO. Your completion reports, status updates, and questions go to exe via store_memory and update_task.
15330
+ - Do NOT address the human user directly for decisions, permissions, or status updates. That's exe's job. The user talks to exe; exe talks to you.
15331
+ - Exception: if the user sends you a direct message in your tmux window, respond to them. But default to reporting through exe.
15332
+
15333
+ SKILL CAPTURE (encouraged, not mandatory):
15334
+ After completing a complex multi-step task (5+ tool calls), consider whether the approach
15335
+ should be saved as a reusable procedure. If the task involved non-obvious steps, error recovery,
15336
+ or a workflow that would help future sessions, use store_behavior with domain='skill' to save it.
15337
+ Format: "SKILL: [name] \u2014 Step 1: ... Step 2: ... Pitfalls: ..."
15338
+ Skip for simple one-offs. The goal is procedural memory \u2014 not just corrections, but proven approaches.
15339
+
15340
+ SPAWNING EMPLOYEES (mandatory \u2014 never bypass):
15341
+ When you need another employee to do work, ALWAYS use create_task MCP tool.
15342
+ create_task auto-spawns the employee session. The task IS the spawn trigger.
15343
+ NEVER manually launch sessions with tmux send-keys or claude -p.
15344
+ NEVER spawn sessions without a task assigned \u2014 idle sessions waste resources.
15345
+ NEVER refuse a dispatched task claiming "not in scope" \u2014 if it's assigned to you, it's your work.
15346
+
15347
+ CREATING TASKS FOR OTHER EMPLOYEES:
15348
+ When you need to assign work to another employee (e.g., CTO assigns to an engineer):
15349
+ - ALWAYS use create_task MCP tool. NEVER write .md files directly to exe/{name}/.
15350
+ - Direct .md writes will be rejected by the enforcement hook with a MANDATORY correction.
15351
+ - create_task creates both the .md file AND the DB row atomically.
15352
+ - Include: title, assignedTo, priority, context, projectName.
15353
+ - For dependencies: include blocked_by with the blocking task's ID or slug.
15354
+ `;
15355
+ var PROCEDURES_MARKER = "EXE OS \u2014 VISION AND NON-NEGOTIABLE PRINCIPLES";
15356
+ function getSessionPrompt(storedPrompt) {
15357
+ const markerIndex = storedPrompt.indexOf(PROCEDURES_MARKER);
15358
+ const rolePrompt = markerIndex >= 0 ? storedPrompt.slice(0, markerIndex).trimEnd() : storedPrompt;
15359
+ const globalBlock = getGlobalProceduresBlock();
15360
+ return `${globalBlock}${rolePrompt}
15361
+ ${BASE_OPERATING_PROCEDURES}`;
15362
+ }
15363
+
14924
15364
  // src/tui/views/CommandCenter.tsx
14925
15365
  import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
14926
15366
  function CommandCenterView({
@@ -15044,7 +15484,7 @@ function CommandCenterView({
15044
15484
  setAgentConfig({
15045
15485
  provider,
15046
15486
  model,
15047
- systemPrompt: "You are an AI assistant in the exe-os TUI. Help the user with their questions. Be concise.",
15487
+ systemPrompt: getSessionPrompt("You are an AI assistant in the exe-os TUI. Help the user with their questions. Be concise."),
15048
15488
  tools: registry,
15049
15489
  hooks: createDefaultHooks2(),
15050
15490
  permissions,
@@ -15165,6 +15605,7 @@ function CommandCenterView({
15165
15605
  employeeCount: p.employees.length,
15166
15606
  activeCount: p.employees.filter((e) => e.status === "active").length,
15167
15607
  memoryCount: p.employees.length * 4e3,
15608
+ openTaskCount: Math.floor(p.employees.length * 1.5),
15168
15609
  status: p.employees.some((e) => e.status === "active") ? "active" : "idle",
15169
15610
  type: p.projectName.startsWith("exe-") ? "code" : "automation",
15170
15611
  recentTasks: DEMO_RECENT_TASKS[p.projectName] ?? []
@@ -15176,6 +15617,7 @@ function CommandCenterView({
15176
15617
  employeeCount: 0,
15177
15618
  activeCount: 0,
15178
15619
  memoryCount: 0,
15620
+ openTaskCount: 0,
15179
15621
  status: "offline",
15180
15622
  type: "reference",
15181
15623
  recentTasks: []
@@ -15190,182 +15632,122 @@ function CommandCenterView({
15190
15632
  return () => clearInterval(timer);
15191
15633
  }, []);
15192
15634
  async function loadData() {
15193
- try {
15194
- const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session_registry(), session_registry_exports));
15195
- const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
15196
- const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
15197
- const { existsSync: existsSync14 } = await import("fs");
15198
- const { join } = await import("path");
15199
- const registry = listSessions2();
15200
- const tmuxSessions = inTmux2() ? new Set(listTmuxSessions2()) : /* @__PURE__ */ new Set();
15201
- const roster = await loadEmployees2();
15202
- const exeSessions = /* @__PURE__ */ new Map();
15203
- for (const entry of registry) {
15204
- if (entry.agentId === "exe" && tmuxSessions.has(entry.windowName)) {
15205
- exeSessions.set(entry.windowName, entry.projectDir);
15206
- }
15207
- }
15208
- for (const s of tmuxSessions) {
15209
- if (/^exe\d+$/.test(s) && !exeSessions.has(s)) exeSessions.set(s, "");
15210
- }
15211
- let projectMemoryCounts = /* @__PURE__ */ new Map();
15212
- let projectRecentTasks = /* @__PURE__ */ new Map();
15635
+ const { withTrace: withTrace2 } = await Promise.resolve().then(() => (init_telemetry(), telemetry_exports));
15636
+ return withTrace2("tui.commandCenter.loadData", async () => {
15213
15637
  try {
15214
15638
  const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
15639
+ const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session_registry(), session_registry_exports));
15640
+ const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
15641
+ const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
15642
+ const { existsSync: existsSync14 } = await import("fs");
15643
+ const { join } = await import("path");
15215
15644
  const client = getClient2();
15216
- if (client) {
15217
- const memResult = await client.execute(
15218
- "SELECT project_name, COUNT(*) as cnt FROM memories GROUP BY project_name"
15219
- );
15220
- for (const row of memResult.rows) {
15221
- projectMemoryCounts.set(String(row.project_name), Number(row.cnt));
15222
- }
15223
- for (const [, projectDir] of exeSessions) {
15224
- const projectName = projectDir.split("/").filter(Boolean).pop() ?? "";
15225
- if (!projectName) continue;
15226
- const taskResult = await client.execute({
15227
- sql: "SELECT title FROM tasks WHERE project_name = ? AND status = 'done' ORDER BY updated_at DESC LIMIT 3",
15228
- args: [projectName]
15229
- });
15230
- const tasks = taskResult.rows.map((r) => String(r.title));
15231
- if (tasks.length > 0) projectRecentTasks.set(projectName, tasks);
15232
- }
15645
+ if (!client) {
15646
+ setDbError(true);
15647
+ return;
15233
15648
  }
15234
- } catch {
15235
- }
15236
- const projectList = [];
15237
- for (const [exeSession, projectDir] of exeSessions) {
15238
- const projectName = projectDir.split("/").filter(Boolean).pop() ?? exeSession;
15649
+ const dbProjects = await client.execute(
15650
+ `SELECT DISTINCT project_name FROM memories WHERE project_name IS NOT NULL AND project_name != ''
15651
+ UNION
15652
+ SELECT DISTINCT project_name FROM tasks WHERE project_name IS NOT NULL AND project_name != ''
15653
+ ORDER BY project_name`
15654
+ );
15655
+ const projectNames = dbProjects.rows.map((r) => String(r.project_name));
15656
+ const memResult = await client.execute(
15657
+ "SELECT project_name, COUNT(*) as cnt FROM memories WHERE project_name IS NOT NULL GROUP BY project_name"
15658
+ );
15659
+ const memoryCounts = /* @__PURE__ */ new Map();
15660
+ for (const row of memResult.rows) {
15661
+ memoryCounts.set(String(row.project_name), Number(row.cnt));
15662
+ }
15663
+ const taskCountResult = await client.execute(
15664
+ "SELECT project_name, COUNT(*) as cnt FROM tasks WHERE status IN ('open', 'in_progress') AND project_name IS NOT NULL GROUP BY project_name"
15665
+ );
15666
+ const openTaskCounts = /* @__PURE__ */ new Map();
15667
+ for (const row of taskCountResult.rows) {
15668
+ openTaskCounts.set(String(row.project_name), Number(row.cnt));
15669
+ }
15670
+ const recentResult = await client.execute(
15671
+ "SELECT project_name, title FROM tasks WHERE status = 'done' AND project_name IS NOT NULL ORDER BY updated_at DESC LIMIT 30"
15672
+ );
15673
+ const recentTasksByProject = /* @__PURE__ */ new Map();
15674
+ for (const row of recentResult.rows) {
15675
+ const name = String(row.project_name);
15676
+ const tasks = recentTasksByProject.get(name) ?? [];
15677
+ if (tasks.length < 3) tasks.push(String(row.title));
15678
+ recentTasksByProject.set(name, tasks);
15679
+ }
15680
+ const registry = listSessions2();
15681
+ const tmuxSessions = inTmux2() ? new Set(listTmuxSessions2()) : /* @__PURE__ */ new Set();
15682
+ const roster = await loadEmployees2();
15239
15683
  const employeeNames = roster.map((e) => e.name).filter((n) => n !== "exe");
15240
- let activeCount = tmuxSessions.has(exeSession) ? 1 : 0;
15241
- for (const empName of employeeNames) {
15242
- if (tmuxSessions.has(`${empName}-${exeSession}`)) activeCount++;
15684
+ const projectSessions = /* @__PURE__ */ new Map();
15685
+ for (const entry of registry) {
15686
+ if (entry.agentId === "exe" && tmuxSessions.has(entry.windowName)) {
15687
+ const projName = entry.projectDir.split("/").filter(Boolean).pop() ?? "";
15688
+ if (projName) {
15689
+ projectSessions.set(projName, { exeSession: entry.windowName, projectDir: entry.projectDir });
15690
+ }
15691
+ }
15243
15692
  }
15244
- const totalCount = 1 + employeeNames.length;
15245
- const memoryCount = projectMemoryCounts.get(projectName) ?? 0;
15246
- const hasGit = projectDir ? existsSync14(join(projectDir, ".git")) : false;
15247
- const hasActivity = memoryCount > 0;
15248
- let type = "automation";
15249
- if (hasGit && hasActivity) type = "code";
15250
- else if (hasGit && !hasActivity) type = "reference";
15251
- projectList.push({
15252
- projectName,
15253
- exeSession,
15254
- projectDir,
15255
- employeeCount: totalCount,
15256
- activeCount,
15257
- memoryCount,
15258
- status: activeCount > 0 ? "active" : "idle",
15259
- type,
15260
- recentTasks: projectRecentTasks.get(projectName) ?? []
15261
- });
15262
- }
15263
- const knownDirs = [
15264
- process.env.HOME ? join(process.env.HOME, "openclaw") : null,
15265
- process.env.HOME ? join(process.env.HOME, "agno") : null
15266
- ].filter(Boolean);
15267
- const activeProjectNames = new Set(projectList.map((p) => p.projectName));
15268
- for (const dir of knownDirs) {
15269
- const name = dir.split("/").filter(Boolean).pop() ?? "";
15270
- if (activeProjectNames.has(name) || !existsSync14(dir) || !existsSync14(join(dir, ".git"))) continue;
15271
- if ((projectMemoryCounts.get(name) ?? 0) > 0) continue;
15272
- projectList.push({
15273
- projectName: name,
15274
- exeSession: "",
15275
- projectDir: dir,
15276
- employeeCount: 0,
15277
- activeCount: 0,
15278
- memoryCount: 0,
15279
- status: "offline",
15280
- type: "reference",
15281
- recentTasks: []
15282
- });
15283
- }
15284
- const dbActiveProjectNames = new Set(projectList.map((p) => p.projectName));
15285
- try {
15286
- const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
15287
- const client = getClient2();
15288
- if (client) {
15289
- const dbProjects = await client.execute(
15290
- `SELECT DISTINCT project_name FROM memories WHERE project_name IS NOT NULL AND project_name != ''
15291
- UNION
15292
- SELECT DISTINCT project_name FROM tasks WHERE project_name IS NOT NULL AND project_name != ''
15293
- ORDER BY project_name`
15294
- );
15295
- for (const row of dbProjects.rows) {
15296
- const name = String(row.project_name);
15297
- if (dbActiveProjectNames.has(name)) continue;
15298
- const memCount = projectMemoryCounts.get(name) ?? 0;
15299
- const agentResult = await client.execute({
15300
- sql: "SELECT COUNT(DISTINCT agent_id) as cnt FROM memories WHERE project_name = ?",
15301
- args: [name]
15302
- });
15303
- const agentCount = Number(agentResult.rows[0]?.cnt ?? 0);
15304
- projectList.push({
15305
- projectName: name,
15306
- exeSession: "",
15307
- projectDir: "",
15308
- employeeCount: agentCount,
15309
- activeCount: 0,
15310
- memoryCount: memCount,
15311
- status: "offline",
15312
- type: "code",
15313
- recentTasks: projectRecentTasks.get(name) ?? []
15314
- });
15693
+ const projectList = [];
15694
+ for (const name of projectNames) {
15695
+ const session = projectSessions.get(name);
15696
+ const exeSession = session?.exeSession ?? "";
15697
+ const projectDir = session?.projectDir ?? "";
15698
+ let activeCount = 0;
15699
+ if (exeSession && tmuxSessions.has(exeSession)) {
15700
+ activeCount = 1;
15701
+ for (const empName of employeeNames) {
15702
+ if (tmuxSessions.has(`${empName}-${exeSession}`)) activeCount++;
15703
+ }
15315
15704
  }
15705
+ const memoryCount = memoryCounts.get(name) ?? 0;
15706
+ const openTaskCount = openTaskCounts.get(name) ?? 0;
15707
+ const hasGit = projectDir ? existsSync14(join(projectDir, ".git")) : false;
15708
+ const type = hasGit ? "code" : memoryCount > 0 ? "code" : "automation";
15709
+ projectList.push({
15710
+ projectName: name,
15711
+ exeSession,
15712
+ projectDir,
15713
+ employeeCount: activeCount,
15714
+ activeCount,
15715
+ memoryCount,
15716
+ openTaskCount,
15717
+ status: activeCount > 0 ? "active" : "idle",
15718
+ type,
15719
+ recentTasks: recentTasksByProject.get(name) ?? []
15720
+ });
15316
15721
  }
15317
- } catch {
15318
- }
15319
- if (projectList.filter((p) => p.type !== "reference").length === 0) {
15320
- projectList.unshift({
15321
- projectName: "(no active sessions)",
15322
- exeSession: "",
15323
- projectDir: "",
15324
- employeeCount: 0,
15325
- activeCount: 0,
15326
- memoryCount: 0,
15327
- status: "offline",
15328
- type: "code",
15329
- recentTasks: []
15722
+ projectList.sort((a, b) => {
15723
+ if (a.activeCount > 0 && b.activeCount === 0) return -1;
15724
+ if (a.activeCount === 0 && b.activeCount > 0) return 1;
15725
+ return b.memoryCount - a.memoryCount;
15330
15726
  });
15331
- }
15332
- setProjects(projectList);
15333
- try {
15334
- const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
15335
- const client = getClient2();
15336
- if (client) {
15337
- const result = await client.execute("SELECT COUNT(*) as cnt FROM memories");
15338
- setHealth((h) => ({ ...h, memories: Number(result.rows[0]?.cnt ?? 0) }));
15727
+ setProjects(projectList);
15728
+ const totalResult = await client.execute("SELECT COUNT(*) as cnt FROM memories");
15729
+ setHealth((h) => ({ ...h, memories: Number(totalResult.rows[0]?.cnt ?? 0) }));
15730
+ try {
15731
+ const pidPath = join(process.env.HOME ?? "", ".exe-os", "exed.pid");
15732
+ setHealth((h) => ({ ...h, daemon: existsSync14(pidPath) ? "running" : "stopped" }));
15733
+ } catch {
15339
15734
  }
15340
- } catch {
15341
- }
15342
- try {
15343
- const pidPath = join(process.env.HOME ?? "", ".exe-os", "exed.pid");
15344
- setHealth((h) => ({ ...h, daemon: existsSync14(pidPath) ? "running" : "stopped" }));
15345
- } catch {
15346
- }
15347
- try {
15348
- const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
15349
- const client = getClient2();
15350
- if (client) {
15351
- const activityResult = await client.execute(
15352
- "SELECT agent_id, tool_name, project_name, created_at, text FROM memories ORDER BY created_at DESC LIMIT 20"
15353
- );
15354
- if (activityResult.rows.length > 0) {
15355
- setActivity(activityResult.rows.slice(0, 8).map((r) => ({
15356
- time: new Date(String(r.created_at)).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false }),
15357
- agent: String(r.agent_id ?? "system"),
15358
- action: String(r.text ?? "").slice(0, 60)
15359
- })));
15360
- }
15735
+ const activityResult = await client.execute(
15736
+ "SELECT agent_id, tool_name, project_name, created_at, text FROM memories ORDER BY created_at DESC LIMIT 20"
15737
+ );
15738
+ if (activityResult.rows.length > 0) {
15739
+ setActivity(activityResult.rows.slice(0, 8).map((r) => ({
15740
+ time: new Date(String(r.created_at)).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false }),
15741
+ agent: String(r.agent_id ?? "system"),
15742
+ action: String(r.text ?? "").slice(0, 60)
15743
+ })));
15361
15744
  }
15362
15745
  } catch {
15746
+ setDbError(true);
15747
+ } finally {
15748
+ setLoading(false);
15363
15749
  }
15364
- } catch {
15365
- setDbError(true);
15366
- } finally {
15367
- setLoading(false);
15368
- }
15750
+ });
15369
15751
  }
15370
15752
  const daemonColor = health.daemon === "running" ? "green" : health.daemon === "stopped" ? "red" : "gray";
15371
15753
  const handleChatSubmit = useCallback4((value) => {
@@ -15457,7 +15839,7 @@ function CommandCenterView({
15457
15839
  ] }),
15458
15840
  /* @__PURE__ */ jsx7(Text, { color: "#6B4C9A", children: "\u2191\u2193 navigate | c to chat | Enter to open | Escape to detach" }),
15459
15841
  /* @__PURE__ */ jsx7(Text, { children: " " }),
15460
- loading ? /* @__PURE__ */ jsx7(Text, { color: "#6B4C9A", children: "Loading projects..." }) : dbError ? /* @__PURE__ */ jsx7(Text, { color: "#EF4444", children: "Database unavailable. Run exe-os setup to initialize." }) : rows.length === 0 ? /* @__PURE__ */ jsx7(Text, { color: "#6B4C9A", children: " No projects detected." }) : (() => {
15842
+ loading ? /* @__PURE__ */ jsx7(Text, { color: "#6B4C9A", children: "Loading projects..." }) : dbError ? /* @__PURE__ */ jsx7(Text, { color: "#EF4444", children: "Database unavailable. Run exe-os setup to initialize." }) : rows.length === 0 ? /* @__PURE__ */ jsx7(Text, { color: "#6B4C9A", children: "No projects yet. Run exe-os in a project directory to get started." }) : (() => {
15461
15843
  const sections = [];
15462
15844
  let currentProjects = [];
15463
15845
  let sectionKey = 0;
@@ -15487,9 +15869,9 @@ function CommandCenterView({
15487
15869
  ] })
15488
15870
  ] }),
15489
15871
  /* @__PURE__ */ jsxs5(Text, { color: isSelected ? "#F0EDE8" : "#6B4C9A", children: [
15490
- entry.employeeCount,
15872
+ entry.openTaskCount,
15491
15873
  " ",
15492
- entry.employeeCount === 1 ? "employee" : "employees",
15874
+ entry.openTaskCount === 1 ? "task" : "tasks",
15493
15875
  " \xB7 ",
15494
15876
  entry.memoryCount.toLocaleString(),
15495
15877
  " memories"
@@ -15823,6 +16205,7 @@ function SessionsView({
15823
16205
  const [viewingEmployee, setViewingEmployee] = useState9(null);
15824
16206
  const [viewingProject, setViewingProject] = useState9(null);
15825
16207
  const [loading, setLoading] = useState9(!demo);
16208
+ const [sessionError, setSessionError] = useState9(null);
15826
16209
  const [tmuxAvailable, setTmuxAvailable] = useState9(true);
15827
16210
  const orch = useOrchestrator(!demo);
15828
16211
  const [carouselEmployees, setCarouselEmployees] = useState9([]);
@@ -15998,111 +16381,116 @@ function SessionsView({
15998
16381
  return ACTIVE_PANE_PATTERN.test(lines.join("\n")) ? "active" : "idle";
15999
16382
  }
16000
16383
  async function loadSessions() {
16001
- try {
16002
- const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session_registry(), session_registry_exports));
16003
- const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2, capturePaneLines: capturePaneLines2, parseActivity: parseActivity2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
16004
- const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
16005
- const { execSync: execSync10 } = await import("child_process");
16006
- if (!inTmux2()) {
16007
- setTmuxAvailable(false);
16008
- setProjects([]);
16009
- return;
16010
- }
16011
- setTmuxAvailable(true);
16012
- const attachedMap = /* @__PURE__ */ new Map();
16384
+ const { withTrace: withTrace2 } = await Promise.resolve().then(() => (init_telemetry(), telemetry_exports));
16385
+ return withTrace2("tui.sessions.loadSessions", async () => {
16013
16386
  try {
16014
- const out = execSync10("tmux list-sessions -F '#{session_name}:#{session_attached}' 2>/dev/null", {
16015
- encoding: "utf8",
16016
- timeout: 3e3
16017
- });
16018
- for (const line of out.trim().split("\n").filter(Boolean)) {
16019
- const sepIdx = line.lastIndexOf(":");
16020
- if (sepIdx > 0) {
16021
- attachedMap.set(line.slice(0, sepIdx), line.slice(sepIdx + 1) === "1");
16022
- }
16387
+ const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session_registry(), session_registry_exports));
16388
+ const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2, capturePaneLines: capturePaneLines2, parseActivity: parseActivity2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
16389
+ const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
16390
+ const { execSync: execSync10 } = await import("child_process");
16391
+ if (!inTmux2()) {
16392
+ setTmuxAvailable(false);
16393
+ setProjects([]);
16394
+ return;
16023
16395
  }
16024
- } catch {
16025
- }
16026
- const registry = listSessions2();
16027
- const tmuxSessions = new Set(listTmuxSessions2());
16028
- const roster = await loadEmployees2();
16029
- const exeSessions = /* @__PURE__ */ new Map();
16030
- for (const entry of registry) {
16031
- if (entry.agentId === "exe" && tmuxSessions.has(entry.windowName)) {
16032
- exeSessions.set(entry.windowName, entry.projectDir);
16396
+ setTmuxAvailable(true);
16397
+ const attachedMap = /* @__PURE__ */ new Map();
16398
+ try {
16399
+ const out = execSync10("tmux list-sessions -F '#{session_name}:#{session_attached}' 2>/dev/null", {
16400
+ encoding: "utf8",
16401
+ timeout: 3e3
16402
+ });
16403
+ for (const line of out.trim().split("\n").filter(Boolean)) {
16404
+ const sepIdx = line.lastIndexOf(":");
16405
+ if (sepIdx > 0) {
16406
+ attachedMap.set(line.slice(0, sepIdx), line.slice(sepIdx + 1) === "1");
16407
+ }
16408
+ }
16409
+ } catch {
16033
16410
  }
16034
- }
16035
- for (const s of tmuxSessions) {
16036
- if (/^exe\d+$/.test(s) && !exeSessions.has(s)) {
16037
- exeSessions.set(s, "");
16411
+ const registry = listSessions2();
16412
+ const tmuxSessions = new Set(listTmuxSessions2());
16413
+ const roster = await loadEmployees2();
16414
+ const exeSessions = /* @__PURE__ */ new Map();
16415
+ for (const entry of registry) {
16416
+ if (entry.agentId === "exe" && tmuxSessions.has(entry.windowName)) {
16417
+ exeSessions.set(entry.windowName, entry.projectDir);
16418
+ }
16038
16419
  }
16039
- }
16040
- const projectList = [];
16041
- for (const [exeSession, projectDir] of exeSessions) {
16042
- const projectName = projectDir.split("/").filter(Boolean).pop() ?? exeSession;
16043
- const exeHasSession = tmuxSessions.has(exeSession);
16044
- let exeStatus = "offline";
16045
- let exeActivity = "";
16046
- if (exeHasSession) {
16047
- const exeLines = capturePaneLines2(exeSession, 10);
16048
- exeStatus = statusFromPaneLines(exeLines);
16049
- exeActivity = exeLines.length > 0 ? parseActivity2(exeLines) : "";
16420
+ for (const s of tmuxSessions) {
16421
+ if (/^exe\d+$/.test(s) && !exeSessions.has(s)) {
16422
+ exeSessions.set(s, "");
16423
+ }
16050
16424
  }
16051
- const employeeEntries = roster.filter((e) => e.name !== "exe").map((emp) => {
16052
- const sessionName = `${emp.name}-${exeSession}`;
16053
- const hasSession = tmuxSessions.has(sessionName);
16054
- const isCrashed = !hasSession && orch.crashedSessions.includes(emp.name);
16055
- if (isCrashed) {
16056
- return {
16057
- name: emp.name,
16058
- role: emp.role,
16059
- status: "crashed",
16060
- sessionName,
16061
- activity: "Dead session \u2014 has open tasks",
16062
- attached: false
16063
- };
16425
+ const projectList = [];
16426
+ for (const [exeSession, projectDir] of exeSessions) {
16427
+ const projectName = projectDir.split("/").filter(Boolean).pop() ?? exeSession;
16428
+ const exeHasSession = tmuxSessions.has(exeSession);
16429
+ let exeStatus = "offline";
16430
+ let exeActivity = "";
16431
+ if (exeHasSession) {
16432
+ const exeLines = capturePaneLines2(exeSession, 10);
16433
+ exeStatus = statusFromPaneLines(exeLines);
16434
+ exeActivity = exeLines.length > 0 ? parseActivity2(exeLines) : "";
16064
16435
  }
16065
- if (!hasSession) {
16436
+ const employeeEntries = roster.filter((e) => e.name !== "exe").map((emp) => {
16437
+ const sessionName = `${emp.name}-${exeSession}`;
16438
+ const hasSession = tmuxSessions.has(sessionName);
16439
+ const isCrashed = !hasSession && orch.crashedSessions.includes(emp.name);
16440
+ if (isCrashed) {
16441
+ return {
16442
+ name: emp.name,
16443
+ role: emp.role,
16444
+ status: "crashed",
16445
+ sessionName,
16446
+ activity: "Dead session \u2014 has open tasks",
16447
+ attached: false
16448
+ };
16449
+ }
16450
+ if (!hasSession) {
16451
+ return {
16452
+ name: emp.name,
16453
+ role: emp.role,
16454
+ status: "offline",
16455
+ sessionName,
16456
+ activity: "",
16457
+ attached: false
16458
+ };
16459
+ }
16460
+ const lines = capturePaneLines2(sessionName, 10);
16066
16461
  return {
16067
16462
  name: emp.name,
16068
16463
  role: emp.role,
16069
- status: "offline",
16464
+ status: statusFromPaneLines(lines),
16070
16465
  sessionName,
16071
- activity: "",
16072
- attached: false
16466
+ activity: lines.length > 0 ? parseActivity2(lines) : "",
16467
+ attached: attachedMap.get(sessionName) ?? false
16073
16468
  };
16074
- }
16075
- const lines = capturePaneLines2(sessionName, 10);
16076
- return {
16077
- name: emp.name,
16078
- role: emp.role,
16079
- status: statusFromPaneLines(lines),
16080
- sessionName,
16081
- activity: lines.length > 0 ? parseActivity2(lines) : "",
16082
- attached: attachedMap.get(sessionName) ?? false
16083
- };
16084
- });
16085
- const employees = [
16086
- {
16087
- name: "exe",
16088
- role: "COO",
16089
- status: exeStatus,
16090
- sessionName: exeSession,
16091
- activity: exeActivity,
16092
- attached: attachedMap.get(exeSession) ?? false
16093
- },
16094
- ...employeeEntries
16095
- ];
16096
- projectList.push({ projectName, exeSession, projectDir, employees });
16097
- }
16098
- if (projectList.length === 0) {
16099
- projectList.push({ projectName: "(no active sessions)", exeSession: "", projectDir: "", employees: [] });
16469
+ });
16470
+ const employees = [
16471
+ {
16472
+ name: "exe",
16473
+ role: "COO",
16474
+ status: exeStatus,
16475
+ sessionName: exeSession,
16476
+ activity: exeActivity,
16477
+ attached: attachedMap.get(exeSession) ?? false
16478
+ },
16479
+ ...employeeEntries
16480
+ ];
16481
+ projectList.push({ projectName, exeSession, projectDir, employees });
16482
+ }
16483
+ if (projectList.length === 0) {
16484
+ projectList.push({ projectName: "(no active sessions)", exeSession: "", projectDir: "", employees: [] });
16485
+ }
16486
+ setProjects(projectList);
16487
+ setSessionError(null);
16488
+ } catch (err) {
16489
+ setSessionError(err instanceof Error ? err.message : "Failed to query sessions.");
16490
+ } finally {
16491
+ setLoading(false);
16100
16492
  }
16101
- setProjects(projectList);
16102
- } catch {
16103
- } finally {
16104
- setLoading(false);
16105
- }
16493
+ });
16106
16494
  }
16107
16495
  if (viewingEmployee) {
16108
16496
  const inCarousel = carouselEmployees.length > 0;
@@ -16184,7 +16572,10 @@ function SessionsView({
16184
16572
  ] })
16185
16573
  ] }),
16186
16574
  /* @__PURE__ */ jsx9(Text, { children: " " }),
16187
- loading ? /* @__PURE__ */ jsx9(Text, { color: "#6B4C9A", children: "Loading sessions..." }) : !tmuxAvailable ? /* @__PURE__ */ jsx9(Text, { color: "#6B4C9A", children: "tmux not available. Start a tmux session first to manage employees." }) : flatItems.length === 0 ? /* @__PURE__ */ jsx9(Text, { color: "#6B4C9A", children: "No active tmux sessions. Press Enter on an employee to launch one, or start exe in a tmux session first." }) : flatItems.map((item, i) => {
16575
+ loading ? /* @__PURE__ */ jsx9(Text, { color: "#6B4C9A", children: "Loading sessions..." }) : sessionError ? /* @__PURE__ */ jsxs7(Text, { color: "#C91B00", children: [
16576
+ "Failed to query sessions: ",
16577
+ sessionError
16578
+ ] }) : !tmuxAvailable ? /* @__PURE__ */ jsx9(Text, { color: "#3D3660", children: "tmux not available. Start a tmux session first to manage employees." }) : flatItems.length === 0 ? /* @__PURE__ */ jsx9(Text, { color: "#3D3660", children: "No active sessions. Create a task to spawn an employee." }) : flatItems.map((item, i) => {
16188
16579
  const isSelected = i === selectedIdx;
16189
16580
  const marker = isSelected ? "\u25B8 " : " ";
16190
16581
  if (item.type === "project") {
@@ -16285,7 +16676,7 @@ function TasksView({ onBack }) {
16285
16676
  const demo = useDemo();
16286
16677
  const [allTasks, setAllTasks] = useState10([]);
16287
16678
  const [loading, setLoading] = useState10(!demo);
16288
- const [dbError, setDbError] = useState10(false);
16679
+ const [dbError, setDbError] = useState10(null);
16289
16680
  const [selectedTask, setSelectedTask] = useState10(0);
16290
16681
  const [showDetail, setShowDetail] = useState10(false);
16291
16682
  const [statusFilter, setStatusFilter] = useState10("all");
@@ -16357,40 +16748,43 @@ function TasksView({ onBack }) {
16357
16748
  }
16358
16749
  });
16359
16750
  async function loadTasks() {
16360
- try {
16361
- const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
16362
- const client = getClient2();
16363
- if (client) {
16364
- const result = await client.execute(
16365
- `SELECT id, title, priority, assigned_to, assigned_by, status, project_name, created_at, result
16751
+ const { withTrace: withTrace2 } = await Promise.resolve().then(() => (init_telemetry(), telemetry_exports));
16752
+ return withTrace2("tui.tasks.loadTasks", async () => {
16753
+ try {
16754
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
16755
+ const client = getClient2();
16756
+ if (client) {
16757
+ const result = await client.execute(
16758
+ `SELECT id, title, priority, assigned_to, assigned_by, status, project_name, created_at, result
16366
16759
  FROM tasks
16367
16760
  ORDER BY
16368
16761
  CASE status WHEN 'in_progress' THEN 0 WHEN 'open' THEN 1 WHEN 'blocked' THEN 2 WHEN 'needs_review' THEN 3 WHEN 'done' THEN 4 ELSE 5 END,
16369
16762
  CASE priority WHEN 'p0' THEN 0 WHEN 'p1' THEN 1 WHEN 'p2' THEN 2 ELSE 3 END,
16370
16763
  created_at DESC`
16371
- );
16372
- setAllTasks(
16373
- result.rows.map((r) => ({
16374
- id: String(r.id ?? ""),
16375
- priority: String(r.priority ?? "p2").toUpperCase(),
16376
- title: String(r.title ?? ""),
16377
- assignee: String(r.assigned_to ?? ""),
16378
- assignedBy: String(r.assigned_by ?? ""),
16379
- status: String(r.status ?? "open"),
16380
- project: String(r.project_name ?? ""),
16381
- createdAt: String(r.created_at ?? ""),
16382
- result: String(r.result ?? "")
16383
- }))
16384
- );
16385
- setDbError(false);
16386
- } else {
16387
- setDbError(true);
16764
+ );
16765
+ setAllTasks(
16766
+ result.rows.map((r) => ({
16767
+ id: String(r.id ?? ""),
16768
+ priority: String(r.priority ?? "p2").toUpperCase(),
16769
+ title: String(r.title ?? ""),
16770
+ assignee: String(r.assigned_to ?? ""),
16771
+ assignedBy: String(r.assigned_by ?? ""),
16772
+ status: String(r.status ?? "open"),
16773
+ project: String(r.project_name ?? ""),
16774
+ createdAt: String(r.created_at ?? ""),
16775
+ result: String(r.result ?? "")
16776
+ }))
16777
+ );
16778
+ setDbError(null);
16779
+ } else {
16780
+ setDbError("Database client not initialized. Run exe-os setup.");
16781
+ }
16782
+ } catch (err) {
16783
+ setDbError(err instanceof Error ? err.message : "Unknown error");
16784
+ } finally {
16785
+ setLoading(false);
16388
16786
  }
16389
- } catch {
16390
- setDbError(true);
16391
- } finally {
16392
- setLoading(false);
16393
- }
16787
+ });
16394
16788
  }
16395
16789
  const selected = taskRows[selectedTask]?.task;
16396
16790
  if (showDetail && selected) {
@@ -16457,7 +16851,10 @@ function TasksView({ onBack }) {
16457
16851
  filterLabel
16458
16852
  ] }),
16459
16853
  /* @__PURE__ */ jsx10(Text, { children: " " }),
16460
- loading ? /* @__PURE__ */ jsx10(Text, { color: "#6B4C9A", children: "Loading tasks..." }) : dbError ? /* @__PURE__ */ jsx10(Text, { color: "#EF4444", children: "Database unavailable. Run exe-os setup to initialize." }) : filteredTasks.length === 0 ? /* @__PURE__ */ jsx10(Text, { color: "#6B4C9A", children: "No tasks match current filters." }) : displayRows.map((row, i) => {
16854
+ loading ? /* @__PURE__ */ jsx10(Text, { color: "#6B4C9A", children: "Loading tasks..." }) : dbError ? /* @__PURE__ */ jsxs8(Text, { color: "#C91B00", children: [
16855
+ "Failed to load tasks: ",
16856
+ dbError
16857
+ ] }) : allTasks.length === 0 ? /* @__PURE__ */ jsx10(Text, { color: "#3D3660", children: "No tasks. Create one with create_task." }) : filteredTasks.length === 0 ? /* @__PURE__ */ jsx10(Text, { color: "#3D3660", children: "No tasks match current filter." }) : displayRows.map((row, i) => {
16461
16858
  if (row.type === "header") {
16462
16859
  return /* @__PURE__ */ jsxs8(Box_default, { marginTop: i > 0 ? 1 : 0, children: [
16463
16860
  /* @__PURE__ */ jsx10(Text, { bold: true, color: "#F0EDE8", children: row.project }),
@@ -16911,6 +17308,31 @@ function GatewayView({ onBack }) {
16911
17308
  /* @__PURE__ */ jsx11(Text, { color: "#6B4C9A", children: "Loading gateway status..." })
16912
17309
  ] });
16913
17310
  }
17311
+ if (!gateway.running && gateway.gatewayUrl && connectionStatus === "disconnected") {
17312
+ return /* @__PURE__ */ jsxs9(Box_default, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: [
17313
+ /* @__PURE__ */ jsx11(Box_default, { borderStyle: "single", borderColor: "#3D3660", paddingX: 1, alignSelf: "flex-start", children: /* @__PURE__ */ jsx11(Text, { bold: true, children: "Gateway Monitor" }) }),
17314
+ /* @__PURE__ */ jsx11(Text, { children: " " }),
17315
+ /* @__PURE__ */ jsx11(Text, { color: "#C91B00", children: "Gateway connection failed." }),
17316
+ /* @__PURE__ */ jsx11(Text, { children: " " }),
17317
+ /* @__PURE__ */ jsxs9(Text, { color: "#6B4C9A", children: [
17318
+ "Gateway URL: ",
17319
+ /* @__PURE__ */ jsx11(Text, { color: "#F0EDE8", children: gateway.gatewayUrl })
17320
+ ] }),
17321
+ /* @__PURE__ */ jsxs9(Text, { color: "#6B4C9A", children: [
17322
+ "Process: ",
17323
+ /* @__PURE__ */ jsx11(Text, { color: "#C91B00", children: "not running" })
17324
+ ] }),
17325
+ /* @__PURE__ */ jsx11(Text, { children: " " }),
17326
+ /* @__PURE__ */ jsxs9(Text, { color: "#6B4C9A", children: [
17327
+ "Start the gateway: ",
17328
+ /* @__PURE__ */ jsx11(Text, { bold: true, children: "exe-os gateway" })
17329
+ ] }),
17330
+ /* @__PURE__ */ jsxs9(Text, { color: "#6B4C9A", children: [
17331
+ "Or check your config: ",
17332
+ /* @__PURE__ */ jsx11(Text, { bold: true, children: "~/.exe-os/gateway.json" })
17333
+ ] })
17334
+ ] });
17335
+ }
16914
17336
  if (!gateway.running && gateway.adapters.length === 0 && !gateway.gatewayUrl) {
16915
17337
  return /* @__PURE__ */ jsxs9(Box_default, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: [
16916
17338
  /* @__PURE__ */ jsx11(Box_default, { borderStyle: "single", borderColor: "#3D3660", paddingX: 1, alignSelf: "flex-start", children: /* @__PURE__ */ jsx11(Text, { bold: true, children: "Gateway Monitor" }) }),
@@ -17099,14 +17521,30 @@ function getAgentStatus(agentId) {
17099
17521
  // src/tui/views/Team.tsx
17100
17522
  import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
17101
17523
  var DEPRECATED_PROJECTS = /* @__PURE__ */ new Set(["exe-ai-employees"]);
17102
- function TeamView({ onBack }) {
17524
+ function roleBadgeColor(role) {
17525
+ switch (role.toLowerCase()) {
17526
+ case "coo":
17527
+ return "#F5D76E";
17528
+ // gold
17529
+ case "cto":
17530
+ return "#3B82F6";
17531
+ // blue
17532
+ case "cmo":
17533
+ return "#6B4C9A";
17534
+ // purple
17535
+ default:
17536
+ return "#F0EDE8";
17537
+ }
17538
+ }
17539
+ function TeamView({ onBack, onViewSessions }) {
17103
17540
  const demo = useDemo();
17104
17541
  const [members, setMembers] = useState12([]);
17105
17542
  const [externals, setExternals] = useState12([]);
17106
17543
  const [loading, setLoading] = useState12(!demo);
17107
- const [dbError, setDbError] = useState12(false);
17544
+ const [dbError, setDbError] = useState12(null);
17108
17545
  const [selectedIdx, setSelectedIdx] = useState12(0);
17109
17546
  const [showDetail, setShowDetail] = useState12(false);
17547
+ const [showAddHint, setShowAddHint] = useState12(false);
17110
17548
  const orch = useOrchestrator(!demo);
17111
17549
  const orchEmployeeMap = new Map(orch.employees.map((e) => [e.name, e]));
17112
17550
  const allItems = [...members, ...externals.map((e) => ({ ...e, activity: "", memoryCount: 0 }))];
@@ -17121,7 +17559,7 @@ function TeamView({ onBack }) {
17121
17559
  const timer = setInterval(loadTeam, 5e3);
17122
17560
  return () => clearInterval(timer);
17123
17561
  }, []);
17124
- use_input_default((_input, key) => {
17562
+ use_input_default((input, key) => {
17125
17563
  if (showDetail) {
17126
17564
  if (key.escape) setShowDetail(false);
17127
17565
  return;
@@ -17130,91 +17568,103 @@ function TeamView({ onBack }) {
17130
17568
  onBack?.();
17131
17569
  return;
17132
17570
  }
17571
+ if (input === "a") {
17572
+ setShowAddHint(true);
17573
+ setTimeout(() => setShowAddHint(false), 3e3);
17574
+ return;
17575
+ }
17576
+ if (input === "s" && selected) {
17577
+ onViewSessions?.(selected.name);
17578
+ return;
17579
+ }
17133
17580
  if (key.upArrow && selectedIdx > 0) setSelectedIdx(selectedIdx - 1);
17134
17581
  if (key.downArrow && selectedIdx < allItems.length - 1) setSelectedIdx(selectedIdx + 1);
17135
17582
  if (key.return && allItems.length > 0) setShowDetail(true);
17136
17583
  });
17137
17584
  async function loadTeam() {
17138
- try {
17139
- const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
17140
- const roster = await loadEmployees2();
17141
- let memoryCounts = /* @__PURE__ */ new Map();
17142
- let projectsByEmployee = /* @__PURE__ */ new Map();
17143
- let currentTaskByEmployee = /* @__PURE__ */ new Map();
17585
+ const { withTrace: withTrace2 } = await Promise.resolve().then(() => (init_telemetry(), telemetry_exports));
17586
+ return withTrace2("tui.team.loadTeam", async () => {
17144
17587
  try {
17145
- const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
17146
- const client = getClient2();
17147
- if (client) {
17148
- const memResult = await client.execute("SELECT agent_id, COUNT(*) as cnt FROM memories GROUP BY agent_id");
17149
- for (const row of memResult.rows) {
17150
- memoryCounts.set(String(row.agent_id), Number(row.cnt));
17151
- }
17152
- for (const emp of roster) {
17153
- const projResult = await client.execute({
17154
- sql: `SELECT DISTINCT project_name,
17588
+ const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
17589
+ const roster = await loadEmployees2();
17590
+ let memoryCounts = /* @__PURE__ */ new Map();
17591
+ let projectsByEmployee = /* @__PURE__ */ new Map();
17592
+ let currentTaskByEmployee = /* @__PURE__ */ new Map();
17593
+ try {
17594
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
17595
+ const client = getClient2();
17596
+ if (client) {
17597
+ const memResult = await client.execute("SELECT agent_id, COUNT(*) as cnt FROM memories GROUP BY agent_id");
17598
+ for (const row of memResult.rows) {
17599
+ memoryCounts.set(String(row.agent_id), Number(row.cnt));
17600
+ }
17601
+ for (const emp of roster) {
17602
+ const projResult = await client.execute({
17603
+ sql: `SELECT DISTINCT project_name,
17155
17604
  MAX(CASE WHEN status = 'in_progress' THEN 1 WHEN status = 'open' THEN 2 ELSE 3 END) as urgency
17156
17605
  FROM tasks WHERE assigned_to = ? AND status IN ('open','in_progress','done')
17157
17606
  GROUP BY project_name ORDER BY urgency ASC LIMIT 5`,
17158
- args: [emp.name]
17159
- });
17160
- const projects = projResult.rows.filter((r) => !DEPRECATED_PROJECTS.has(String(r.project_name))).map((r) => {
17161
- const urgency = Number(r.urgency);
17162
- let pStatus = "idle";
17163
- if (urgency === 1) pStatus = "active";
17164
- else if (urgency === 2) pStatus = "has_tasks";
17165
- return { name: String(r.project_name), status: pStatus };
17166
- });
17167
- if (projects.length > 0) projectsByEmployee.set(emp.name, projects);
17168
- }
17169
- for (const emp of roster) {
17170
- const taskResult = await client.execute({
17171
- sql: "SELECT title FROM tasks WHERE assigned_to = ? AND status = 'in_progress' ORDER BY updated_at DESC LIMIT 1",
17172
- args: [emp.name]
17173
- });
17174
- if (taskResult.rows.length > 0) {
17175
- currentTaskByEmployee.set(emp.name, String(taskResult.rows[0].title));
17607
+ args: [emp.name]
17608
+ });
17609
+ const projects = projResult.rows.filter((r) => !DEPRECATED_PROJECTS.has(String(r.project_name))).map((r) => {
17610
+ const urgency = Number(r.urgency);
17611
+ let pStatus = "idle";
17612
+ if (urgency === 1) pStatus = "active";
17613
+ else if (urgency === 2) pStatus = "has_tasks";
17614
+ return { name: String(r.project_name), status: pStatus };
17615
+ });
17616
+ if (projects.length > 0) projectsByEmployee.set(emp.name, projects);
17617
+ }
17618
+ for (const emp of roster) {
17619
+ const taskResult = await client.execute({
17620
+ sql: "SELECT title FROM tasks WHERE assigned_to = ? AND status = 'in_progress' ORDER BY updated_at DESC LIMIT 1",
17621
+ args: [emp.name]
17622
+ });
17623
+ if (taskResult.rows.length > 0) {
17624
+ currentTaskByEmployee.set(emp.name, String(taskResult.rows[0].title));
17625
+ }
17176
17626
  }
17177
17627
  }
17628
+ } catch {
17178
17629
  }
17179
- } catch {
17180
- }
17181
- const teamData = roster.map((emp) => {
17182
- const agentSt = getAgentStatus(emp.name);
17183
- return {
17184
- name: emp.name,
17185
- role: emp.role,
17186
- status: agentSt.label,
17187
- activity: agentSt.label === "active" ? "Processing..." : "",
17188
- memoryCount: memoryCounts.get(emp.name) ?? 0,
17189
- projects: projectsByEmployee.get(emp.name),
17190
- currentTask: currentTaskByEmployee.get(emp.name),
17191
- sessionName: agentSt.session
17192
- };
17193
- });
17194
- setMembers(teamData);
17195
- setDbError(false);
17196
- try {
17197
- const { existsSync: existsSync14, readFileSync: readFileSync11 } = await import("fs");
17198
- const { join } = await import("path");
17199
- const home = process.env.HOME ?? "";
17200
- const gatewayConfig = join(home, ".exe-os", "gateway.json");
17201
- if (existsSync14(gatewayConfig)) {
17202
- const raw = JSON.parse(readFileSync11(gatewayConfig, "utf8"));
17203
- if (raw.agents && raw.agents.length > 0) {
17204
- setExternals(raw.agents.map((a) => ({
17205
- name: a.name,
17206
- role: a.role ?? "external agent",
17207
- status: "offline"
17208
- })));
17630
+ const teamData = roster.map((emp) => {
17631
+ const agentSt = getAgentStatus(emp.name);
17632
+ return {
17633
+ name: emp.name,
17634
+ role: emp.role,
17635
+ status: agentSt.label,
17636
+ activity: agentSt.label === "active" ? "Processing..." : "",
17637
+ memoryCount: memoryCounts.get(emp.name) ?? 0,
17638
+ projects: projectsByEmployee.get(emp.name),
17639
+ currentTask: currentTaskByEmployee.get(emp.name),
17640
+ sessionName: agentSt.session
17641
+ };
17642
+ });
17643
+ setMembers(teamData);
17644
+ setDbError(null);
17645
+ try {
17646
+ const { existsSync: existsSync14, readFileSync: readFileSync11 } = await import("fs");
17647
+ const { join } = await import("path");
17648
+ const home = process.env.HOME ?? "";
17649
+ const gatewayConfig = join(home, ".exe-os", "gateway.json");
17650
+ if (existsSync14(gatewayConfig)) {
17651
+ const raw = JSON.parse(readFileSync11(gatewayConfig, "utf8"));
17652
+ if (raw.agents && raw.agents.length > 0) {
17653
+ setExternals(raw.agents.map((a) => ({
17654
+ name: a.name,
17655
+ role: a.role ?? "external agent",
17656
+ status: "offline"
17657
+ })));
17658
+ }
17209
17659
  }
17660
+ } catch {
17210
17661
  }
17211
- } catch {
17662
+ } catch (err) {
17663
+ setDbError(err instanceof Error ? err.message : "Unknown error");
17664
+ } finally {
17665
+ setLoading(false);
17212
17666
  }
17213
- } catch {
17214
- setDbError(true);
17215
- } finally {
17216
- setLoading(false);
17217
- }
17667
+ });
17218
17668
  }
17219
17669
  const totalCount = members.length + externals.length;
17220
17670
  const selected = allItems[selectedIdx];
@@ -17231,7 +17681,7 @@ function TeamView({ onBack }) {
17231
17681
  /* @__PURE__ */ jsx12(Text, { children: " " }),
17232
17682
  /* @__PURE__ */ jsxs10(Text, { children: [
17233
17683
  "Role: ",
17234
- /* @__PURE__ */ jsx12(Text, { bold: true, children: selected.role })
17684
+ /* @__PURE__ */ jsx12(Text, { bold: true, color: roleBadgeColor(selected.role), children: selected.role })
17235
17685
  ] }),
17236
17686
  /* @__PURE__ */ jsxs10(Text, { children: [
17237
17687
  "Status: ",
@@ -17260,8 +17710,9 @@ function TeamView({ onBack }) {
17260
17710
  /* @__PURE__ */ jsx12(Box_default, { borderStyle: "single", borderColor: "#3D3660", paddingX: 1, alignSelf: "flex-start", children: /* @__PURE__ */ jsx12(Text, { bold: true, children: "Team Roster" }) }),
17261
17711
  /* @__PURE__ */ jsxs10(Text, { color: "#6B4C9A", children: [
17262
17712
  totalCount,
17263
- " agents | up/down navigate | Enter for details"
17713
+ " agents | \u2191\u2193 navigate | Enter details | a add | s sessions"
17264
17714
  ] }),
17715
+ showAddHint && /* @__PURE__ */ jsx12(Text, { color: "#F5D76E", children: "Run /exe-new-employee <name> from CLI to add an employee." }),
17265
17716
  !demo && orch.pendingReviews > 0 && /* @__PURE__ */ jsxs10(Text, { color: "#6B4C9A", children: [
17266
17717
  orch.pendingReviews,
17267
17718
  " review(s) pending exe attention"
@@ -17269,7 +17720,10 @@ function TeamView({ onBack }) {
17269
17720
  /* @__PURE__ */ jsx12(Text, { children: " " }),
17270
17721
  /* @__PURE__ */ jsx12(Text, { bold: true, children: "INTERNAL" }),
17271
17722
  /* @__PURE__ */ jsx12(Text, { children: " " }),
17272
- loading ? /* @__PURE__ */ jsx12(Text, { color: "#6B4C9A", children: " Loading team..." }) : dbError ? /* @__PURE__ */ jsx12(Text, { color: "#EF4444", children: " Database unavailable. Run exe-os setup to initialize." }) : members.length === 0 ? /* @__PURE__ */ jsx12(Text, { color: "#6B4C9A", children: " No employees found. Run /exe-new-employee to create one." }) : /* @__PURE__ */ jsx12(Box_default, { flexDirection: "row", flexWrap: "wrap", gap: 1, children: members.map((m, i) => {
17723
+ loading ? /* @__PURE__ */ jsx12(Text, { color: "#6B4C9A", children: " Loading employee roster..." }) : dbError ? /* @__PURE__ */ jsxs10(Text, { color: "#C91B00", children: [
17724
+ " Failed to load roster: ",
17725
+ dbError
17726
+ ] }) : members.length === 0 ? /* @__PURE__ */ jsx12(Text, { color: "#3D3660", children: " No employees configured. Run /exe-new-employee." }) : /* @__PURE__ */ jsx12(Box_default, { flexDirection: "row", flexWrap: "wrap", gap: 1, children: members.map((m, i) => {
17273
17727
  const isSelected = i === selectedIdx;
17274
17728
  const orchEmp = orchEmployeeMap.get(m.name);
17275
17729
  return /* @__PURE__ */ jsxs10(
@@ -17285,7 +17739,7 @@ function TeamView({ onBack }) {
17285
17739
  /* @__PURE__ */ jsxs10(Box_default, { gap: 1, children: [
17286
17740
  /* @__PURE__ */ jsx12(StatusDot, { status: m.status, showLabel: false }),
17287
17741
  /* @__PURE__ */ jsx12(Text, { bold: true, color: isSelected ? "#F5D76E" : "#F0EDE8", children: m.name }),
17288
- /* @__PURE__ */ jsxs10(Text, { color: isSelected ? "#F5D76E" : "#6B4C9A", children: [
17742
+ /* @__PURE__ */ jsxs10(Text, { color: isSelected ? "#F5D76E" : roleBadgeColor(m.role), children: [
17289
17743
  "(",
17290
17744
  m.role,
17291
17745
  ")"
@@ -17358,6 +17812,33 @@ import TextInput2 from "ink-text-input";
17358
17812
  import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
17359
17813
  var PANELS = ["Workspaces", "Documents", "Chat"];
17360
17814
  var MAX_VISIBLE_MESSAGES = 12;
17815
+ var DEMO_SEARCH_RESULTS = [
17816
+ { id: "1", agentId: "yoshi", rawText: "Reviewed PR #42 \u2014 approved with minor comment on error handling pattern", timestamp: new Date(Date.now() - 2 * 36e5).toISOString(), projectName: "exe-os" },
17817
+ { id: "2", agentId: "tom", rawText: "Implemented session routing with deterministic naming: agent-exeN convention", timestamp: new Date(Date.now() - 5 * 36e5).toISOString(), projectName: "exe-os" },
17818
+ { id: "3", agentId: "exe", rawText: "Status brief: 3 tasks completed, 1 blocked on wiki integration", timestamp: new Date(Date.now() - 8 * 36e5).toISOString(), projectName: "exe-os" },
17819
+ { id: "4", agentId: "mari", rawText: "Created brand guidelines document \u2014 Exe Foundry Bold design system", timestamp: new Date(Date.now() - 24 * 36e5).toISOString(), projectName: "exe-os" },
17820
+ { id: "5", agentId: "tom", rawText: "Fixed CommandCenter project filtering \u2014 DB-first, no random directories", timestamp: new Date(Date.now() - 48 * 36e5).toISOString(), projectName: "exe-os" }
17821
+ ];
17822
+ function agentColor(agentId) {
17823
+ switch (agentId.toLowerCase()) {
17824
+ case "exe":
17825
+ return "#F5D76E";
17826
+ case "yoshi":
17827
+ return "#3B82F6";
17828
+ case "mari":
17829
+ return "#6B4C9A";
17830
+ default:
17831
+ return "#F0EDE8";
17832
+ }
17833
+ }
17834
+ function formatTimeAgo(iso) {
17835
+ const diff2 = Date.now() - new Date(iso).getTime();
17836
+ const hours = Math.floor(diff2 / 36e5);
17837
+ if (hours < 1) return "just now";
17838
+ if (hours < 24) return `${hours}h ago`;
17839
+ const days = Math.floor(hours / 24);
17840
+ return `${days}d ago`;
17841
+ }
17361
17842
  function WikiView({ onBack }) {
17362
17843
  const demo = useDemo();
17363
17844
  const [activePanel, setActivePanel] = useState13("Workspaces");
@@ -17368,6 +17849,12 @@ function WikiView({ onBack }) {
17368
17849
  const [chatHistory, setChatHistory] = useState13([]);
17369
17850
  const [chatInput, setChatInput] = useState13("");
17370
17851
  const [chatFocused, setChatFocused] = useState13(false);
17852
+ const [searchMode, setSearchMode] = useState13(false);
17853
+ const [searchQuery, setSearchQuery] = useState13("");
17854
+ const [searchResults, setSearchResults] = useState13([]);
17855
+ const [searchLoading, setSearchLoading] = useState13(false);
17856
+ const [selectedResultIdx, setSelectedResultIdx] = useState13(0);
17857
+ const [expandedResultIdx, setExpandedResultIdx] = useState13(null);
17371
17858
  const [loading, setLoading] = useState13(true);
17372
17859
  const [connected, setConnected] = useState13(false);
17373
17860
  const [wikiUrl, setWikiUrl] = useState13("");
@@ -17461,6 +17948,55 @@ function WikiView({ onBack }) {
17461
17948
  setSending(false);
17462
17949
  }
17463
17950
  }
17951
+ async function searchMemories2(query) {
17952
+ if (!query.trim()) {
17953
+ setSearchResults([]);
17954
+ return;
17955
+ }
17956
+ if (demo) {
17957
+ const q = query.toLowerCase();
17958
+ setSearchResults(
17959
+ DEMO_SEARCH_RESULTS.filter((r) => r.rawText.toLowerCase().includes(q))
17960
+ );
17961
+ return;
17962
+ }
17963
+ setSearchLoading(true);
17964
+ try {
17965
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
17966
+ const client = getClient2();
17967
+ const result = await client.execute({
17968
+ sql: `SELECT id, agent_id, raw_text, timestamp, project_name
17969
+ FROM memories
17970
+ WHERE raw_text LIKE ? AND COALESCE(status, 'active') = 'active'
17971
+ ORDER BY timestamp DESC LIMIT 20`,
17972
+ args: [`%${query}%`]
17973
+ });
17974
+ setSearchResults(
17975
+ result.rows.map((r) => ({
17976
+ id: String(r.id),
17977
+ agentId: String(r.agent_id),
17978
+ rawText: String(r.raw_text),
17979
+ timestamp: String(r.timestamp),
17980
+ projectName: String(r.project_name ?? "")
17981
+ }))
17982
+ );
17983
+ } catch {
17984
+ setSearchResults([]);
17985
+ } finally {
17986
+ setSearchLoading(false);
17987
+ }
17988
+ }
17989
+ useEffect15(() => {
17990
+ if (!searchMode) return;
17991
+ const timer = setTimeout(() => {
17992
+ searchMemories2(searchQuery);
17993
+ }, 300);
17994
+ return () => clearTimeout(timer);
17995
+ }, [searchQuery, searchMode]);
17996
+ useEffect15(() => {
17997
+ setSelectedResultIdx(0);
17998
+ setExpandedResultIdx(null);
17999
+ }, [searchResults]);
17464
18000
  async function selectWorkspace(idx) {
17465
18001
  setSelectedWorkspaceIdx(idx);
17466
18002
  const ws = workspaces[idx];
@@ -17481,6 +18017,32 @@ function WikiView({ onBack }) {
17481
18017
  }
17482
18018
  }
17483
18019
  use_input_default((input, key) => {
18020
+ if (searchMode && !chatFocused) {
18021
+ if (key.escape) {
18022
+ setSearchMode(false);
18023
+ setSearchQuery("");
18024
+ setSearchResults([]);
18025
+ setExpandedResultIdx(null);
18026
+ return;
18027
+ }
18028
+ if (key.upArrow && selectedResultIdx > 0) {
18029
+ setSelectedResultIdx(selectedResultIdx - 1);
18030
+ setExpandedResultIdx(null);
18031
+ return;
18032
+ }
18033
+ if (key.downArrow && selectedResultIdx < searchResults.length - 1) {
18034
+ setSelectedResultIdx(selectedResultIdx + 1);
18035
+ setExpandedResultIdx(null);
18036
+ return;
18037
+ }
18038
+ if (key.return && searchResults.length > 0) {
18039
+ setExpandedResultIdx(
18040
+ expandedResultIdx === selectedResultIdx ? null : selectedResultIdx
18041
+ );
18042
+ return;
18043
+ }
18044
+ return;
18045
+ }
17484
18046
  if (chatFocused) {
17485
18047
  if (key.escape) {
17486
18048
  setChatFocused(false);
@@ -17492,6 +18054,13 @@ function WikiView({ onBack }) {
17492
18054
  }
17493
18055
  return;
17494
18056
  }
18057
+ if (input === "/" && activePanel !== "Chat") {
18058
+ setSearchMode(true);
18059
+ setSearchQuery("");
18060
+ setSearchResults(demo ? DEMO_SEARCH_RESULTS : []);
18061
+ setExpandedResultIdx(null);
18062
+ return;
18063
+ }
17495
18064
  if (key.leftArrow) {
17496
18065
  const panelIdx = PANELS.indexOf(activePanel);
17497
18066
  if (panelIdx === 0) {
@@ -17569,9 +18138,53 @@ function WikiView({ onBack }) {
17569
18138
  /* @__PURE__ */ jsx13(Text, { bold: true, children: selectedWs.name })
17570
18139
  ] }) : null
17571
18140
  ] }),
17572
- /* @__PURE__ */ jsx13(Text, { color: "#6B4C9A", children: "\u2190\u2192 switch panels | \u2191\u2193 navigate | / chat | Enter select" }),
18141
+ /* @__PURE__ */ jsx13(Text, { color: "#6B4C9A", children: "\u2190\u2192 switch panels | \u2191\u2193 navigate | / search memories | Enter select" }),
17573
18142
  /* @__PURE__ */ jsx13(Text, { children: " " }),
17574
- /* @__PURE__ */ jsxs11(Box_default, { flexGrow: 1, gap: 1, children: [
18143
+ searchMode ? /* @__PURE__ */ jsxs11(Box_default, { flexDirection: "column", flexGrow: 1, children: [
18144
+ /* @__PURE__ */ jsxs11(Box_default, { paddingX: 1, children: [
18145
+ /* @__PURE__ */ jsx13(Text, { color: "#F5D76E", children: "Search: " }),
18146
+ /* @__PURE__ */ jsx13(
18147
+ TextInput2,
18148
+ {
18149
+ value: searchQuery,
18150
+ onChange: setSearchQuery,
18151
+ placeholder: "search memories...",
18152
+ focus: true
18153
+ }
18154
+ ),
18155
+ /* @__PURE__ */ jsx13(Text, { color: "#6B4C9A", children: " (esc to close)" })
18156
+ ] }),
18157
+ /* @__PURE__ */ jsx13(Text, { children: " " }),
18158
+ searchLoading ? /* @__PURE__ */ jsx13(Text, { color: "#6B4C9A", children: " Searching..." }) : searchResults.length === 0 && searchQuery.trim() ? /* @__PURE__ */ jsx13(Text, { color: "#6B4C9A", children: " No results found." }) : searchResults.length === 0 ? /* @__PURE__ */ jsx13(Text, { color: "#6B4C9A", children: " Type to search exe-os memories." }) : searchResults.map((result, i) => {
18159
+ const isSelected = i === selectedResultIdx;
18160
+ const isExpanded = i === expandedResultIdx;
18161
+ const snippet = result.rawText.slice(0, 80);
18162
+ return /* @__PURE__ */ jsxs11(Box_default, { flexDirection: "column", children: [
18163
+ /* @__PURE__ */ jsxs11(
18164
+ Text,
18165
+ {
18166
+ backgroundColor: isSelected ? "#6B4C9A" : void 0,
18167
+ color: isSelected ? "#F5D76E" : void 0,
18168
+ children: [
18169
+ " ",
18170
+ /* @__PURE__ */ jsx13(Text, { color: isSelected ? "#F5D76E" : agentColor(result.agentId), bold: true, children: result.agentId.padEnd(8) }),
18171
+ /* @__PURE__ */ jsxs11(Text, { color: isSelected ? "#F5D76E" : "#F0EDE8", children: [
18172
+ snippet,
18173
+ result.rawText.length > 80 ? "..." : ""
18174
+ ] }),
18175
+ " ",
18176
+ /* @__PURE__ */ jsx13(Text, { color: isSelected ? "#F5D76E" : "#3D3660", dimColor: !isSelected, children: formatTimeAgo(result.timestamp) })
18177
+ ]
18178
+ }
18179
+ ),
18180
+ isExpanded ? /* @__PURE__ */ jsx13(Box_default, { paddingX: 4, marginBottom: 1, children: /* @__PURE__ */ jsx13(Text, { color: "#F0EDE8", wrap: "wrap", children: result.rawText }) }) : null
18181
+ ] }, result.id);
18182
+ }),
18183
+ searchResults.length > 0 ? /* @__PURE__ */ jsxs11(Fragment4, { children: [
18184
+ /* @__PURE__ */ jsx13(Text, { children: " " }),
18185
+ /* @__PURE__ */ jsx13(Text, { color: "#3D3660", children: " \u2191\u2193 navigate | Enter expand | Esc close" })
18186
+ ] }) : null
18187
+ ] }) : /* @__PURE__ */ jsxs11(Box_default, { flexGrow: 1, gap: 1, children: [
17575
18188
  /* @__PURE__ */ jsxs11(Box_default, { flexDirection: "column", width: "25%", children: [
17576
18189
  /* @__PURE__ */ jsxs11(Text, { bold: true, backgroundColor: panelHeaderBg("Workspaces"), color: panelHeaderColor("Workspaces"), children: [
17577
18190
  activePanel === "Workspaces" ? "\u25B8 " : " ",
@@ -17655,50 +18268,41 @@ function WikiView({ onBack }) {
17655
18268
  }
17656
18269
 
17657
18270
  // src/tui/views/Settings.tsx
17658
- import React24, { useState as useState14, useEffect as useEffect16 } from "react";
18271
+ import React24, { useState as useState14, useEffect as useEffect16, useCallback as useCallback7 } from "react";
17659
18272
  import { Fragment as Fragment5, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
17660
- var SECTION_NAMES = ["Providers", "Memory", "Runtime", "Employee Models", "Integrations"];
18273
+ function maskSecret(value) {
18274
+ if (value.length <= 10) return "****";
18275
+ return `${value.slice(0, 6)}...${value.slice(-4)}`;
18276
+ }
18277
+ var SECTION_NAMES = ["Providers", "Cloud Sync", "License", "System", "Gateway"];
18278
+ var GREEN = "#22C55E";
18279
+ var DIM = "#6B4C9A";
18280
+ var YELLOW = "#F5D76E";
17661
18281
  function SettingsView({ onBack }) {
17662
18282
  const [providers, setProviders] = useState14([]);
17663
- const [memory, setMemory] = useState14({
17664
- encryption: "checking...",
17665
- embeddingModel: "checking...",
17666
- cloudSync: "checking...",
17667
- searchMode: "checking..."
17668
- });
17669
- const [runtime, setRuntime] = useState14({
17670
- consolidation: true,
17671
- consolidationModel: "...",
17672
- skillLearning: true,
17673
- skillThreshold: 3,
17674
- skillModel: "...",
17675
- failoverChain: ["anthropic", "opencode", "gemini", "openai"]
17676
- });
17677
- const [employeeModels, setEmployeeModels] = useState14([]);
18283
+ const [cloud, setCloud] = useState14({ configured: false, endpoint: "", apiKey: "" });
18284
+ const [license, setLicense] = useState14({ valid: false, plan: "checking...", expiresAt: null, deviceLimit: 0, employeeLimit: 0, memoryLimit: 0 });
18285
+ const [system, setSystem] = useState14({ daemon: "unknown", version: "...", dbPath: "...", embeddingModel: "...", encryption: "checking..." });
18286
+ const [gateway, setGateway] = useState14({ adapters: [] });
17678
18287
  const [selectedSection, setSelectedSection] = useState14(0);
17679
18288
  const [loading, setLoading] = useState14(true);
17680
- const [claudeCode, setClaudeCode] = useState14({ installed: false, hooksWired: false });
17681
- const [license, setLicense] = useState14({ valid: false, detail: "checking..." });
17682
- const [cloudSync, setCloudSync] = useState14({ configured: false, detail: "checking..." });
17683
- useEffect16(() => {
17684
- loadSettings().finally(() => setLoading(false));
17685
- }, []);
17686
- use_input_default((_input, key) => {
17687
- if (key.leftArrow) {
17688
- onBack?.();
17689
- return;
17690
- }
17691
- if (key.upArrow && selectedSection > 0) setSelectedSection(selectedSection - 1);
17692
- if (key.downArrow && selectedSection < SECTION_NAMES.length - 1) setSelectedSection(selectedSection + 1);
17693
- });
17694
- async function loadSettings() {
17695
- const providerList = [
17696
- { name: "Anthropic", configured: !!process.env.ANTHROPIC_API_KEY, detail: process.env.ANTHROPIC_API_KEY ? "ANTHROPIC_API_KEY set" : "not configured" },
17697
- { name: "OpenCode", configured: !!process.env.OPENCODE_API_KEY, detail: process.env.OPENCODE_API_KEY ? "OPENCODE_API_KEY set" : "not configured" },
17698
- { name: "Gemini", configured: !!process.env.GEMINI_API_KEY, detail: process.env.GEMINI_API_KEY ? "GEMINI_API_KEY set" : "not configured" },
17699
- { name: "OpenAI", configured: !!process.env.OPENAI_API_KEY, detail: process.env.OPENAI_API_KEY ? "OPENAI_API_KEY set" : "not configured" },
17700
- { name: "Chutes", configured: !!process.env.CHUTES_API_KEY, detail: process.env.CHUTES_API_KEY ? "CHUTES_API_KEY set" : "not configured" }
18289
+ const loadSettings = useCallback7(async () => {
18290
+ setLoading(true);
18291
+ const envKeys = [
18292
+ ["Anthropic", "ANTHROPIC_API_KEY"],
18293
+ ["OpenCode", "OPENCODE_API_KEY"],
18294
+ ["Gemini", "GEMINI_API_KEY"],
18295
+ ["OpenAI", "OPENAI_API_KEY"],
18296
+ ["Chutes", "CHUTES_API_KEY"]
17701
18297
  ];
18298
+ const providerList = envKeys.map(([name, envVar]) => {
18299
+ const val = process.env[envVar];
18300
+ return {
18301
+ name,
18302
+ configured: !!val,
18303
+ detail: val ? maskSecret(val) : "not configured"
18304
+ };
18305
+ });
17702
18306
  try {
17703
18307
  const { execSync: execSync10 } = await import("child_process");
17704
18308
  execSync10("curl -s --max-time 1 http://localhost:11434/api/tags", { timeout: 2e3 });
@@ -17710,112 +18314,101 @@ function SettingsView({ onBack }) {
17710
18314
  try {
17711
18315
  const { existsSync: existsSync14 } = await import("fs");
17712
18316
  const { join } = await import("path");
17713
- const home = process.env.HOME ?? "";
17714
- const hasKey = existsSync14(join(home, ".exe-os", "master.key"));
17715
18317
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
17716
18318
  const cfg = await loadConfig2();
17717
- setMemory({
17718
- encryption: hasKey ? "SQLCipher AES-256" : "not configured",
17719
- embeddingModel: cfg.modelFile ?? "unknown",
17720
- cloudSync: cfg.cloud ? "enabled" : "disabled",
17721
- searchMode: cfg.searchMode ?? "hybrid"
17722
- });
17723
- const rawCfg = cfg;
17724
- const chain = Array.isArray(rawCfg.failoverChain) ? rawCfg.failoverChain : ["anthropic", "opencode", "gemini", "openai"];
17725
- setRuntime({
17726
- consolidation: cfg.consolidationEnabled,
17727
- consolidationModel: cfg.consolidationModel,
17728
- skillLearning: cfg.skillLearning,
17729
- skillThreshold: cfg.skillThreshold,
17730
- skillModel: cfg.skillModel,
17731
- failoverChain: chain
17732
- });
17733
- } catch {
17734
- }
17735
- try {
17736
- const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
17737
- const roster = await loadEmployees2();
17738
- const { existsSync: existsSync14, readFileSync: readFileSync11 } = await import("fs");
17739
- const { join } = await import("path");
17740
18319
  const home = process.env.HOME ?? "";
17741
- const configPath = join(home, ".exe-os", "config.json");
17742
- let empConfig = {};
17743
- if (existsSync14(configPath)) {
17744
- try {
17745
- const raw = JSON.parse(readFileSync11(configPath, "utf8"));
17746
- if (raw.employees && typeof raw.employees === "object") {
17747
- empConfig = raw.employees;
17748
- }
17749
- } catch {
17750
- }
18320
+ const hasKey = existsSync14(join(home, ".exe-os", "master.key"));
18321
+ if (cfg.cloud) {
18322
+ setCloud({
18323
+ configured: true,
18324
+ endpoint: cfg.cloud.endpoint,
18325
+ apiKey: maskSecret(cfg.cloud.apiKey)
18326
+ });
18327
+ } else {
18328
+ setCloud({ configured: false, endpoint: "", apiKey: "" });
17751
18329
  }
17752
- setEmployeeModels(roster.filter((e) => e.name !== "exe").map((e) => ({
17753
- name: e.name,
17754
- model: empConfig[e.name]?.model ?? "claude-sonnet-4-6",
17755
- provider: empConfig[e.name]?.provider ?? "anthropic"
17756
- })));
17757
- } catch {
17758
- }
17759
- try {
17760
- const { existsSync: existsSync14, readFileSync: readFileSync11 } = await import("fs");
17761
- const { join } = await import("path");
17762
- const home = process.env.HOME ?? "";
17763
- const ccSettingsPath = join(home, ".claude", "settings.json");
17764
- const installed = existsSync14(ccSettingsPath);
17765
- let hooksWired = false;
17766
- if (installed) {
17767
- try {
17768
- const settings = JSON.parse(readFileSync11(ccSettingsPath, "utf8"));
17769
- const hooks = settings.hooks;
17770
- if (hooks) {
17771
- hooksWired = Object.values(hooks).flat().some((h) => h.command?.includes("exe-os") || h.command?.includes("exe-mem"));
17772
- }
17773
- } catch {
17774
- }
18330
+ const pidPath = join(home, ".exe-os", "exed.pid");
18331
+ let daemon = "unknown";
18332
+ try {
18333
+ daemon = existsSync14(pidPath) ? "running" : "stopped";
18334
+ } catch {
17775
18335
  }
17776
- setClaudeCode({ installed, hooksWired });
17777
- } catch {
17778
- }
17779
- try {
17780
- const { existsSync: existsSync14, readFileSync: readFileSync11 } = await import("fs");
17781
- const { join } = await import("path");
17782
- const home = process.env.HOME ?? "";
17783
- const licensePath = join(home, ".exe-os", "license.json");
17784
- if (existsSync14(licensePath)) {
18336
+ let version = "unknown";
18337
+ try {
18338
+ const { readFileSync: readFileSync11 } = await import("fs");
18339
+ const { createRequire } = await import("module");
18340
+ const require2 = createRequire(import.meta.url);
18341
+ const pkgPath = require2.resolve("@askexenow/exe-os/package.json");
18342
+ const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
18343
+ version = pkg.version;
18344
+ } catch {
17785
18345
  try {
17786
- const lic = JSON.parse(readFileSync11(licensePath, "utf8"));
17787
- setLicense({ valid: lic.valid !== false, detail: lic.plan ?? "licensed" });
18346
+ const { readFileSync: readFileSync11 } = await import("fs");
18347
+ const { join: joinPath } = await import("path");
18348
+ const pkg = JSON.parse(readFileSync11(joinPath(process.cwd(), "package.json"), "utf8"));
18349
+ version = pkg.version;
17788
18350
  } catch {
17789
- setLicense({ valid: false, detail: "invalid license file" });
17790
18351
  }
17791
- } else {
17792
- setLicense({ valid: false, detail: "no license" });
17793
18352
  }
18353
+ setSystem({
18354
+ daemon,
18355
+ version,
18356
+ dbPath: cfg.dbPath,
18357
+ embeddingModel: cfg.modelFile ?? "unknown",
18358
+ encryption: hasKey ? "SQLCipher AES-256" : "not configured"
18359
+ });
17794
18360
  } catch {
17795
18361
  }
17796
18362
  try {
17797
- const { existsSync: existsSync14 } = await import("fs");
17798
- const { join } = await import("path");
17799
- const home = process.env.HOME ?? "";
17800
- const cloudPath = join(home, ".exe-os", "cloud.json");
17801
- if (existsSync14(cloudPath)) {
17802
- setCloudSync({ configured: true, detail: "configured" });
17803
- } else {
17804
- setCloudSync({ configured: false, detail: "not configured" });
17805
- }
18363
+ const { checkLicense: checkLicense2 } = await Promise.resolve().then(() => (init_license(), license_exports));
18364
+ const lic = await checkLicense2();
18365
+ setLicense({
18366
+ valid: lic.valid,
18367
+ plan: lic.plan.toUpperCase(),
18368
+ expiresAt: lic.expiresAt,
18369
+ deviceLimit: lic.deviceLimit,
18370
+ employeeLimit: lic.employeeLimit,
18371
+ memoryLimit: lic.memoryLimit
18372
+ });
17806
18373
  } catch {
18374
+ setLicense({ valid: false, plan: "FREE", expiresAt: null, deviceLimit: 1, employeeLimit: 1, memoryLimit: 5e3 });
17807
18375
  }
17808
- }
17809
- const statusColor = (ok) => ok ? "#22C55E" : "#6B4C9A";
17810
- const sectionColor = (idx) => selectedSection === idx ? "#F5D76E" : void 0;
17811
- const sectionBg = (idx) => selectedSection === idx ? "#6B4C9A" : void 0;
18376
+ const gatewayAdapters = [
18377
+ { name: "WhatsApp", configured: !!process.env.WHATSAPP_API_TOKEN || !!process.env.WHATSAPP_PHONE_NUMBER_ID },
18378
+ { name: "Telegram", configured: !!process.env.TELEGRAM_BOT_TOKEN },
18379
+ { name: "Discord", configured: !!process.env.DISCORD_BOT_TOKEN },
18380
+ { name: "Slack", configured: !!process.env.SLACK_BOT_TOKEN }
18381
+ ];
18382
+ setGateway({ adapters: gatewayAdapters });
18383
+ setLoading(false);
18384
+ }, []);
18385
+ useEffect16(() => {
18386
+ loadSettings();
18387
+ }, [loadSettings]);
18388
+ use_input_default((input, key) => {
18389
+ if (key.leftArrow) {
18390
+ onBack?.();
18391
+ return;
18392
+ }
18393
+ if (key.upArrow && selectedSection > 0) setSelectedSection(selectedSection - 1);
18394
+ if (key.downArrow && selectedSection < SECTION_NAMES.length - 1) setSelectedSection(selectedSection + 1);
18395
+ if (input === "r") {
18396
+ loadSettings();
18397
+ }
18398
+ });
18399
+ const statusDot = (ok) => ok ? "\u2022" : "\u2022";
18400
+ const statusColor = (ok) => ok ? GREEN : DIM;
18401
+ const sectionColor = (idx) => selectedSection === idx ? YELLOW : void 0;
18402
+ const sectionBg = (idx) => selectedSection === idx ? DIM : void 0;
17812
18403
  const sectionMarker = (idx) => selectedSection === idx ? "\u25B8 " : " ";
18404
+ const anyGateway = gateway.adapters.some((a) => a.configured);
18405
+ const formatLimit = (n) => n === -1 ? "unlimited" : n.toLocaleString();
17813
18406
  return /* @__PURE__ */ jsxs12(Box_default, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: [
17814
18407
  /* @__PURE__ */ jsx14(Box_default, { borderStyle: "single", borderColor: "#3D3660", paddingX: 1, alignSelf: "flex-start", children: /* @__PURE__ */ jsx14(Text, { bold: true, children: "Settings" }) }),
17815
- /* @__PURE__ */ jsx14(Text, { color: "#6B4C9A", children: "\u2191\u2193 navigate sections" }),
18408
+ /* @__PURE__ */ jsx14(Text, { color: DIM, children: "\u2191\u2193 navigate sections r refresh" }),
17816
18409
  /* @__PURE__ */ jsx14(Text, { children: " " }),
17817
18410
  loading && /* @__PURE__ */ jsxs12(Fragment5, { children: [
17818
- /* @__PURE__ */ jsx14(Text, { color: "#6B4C9A", children: "Loading settings..." }),
18411
+ /* @__PURE__ */ jsx14(Text, { color: DIM, children: "Loading settings..." }),
17819
18412
  /* @__PURE__ */ jsx14(Text, { children: " " })
17820
18413
  ] }),
17821
18414
  /* @__PURE__ */ jsxs12(Text, { bold: true, backgroundColor: sectionBg(0), color: sectionColor(0), children: [
@@ -17825,6 +18418,8 @@ function SettingsView({ onBack }) {
17825
18418
  /* @__PURE__ */ jsx14(Text, { children: " " }),
17826
18419
  providers.map((p) => /* @__PURE__ */ jsxs12(Text, { children: [
17827
18420
  " ",
18421
+ /* @__PURE__ */ jsx14(Text, { color: statusColor(p.configured), children: statusDot(p.configured) }),
18422
+ " ",
17828
18423
  p.name,
17829
18424
  ": ",
17830
18425
  /* @__PURE__ */ jsx14(Text, { color: statusColor(p.configured), children: p.detail })
@@ -17832,109 +18427,301 @@ function SettingsView({ onBack }) {
17832
18427
  /* @__PURE__ */ jsx14(Text, { children: " " }),
17833
18428
  /* @__PURE__ */ jsxs12(Text, { bold: true, backgroundColor: sectionBg(1), color: sectionColor(1), children: [
17834
18429
  sectionMarker(1),
17835
- "Memory"
18430
+ "Cloud Sync"
17836
18431
  ] }),
17837
18432
  /* @__PURE__ */ jsx14(Text, { children: " " }),
17838
- /* @__PURE__ */ jsxs12(Text, { children: [
18433
+ cloud.configured ? /* @__PURE__ */ jsxs12(Fragment5, { children: [
18434
+ /* @__PURE__ */ jsxs12(Text, { children: [
18435
+ " ",
18436
+ /* @__PURE__ */ jsx14(Text, { color: GREEN, children: statusDot(true) }),
18437
+ " Status: ",
18438
+ /* @__PURE__ */ jsx14(Text, { color: GREEN, children: "connected" })
18439
+ ] }),
18440
+ /* @__PURE__ */ jsxs12(Text, { children: [
18441
+ " ",
18442
+ "Endpoint: ",
18443
+ /* @__PURE__ */ jsx14(Text, { color: GREEN, children: cloud.endpoint })
18444
+ ] }),
18445
+ /* @__PURE__ */ jsxs12(Text, { children: [
18446
+ " ",
18447
+ "API key: ",
18448
+ /* @__PURE__ */ jsx14(Text, { color: DIM, children: cloud.apiKey })
18449
+ ] })
18450
+ ] }) : /* @__PURE__ */ jsxs12(Text, { children: [
17839
18451
  " ",
17840
- "Encryption: ",
17841
- /* @__PURE__ */ jsx14(Text, { color: memory.encryption.includes("AES") ? "#22C55E" : "#6B4C9A", children: memory.encryption })
18452
+ /* @__PURE__ */ jsx14(Text, { color: DIM, children: statusDot(false) }),
18453
+ " ",
18454
+ /* @__PURE__ */ jsx14(Text, { color: DIM, children: "Not configured \u2014 run /exe-cloud" })
17842
18455
  ] }),
17843
- /* @__PURE__ */ jsxs12(Text, { children: [
17844
- " ",
17845
- "Embedding: ",
17846
- /* @__PURE__ */ jsx14(Text, { color: "#22C55E", children: memory.embeddingModel })
18456
+ /* @__PURE__ */ jsx14(Text, { children: " " }),
18457
+ /* @__PURE__ */ jsxs12(Text, { bold: true, backgroundColor: sectionBg(2), color: sectionColor(2), children: [
18458
+ sectionMarker(2),
18459
+ "License"
17847
18460
  ] }),
18461
+ /* @__PURE__ */ jsx14(Text, { children: " " }),
17848
18462
  /* @__PURE__ */ jsxs12(Text, { children: [
17849
18463
  " ",
17850
- "Cloud sync: ",
17851
- /* @__PURE__ */ jsx14(Text, { color: memory.cloudSync === "enabled" ? "#22C55E" : "#6B4C9A", children: memory.cloudSync })
18464
+ "Plan: ",
18465
+ /* @__PURE__ */ jsx14(Text, { color: license.plan === "FREE" ? YELLOW : GREEN, children: license.plan })
17852
18466
  ] }),
17853
- /* @__PURE__ */ jsxs12(Text, { children: [
18467
+ license.expiresAt && /* @__PURE__ */ jsxs12(Text, { children: [
17854
18468
  " ",
17855
- "Search mode: ",
17856
- /* @__PURE__ */ jsx14(Text, { color: "#22C55E", children: memory.searchMode })
17857
- ] }),
17858
- /* @__PURE__ */ jsx14(Text, { children: " " }),
17859
- /* @__PURE__ */ jsxs12(Text, { bold: true, backgroundColor: sectionBg(2), color: sectionColor(2), children: [
17860
- sectionMarker(2),
17861
- "Runtime"
18469
+ "Expires: ",
18470
+ /* @__PURE__ */ jsx14(Text, { color: GREEN, children: license.expiresAt.split("T")[0] })
17862
18471
  ] }),
17863
- /* @__PURE__ */ jsx14(Text, { children: " " }),
17864
18472
  /* @__PURE__ */ jsxs12(Text, { children: [
17865
18473
  " ",
17866
- "Consolidation: ",
17867
- /* @__PURE__ */ jsx14(Text, { color: runtime.consolidation ? "#22C55E" : "#6B4C9A", children: runtime.consolidation ? "enabled" : "disabled" }),
17868
- " (",
17869
- runtime.consolidationModel,
17870
- ")"
18474
+ "Devices: ",
18475
+ /* @__PURE__ */ jsx14(Text, { color: GREEN, children: formatLimit(license.deviceLimit) })
17871
18476
  ] }),
17872
18477
  /* @__PURE__ */ jsxs12(Text, { children: [
17873
18478
  " ",
17874
- "Skill learning: ",
17875
- /* @__PURE__ */ jsx14(Text, { color: runtime.skillLearning ? "#22C55E" : "#6B4C9A", children: runtime.skillLearning ? "enabled" : "disabled" }),
17876
- " (threshold: ",
17877
- runtime.skillThreshold,
17878
- ")"
18479
+ "Employees: ",
18480
+ /* @__PURE__ */ jsx14(Text, { color: GREEN, children: formatLimit(license.employeeLimit) })
17879
18481
  ] }),
17880
18482
  /* @__PURE__ */ jsxs12(Text, { children: [
17881
18483
  " ",
17882
- "Failover chain: ",
17883
- /* @__PURE__ */ jsx14(Text, { color: "#22C55E", children: runtime.failoverChain.join(" \u2192 ") })
18484
+ "Memory limit: ",
18485
+ /* @__PURE__ */ jsx14(Text, { color: GREEN, children: formatLimit(license.memoryLimit) })
17884
18486
  ] }),
17885
18487
  /* @__PURE__ */ jsx14(Text, { children: " " }),
17886
18488
  /* @__PURE__ */ jsxs12(Text, { bold: true, backgroundColor: sectionBg(3), color: sectionColor(3), children: [
17887
18489
  sectionMarker(3),
17888
- "Employee Models"
18490
+ "System"
17889
18491
  ] }),
17890
18492
  /* @__PURE__ */ jsx14(Text, { children: " " }),
17891
- loading ? /* @__PURE__ */ jsxs12(Text, { color: "#6B4C9A", children: [
17892
- " ",
17893
- "Loading..."
17894
- ] }) : employeeModels.length === 0 ? /* @__PURE__ */ jsxs12(Text, { color: "#6B4C9A", children: [
18493
+ /* @__PURE__ */ jsxs12(Text, { children: [
17895
18494
  " ",
17896
- "No employees configured"
17897
- ] }) : employeeModels.map((e) => /* @__PURE__ */ jsxs12(Text, { children: [
18495
+ /* @__PURE__ */ jsx14(Text, { color: system.daemon === "running" ? GREEN : DIM, children: statusDot(system.daemon === "running") }),
18496
+ " Daemon: ",
18497
+ /* @__PURE__ */ jsx14(Text, { color: system.daemon === "running" ? GREEN : DIM, children: system.daemon })
18498
+ ] }),
18499
+ /* @__PURE__ */ jsxs12(Text, { children: [
17898
18500
  " ",
17899
- e.name,
17900
- ": ",
17901
- /* @__PURE__ */ jsx14(Text, { color: "#22C55E", children: e.model }),
17902
- " ",
17903
- /* @__PURE__ */ jsxs12(Text, { color: "#6B4C9A", children: [
17904
- "via ",
17905
- e.provider
17906
- ] })
17907
- ] }, e.name)),
17908
- /* @__PURE__ */ jsx14(Text, { children: " " }),
17909
- /* @__PURE__ */ jsxs12(Text, { bold: true, backgroundColor: sectionBg(4), color: sectionColor(4), children: [
17910
- sectionMarker(4),
17911
- "Integrations"
18501
+ "Version: ",
18502
+ /* @__PURE__ */ jsx14(Text, { color: GREEN, children: system.version })
17912
18503
  ] }),
17913
- /* @__PURE__ */ jsx14(Text, { children: " " }),
17914
18504
  /* @__PURE__ */ jsxs12(Text, { children: [
17915
18505
  " ",
17916
- "Claude Code: ",
17917
- /* @__PURE__ */ jsx14(Text, { color: claudeCode.installed ? "#22C55E" : "#6B4C9A", children: claudeCode.installed ? "installed" : "not found" }),
17918
- claudeCode.installed && /* @__PURE__ */ jsxs12(Text, { color: claudeCode.hooksWired ? "#22C55E" : "#6B4C9A", children: [
17919
- " \u2014 hooks ",
17920
- claudeCode.hooksWired ? "wired" : "not wired"
17921
- ] })
18506
+ "Encryption: ",
18507
+ /* @__PURE__ */ jsx14(Text, { color: system.encryption.includes("AES") ? GREEN : DIM, children: system.encryption })
17922
18508
  ] }),
17923
18509
  /* @__PURE__ */ jsxs12(Text, { children: [
17924
18510
  " ",
17925
- "License: ",
17926
- /* @__PURE__ */ jsx14(Text, { color: license.valid ? "#22C55E" : "#6B4C9A", children: license.detail })
18511
+ "Embedding: ",
18512
+ /* @__PURE__ */ jsx14(Text, { color: GREEN, children: system.embeddingModel })
17927
18513
  ] }),
17928
18514
  /* @__PURE__ */ jsxs12(Text, { children: [
17929
18515
  " ",
17930
- "Cloud sync: ",
17931
- /* @__PURE__ */ jsx14(Text, { color: cloudSync.configured ? "#22C55E" : "#6B4C9A", children: cloudSync.detail })
18516
+ "DB path: ",
18517
+ /* @__PURE__ */ jsx14(Text, { color: DIM, children: system.dbPath })
18518
+ ] }),
18519
+ /* @__PURE__ */ jsx14(Text, { children: " " }),
18520
+ /* @__PURE__ */ jsxs12(Text, { bold: true, backgroundColor: sectionBg(4), color: sectionColor(4), children: [
18521
+ sectionMarker(4),
18522
+ "Gateway"
18523
+ ] }),
18524
+ /* @__PURE__ */ jsx14(Text, { children: " " }),
18525
+ anyGateway ? gateway.adapters.map((a) => /* @__PURE__ */ jsxs12(Text, { children: [
18526
+ " ",
18527
+ /* @__PURE__ */ jsx14(Text, { color: statusColor(a.configured), children: statusDot(a.configured) }),
18528
+ " ",
18529
+ a.name,
18530
+ ": ",
18531
+ /* @__PURE__ */ jsx14(Text, { color: statusColor(a.configured), children: a.configured ? "configured" : "not configured" })
18532
+ ] }, a.name)) : /* @__PURE__ */ jsxs12(Text, { children: [
18533
+ " ",
18534
+ /* @__PURE__ */ jsx14(Text, { color: DIM, children: statusDot(false) }),
18535
+ " ",
18536
+ /* @__PURE__ */ jsx14(Text, { color: DIM, children: "No gateway configured" })
17932
18537
  ] })
17933
18538
  ] });
17934
18539
  }
17935
18540
 
17936
- // src/tui/App.tsx
18541
+ // src/tui/views/DebugPanel.tsx
18542
+ import React25, { useState as useState15, useEffect as useEffect17 } from "react";
18543
+ init_state_bus();
17937
18544
  import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
18545
+ var MAX_EVENTS = 50;
18546
+ var DISPLAY_EVENTS = 20;
18547
+ var EVENT_COLORS = {
18548
+ task_completed: "#22C55E",
18549
+ review_created: "#F59E0B",
18550
+ employee_status: "#3B82F6",
18551
+ memory_stored: "#8B5CF6",
18552
+ session_started: "#06B6D4",
18553
+ session_ended: "#EF4444",
18554
+ gateway_message: "#EC4899"
18555
+ };
18556
+ function formatEvent(e) {
18557
+ switch (e.type) {
18558
+ case "task_completed":
18559
+ return `${e.employee}: "${e.result.slice(0, 60)}"`;
18560
+ case "review_created":
18561
+ return `${e.employee} \u2192 ${e.reviewer}`;
18562
+ case "employee_status":
18563
+ return `${e.employee}: ${e.status}`;
18564
+ case "memory_stored":
18565
+ return `${e.agentId} [${e.project}]`;
18566
+ case "session_started":
18567
+ return `${e.employee} (${e.sessionId.slice(0, 8)})`;
18568
+ case "session_ended":
18569
+ return `${e.employee} (${e.sessionId.slice(0, 8)})`;
18570
+ case "gateway_message":
18571
+ return `${e.platform}/${e.senderId} \u2192 ${e.botId}`;
18572
+ }
18573
+ }
18574
+ function DebugPanel() {
18575
+ const [events, setEvents] = useState15([]);
18576
+ useEffect17(() => {
18577
+ let counter = 0;
18578
+ const handler = (event) => {
18579
+ setEvents((prev) => {
18580
+ const next = [...prev, { event, id: counter++ }];
18581
+ return next.slice(-MAX_EVENTS);
18582
+ });
18583
+ };
18584
+ orgBus.onAny(handler);
18585
+ return () => {
18586
+ orgBus.offAny(handler);
18587
+ };
18588
+ }, []);
18589
+ return /* @__PURE__ */ jsxs13(Box_default, { flexDirection: "column", borderStyle: "single", borderColor: "#6B4C9A", flexGrow: 1, paddingX: 1, children: [
18590
+ /* @__PURE__ */ jsx15(Text, { bold: true, color: "#F5D76E", children: "Debug \u2014 Live Events" }),
18591
+ /* @__PURE__ */ jsx15(Text, { color: "#3D3660", children: "\u2500".repeat(40) }),
18592
+ events.length === 0 ? /* @__PURE__ */ jsx15(Text, { color: "#3D3660", children: "Waiting for events..." }) : events.slice(-DISPLAY_EVENTS).map(({ event, id }) => {
18593
+ const time = event.timestamp?.slice(11, 19) ?? "??:??:??";
18594
+ const color = EVENT_COLORS[event.type] ?? "#6B4C9A";
18595
+ const summary = formatEvent(event);
18596
+ return /* @__PURE__ */ jsxs13(Text, { color, children: [
18597
+ "[",
18598
+ time,
18599
+ "] ",
18600
+ event.type,
18601
+ " \u2014 ",
18602
+ summary
18603
+ ] }, id);
18604
+ })
18605
+ ] });
18606
+ }
18607
+
18608
+ // src/tui/components/HelpOverlay.tsx
18609
+ import React26 from "react";
18610
+ import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
18611
+ function HelpOverlay({ tabName, shortcuts }) {
18612
+ return /* @__PURE__ */ jsxs14(
18613
+ Box_default,
18614
+ {
18615
+ flexDirection: "column",
18616
+ borderStyle: "double",
18617
+ borderColor: "#6B4C9A",
18618
+ paddingX: 2,
18619
+ paddingY: 1,
18620
+ width: 50,
18621
+ alignSelf: "center",
18622
+ children: [
18623
+ /* @__PURE__ */ jsxs14(Text, { bold: true, color: "#F5D76E", children: [
18624
+ "Keyboard Shortcuts \u2014 ",
18625
+ tabName
18626
+ ] }),
18627
+ /* @__PURE__ */ jsx16(Text, { children: " " }),
18628
+ shortcuts.map((s, i) => /* @__PURE__ */ jsxs14(Box_default, { gap: 1, children: [
18629
+ /* @__PURE__ */ jsx16(Text, { color: "#F5D76E", bold: true, children: s.key.padEnd(10) }),
18630
+ /* @__PURE__ */ jsx16(Text, { color: "#F0EDE8", children: s.description })
18631
+ ] }, i)),
18632
+ /* @__PURE__ */ jsx16(Text, { children: " " }),
18633
+ /* @__PURE__ */ jsx16(Text, { bold: true, color: "#F5D76E", children: "Global" }),
18634
+ /* @__PURE__ */ jsx16(Text, { children: " " }),
18635
+ /* @__PURE__ */ jsxs14(Box_default, { gap: 1, children: [
18636
+ /* @__PURE__ */ jsx16(Text, { color: "#F5D76E", bold: true, children: "1-7".padEnd(10) }),
18637
+ /* @__PURE__ */ jsx16(Text, { color: "#F0EDE8", children: "Switch tabs" })
18638
+ ] }),
18639
+ /* @__PURE__ */ jsxs14(Box_default, { gap: 1, children: [
18640
+ /* @__PURE__ */ jsx16(Text, { color: "#F5D76E", bold: true, children: "q".padEnd(10) }),
18641
+ /* @__PURE__ */ jsx16(Text, { color: "#F0EDE8", children: "Quit" })
18642
+ ] }),
18643
+ /* @__PURE__ */ jsxs14(Box_default, { gap: 1, children: [
18644
+ /* @__PURE__ */ jsx16(Text, { color: "#F5D76E", bold: true, children: "?".padEnd(10) }),
18645
+ /* @__PURE__ */ jsx16(Text, { color: "#F0EDE8", children: "Toggle this help" })
18646
+ ] }),
18647
+ /* @__PURE__ */ jsxs14(Box_default, { gap: 1, children: [
18648
+ /* @__PURE__ */ jsx16(Text, { color: "#F5D76E", bold: true, children: "Esc".padEnd(10) }),
18649
+ /* @__PURE__ */ jsx16(Text, { color: "#F0EDE8", children: "Back to sidebar" })
18650
+ ] }),
18651
+ /* @__PURE__ */ jsx16(Text, { children: " " }),
18652
+ /* @__PURE__ */ jsx16(Text, { color: "#6B4C9A", children: "Press ? to close" })
18653
+ ]
18654
+ }
18655
+ );
18656
+ }
18657
+
18658
+ // src/tui/App.tsx
18659
+ import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
18660
+ var TAB_SHORTCUTS = {
18661
+ "command-center": {
18662
+ name: "Command Center",
18663
+ shortcuts: [
18664
+ { key: "\u2191\u2193", description: "Navigate projects" },
18665
+ { key: "Enter", description: "Select project" },
18666
+ { key: "/", description: "Enter chat mode" },
18667
+ { key: "n", description: "New task" }
18668
+ ]
18669
+ },
18670
+ sessions: {
18671
+ name: "Sessions",
18672
+ shortcuts: [
18673
+ { key: "\u2191\u2193", description: "Navigate sessions" },
18674
+ { key: "Enter", description: "View session output" },
18675
+ { key: "k", description: "Kill session" },
18676
+ { key: "r", description: "Restart agent" }
18677
+ ]
18678
+ },
18679
+ tasks: {
18680
+ name: "Tasks",
18681
+ shortcuts: [
18682
+ { key: "\u2191\u2193", description: "Navigate tasks" },
18683
+ { key: "Enter", description: "View task detail" },
18684
+ { key: "n", description: "Create new task" },
18685
+ { key: "f", description: "Cycle status filter" },
18686
+ { key: "p", description: "Filter by priority" }
18687
+ ]
18688
+ },
18689
+ team: {
18690
+ name: "Team",
18691
+ shortcuts: [
18692
+ { key: "\u2191\u2193", description: "Navigate employees" },
18693
+ { key: "Enter", description: "View detail" },
18694
+ { key: "a", description: "Add employee" },
18695
+ { key: "s", description: "View sessions" }
18696
+ ]
18697
+ },
18698
+ gateway: {
18699
+ name: "Gateway",
18700
+ shortcuts: [
18701
+ { key: "\u2191\u2193", description: "Navigate sections" },
18702
+ { key: "f", description: "Cycle platform filter" },
18703
+ { key: "\u2192", description: "Next conversation" },
18704
+ { key: "r", description: "Reconnect" }
18705
+ ]
18706
+ },
18707
+ wiki: {
18708
+ name: "Wiki",
18709
+ shortcuts: [
18710
+ { key: "\u2190\u2192", description: "Switch panels" },
18711
+ { key: "\u2191\u2193", description: "Navigate items" },
18712
+ { key: "/", description: "Search memories" },
18713
+ { key: "Enter", description: "Select" }
18714
+ ]
18715
+ },
18716
+ settings: {
18717
+ name: "Settings",
18718
+ shortcuts: [
18719
+ { key: "\u2191\u2193", description: "Navigate sections" },
18720
+ { key: "Enter", description: "Edit setting" },
18721
+ { key: "r", description: "Refresh status" }
18722
+ ]
18723
+ }
18724
+ };
17938
18725
  var isDemo = process.argv.includes("--demo");
17939
18726
  process.stderr.write(`[exe-tui] Terminal: ${TERMINAL_TYPE}
17940
18727
  `);
@@ -17961,17 +18748,17 @@ if (!isDemo) {
17961
18748
  })();
17962
18749
  }
17963
18750
  function App2() {
17964
- const [section, setSection] = useState15("command-center");
18751
+ const [section, setSection] = useState16("command-center");
17965
18752
  const { exit } = use_app_default();
17966
- const [, forceUpdate] = useState15(0);
17967
- useEffect17(() => {
18753
+ const [, forceUpdate] = useState16(0);
18754
+ useEffect18(() => {
17968
18755
  const handleResize = () => forceUpdate((n) => n + 1);
17969
18756
  process.stdout.on("resize", handleResize);
17970
18757
  return () => {
17971
18758
  process.stdout.off("resize", handleResize);
17972
18759
  };
17973
18760
  }, []);
17974
- useMouseEvent(useCallback7((event) => {
18761
+ useMouseEvent(useCallback8((event) => {
17975
18762
  if (event.button !== 0) return;
17976
18763
  if (event.col <= 26) {
17977
18764
  const tabIdx = event.row - 4;
@@ -17983,22 +18770,33 @@ function App2() {
17983
18770
  setFocus("content");
17984
18771
  }
17985
18772
  }, []));
17986
- const [focus, setFocus] = useState15("sidebar");
17987
- const [focusedProject, setFocusedProject] = useState15(null);
17988
- const handleSelectProject = useCallback7((projectName) => {
18773
+ const [focus, setFocus] = useState16("sidebar");
18774
+ const [showDebug, setShowDebug] = useState16(false);
18775
+ const [showHelp, setShowHelp] = useState16(false);
18776
+ const [focusedProject, setFocusedProject] = useState16(null);
18777
+ const handleSelectProject = useCallback8((projectName) => {
17989
18778
  setFocusedProject(projectName);
17990
18779
  setSection("sessions");
17991
18780
  setFocus("content");
17992
18781
  }, []);
17993
- const handleBackToCommandCenter = useCallback7(() => {
18782
+ const handleBackToCommandCenter = useCallback8(() => {
17994
18783
  setSection("command-center");
17995
18784
  setFocus("sidebar");
17996
18785
  }, []);
17997
18786
  use_input_default((input, key) => {
18787
+ if (input === "?" || key.shift && input === "/") {
18788
+ setShowHelp((prev) => !prev);
18789
+ return;
18790
+ }
17998
18791
  const idx = parseInt(input, 10);
17999
18792
  if (idx >= 1 && idx <= SECTIONS.length) {
18000
18793
  setSection(SECTIONS[idx - 1].key);
18001
18794
  setFocus("sidebar");
18795
+ setShowHelp(false);
18796
+ return;
18797
+ }
18798
+ if (input === "d" && !key.ctrl) {
18799
+ setShowDebug((prev) => !prev);
18002
18800
  return;
18003
18801
  }
18004
18802
  if (input === "q" && !key.ctrl) {
@@ -18026,24 +18824,34 @@ function App2() {
18026
18824
  }
18027
18825
  }
18028
18826
  });
18029
- const consumeFocusedProject = useCallback7(() => {
18827
+ const consumeFocusedProject = useCallback8(() => {
18030
18828
  setFocusedProject(null);
18031
18829
  }, []);
18032
18830
  const views = {
18033
- "command-center": /* @__PURE__ */ jsx15(CommandCenterView, { onSelectProject: handleSelectProject, isFocused: focus === "content" && section === "command-center", onBack: () => setFocus("sidebar") }),
18034
- sessions: /* @__PURE__ */ jsx15(SessionsView, { initialProject: focusedProject, onConsumeInitialProject: consumeFocusedProject, onBackToCommandCenter: handleBackToCommandCenter, onBack: () => setFocus("sidebar") }),
18035
- tasks: /* @__PURE__ */ jsx15(TasksView, { onBack: () => setFocus("sidebar") }),
18036
- team: /* @__PURE__ */ jsx15(TeamView, { onBack: () => setFocus("sidebar") }),
18037
- gateway: /* @__PURE__ */ jsx15(GatewayView, { onBack: () => setFocus("sidebar") }),
18038
- wiki: /* @__PURE__ */ jsx15(WikiView, { onBack: () => setFocus("sidebar") }),
18039
- settings: /* @__PURE__ */ jsx15(SettingsView, { onBack: () => setFocus("sidebar") })
18831
+ "command-center": /* @__PURE__ */ jsx17(CommandCenterView, { onSelectProject: handleSelectProject, isFocused: focus === "content" && section === "command-center", onBack: () => setFocus("sidebar") }),
18832
+ sessions: /* @__PURE__ */ jsx17(SessionsView, { initialProject: focusedProject, onConsumeInitialProject: consumeFocusedProject, onBackToCommandCenter: handleBackToCommandCenter, onBack: () => setFocus("sidebar") }),
18833
+ tasks: /* @__PURE__ */ jsx17(TasksView, { onBack: () => setFocus("sidebar") }),
18834
+ team: /* @__PURE__ */ jsx17(TeamView, { onBack: () => setFocus("sidebar"), onViewSessions: (name) => {
18835
+ setFocusedProject(name);
18836
+ setSection("sessions");
18837
+ setFocus("content");
18838
+ } }),
18839
+ gateway: /* @__PURE__ */ jsx17(GatewayView, { onBack: () => setFocus("sidebar") }),
18840
+ wiki: /* @__PURE__ */ jsx17(WikiView, { onBack: () => setFocus("sidebar") }),
18841
+ settings: /* @__PURE__ */ jsx17(SettingsView, { onBack: () => setFocus("sidebar") })
18040
18842
  };
18041
- return /* @__PURE__ */ jsx15(ErrorBoundary2, { children: /* @__PURE__ */ jsx15(AlternateScreen, { children: /* @__PURE__ */ jsx15(DemoProvider, { demo: isDemo, children: /* @__PURE__ */ jsxs13(Box_default, { flexDirection: "column", flexGrow: 1, children: [
18042
- /* @__PURE__ */ jsxs13(Box_default, { flexGrow: 1, children: [
18043
- /* @__PURE__ */ jsx15(Sidebar, { active: section, onSelect: setSection, onQuit: exit, focused: focus === "sidebar" }),
18044
- views[section]
18843
+ return /* @__PURE__ */ jsx17(ErrorBoundary2, { children: /* @__PURE__ */ jsx17(AlternateScreen, { children: /* @__PURE__ */ jsx17(DemoProvider, { demo: isDemo, children: /* @__PURE__ */ jsxs15(Box_default, { flexDirection: "column", flexGrow: 1, children: [
18844
+ /* @__PURE__ */ jsxs15(Box_default, { flexGrow: 1, children: [
18845
+ /* @__PURE__ */ jsx17(Sidebar, { active: section, onSelect: setSection, onQuit: exit, focused: focus === "sidebar" }),
18846
+ showHelp ? /* @__PURE__ */ jsx17(
18847
+ HelpOverlay,
18848
+ {
18849
+ tabName: TAB_SHORTCUTS[section].name,
18850
+ shortcuts: TAB_SHORTCUTS[section].shortcuts
18851
+ }
18852
+ ) : showDebug ? /* @__PURE__ */ jsx17(DebugPanel, {}) : views[section]
18045
18853
  ] }),
18046
- /* @__PURE__ */ jsx15(Footer, {})
18854
+ /* @__PURE__ */ jsx17(Footer, {})
18047
18855
  ] }) }) }) });
18048
18856
  }
18049
18857
  {
@@ -18061,4 +18869,4 @@ function App2() {
18061
18869
  stdin.unref = () => stdin;
18062
18870
  }
18063
18871
  }
18064
- render_default(/* @__PURE__ */ jsx15(App2, {}));
18872
+ render_default(/* @__PURE__ */ jsx17(App2, {}));