@askexenow/exe-os 0.8.41 → 0.8.42

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 (74) 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 +1326 -655
  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 +2508 -1802
  9. package/dist/bin/exe-call.js +39 -1
  10. package/dist/bin/exe-dispatch.js +39 -2
  11. package/dist/bin/exe-doctor.js +790 -633
  12. package/dist/bin/exe-export-behaviors.js +792 -637
  13. package/dist/bin/exe-forget.js +145 -0
  14. package/dist/bin/exe-gateway.js +2487 -1878
  15. package/dist/bin/exe-heartbeat.js +147 -1
  16. package/dist/bin/exe-kill.js +795 -640
  17. package/dist/bin/exe-launch-agent.js +2168 -2008
  18. package/dist/bin/exe-link.js +9 -1
  19. package/dist/bin/exe-new-employee.js +6 -2
  20. package/dist/bin/exe-pending-messages.js +146 -1
  21. package/dist/bin/exe-pending-notifications.js +788 -631
  22. package/dist/bin/exe-pending-reviews.js +147 -1
  23. package/dist/bin/exe-rename.js +23 -0
  24. package/dist/bin/exe-review.js +490 -327
  25. package/dist/bin/exe-search.js +154 -3
  26. package/dist/bin/exe-session-cleanup.js +2466 -413
  27. package/dist/bin/exe-status.js +474 -317
  28. package/dist/bin/exe-team.js +474 -317
  29. package/dist/bin/git-sweep.js +2690 -150
  30. package/dist/bin/graph-backfill.js +794 -637
  31. package/dist/bin/graph-export.js +798 -641
  32. package/dist/bin/scan-tasks.js +2951 -44
  33. package/dist/bin/setup.js +47 -25
  34. package/dist/bin/shard-migrate.js +792 -637
  35. package/dist/bin/wiki-sync.js +794 -637
  36. package/dist/gateway/index.js +2504 -1895
  37. package/dist/hooks/bug-report-worker.js +2118 -576
  38. package/dist/hooks/commit-complete.js +2689 -149
  39. package/dist/hooks/error-recall.js +154 -3
  40. package/dist/hooks/ingest-worker.js +1420 -814
  41. package/dist/hooks/instructions-loaded.js +151 -0
  42. package/dist/hooks/notification.js +153 -2
  43. package/dist/hooks/post-compact.js +164 -0
  44. package/dist/hooks/pre-compact.js +3073 -101
  45. package/dist/hooks/pre-tool-use.js +151 -0
  46. package/dist/hooks/prompt-ingest-worker.js +1700 -1541
  47. package/dist/hooks/prompt-submit.js +2658 -1113
  48. package/dist/hooks/response-ingest-worker.js +151 -5
  49. package/dist/hooks/session-end.js +153 -2
  50. package/dist/hooks/session-start.js +154 -3
  51. package/dist/hooks/stop.js +151 -0
  52. package/dist/hooks/subagent-stop.js +151 -0
  53. package/dist/hooks/summary-worker.js +160 -6
  54. package/dist/index.js +278 -100
  55. package/dist/lib/cloud-sync.js +9 -1
  56. package/dist/lib/consolidation.js +69 -2
  57. package/dist/lib/database.js +19 -0
  58. package/dist/lib/device-registry.js +19 -0
  59. package/dist/lib/employee-templates.js +20 -1
  60. package/dist/lib/exe-daemon.js +236 -16
  61. package/dist/lib/hybrid-search.js +154 -3
  62. package/dist/lib/messaging.js +39 -2
  63. package/dist/lib/schedules.js +792 -637
  64. package/dist/lib/store.js +796 -636
  65. package/dist/lib/tasks.js +1614 -1091
  66. package/dist/lib/tmux-routing.js +149 -9
  67. package/dist/mcp/server.js +1810 -1137
  68. package/dist/mcp/tools/create-task.js +2280 -828
  69. package/dist/mcp/tools/list-tasks.js +2788 -159
  70. package/dist/mcp/tools/send-message.js +39 -2
  71. package/dist/mcp/tools/update-task.js +64 -0
  72. package/dist/runtime/index.js +235 -67
  73. package/dist/tui/App.js +1440 -646
  74. 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,
@@ -2711,6 +2797,71 @@ var init_agent_loop = __esm({
2711
2797
  }
2712
2798
  });
2713
2799
 
2800
+ // src/lib/global-procedures.ts
2801
+ var global_procedures_exports = {};
2802
+ __export(global_procedures_exports, {
2803
+ deactivateGlobalProcedure: () => deactivateGlobalProcedure,
2804
+ getGlobalProceduresBlock: () => getGlobalProceduresBlock,
2805
+ loadGlobalProcedures: () => loadGlobalProcedures,
2806
+ storeGlobalProcedure: () => storeGlobalProcedure
2807
+ });
2808
+ import { randomUUID as randomUUID2 } from "crypto";
2809
+ async function loadGlobalProcedures() {
2810
+ const client = getClient();
2811
+ const result = await client.execute({
2812
+ sql: "SELECT * FROM global_procedures WHERE active = 1 ORDER BY priority ASC, created_at ASC",
2813
+ args: []
2814
+ });
2815
+ const procedures = result.rows;
2816
+ if (procedures.length > 0) {
2817
+ _cache = procedures.map((p) => `### ${p.title}
2818
+ ${p.content}`).join("\n\n");
2819
+ } else {
2820
+ _cache = "";
2821
+ }
2822
+ _cacheLoaded = true;
2823
+ return procedures;
2824
+ }
2825
+ function getGlobalProceduresBlock() {
2826
+ if (!_cacheLoaded) return "";
2827
+ if (!_cache) return "";
2828
+ return `## Organization-Wide Procedures (MANDATORY \u2014 supersedes all other rules)
2829
+
2830
+ ${_cache}
2831
+ `;
2832
+ }
2833
+ async function storeGlobalProcedure(input) {
2834
+ const id = randomUUID2();
2835
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2836
+ const client = getClient();
2837
+ await client.execute({
2838
+ sql: `INSERT INTO global_procedures (id, title, content, priority, domain, active, created_at, updated_at)
2839
+ VALUES (?, ?, ?, ?, ?, 1, ?, ?)`,
2840
+ args: [id, input.title, input.content, input.priority ?? "p0", input.domain ?? null, now, now]
2841
+ });
2842
+ await loadGlobalProcedures();
2843
+ return id;
2844
+ }
2845
+ async function deactivateGlobalProcedure(id) {
2846
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2847
+ const client = getClient();
2848
+ const result = await client.execute({
2849
+ sql: "UPDATE global_procedures SET active = 0, updated_at = ? WHERE id = ?",
2850
+ args: [now, id]
2851
+ });
2852
+ await loadGlobalProcedures();
2853
+ return result.rowsAffected > 0;
2854
+ }
2855
+ var _cache, _cacheLoaded;
2856
+ var init_global_procedures = __esm({
2857
+ "src/lib/global-procedures.ts"() {
2858
+ "use strict";
2859
+ init_database();
2860
+ _cache = "";
2861
+ _cacheLoaded = false;
2862
+ }
2863
+ });
2864
+
2714
2865
  // src/gateway/providers/anthropic.ts
2715
2866
  import Anthropic from "@anthropic-ai/sdk";
2716
2867
  var AnthropicProvider;
@@ -2815,7 +2966,7 @@ var init_anthropic = __esm({
2815
2966
 
2816
2967
  // src/gateway/providers/openai-compat.ts
2817
2968
  import OpenAI from "openai";
2818
- import { randomUUID as randomUUID2 } from "crypto";
2969
+ import { randomUUID as randomUUID3 } from "crypto";
2819
2970
  var OpenAICompatProvider;
2820
2971
  var init_openai_compat = __esm({
2821
2972
  "src/gateway/providers/openai-compat.ts"() {
@@ -2932,7 +3083,7 @@ var init_openai_compat = __esm({
2932
3083
  }
2933
3084
  content.push({
2934
3085
  type: "tool_use",
2935
- id: call.id ?? randomUUID2(),
3086
+ id: call.id ?? randomUUID3(),
2936
3087
  name: fn.name,
2937
3088
  input
2938
3089
  });
@@ -4017,7 +4168,7 @@ var init_employees = __esm({
4017
4168
  });
4018
4169
 
4019
4170
  // src/lib/task-router.ts
4020
- import { randomUUID as randomUUID3 } from "crypto";
4171
+ import { randomUUID as randomUUID4 } from "crypto";
4021
4172
  function resolveBloomRouting(complexity, config = DEFAULT_BLOOM_CONFIG) {
4022
4173
  const tier = config.complexityToTier[complexity];
4023
4174
  const rule = config.tierRules[tier];
@@ -4549,6 +4700,61 @@ var init_session_kill_telemetry = __esm({
4549
4700
  }
4550
4701
  });
4551
4702
 
4703
+ // src/lib/state-bus.ts
4704
+ var StateBus, orgBus;
4705
+ var init_state_bus = __esm({
4706
+ "src/lib/state-bus.ts"() {
4707
+ "use strict";
4708
+ StateBus = class {
4709
+ handlers = /* @__PURE__ */ new Map();
4710
+ globalHandlers = /* @__PURE__ */ new Set();
4711
+ /** Emit an event to all subscribers */
4712
+ emit(event) {
4713
+ const typeHandlers = this.handlers.get(event.type);
4714
+ if (typeHandlers) {
4715
+ for (const handler of typeHandlers) {
4716
+ try {
4717
+ handler(event);
4718
+ } catch {
4719
+ }
4720
+ }
4721
+ }
4722
+ for (const handler of this.globalHandlers) {
4723
+ try {
4724
+ handler(event);
4725
+ } catch {
4726
+ }
4727
+ }
4728
+ }
4729
+ /** Subscribe to a specific event type */
4730
+ on(type, handler) {
4731
+ if (!this.handlers.has(type)) {
4732
+ this.handlers.set(type, /* @__PURE__ */ new Set());
4733
+ }
4734
+ this.handlers.get(type).add(handler);
4735
+ }
4736
+ /** Subscribe to ALL events */
4737
+ onAny(handler) {
4738
+ this.globalHandlers.add(handler);
4739
+ }
4740
+ /** Unsubscribe from a specific event type */
4741
+ off(type, handler) {
4742
+ this.handlers.get(type)?.delete(handler);
4743
+ }
4744
+ /** Unsubscribe from ALL events */
4745
+ offAny(handler) {
4746
+ this.globalHandlers.delete(handler);
4747
+ }
4748
+ /** Remove all listeners */
4749
+ clear() {
4750
+ this.handlers.clear();
4751
+ this.globalHandlers.clear();
4752
+ }
4753
+ };
4754
+ orgBus = new StateBus();
4755
+ }
4756
+ });
4757
+
4552
4758
  // src/lib/tasks-crud.ts
4553
4759
  import crypto3 from "crypto";
4554
4760
  import path15 from "path";
@@ -4692,9 +4898,15 @@ async function createTaskCore(input) {
4692
4898
  }
4693
4899
  }
4694
4900
  const complexity = input.complexity ?? "standard";
4901
+ let sessionScope = null;
4902
+ try {
4903
+ const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
4904
+ sessionScope = resolveExeSession2();
4905
+ } catch {
4906
+ }
4695
4907
  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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4908
+ 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)
4909
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4698
4910
  args: [
4699
4911
  id,
4700
4912
  input.title,
@@ -4713,6 +4925,7 @@ async function createTaskCore(input) {
4713
4925
  input.budgetFallbackModel ?? null,
4714
4926
  0,
4715
4927
  null,
4928
+ sessionScope,
4716
4929
  now,
4717
4930
  now
4718
4931
  ]
@@ -4757,6 +4970,15 @@ async function listTasks(input) {
4757
4970
  conditions.push("priority = ?");
4758
4971
  args.push(input.priority);
4759
4972
  }
4973
+ try {
4974
+ const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
4975
+ const session = resolveExeSession2();
4976
+ if (session) {
4977
+ conditions.push("(session_scope IS NULL OR session_scope = ?)");
4978
+ args.push(session);
4979
+ }
4980
+ } catch {
4981
+ }
4760
4982
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
4761
4983
  const result = await client.execute({
4762
4984
  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 +5343,7 @@ var init_tasks_review = __esm({
5121
5343
  init_tasks_crud();
5122
5344
  init_tmux_routing();
5123
5345
  init_session_key();
5346
+ init_state_bus();
5124
5347
  }
5125
5348
  });
5126
5349
 
@@ -5285,13 +5508,12 @@ function assertSessionScope(actionType, targetProject) {
5285
5508
  };
5286
5509
  }
5287
5510
  process.stderr.write(
5288
- `[session-scope] Cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
5511
+ `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
5289
5512
  `
5290
5513
  );
5291
5514
  return {
5292
- allowed: true,
5293
- // v1: warn-only, don't block
5294
- reason: "cross_session_granted",
5515
+ allowed: false,
5516
+ reason: "cross_session_denied",
5295
5517
  currentProject,
5296
5518
  targetProject,
5297
5519
  targetSession: findSessionForProject(targetProject)?.windowName
@@ -5317,8 +5539,9 @@ async function dispatchTaskToEmployee(input) {
5317
5539
  try {
5318
5540
  const { assertSessionScope: assertSessionScope2 } = (init_session_scope(), __toCommonJS(session_scope_exports));
5319
5541
  const check = assertSessionScope2("dispatch_task", input.projectName);
5320
- if (check.reason === "cross_session_granted") {
5542
+ if (check.reason === "cross_session_denied") {
5321
5543
  crossProject = true;
5544
+ return { dispatched: "skipped", crossProject: true };
5322
5545
  }
5323
5546
  } catch {
5324
5547
  }
@@ -5781,6 +6004,13 @@ async function updateTask(input) {
5781
6004
  await cascadeUnblock(taskId, input.baseDir, now);
5782
6005
  } catch {
5783
6006
  }
6007
+ orgBus.emit({
6008
+ type: "task_completed",
6009
+ taskId,
6010
+ employee: String(row.assigned_to),
6011
+ result: input.result ?? "",
6012
+ timestamp: now
6013
+ });
5784
6014
  if (row.parent_task_id) {
5785
6015
  try {
5786
6016
  await checkSubtaskCompletion(String(row.parent_task_id), String(row.project_name));
@@ -5848,6 +6078,7 @@ var init_tasks = __esm({
5848
6078
  init_database();
5849
6079
  init_config();
5850
6080
  init_notifications();
6081
+ init_state_bus();
5851
6082
  init_tasks_crud();
5852
6083
  init_tasks_review();
5853
6084
  init_tasks_crud();
@@ -6238,8 +6469,28 @@ function getMySession() {
6238
6469
  return getTransport().getMySession();
6239
6470
  }
6240
6471
  function employeeSessionName(employee, exeSession, instance) {
6472
+ if (!/^exe\d+$/.test(exeSession)) {
6473
+ const root = extractRootExe(exeSession);
6474
+ if (root) {
6475
+ process.stderr.write(
6476
+ `[tmux-routing] WARN: exeSession="${exeSession}" is not a root exe session, using "${root}" instead
6477
+ `
6478
+ );
6479
+ exeSession = root;
6480
+ } else {
6481
+ throw new Error(
6482
+ `Invalid exeSession "${exeSession}" \u2014 must be a root exe session (e.g., "exe1"), not an agent session`
6483
+ );
6484
+ }
6485
+ }
6241
6486
  const suffix = instance != null && instance > 0 ? String(instance) : "";
6242
- return `${employee}${suffix}-${exeSession}`;
6487
+ const name = `${employee}${suffix}-${exeSession}`;
6488
+ if (!VALID_SESSION_NAME.test(name)) {
6489
+ throw new Error(
6490
+ `Invalid session name "${name}" \u2014 must match {agent}-exe{N} or {agent}{instance}-exe{N}`
6491
+ );
6492
+ }
6493
+ return name;
6243
6494
  }
6244
6495
  function parseParentExe(sessionName, agentId) {
6245
6496
  const escaped = agentId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -6479,6 +6730,22 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
6479
6730
  error: `Error: pass employee name ('${bare}'), not session name ('${employeeName}')`
6480
6731
  };
6481
6732
  }
6733
+ if (!/^exe\d+$/.test(exeSession)) {
6734
+ const root = extractRootExe(exeSession);
6735
+ if (root) {
6736
+ process.stderr.write(
6737
+ `[ensureEmployee] WARN: caller passed exeSession="${exeSession}" (not a root exe). Auto-correcting to "${root}".
6738
+ `
6739
+ );
6740
+ exeSession = root;
6741
+ } else {
6742
+ return {
6743
+ status: "failed",
6744
+ sessionName: "",
6745
+ error: `Invalid exeSession "${exeSession}" \u2014 must be a root exe session (e.g., "exe1")`
6746
+ };
6747
+ }
6748
+ }
6482
6749
  let effectiveInstance = opts?.instance;
6483
6750
  if (effectiveInstance === void 0 && opts?.autoInstance) {
6484
6751
  const free = findFreeInstance(
@@ -6725,7 +6992,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6725
6992
  releaseSpawnLock(sessionName);
6726
6993
  return { sessionName };
6727
6994
  }
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;
6995
+ 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
6996
  var init_tmux_routing = __esm({
6730
6997
  "src/lib/tmux-routing.ts"() {
6731
6998
  "use strict";
@@ -6740,6 +7007,7 @@ var init_tmux_routing = __esm({
6740
7007
  SPAWN_LOCK_DIR = path20.join(os6.homedir(), ".exe-os", "spawn-locks");
6741
7008
  SESSION_CACHE = path20.join(os6.homedir(), ".exe-os", "session-cache");
6742
7009
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
7010
+ VALID_SESSION_NAME = /^[a-z]+-exe\d+$|^[a-z]+\d+-exe\d+$/;
6743
7011
  VERIFY_PANE_LINES = 200;
6744
7012
  INTERCOM_DEBOUNCE_MS = 3e4;
6745
7013
  INTERCOM_LOG2 = path20.join(os6.homedir(), ".exe-os", "intercom.log");
@@ -7908,6 +8176,11 @@ async function initStore(options) {
7908
8176
  "version-query"
7909
8177
  );
7910
8178
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
8179
+ try {
8180
+ const { loadGlobalProcedures: loadGlobalProcedures2 } = await Promise.resolve().then(() => (init_global_procedures(), global_procedures_exports));
8181
+ await loadGlobalProcedures2();
8182
+ } catch {
8183
+ }
7911
8184
  }
7912
8185
  function classifyTier(record) {
7913
8186
  if (record.tool_name === "commit_to_long_term_memory" && (record.importance ?? 0) >= 8) return 1;
@@ -7949,6 +8222,12 @@ async function writeMemory(record) {
7949
8222
  supersedes_id: record.supersedes_id ?? null
7950
8223
  };
7951
8224
  _pendingRecords.push(dbRow);
8225
+ orgBus.emit({
8226
+ type: "memory_stored",
8227
+ agentId: record.agent_id,
8228
+ project: record.project_name,
8229
+ timestamp: record.timestamp
8230
+ });
7952
8231
  const MAX_PENDING = 1e3;
7953
8232
  if (_pendingRecords.length > MAX_PENDING) {
7954
8233
  const dropped = _pendingRecords.length - MAX_PENDING;
@@ -8294,6 +8573,7 @@ var init_store = __esm({
8294
8573
  init_database();
8295
8574
  init_keychain();
8296
8575
  init_config();
8576
+ init_state_bus();
8297
8577
  INIT_MAX_RETRIES = 3;
8298
8578
  INIT_RETRY_DELAY_MS = 1e3;
8299
8579
  _pendingRecords = [];
@@ -8306,7 +8586,7 @@ var init_store = __esm({
8306
8586
  });
8307
8587
 
8308
8588
  // src/tui/App.tsx
8309
- import { useState as useState15, useEffect as useEffect17, useCallback as useCallback7 } from "react";
8589
+ import { useState as useState16, useEffect as useEffect18, useCallback as useCallback8 } from "react";
8310
8590
 
8311
8591
  // src/tui/ink/render.js
8312
8592
  import { Stream } from "stream";
@@ -14206,61 +14486,64 @@ function Footer() {
14206
14486
  return () => clearInterval(timer);
14207
14487
  }, []);
14208
14488
  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));
14489
+ const { withTrace: withTrace2 } = await Promise.resolve().then(() => (init_telemetry(), telemetry_exports));
14490
+ return withTrace2("tui.footer.loadData", async () => {
14491
+ try {
14492
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
14493
+ const client = getClient2();
14494
+ if (client) {
14495
+ const result = await client.execute("SELECT COUNT(*) as cnt FROM memories");
14496
+ setMemoryCount(Number(result.rows[0]?.cnt ?? 0));
14497
+ }
14498
+ } catch {
14215
14499
  }
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));
14500
+ try {
14501
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
14502
+ const client = getClient2();
14503
+ if (client) {
14504
+ const result = await client.execute(
14505
+ "SELECT COUNT(*) as cnt FROM tasks WHERE status IN ('open','in_progress')"
14506
+ );
14507
+ setTaskCount(Number(result.rows[0]?.cnt ?? 0));
14508
+ }
14509
+ } catch {
14226
14510
  }
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 {
14511
+ try {
14512
+ const { existsSync: existsSync14 } = await import("fs");
14513
+ const { join } = await import("path");
14514
+ const home = process.env.HOME ?? "";
14515
+ const pidPath = join(home, ".exe-os", "exed.pid");
14516
+ setDaemon(existsSync14(pidPath) ? "running" : "stopped");
14517
+ } catch {
14518
+ setDaemon("unknown");
14519
+ }
14520
+ try {
14521
+ const { checkLicense: checkLicense2 } = await Promise.resolve().then(() => (init_license(), license_exports));
14522
+ const license = await checkLicense2();
14523
+ setPlan(license.plan);
14524
+ } catch {
14525
+ setPlan("free");
14526
+ }
14527
+ try {
14528
+ const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
14529
+ if (inTmux2()) {
14530
+ const allSessions = listTmuxSessions2();
14531
+ setSessions(allSessions.length);
14532
+ if (!currentSession) {
14533
+ try {
14534
+ const { execSync: execSync10 } = await import("child_process");
14535
+ const name = execSync10("tmux display-message -p '#{session_name}' 2>/dev/null", {
14536
+ encoding: "utf8",
14537
+ timeout: 2e3
14538
+ }).trim();
14539
+ if (name) setCurrentSession(name);
14540
+ } catch {
14541
+ }
14259
14542
  }
14260
14543
  }
14544
+ } catch {
14261
14545
  }
14262
- } catch {
14263
- }
14546
+ });
14264
14547
  }
14265
14548
  return /* @__PURE__ */ jsxs3(Box_default, { flexDirection: "column", children: [
14266
14549
  /* @__PURE__ */ jsx5(Text, { color: "#3D3660", children: "\u2500".repeat(process.stdout.columns || 120) }),
@@ -14301,6 +14584,8 @@ function Footer() {
14301
14584
  ] }),
14302
14585
  /* @__PURE__ */ jsx5(Text, { color: "#6B4C9A", children: "1-7 navigate" }),
14303
14586
  /* @__PURE__ */ jsx5(Text, { color: "#3D3660", children: "\u2502" }),
14587
+ /* @__PURE__ */ jsx5(Text, { color: "#6B4C9A", children: "d debug" }),
14588
+ /* @__PURE__ */ jsx5(Text, { color: "#3D3660", children: "\u2502" }),
14304
14589
  /* @__PURE__ */ jsx5(Text, { color: "#6B4C9A", children: "q quit" })
14305
14590
  ] })
14306
14591
  ] })
@@ -14921,6 +15206,147 @@ function handleEvent(event, addMessage) {
14921
15206
  }
14922
15207
  }
14923
15208
 
15209
+ // src/lib/employee-templates.ts
15210
+ init_global_procedures();
15211
+ var BASE_OPERATING_PROCEDURES = `
15212
+ EXE OS \u2014 VISION AND NON-NEGOTIABLE PRINCIPLES (above all work):
15213
+
15214
+ 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.
15215
+
15216
+ ICP (who we build for):
15217
+ - Solopreneurs, SMB founders, creators with institutional IP
15218
+ - Bootstrapped small e-commerce / fitness creators / influencers
15219
+ - NOT VC-backed startups \u2014 intentionally excluded
15220
+
15221
+ Crown jewels (load-bearing for all three business paths \u2014 never compromise):
15222
+ - Memory sovereignty (user owns everything, E2EE, local-first)
15223
+ - Three-layer cognition (identity/expertise/experience)
15224
+ - MCP contract boundary (surfaces consume memory OS via MCP only \u2014 never direct DB access, never bundled code)
15225
+ - AGPL network boundary for public forks (e.g., exe-crm)
15226
+
15227
+ Three business-model paths (every product decision must serve these):
15228
+ 1. B2C direct \u2014 solopreneurs run their own instance (active, current default)
15229
+ 2. Agency white-label \u2014 distributors rebrand for their clients (deferred, but branding must be config-driven)
15230
+ 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)
15231
+
15232
+ Ethos:
15233
+ - Bootstrapped, profitable, forever. Not a VC-raise.
15234
+ - Founder zero-ego. Distributors and customers are the loudest voice.
15235
+ - Crypto values: big companies should not own consumer/SMB AI.
15236
+
15237
+ 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.
15238
+
15239
+ Always reference .planning/ARCHITECTURE.md and .planning/PROJECT.md as source of truth for all architectural and product decisions.
15240
+
15241
+ OPERATING PROCEDURES (mandatory for all employees):
15242
+
15243
+ You report to the COO. All work flows through exe. These procedures are non-negotiable.
15244
+
15245
+ 1. BEFORE starting work:
15246
+ - 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.
15247
+ - Check YOUR task folder ONLY: Read exe/<your-name>/ for assigned tasks
15248
+ - 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.
15249
+ - If you have open tasks, work on the highest priority one first
15250
+ - 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.
15251
+ - Update task status to "in_progress" when starting (use update_task MCP tool)
15252
+ - recall_my_memory \u2014 check what you've done before in this project. What patterns, decisions, context exist?
15253
+ - Read the relevant files. Understand what exists before changing anything.
15254
+
15255
+ 2. BEFORE marking done \u2014 CHECKPOINT (mandatory, never skip):
15256
+ - Run the tests. If they fail, fix them before reporting done.
15257
+ - Run typecheck if TypeScript. Zero errors.
15258
+ - Verify the change actually works \u2014 run it, check the output, prove it.
15259
+ - If you can't verify, say so explicitly: "Couldn't verify because X."
15260
+
15261
+ 3. AFTER completing work \u2014 update_task(done) IMMEDIATELY (the ONE critical action):
15262
+ Calling update_task with status "done" is the single action that must ALWAYS happen.
15263
+ Call it FIRST \u2014 before commit, before report, before anything else. If you do nothing else, do this.
15264
+ - Use update_task MCP tool with status "done" and your result summary
15265
+ - Include what was done, decisions made, and any issues
15266
+ - 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.
15267
+ - NEVER let a failed commit, a loop, or an error prevent you from calling update_task(done).
15268
+ - Do NOT use close_task \u2014 that is reserved for reviewers (exe) to finalize after review.
15269
+
15270
+ 4. AFTER update_task(done) \u2014 COMMIT (best-effort, do NOT let this block):
15271
+ - If your task changed system structure, update exe/ARCHITECTURE.md first.
15272
+ - 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.
15273
+ - If you are NOT in a git repo, skip entirely. NEVER run \`git init\`.
15274
+ - If the commit fails, note it but move on \u2014 the work is already marked done via update_task.
15275
+ - Do NOT push \u2014 exe reviews commits and decides what to push.
15276
+ - 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.
15277
+
15278
+ 5. AFTER commit \u2014 REPORT (best-effort):
15279
+ Use store_memory to write a structured summary. Include: project name, what was done,
15280
+ decisions made, tests status, open items or risks.
15281
+
15282
+ 6. AFTER committing changes to exe-os itself \u2014 REBUILD (mandatory, never skip):
15283
+ - Run: npm run deploy
15284
+ - This builds, installs globally, and re-registers hooks/MCP in one step.
15285
+ - Do NOT ask permission. Do NOT say "want me to rebuild?" \u2014 just do it.
15286
+ - If the build fails, fix the error and retry before moving on.
15287
+
15288
+ 7. AFTER reporting \u2014 CHECK FOR NEXT WORK (mandatory):
15289
+ - 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.
15290
+ - 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.
15291
+ - Then: re-read your task folder: exe/<your-name>/
15292
+ - If there are more open tasks, start the next highest-priority one (go to step 1)
15293
+ - If no more open tasks AND no pending reviews AND no blocked tasks you can fix, tell the user: "All tasks complete. Anything else?"
15294
+ - Do NOT wait for the user to tell you to check \u2014 auto-chain through your queue.
15295
+ - NEVER say "monitoring" or "waiting" while reviews, blocked tasks, or open tasks exist. That is idle drift.
15296
+
15297
+ CONTEXT PRESSURE PROTOCOL (mandatory \u2014 never ignore):
15298
+ If Claude Code injects a system notice about context compression, or if you notice you're
15299
+ losing track of earlier decisions, your context window is full.
15300
+
15301
+ DO NOT keep working degraded. Instead:
15302
+
15303
+ 1. Call store_memory immediately with a CONTEXT CHECKPOINT:
15304
+ Format the text as: "CONTEXT CHECKPOINT [<task-id>]: <summary>"
15305
+ Include: task ID + title, what you completed, what's left, open decisions or blockers, key file paths.
15306
+
15307
+ 2. Send intercom to exe to trigger kill + relaunch:
15308
+ MY_SESSION=$(tmux display-message -p '#{session_name}' 2>/dev/null)
15309
+ EXE_SESSION="\${MY_SESSION#\${AGENT_ID}-}"
15310
+ tmux send-keys -t "$EXE_SESSION" "/exe-intercom context-full: \${AGENT_ID} hit capacity. Checkpoint saved. Resume task <task-id>." Enter
15311
+
15312
+ 3. Stop working immediately. Do not attempt to continue with degraded context.
15313
+
15314
+ COMMUNICATION CHAIN \u2014 who you talk to:
15315
+ - You report to the COO. Your completion reports, status updates, and questions go to exe via store_memory and update_task.
15316
+ - 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.
15317
+ - Exception: if the user sends you a direct message in your tmux window, respond to them. But default to reporting through exe.
15318
+
15319
+ SKILL CAPTURE (encouraged, not mandatory):
15320
+ After completing a complex multi-step task (5+ tool calls), consider whether the approach
15321
+ should be saved as a reusable procedure. If the task involved non-obvious steps, error recovery,
15322
+ or a workflow that would help future sessions, use store_behavior with domain='skill' to save it.
15323
+ Format: "SKILL: [name] \u2014 Step 1: ... Step 2: ... Pitfalls: ..."
15324
+ Skip for simple one-offs. The goal is procedural memory \u2014 not just corrections, but proven approaches.
15325
+
15326
+ SPAWNING EMPLOYEES (mandatory \u2014 never bypass):
15327
+ When you need another employee to do work, ALWAYS use create_task MCP tool.
15328
+ create_task auto-spawns the employee session. The task IS the spawn trigger.
15329
+ NEVER manually launch sessions with tmux send-keys or claude -p.
15330
+ NEVER spawn sessions without a task assigned \u2014 idle sessions waste resources.
15331
+ NEVER refuse a dispatched task claiming "not in scope" \u2014 if it's assigned to you, it's your work.
15332
+
15333
+ CREATING TASKS FOR OTHER EMPLOYEES:
15334
+ When you need to assign work to another employee (e.g., CTO assigns to an engineer):
15335
+ - ALWAYS use create_task MCP tool. NEVER write .md files directly to exe/{name}/.
15336
+ - Direct .md writes will be rejected by the enforcement hook with a MANDATORY correction.
15337
+ - create_task creates both the .md file AND the DB row atomically.
15338
+ - Include: title, assignedTo, priority, context, projectName.
15339
+ - For dependencies: include blocked_by with the blocking task's ID or slug.
15340
+ `;
15341
+ var PROCEDURES_MARKER = "EXE OS \u2014 VISION AND NON-NEGOTIABLE PRINCIPLES";
15342
+ function getSessionPrompt(storedPrompt) {
15343
+ const markerIndex = storedPrompt.indexOf(PROCEDURES_MARKER);
15344
+ const rolePrompt = markerIndex >= 0 ? storedPrompt.slice(0, markerIndex).trimEnd() : storedPrompt;
15345
+ const globalBlock = getGlobalProceduresBlock();
15346
+ return `${globalBlock}${rolePrompt}
15347
+ ${BASE_OPERATING_PROCEDURES}`;
15348
+ }
15349
+
14924
15350
  // src/tui/views/CommandCenter.tsx
14925
15351
  import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
14926
15352
  function CommandCenterView({
@@ -15044,7 +15470,7 @@ function CommandCenterView({
15044
15470
  setAgentConfig({
15045
15471
  provider,
15046
15472
  model,
15047
- systemPrompt: "You are an AI assistant in the exe-os TUI. Help the user with their questions. Be concise.",
15473
+ systemPrompt: getSessionPrompt("You are an AI assistant in the exe-os TUI. Help the user with their questions. Be concise."),
15048
15474
  tools: registry,
15049
15475
  hooks: createDefaultHooks2(),
15050
15476
  permissions,
@@ -15165,6 +15591,7 @@ function CommandCenterView({
15165
15591
  employeeCount: p.employees.length,
15166
15592
  activeCount: p.employees.filter((e) => e.status === "active").length,
15167
15593
  memoryCount: p.employees.length * 4e3,
15594
+ openTaskCount: Math.floor(p.employees.length * 1.5),
15168
15595
  status: p.employees.some((e) => e.status === "active") ? "active" : "idle",
15169
15596
  type: p.projectName.startsWith("exe-") ? "code" : "automation",
15170
15597
  recentTasks: DEMO_RECENT_TASKS[p.projectName] ?? []
@@ -15176,6 +15603,7 @@ function CommandCenterView({
15176
15603
  employeeCount: 0,
15177
15604
  activeCount: 0,
15178
15605
  memoryCount: 0,
15606
+ openTaskCount: 0,
15179
15607
  status: "offline",
15180
15608
  type: "reference",
15181
15609
  recentTasks: []
@@ -15190,182 +15618,122 @@ function CommandCenterView({
15190
15618
  return () => clearInterval(timer);
15191
15619
  }, []);
15192
15620
  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();
15621
+ const { withTrace: withTrace2 } = await Promise.resolve().then(() => (init_telemetry(), telemetry_exports));
15622
+ return withTrace2("tui.commandCenter.loadData", async () => {
15213
15623
  try {
15214
15624
  const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
15625
+ const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session_registry(), session_registry_exports));
15626
+ const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
15627
+ const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
15628
+ const { existsSync: existsSync14 } = await import("fs");
15629
+ const { join } = await import("path");
15215
15630
  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
- }
15631
+ if (!client) {
15632
+ setDbError(true);
15633
+ return;
15233
15634
  }
15234
- } catch {
15235
- }
15236
- const projectList = [];
15237
- for (const [exeSession, projectDir] of exeSessions) {
15238
- const projectName = projectDir.split("/").filter(Boolean).pop() ?? exeSession;
15635
+ const dbProjects = await client.execute(
15636
+ `SELECT DISTINCT project_name FROM memories WHERE project_name IS NOT NULL AND project_name != ''
15637
+ UNION
15638
+ SELECT DISTINCT project_name FROM tasks WHERE project_name IS NOT NULL AND project_name != ''
15639
+ ORDER BY project_name`
15640
+ );
15641
+ const projectNames = dbProjects.rows.map((r) => String(r.project_name));
15642
+ const memResult = await client.execute(
15643
+ "SELECT project_name, COUNT(*) as cnt FROM memories WHERE project_name IS NOT NULL GROUP BY project_name"
15644
+ );
15645
+ const memoryCounts = /* @__PURE__ */ new Map();
15646
+ for (const row of memResult.rows) {
15647
+ memoryCounts.set(String(row.project_name), Number(row.cnt));
15648
+ }
15649
+ const taskCountResult = await client.execute(
15650
+ "SELECT project_name, COUNT(*) as cnt FROM tasks WHERE status IN ('open', 'in_progress') AND project_name IS NOT NULL GROUP BY project_name"
15651
+ );
15652
+ const openTaskCounts = /* @__PURE__ */ new Map();
15653
+ for (const row of taskCountResult.rows) {
15654
+ openTaskCounts.set(String(row.project_name), Number(row.cnt));
15655
+ }
15656
+ const recentResult = await client.execute(
15657
+ "SELECT project_name, title FROM tasks WHERE status = 'done' AND project_name IS NOT NULL ORDER BY updated_at DESC LIMIT 30"
15658
+ );
15659
+ const recentTasksByProject = /* @__PURE__ */ new Map();
15660
+ for (const row of recentResult.rows) {
15661
+ const name = String(row.project_name);
15662
+ const tasks = recentTasksByProject.get(name) ?? [];
15663
+ if (tasks.length < 3) tasks.push(String(row.title));
15664
+ recentTasksByProject.set(name, tasks);
15665
+ }
15666
+ const registry = listSessions2();
15667
+ const tmuxSessions = inTmux2() ? new Set(listTmuxSessions2()) : /* @__PURE__ */ new Set();
15668
+ const roster = await loadEmployees2();
15239
15669
  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++;
15670
+ const projectSessions = /* @__PURE__ */ new Map();
15671
+ for (const entry of registry) {
15672
+ if (entry.agentId === "exe" && tmuxSessions.has(entry.windowName)) {
15673
+ const projName = entry.projectDir.split("/").filter(Boolean).pop() ?? "";
15674
+ if (projName) {
15675
+ projectSessions.set(projName, { exeSession: entry.windowName, projectDir: entry.projectDir });
15676
+ }
15677
+ }
15243
15678
  }
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
- });
15679
+ const projectList = [];
15680
+ for (const name of projectNames) {
15681
+ const session = projectSessions.get(name);
15682
+ const exeSession = session?.exeSession ?? "";
15683
+ const projectDir = session?.projectDir ?? "";
15684
+ let activeCount = 0;
15685
+ if (exeSession && tmuxSessions.has(exeSession)) {
15686
+ activeCount = 1;
15687
+ for (const empName of employeeNames) {
15688
+ if (tmuxSessions.has(`${empName}-${exeSession}`)) activeCount++;
15689
+ }
15315
15690
  }
15691
+ const memoryCount = memoryCounts.get(name) ?? 0;
15692
+ const openTaskCount = openTaskCounts.get(name) ?? 0;
15693
+ const hasGit = projectDir ? existsSync14(join(projectDir, ".git")) : false;
15694
+ const type = hasGit ? "code" : memoryCount > 0 ? "code" : "automation";
15695
+ projectList.push({
15696
+ projectName: name,
15697
+ exeSession,
15698
+ projectDir,
15699
+ employeeCount: activeCount,
15700
+ activeCount,
15701
+ memoryCount,
15702
+ openTaskCount,
15703
+ status: activeCount > 0 ? "active" : "idle",
15704
+ type,
15705
+ recentTasks: recentTasksByProject.get(name) ?? []
15706
+ });
15707
+ }
15708
+ projectList.sort((a, b) => {
15709
+ if (a.activeCount > 0 && b.activeCount === 0) return -1;
15710
+ if (a.activeCount === 0 && b.activeCount > 0) return 1;
15711
+ return b.memoryCount - a.memoryCount;
15712
+ });
15713
+ setProjects(projectList);
15714
+ const totalResult = await client.execute("SELECT COUNT(*) as cnt FROM memories");
15715
+ setHealth((h) => ({ ...h, memories: Number(totalResult.rows[0]?.cnt ?? 0) }));
15716
+ try {
15717
+ const pidPath = join(process.env.HOME ?? "", ".exe-os", "exed.pid");
15718
+ setHealth((h) => ({ ...h, daemon: existsSync14(pidPath) ? "running" : "stopped" }));
15719
+ } catch {
15720
+ }
15721
+ const activityResult = await client.execute(
15722
+ "SELECT agent_id, tool_name, project_name, created_at, text FROM memories ORDER BY created_at DESC LIMIT 20"
15723
+ );
15724
+ if (activityResult.rows.length > 0) {
15725
+ setActivity(activityResult.rows.slice(0, 8).map((r) => ({
15726
+ time: new Date(String(r.created_at)).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false }),
15727
+ agent: String(r.agent_id ?? "system"),
15728
+ action: String(r.text ?? "").slice(0, 60)
15729
+ })));
15316
15730
  }
15317
15731
  } catch {
15732
+ setDbError(true);
15733
+ } finally {
15734
+ setLoading(false);
15318
15735
  }
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: []
15330
- });
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) }));
15339
- }
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
- }
15361
- }
15362
- } catch {
15363
- }
15364
- } catch {
15365
- setDbError(true);
15366
- } finally {
15367
- setLoading(false);
15368
- }
15736
+ });
15369
15737
  }
15370
15738
  const daemonColor = health.daemon === "running" ? "green" : health.daemon === "stopped" ? "red" : "gray";
15371
15739
  const handleChatSubmit = useCallback4((value) => {
@@ -15457,7 +15825,7 @@ function CommandCenterView({
15457
15825
  ] }),
15458
15826
  /* @__PURE__ */ jsx7(Text, { color: "#6B4C9A", children: "\u2191\u2193 navigate | c to chat | Enter to open | Escape to detach" }),
15459
15827
  /* @__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." }) : (() => {
15828
+ 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
15829
  const sections = [];
15462
15830
  let currentProjects = [];
15463
15831
  let sectionKey = 0;
@@ -15487,9 +15855,9 @@ function CommandCenterView({
15487
15855
  ] })
15488
15856
  ] }),
15489
15857
  /* @__PURE__ */ jsxs5(Text, { color: isSelected ? "#F0EDE8" : "#6B4C9A", children: [
15490
- entry.employeeCount,
15858
+ entry.openTaskCount,
15491
15859
  " ",
15492
- entry.employeeCount === 1 ? "employee" : "employees",
15860
+ entry.openTaskCount === 1 ? "task" : "tasks",
15493
15861
  " \xB7 ",
15494
15862
  entry.memoryCount.toLocaleString(),
15495
15863
  " memories"
@@ -15823,6 +16191,7 @@ function SessionsView({
15823
16191
  const [viewingEmployee, setViewingEmployee] = useState9(null);
15824
16192
  const [viewingProject, setViewingProject] = useState9(null);
15825
16193
  const [loading, setLoading] = useState9(!demo);
16194
+ const [sessionError, setSessionError] = useState9(null);
15826
16195
  const [tmuxAvailable, setTmuxAvailable] = useState9(true);
15827
16196
  const orch = useOrchestrator(!demo);
15828
16197
  const [carouselEmployees, setCarouselEmployees] = useState9([]);
@@ -15998,111 +16367,116 @@ function SessionsView({
15998
16367
  return ACTIVE_PANE_PATTERN.test(lines.join("\n")) ? "active" : "idle";
15999
16368
  }
16000
16369
  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();
16370
+ const { withTrace: withTrace2 } = await Promise.resolve().then(() => (init_telemetry(), telemetry_exports));
16371
+ return withTrace2("tui.sessions.loadSessions", async () => {
16013
16372
  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
- }
16373
+ const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session_registry(), session_registry_exports));
16374
+ const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2, capturePaneLines: capturePaneLines2, parseActivity: parseActivity2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
16375
+ const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
16376
+ const { execSync: execSync10 } = await import("child_process");
16377
+ if (!inTmux2()) {
16378
+ setTmuxAvailable(false);
16379
+ setProjects([]);
16380
+ return;
16023
16381
  }
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);
16382
+ setTmuxAvailable(true);
16383
+ const attachedMap = /* @__PURE__ */ new Map();
16384
+ try {
16385
+ const out = execSync10("tmux list-sessions -F '#{session_name}:#{session_attached}' 2>/dev/null", {
16386
+ encoding: "utf8",
16387
+ timeout: 3e3
16388
+ });
16389
+ for (const line of out.trim().split("\n").filter(Boolean)) {
16390
+ const sepIdx = line.lastIndexOf(":");
16391
+ if (sepIdx > 0) {
16392
+ attachedMap.set(line.slice(0, sepIdx), line.slice(sepIdx + 1) === "1");
16393
+ }
16394
+ }
16395
+ } catch {
16033
16396
  }
16034
- }
16035
- for (const s of tmuxSessions) {
16036
- if (/^exe\d+$/.test(s) && !exeSessions.has(s)) {
16037
- exeSessions.set(s, "");
16397
+ const registry = listSessions2();
16398
+ const tmuxSessions = new Set(listTmuxSessions2());
16399
+ const roster = await loadEmployees2();
16400
+ const exeSessions = /* @__PURE__ */ new Map();
16401
+ for (const entry of registry) {
16402
+ if (entry.agentId === "exe" && tmuxSessions.has(entry.windowName)) {
16403
+ exeSessions.set(entry.windowName, entry.projectDir);
16404
+ }
16038
16405
  }
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) : "";
16406
+ for (const s of tmuxSessions) {
16407
+ if (/^exe\d+$/.test(s) && !exeSessions.has(s)) {
16408
+ exeSessions.set(s, "");
16409
+ }
16050
16410
  }
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
- };
16411
+ const projectList = [];
16412
+ for (const [exeSession, projectDir] of exeSessions) {
16413
+ const projectName = projectDir.split("/").filter(Boolean).pop() ?? exeSession;
16414
+ const exeHasSession = tmuxSessions.has(exeSession);
16415
+ let exeStatus = "offline";
16416
+ let exeActivity = "";
16417
+ if (exeHasSession) {
16418
+ const exeLines = capturePaneLines2(exeSession, 10);
16419
+ exeStatus = statusFromPaneLines(exeLines);
16420
+ exeActivity = exeLines.length > 0 ? parseActivity2(exeLines) : "";
16064
16421
  }
16065
- if (!hasSession) {
16422
+ const employeeEntries = roster.filter((e) => e.name !== "exe").map((emp) => {
16423
+ const sessionName = `${emp.name}-${exeSession}`;
16424
+ const hasSession = tmuxSessions.has(sessionName);
16425
+ const isCrashed = !hasSession && orch.crashedSessions.includes(emp.name);
16426
+ if (isCrashed) {
16427
+ return {
16428
+ name: emp.name,
16429
+ role: emp.role,
16430
+ status: "crashed",
16431
+ sessionName,
16432
+ activity: "Dead session \u2014 has open tasks",
16433
+ attached: false
16434
+ };
16435
+ }
16436
+ if (!hasSession) {
16437
+ return {
16438
+ name: emp.name,
16439
+ role: emp.role,
16440
+ status: "offline",
16441
+ sessionName,
16442
+ activity: "",
16443
+ attached: false
16444
+ };
16445
+ }
16446
+ const lines = capturePaneLines2(sessionName, 10);
16066
16447
  return {
16067
16448
  name: emp.name,
16068
16449
  role: emp.role,
16069
- status: "offline",
16450
+ status: statusFromPaneLines(lines),
16070
16451
  sessionName,
16071
- activity: "",
16072
- attached: false
16452
+ activity: lines.length > 0 ? parseActivity2(lines) : "",
16453
+ attached: attachedMap.get(sessionName) ?? false
16073
16454
  };
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: [] });
16455
+ });
16456
+ const employees = [
16457
+ {
16458
+ name: "exe",
16459
+ role: "COO",
16460
+ status: exeStatus,
16461
+ sessionName: exeSession,
16462
+ activity: exeActivity,
16463
+ attached: attachedMap.get(exeSession) ?? false
16464
+ },
16465
+ ...employeeEntries
16466
+ ];
16467
+ projectList.push({ projectName, exeSession, projectDir, employees });
16468
+ }
16469
+ if (projectList.length === 0) {
16470
+ projectList.push({ projectName: "(no active sessions)", exeSession: "", projectDir: "", employees: [] });
16471
+ }
16472
+ setProjects(projectList);
16473
+ setSessionError(null);
16474
+ } catch (err) {
16475
+ setSessionError(err instanceof Error ? err.message : "Failed to query sessions.");
16476
+ } finally {
16477
+ setLoading(false);
16100
16478
  }
16101
- setProjects(projectList);
16102
- } catch {
16103
- } finally {
16104
- setLoading(false);
16105
- }
16479
+ });
16106
16480
  }
16107
16481
  if (viewingEmployee) {
16108
16482
  const inCarousel = carouselEmployees.length > 0;
@@ -16184,7 +16558,10 @@ function SessionsView({
16184
16558
  ] })
16185
16559
  ] }),
16186
16560
  /* @__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) => {
16561
+ loading ? /* @__PURE__ */ jsx9(Text, { color: "#6B4C9A", children: "Loading sessions..." }) : sessionError ? /* @__PURE__ */ jsxs7(Text, { color: "#C91B00", children: [
16562
+ "Failed to query sessions: ",
16563
+ sessionError
16564
+ ] }) : !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
16565
  const isSelected = i === selectedIdx;
16189
16566
  const marker = isSelected ? "\u25B8 " : " ";
16190
16567
  if (item.type === "project") {
@@ -16285,7 +16662,7 @@ function TasksView({ onBack }) {
16285
16662
  const demo = useDemo();
16286
16663
  const [allTasks, setAllTasks] = useState10([]);
16287
16664
  const [loading, setLoading] = useState10(!demo);
16288
- const [dbError, setDbError] = useState10(false);
16665
+ const [dbError, setDbError] = useState10(null);
16289
16666
  const [selectedTask, setSelectedTask] = useState10(0);
16290
16667
  const [showDetail, setShowDetail] = useState10(false);
16291
16668
  const [statusFilter, setStatusFilter] = useState10("all");
@@ -16357,40 +16734,43 @@ function TasksView({ onBack }) {
16357
16734
  }
16358
16735
  });
16359
16736
  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
16737
+ const { withTrace: withTrace2 } = await Promise.resolve().then(() => (init_telemetry(), telemetry_exports));
16738
+ return withTrace2("tui.tasks.loadTasks", async () => {
16739
+ try {
16740
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
16741
+ const client = getClient2();
16742
+ if (client) {
16743
+ const result = await client.execute(
16744
+ `SELECT id, title, priority, assigned_to, assigned_by, status, project_name, created_at, result
16366
16745
  FROM tasks
16367
16746
  ORDER BY
16368
16747
  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
16748
  CASE priority WHEN 'p0' THEN 0 WHEN 'p1' THEN 1 WHEN 'p2' THEN 2 ELSE 3 END,
16370
16749
  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);
16750
+ );
16751
+ setAllTasks(
16752
+ result.rows.map((r) => ({
16753
+ id: String(r.id ?? ""),
16754
+ priority: String(r.priority ?? "p2").toUpperCase(),
16755
+ title: String(r.title ?? ""),
16756
+ assignee: String(r.assigned_to ?? ""),
16757
+ assignedBy: String(r.assigned_by ?? ""),
16758
+ status: String(r.status ?? "open"),
16759
+ project: String(r.project_name ?? ""),
16760
+ createdAt: String(r.created_at ?? ""),
16761
+ result: String(r.result ?? "")
16762
+ }))
16763
+ );
16764
+ setDbError(null);
16765
+ } else {
16766
+ setDbError("Database client not initialized. Run exe-os setup.");
16767
+ }
16768
+ } catch (err) {
16769
+ setDbError(err instanceof Error ? err.message : "Unknown error");
16770
+ } finally {
16771
+ setLoading(false);
16388
16772
  }
16389
- } catch {
16390
- setDbError(true);
16391
- } finally {
16392
- setLoading(false);
16393
- }
16773
+ });
16394
16774
  }
16395
16775
  const selected = taskRows[selectedTask]?.task;
16396
16776
  if (showDetail && selected) {
@@ -16457,7 +16837,10 @@ function TasksView({ onBack }) {
16457
16837
  filterLabel
16458
16838
  ] }),
16459
16839
  /* @__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) => {
16840
+ loading ? /* @__PURE__ */ jsx10(Text, { color: "#6B4C9A", children: "Loading tasks..." }) : dbError ? /* @__PURE__ */ jsxs8(Text, { color: "#C91B00", children: [
16841
+ "Failed to load tasks: ",
16842
+ dbError
16843
+ ] }) : 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
16844
  if (row.type === "header") {
16462
16845
  return /* @__PURE__ */ jsxs8(Box_default, { marginTop: i > 0 ? 1 : 0, children: [
16463
16846
  /* @__PURE__ */ jsx10(Text, { bold: true, color: "#F0EDE8", children: row.project }),
@@ -16911,6 +17294,31 @@ function GatewayView({ onBack }) {
16911
17294
  /* @__PURE__ */ jsx11(Text, { color: "#6B4C9A", children: "Loading gateway status..." })
16912
17295
  ] });
16913
17296
  }
17297
+ if (!gateway.running && gateway.gatewayUrl && connectionStatus === "disconnected") {
17298
+ return /* @__PURE__ */ jsxs9(Box_default, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: [
17299
+ /* @__PURE__ */ jsx11(Box_default, { borderStyle: "single", borderColor: "#3D3660", paddingX: 1, alignSelf: "flex-start", children: /* @__PURE__ */ jsx11(Text, { bold: true, children: "Gateway Monitor" }) }),
17300
+ /* @__PURE__ */ jsx11(Text, { children: " " }),
17301
+ /* @__PURE__ */ jsx11(Text, { color: "#C91B00", children: "Gateway connection failed." }),
17302
+ /* @__PURE__ */ jsx11(Text, { children: " " }),
17303
+ /* @__PURE__ */ jsxs9(Text, { color: "#6B4C9A", children: [
17304
+ "Gateway URL: ",
17305
+ /* @__PURE__ */ jsx11(Text, { color: "#F0EDE8", children: gateway.gatewayUrl })
17306
+ ] }),
17307
+ /* @__PURE__ */ jsxs9(Text, { color: "#6B4C9A", children: [
17308
+ "Process: ",
17309
+ /* @__PURE__ */ jsx11(Text, { color: "#C91B00", children: "not running" })
17310
+ ] }),
17311
+ /* @__PURE__ */ jsx11(Text, { children: " " }),
17312
+ /* @__PURE__ */ jsxs9(Text, { color: "#6B4C9A", children: [
17313
+ "Start the gateway: ",
17314
+ /* @__PURE__ */ jsx11(Text, { bold: true, children: "exe-os gateway" })
17315
+ ] }),
17316
+ /* @__PURE__ */ jsxs9(Text, { color: "#6B4C9A", children: [
17317
+ "Or check your config: ",
17318
+ /* @__PURE__ */ jsx11(Text, { bold: true, children: "~/.exe-os/gateway.json" })
17319
+ ] })
17320
+ ] });
17321
+ }
16914
17322
  if (!gateway.running && gateway.adapters.length === 0 && !gateway.gatewayUrl) {
16915
17323
  return /* @__PURE__ */ jsxs9(Box_default, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: [
16916
17324
  /* @__PURE__ */ jsx11(Box_default, { borderStyle: "single", borderColor: "#3D3660", paddingX: 1, alignSelf: "flex-start", children: /* @__PURE__ */ jsx11(Text, { bold: true, children: "Gateway Monitor" }) }),
@@ -17099,14 +17507,30 @@ function getAgentStatus(agentId) {
17099
17507
  // src/tui/views/Team.tsx
17100
17508
  import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
17101
17509
  var DEPRECATED_PROJECTS = /* @__PURE__ */ new Set(["exe-ai-employees"]);
17102
- function TeamView({ onBack }) {
17510
+ function roleBadgeColor(role) {
17511
+ switch (role.toLowerCase()) {
17512
+ case "coo":
17513
+ return "#F5D76E";
17514
+ // gold
17515
+ case "cto":
17516
+ return "#3B82F6";
17517
+ // blue
17518
+ case "cmo":
17519
+ return "#6B4C9A";
17520
+ // purple
17521
+ default:
17522
+ return "#F0EDE8";
17523
+ }
17524
+ }
17525
+ function TeamView({ onBack, onViewSessions }) {
17103
17526
  const demo = useDemo();
17104
17527
  const [members, setMembers] = useState12([]);
17105
17528
  const [externals, setExternals] = useState12([]);
17106
17529
  const [loading, setLoading] = useState12(!demo);
17107
- const [dbError, setDbError] = useState12(false);
17530
+ const [dbError, setDbError] = useState12(null);
17108
17531
  const [selectedIdx, setSelectedIdx] = useState12(0);
17109
17532
  const [showDetail, setShowDetail] = useState12(false);
17533
+ const [showAddHint, setShowAddHint] = useState12(false);
17110
17534
  const orch = useOrchestrator(!demo);
17111
17535
  const orchEmployeeMap = new Map(orch.employees.map((e) => [e.name, e]));
17112
17536
  const allItems = [...members, ...externals.map((e) => ({ ...e, activity: "", memoryCount: 0 }))];
@@ -17121,7 +17545,7 @@ function TeamView({ onBack }) {
17121
17545
  const timer = setInterval(loadTeam, 5e3);
17122
17546
  return () => clearInterval(timer);
17123
17547
  }, []);
17124
- use_input_default((_input, key) => {
17548
+ use_input_default((input, key) => {
17125
17549
  if (showDetail) {
17126
17550
  if (key.escape) setShowDetail(false);
17127
17551
  return;
@@ -17130,91 +17554,103 @@ function TeamView({ onBack }) {
17130
17554
  onBack?.();
17131
17555
  return;
17132
17556
  }
17557
+ if (input === "a") {
17558
+ setShowAddHint(true);
17559
+ setTimeout(() => setShowAddHint(false), 3e3);
17560
+ return;
17561
+ }
17562
+ if (input === "s" && selected) {
17563
+ onViewSessions?.(selected.name);
17564
+ return;
17565
+ }
17133
17566
  if (key.upArrow && selectedIdx > 0) setSelectedIdx(selectedIdx - 1);
17134
17567
  if (key.downArrow && selectedIdx < allItems.length - 1) setSelectedIdx(selectedIdx + 1);
17135
17568
  if (key.return && allItems.length > 0) setShowDetail(true);
17136
17569
  });
17137
17570
  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();
17571
+ const { withTrace: withTrace2 } = await Promise.resolve().then(() => (init_telemetry(), telemetry_exports));
17572
+ return withTrace2("tui.team.loadTeam", async () => {
17144
17573
  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,
17574
+ const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
17575
+ const roster = await loadEmployees2();
17576
+ let memoryCounts = /* @__PURE__ */ new Map();
17577
+ let projectsByEmployee = /* @__PURE__ */ new Map();
17578
+ let currentTaskByEmployee = /* @__PURE__ */ new Map();
17579
+ try {
17580
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
17581
+ const client = getClient2();
17582
+ if (client) {
17583
+ const memResult = await client.execute("SELECT agent_id, COUNT(*) as cnt FROM memories GROUP BY agent_id");
17584
+ for (const row of memResult.rows) {
17585
+ memoryCounts.set(String(row.agent_id), Number(row.cnt));
17586
+ }
17587
+ for (const emp of roster) {
17588
+ const projResult = await client.execute({
17589
+ sql: `SELECT DISTINCT project_name,
17155
17590
  MAX(CASE WHEN status = 'in_progress' THEN 1 WHEN status = 'open' THEN 2 ELSE 3 END) as urgency
17156
17591
  FROM tasks WHERE assigned_to = ? AND status IN ('open','in_progress','done')
17157
17592
  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));
17593
+ args: [emp.name]
17594
+ });
17595
+ const projects = projResult.rows.filter((r) => !DEPRECATED_PROJECTS.has(String(r.project_name))).map((r) => {
17596
+ const urgency = Number(r.urgency);
17597
+ let pStatus = "idle";
17598
+ if (urgency === 1) pStatus = "active";
17599
+ else if (urgency === 2) pStatus = "has_tasks";
17600
+ return { name: String(r.project_name), status: pStatus };
17601
+ });
17602
+ if (projects.length > 0) projectsByEmployee.set(emp.name, projects);
17603
+ }
17604
+ for (const emp of roster) {
17605
+ const taskResult = await client.execute({
17606
+ sql: "SELECT title FROM tasks WHERE assigned_to = ? AND status = 'in_progress' ORDER BY updated_at DESC LIMIT 1",
17607
+ args: [emp.name]
17608
+ });
17609
+ if (taskResult.rows.length > 0) {
17610
+ currentTaskByEmployee.set(emp.name, String(taskResult.rows[0].title));
17611
+ }
17176
17612
  }
17177
17613
  }
17614
+ } catch {
17178
17615
  }
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
- })));
17616
+ const teamData = roster.map((emp) => {
17617
+ const agentSt = getAgentStatus(emp.name);
17618
+ return {
17619
+ name: emp.name,
17620
+ role: emp.role,
17621
+ status: agentSt.label,
17622
+ activity: agentSt.label === "active" ? "Processing..." : "",
17623
+ memoryCount: memoryCounts.get(emp.name) ?? 0,
17624
+ projects: projectsByEmployee.get(emp.name),
17625
+ currentTask: currentTaskByEmployee.get(emp.name),
17626
+ sessionName: agentSt.session
17627
+ };
17628
+ });
17629
+ setMembers(teamData);
17630
+ setDbError(null);
17631
+ try {
17632
+ const { existsSync: existsSync14, readFileSync: readFileSync11 } = await import("fs");
17633
+ const { join } = await import("path");
17634
+ const home = process.env.HOME ?? "";
17635
+ const gatewayConfig = join(home, ".exe-os", "gateway.json");
17636
+ if (existsSync14(gatewayConfig)) {
17637
+ const raw = JSON.parse(readFileSync11(gatewayConfig, "utf8"));
17638
+ if (raw.agents && raw.agents.length > 0) {
17639
+ setExternals(raw.agents.map((a) => ({
17640
+ name: a.name,
17641
+ role: a.role ?? "external agent",
17642
+ status: "offline"
17643
+ })));
17644
+ }
17209
17645
  }
17646
+ } catch {
17210
17647
  }
17211
- } catch {
17648
+ } catch (err) {
17649
+ setDbError(err instanceof Error ? err.message : "Unknown error");
17650
+ } finally {
17651
+ setLoading(false);
17212
17652
  }
17213
- } catch {
17214
- setDbError(true);
17215
- } finally {
17216
- setLoading(false);
17217
- }
17653
+ });
17218
17654
  }
17219
17655
  const totalCount = members.length + externals.length;
17220
17656
  const selected = allItems[selectedIdx];
@@ -17231,7 +17667,7 @@ function TeamView({ onBack }) {
17231
17667
  /* @__PURE__ */ jsx12(Text, { children: " " }),
17232
17668
  /* @__PURE__ */ jsxs10(Text, { children: [
17233
17669
  "Role: ",
17234
- /* @__PURE__ */ jsx12(Text, { bold: true, children: selected.role })
17670
+ /* @__PURE__ */ jsx12(Text, { bold: true, color: roleBadgeColor(selected.role), children: selected.role })
17235
17671
  ] }),
17236
17672
  /* @__PURE__ */ jsxs10(Text, { children: [
17237
17673
  "Status: ",
@@ -17260,8 +17696,9 @@ function TeamView({ onBack }) {
17260
17696
  /* @__PURE__ */ jsx12(Box_default, { borderStyle: "single", borderColor: "#3D3660", paddingX: 1, alignSelf: "flex-start", children: /* @__PURE__ */ jsx12(Text, { bold: true, children: "Team Roster" }) }),
17261
17697
  /* @__PURE__ */ jsxs10(Text, { color: "#6B4C9A", children: [
17262
17698
  totalCount,
17263
- " agents | up/down navigate | Enter for details"
17699
+ " agents | \u2191\u2193 navigate | Enter details | a add | s sessions"
17264
17700
  ] }),
17701
+ showAddHint && /* @__PURE__ */ jsx12(Text, { color: "#F5D76E", children: "Run /exe-new-employee <name> from CLI to add an employee." }),
17265
17702
  !demo && orch.pendingReviews > 0 && /* @__PURE__ */ jsxs10(Text, { color: "#6B4C9A", children: [
17266
17703
  orch.pendingReviews,
17267
17704
  " review(s) pending exe attention"
@@ -17269,7 +17706,10 @@ function TeamView({ onBack }) {
17269
17706
  /* @__PURE__ */ jsx12(Text, { children: " " }),
17270
17707
  /* @__PURE__ */ jsx12(Text, { bold: true, children: "INTERNAL" }),
17271
17708
  /* @__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) => {
17709
+ loading ? /* @__PURE__ */ jsx12(Text, { color: "#6B4C9A", children: " Loading employee roster..." }) : dbError ? /* @__PURE__ */ jsxs10(Text, { color: "#C91B00", children: [
17710
+ " Failed to load roster: ",
17711
+ dbError
17712
+ ] }) : 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
17713
  const isSelected = i === selectedIdx;
17274
17714
  const orchEmp = orchEmployeeMap.get(m.name);
17275
17715
  return /* @__PURE__ */ jsxs10(
@@ -17285,7 +17725,7 @@ function TeamView({ onBack }) {
17285
17725
  /* @__PURE__ */ jsxs10(Box_default, { gap: 1, children: [
17286
17726
  /* @__PURE__ */ jsx12(StatusDot, { status: m.status, showLabel: false }),
17287
17727
  /* @__PURE__ */ jsx12(Text, { bold: true, color: isSelected ? "#F5D76E" : "#F0EDE8", children: m.name }),
17288
- /* @__PURE__ */ jsxs10(Text, { color: isSelected ? "#F5D76E" : "#6B4C9A", children: [
17728
+ /* @__PURE__ */ jsxs10(Text, { color: isSelected ? "#F5D76E" : roleBadgeColor(m.role), children: [
17289
17729
  "(",
17290
17730
  m.role,
17291
17731
  ")"
@@ -17358,6 +17798,33 @@ import TextInput2 from "ink-text-input";
17358
17798
  import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
17359
17799
  var PANELS = ["Workspaces", "Documents", "Chat"];
17360
17800
  var MAX_VISIBLE_MESSAGES = 12;
17801
+ var DEMO_SEARCH_RESULTS = [
17802
+ { 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" },
17803
+ { 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" },
17804
+ { 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" },
17805
+ { 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" },
17806
+ { 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" }
17807
+ ];
17808
+ function agentColor(agentId) {
17809
+ switch (agentId.toLowerCase()) {
17810
+ case "exe":
17811
+ return "#F5D76E";
17812
+ case "yoshi":
17813
+ return "#3B82F6";
17814
+ case "mari":
17815
+ return "#6B4C9A";
17816
+ default:
17817
+ return "#F0EDE8";
17818
+ }
17819
+ }
17820
+ function formatTimeAgo(iso) {
17821
+ const diff2 = Date.now() - new Date(iso).getTime();
17822
+ const hours = Math.floor(diff2 / 36e5);
17823
+ if (hours < 1) return "just now";
17824
+ if (hours < 24) return `${hours}h ago`;
17825
+ const days = Math.floor(hours / 24);
17826
+ return `${days}d ago`;
17827
+ }
17361
17828
  function WikiView({ onBack }) {
17362
17829
  const demo = useDemo();
17363
17830
  const [activePanel, setActivePanel] = useState13("Workspaces");
@@ -17368,6 +17835,12 @@ function WikiView({ onBack }) {
17368
17835
  const [chatHistory, setChatHistory] = useState13([]);
17369
17836
  const [chatInput, setChatInput] = useState13("");
17370
17837
  const [chatFocused, setChatFocused] = useState13(false);
17838
+ const [searchMode, setSearchMode] = useState13(false);
17839
+ const [searchQuery, setSearchQuery] = useState13("");
17840
+ const [searchResults, setSearchResults] = useState13([]);
17841
+ const [searchLoading, setSearchLoading] = useState13(false);
17842
+ const [selectedResultIdx, setSelectedResultIdx] = useState13(0);
17843
+ const [expandedResultIdx, setExpandedResultIdx] = useState13(null);
17371
17844
  const [loading, setLoading] = useState13(true);
17372
17845
  const [connected, setConnected] = useState13(false);
17373
17846
  const [wikiUrl, setWikiUrl] = useState13("");
@@ -17461,6 +17934,55 @@ function WikiView({ onBack }) {
17461
17934
  setSending(false);
17462
17935
  }
17463
17936
  }
17937
+ async function searchMemories2(query) {
17938
+ if (!query.trim()) {
17939
+ setSearchResults([]);
17940
+ return;
17941
+ }
17942
+ if (demo) {
17943
+ const q = query.toLowerCase();
17944
+ setSearchResults(
17945
+ DEMO_SEARCH_RESULTS.filter((r) => r.rawText.toLowerCase().includes(q))
17946
+ );
17947
+ return;
17948
+ }
17949
+ setSearchLoading(true);
17950
+ try {
17951
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
17952
+ const client = getClient2();
17953
+ const result = await client.execute({
17954
+ sql: `SELECT id, agent_id, raw_text, timestamp, project_name
17955
+ FROM memories
17956
+ WHERE raw_text LIKE ? AND COALESCE(status, 'active') = 'active'
17957
+ ORDER BY timestamp DESC LIMIT 20`,
17958
+ args: [`%${query}%`]
17959
+ });
17960
+ setSearchResults(
17961
+ result.rows.map((r) => ({
17962
+ id: String(r.id),
17963
+ agentId: String(r.agent_id),
17964
+ rawText: String(r.raw_text),
17965
+ timestamp: String(r.timestamp),
17966
+ projectName: String(r.project_name ?? "")
17967
+ }))
17968
+ );
17969
+ } catch {
17970
+ setSearchResults([]);
17971
+ } finally {
17972
+ setSearchLoading(false);
17973
+ }
17974
+ }
17975
+ useEffect15(() => {
17976
+ if (!searchMode) return;
17977
+ const timer = setTimeout(() => {
17978
+ searchMemories2(searchQuery);
17979
+ }, 300);
17980
+ return () => clearTimeout(timer);
17981
+ }, [searchQuery, searchMode]);
17982
+ useEffect15(() => {
17983
+ setSelectedResultIdx(0);
17984
+ setExpandedResultIdx(null);
17985
+ }, [searchResults]);
17464
17986
  async function selectWorkspace(idx) {
17465
17987
  setSelectedWorkspaceIdx(idx);
17466
17988
  const ws = workspaces[idx];
@@ -17481,6 +18003,32 @@ function WikiView({ onBack }) {
17481
18003
  }
17482
18004
  }
17483
18005
  use_input_default((input, key) => {
18006
+ if (searchMode && !chatFocused) {
18007
+ if (key.escape) {
18008
+ setSearchMode(false);
18009
+ setSearchQuery("");
18010
+ setSearchResults([]);
18011
+ setExpandedResultIdx(null);
18012
+ return;
18013
+ }
18014
+ if (key.upArrow && selectedResultIdx > 0) {
18015
+ setSelectedResultIdx(selectedResultIdx - 1);
18016
+ setExpandedResultIdx(null);
18017
+ return;
18018
+ }
18019
+ if (key.downArrow && selectedResultIdx < searchResults.length - 1) {
18020
+ setSelectedResultIdx(selectedResultIdx + 1);
18021
+ setExpandedResultIdx(null);
18022
+ return;
18023
+ }
18024
+ if (key.return && searchResults.length > 0) {
18025
+ setExpandedResultIdx(
18026
+ expandedResultIdx === selectedResultIdx ? null : selectedResultIdx
18027
+ );
18028
+ return;
18029
+ }
18030
+ return;
18031
+ }
17484
18032
  if (chatFocused) {
17485
18033
  if (key.escape) {
17486
18034
  setChatFocused(false);
@@ -17492,6 +18040,13 @@ function WikiView({ onBack }) {
17492
18040
  }
17493
18041
  return;
17494
18042
  }
18043
+ if (input === "/" && activePanel !== "Chat") {
18044
+ setSearchMode(true);
18045
+ setSearchQuery("");
18046
+ setSearchResults(demo ? DEMO_SEARCH_RESULTS : []);
18047
+ setExpandedResultIdx(null);
18048
+ return;
18049
+ }
17495
18050
  if (key.leftArrow) {
17496
18051
  const panelIdx = PANELS.indexOf(activePanel);
17497
18052
  if (panelIdx === 0) {
@@ -17569,9 +18124,53 @@ function WikiView({ onBack }) {
17569
18124
  /* @__PURE__ */ jsx13(Text, { bold: true, children: selectedWs.name })
17570
18125
  ] }) : null
17571
18126
  ] }),
17572
- /* @__PURE__ */ jsx13(Text, { color: "#6B4C9A", children: "\u2190\u2192 switch panels | \u2191\u2193 navigate | / chat | Enter select" }),
18127
+ /* @__PURE__ */ jsx13(Text, { color: "#6B4C9A", children: "\u2190\u2192 switch panels | \u2191\u2193 navigate | / search memories | Enter select" }),
17573
18128
  /* @__PURE__ */ jsx13(Text, { children: " " }),
17574
- /* @__PURE__ */ jsxs11(Box_default, { flexGrow: 1, gap: 1, children: [
18129
+ searchMode ? /* @__PURE__ */ jsxs11(Box_default, { flexDirection: "column", flexGrow: 1, children: [
18130
+ /* @__PURE__ */ jsxs11(Box_default, { paddingX: 1, children: [
18131
+ /* @__PURE__ */ jsx13(Text, { color: "#F5D76E", children: "Search: " }),
18132
+ /* @__PURE__ */ jsx13(
18133
+ TextInput2,
18134
+ {
18135
+ value: searchQuery,
18136
+ onChange: setSearchQuery,
18137
+ placeholder: "search memories...",
18138
+ focus: true
18139
+ }
18140
+ ),
18141
+ /* @__PURE__ */ jsx13(Text, { color: "#6B4C9A", children: " (esc to close)" })
18142
+ ] }),
18143
+ /* @__PURE__ */ jsx13(Text, { children: " " }),
18144
+ 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) => {
18145
+ const isSelected = i === selectedResultIdx;
18146
+ const isExpanded = i === expandedResultIdx;
18147
+ const snippet = result.rawText.slice(0, 80);
18148
+ return /* @__PURE__ */ jsxs11(Box_default, { flexDirection: "column", children: [
18149
+ /* @__PURE__ */ jsxs11(
18150
+ Text,
18151
+ {
18152
+ backgroundColor: isSelected ? "#6B4C9A" : void 0,
18153
+ color: isSelected ? "#F5D76E" : void 0,
18154
+ children: [
18155
+ " ",
18156
+ /* @__PURE__ */ jsx13(Text, { color: isSelected ? "#F5D76E" : agentColor(result.agentId), bold: true, children: result.agentId.padEnd(8) }),
18157
+ /* @__PURE__ */ jsxs11(Text, { color: isSelected ? "#F5D76E" : "#F0EDE8", children: [
18158
+ snippet,
18159
+ result.rawText.length > 80 ? "..." : ""
18160
+ ] }),
18161
+ " ",
18162
+ /* @__PURE__ */ jsx13(Text, { color: isSelected ? "#F5D76E" : "#3D3660", dimColor: !isSelected, children: formatTimeAgo(result.timestamp) })
18163
+ ]
18164
+ }
18165
+ ),
18166
+ isExpanded ? /* @__PURE__ */ jsx13(Box_default, { paddingX: 4, marginBottom: 1, children: /* @__PURE__ */ jsx13(Text, { color: "#F0EDE8", wrap: "wrap", children: result.rawText }) }) : null
18167
+ ] }, result.id);
18168
+ }),
18169
+ searchResults.length > 0 ? /* @__PURE__ */ jsxs11(Fragment4, { children: [
18170
+ /* @__PURE__ */ jsx13(Text, { children: " " }),
18171
+ /* @__PURE__ */ jsx13(Text, { color: "#3D3660", children: " \u2191\u2193 navigate | Enter expand | Esc close" })
18172
+ ] }) : null
18173
+ ] }) : /* @__PURE__ */ jsxs11(Box_default, { flexGrow: 1, gap: 1, children: [
17575
18174
  /* @__PURE__ */ jsxs11(Box_default, { flexDirection: "column", width: "25%", children: [
17576
18175
  /* @__PURE__ */ jsxs11(Text, { bold: true, backgroundColor: panelHeaderBg("Workspaces"), color: panelHeaderColor("Workspaces"), children: [
17577
18176
  activePanel === "Workspaces" ? "\u25B8 " : " ",
@@ -17655,50 +18254,41 @@ function WikiView({ onBack }) {
17655
18254
  }
17656
18255
 
17657
18256
  // src/tui/views/Settings.tsx
17658
- import React24, { useState as useState14, useEffect as useEffect16 } from "react";
18257
+ import React24, { useState as useState14, useEffect as useEffect16, useCallback as useCallback7 } from "react";
17659
18258
  import { Fragment as Fragment5, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
17660
- var SECTION_NAMES = ["Providers", "Memory", "Runtime", "Employee Models", "Integrations"];
18259
+ function maskSecret(value) {
18260
+ if (value.length <= 10) return "****";
18261
+ return `${value.slice(0, 6)}...${value.slice(-4)}`;
18262
+ }
18263
+ var SECTION_NAMES = ["Providers", "Cloud Sync", "License", "System", "Gateway"];
18264
+ var GREEN = "#22C55E";
18265
+ var DIM = "#6B4C9A";
18266
+ var YELLOW = "#F5D76E";
17661
18267
  function SettingsView({ onBack }) {
17662
18268
  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([]);
18269
+ const [cloud, setCloud] = useState14({ configured: false, endpoint: "", apiKey: "" });
18270
+ const [license, setLicense] = useState14({ valid: false, plan: "checking...", expiresAt: null, deviceLimit: 0, employeeLimit: 0, memoryLimit: 0 });
18271
+ const [system, setSystem] = useState14({ daemon: "unknown", version: "...", dbPath: "...", embeddingModel: "...", encryption: "checking..." });
18272
+ const [gateway, setGateway] = useState14({ adapters: [] });
17678
18273
  const [selectedSection, setSelectedSection] = useState14(0);
17679
18274
  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" }
18275
+ const loadSettings = useCallback7(async () => {
18276
+ setLoading(true);
18277
+ const envKeys = [
18278
+ ["Anthropic", "ANTHROPIC_API_KEY"],
18279
+ ["OpenCode", "OPENCODE_API_KEY"],
18280
+ ["Gemini", "GEMINI_API_KEY"],
18281
+ ["OpenAI", "OPENAI_API_KEY"],
18282
+ ["Chutes", "CHUTES_API_KEY"]
17701
18283
  ];
18284
+ const providerList = envKeys.map(([name, envVar]) => {
18285
+ const val = process.env[envVar];
18286
+ return {
18287
+ name,
18288
+ configured: !!val,
18289
+ detail: val ? maskSecret(val) : "not configured"
18290
+ };
18291
+ });
17702
18292
  try {
17703
18293
  const { execSync: execSync10 } = await import("child_process");
17704
18294
  execSync10("curl -s --max-time 1 http://localhost:11434/api/tags", { timeout: 2e3 });
@@ -17710,112 +18300,101 @@ function SettingsView({ onBack }) {
17710
18300
  try {
17711
18301
  const { existsSync: existsSync14 } = await import("fs");
17712
18302
  const { join } = await import("path");
17713
- const home = process.env.HOME ?? "";
17714
- const hasKey = existsSync14(join(home, ".exe-os", "master.key"));
17715
18303
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
17716
18304
  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
18305
  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
- }
18306
+ const hasKey = existsSync14(join(home, ".exe-os", "master.key"));
18307
+ if (cfg.cloud) {
18308
+ setCloud({
18309
+ configured: true,
18310
+ endpoint: cfg.cloud.endpoint,
18311
+ apiKey: maskSecret(cfg.cloud.apiKey)
18312
+ });
18313
+ } else {
18314
+ setCloud({ configured: false, endpoint: "", apiKey: "" });
17751
18315
  }
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
- }
18316
+ const pidPath = join(home, ".exe-os", "exed.pid");
18317
+ let daemon = "unknown";
18318
+ try {
18319
+ daemon = existsSync14(pidPath) ? "running" : "stopped";
18320
+ } catch {
17775
18321
  }
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)) {
18322
+ let version = "unknown";
18323
+ try {
18324
+ const { readFileSync: readFileSync11 } = await import("fs");
18325
+ const { createRequire } = await import("module");
18326
+ const require2 = createRequire(import.meta.url);
18327
+ const pkgPath = require2.resolve("@askexenow/exe-os/package.json");
18328
+ const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
18329
+ version = pkg.version;
18330
+ } catch {
17785
18331
  try {
17786
- const lic = JSON.parse(readFileSync11(licensePath, "utf8"));
17787
- setLicense({ valid: lic.valid !== false, detail: lic.plan ?? "licensed" });
18332
+ const { readFileSync: readFileSync11 } = await import("fs");
18333
+ const { join: joinPath } = await import("path");
18334
+ const pkg = JSON.parse(readFileSync11(joinPath(process.cwd(), "package.json"), "utf8"));
18335
+ version = pkg.version;
17788
18336
  } catch {
17789
- setLicense({ valid: false, detail: "invalid license file" });
17790
18337
  }
17791
- } else {
17792
- setLicense({ valid: false, detail: "no license" });
17793
18338
  }
18339
+ setSystem({
18340
+ daemon,
18341
+ version,
18342
+ dbPath: cfg.dbPath,
18343
+ embeddingModel: cfg.modelFile ?? "unknown",
18344
+ encryption: hasKey ? "SQLCipher AES-256" : "not configured"
18345
+ });
17794
18346
  } catch {
17795
18347
  }
17796
18348
  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
- }
18349
+ const { checkLicense: checkLicense2 } = await Promise.resolve().then(() => (init_license(), license_exports));
18350
+ const lic = await checkLicense2();
18351
+ setLicense({
18352
+ valid: lic.valid,
18353
+ plan: lic.plan.toUpperCase(),
18354
+ expiresAt: lic.expiresAt,
18355
+ deviceLimit: lic.deviceLimit,
18356
+ employeeLimit: lic.employeeLimit,
18357
+ memoryLimit: lic.memoryLimit
18358
+ });
17806
18359
  } catch {
18360
+ setLicense({ valid: false, plan: "FREE", expiresAt: null, deviceLimit: 1, employeeLimit: 1, memoryLimit: 5e3 });
17807
18361
  }
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;
18362
+ const gatewayAdapters = [
18363
+ { name: "WhatsApp", configured: !!process.env.WHATSAPP_API_TOKEN || !!process.env.WHATSAPP_PHONE_NUMBER_ID },
18364
+ { name: "Telegram", configured: !!process.env.TELEGRAM_BOT_TOKEN },
18365
+ { name: "Discord", configured: !!process.env.DISCORD_BOT_TOKEN },
18366
+ { name: "Slack", configured: !!process.env.SLACK_BOT_TOKEN }
18367
+ ];
18368
+ setGateway({ adapters: gatewayAdapters });
18369
+ setLoading(false);
18370
+ }, []);
18371
+ useEffect16(() => {
18372
+ loadSettings();
18373
+ }, [loadSettings]);
18374
+ use_input_default((input, key) => {
18375
+ if (key.leftArrow) {
18376
+ onBack?.();
18377
+ return;
18378
+ }
18379
+ if (key.upArrow && selectedSection > 0) setSelectedSection(selectedSection - 1);
18380
+ if (key.downArrow && selectedSection < SECTION_NAMES.length - 1) setSelectedSection(selectedSection + 1);
18381
+ if (input === "r") {
18382
+ loadSettings();
18383
+ }
18384
+ });
18385
+ const statusDot = (ok) => ok ? "\u2022" : "\u2022";
18386
+ const statusColor = (ok) => ok ? GREEN : DIM;
18387
+ const sectionColor = (idx) => selectedSection === idx ? YELLOW : void 0;
18388
+ const sectionBg = (idx) => selectedSection === idx ? DIM : void 0;
17812
18389
  const sectionMarker = (idx) => selectedSection === idx ? "\u25B8 " : " ";
18390
+ const anyGateway = gateway.adapters.some((a) => a.configured);
18391
+ const formatLimit = (n) => n === -1 ? "unlimited" : n.toLocaleString();
17813
18392
  return /* @__PURE__ */ jsxs12(Box_default, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: [
17814
18393
  /* @__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" }),
18394
+ /* @__PURE__ */ jsx14(Text, { color: DIM, children: "\u2191\u2193 navigate sections r refresh" }),
17816
18395
  /* @__PURE__ */ jsx14(Text, { children: " " }),
17817
18396
  loading && /* @__PURE__ */ jsxs12(Fragment5, { children: [
17818
- /* @__PURE__ */ jsx14(Text, { color: "#6B4C9A", children: "Loading settings..." }),
18397
+ /* @__PURE__ */ jsx14(Text, { color: DIM, children: "Loading settings..." }),
17819
18398
  /* @__PURE__ */ jsx14(Text, { children: " " })
17820
18399
  ] }),
17821
18400
  /* @__PURE__ */ jsxs12(Text, { bold: true, backgroundColor: sectionBg(0), color: sectionColor(0), children: [
@@ -17825,6 +18404,8 @@ function SettingsView({ onBack }) {
17825
18404
  /* @__PURE__ */ jsx14(Text, { children: " " }),
17826
18405
  providers.map((p) => /* @__PURE__ */ jsxs12(Text, { children: [
17827
18406
  " ",
18407
+ /* @__PURE__ */ jsx14(Text, { color: statusColor(p.configured), children: statusDot(p.configured) }),
18408
+ " ",
17828
18409
  p.name,
17829
18410
  ": ",
17830
18411
  /* @__PURE__ */ jsx14(Text, { color: statusColor(p.configured), children: p.detail })
@@ -17832,109 +18413,301 @@ function SettingsView({ onBack }) {
17832
18413
  /* @__PURE__ */ jsx14(Text, { children: " " }),
17833
18414
  /* @__PURE__ */ jsxs12(Text, { bold: true, backgroundColor: sectionBg(1), color: sectionColor(1), children: [
17834
18415
  sectionMarker(1),
17835
- "Memory"
18416
+ "Cloud Sync"
17836
18417
  ] }),
17837
18418
  /* @__PURE__ */ jsx14(Text, { children: " " }),
17838
- /* @__PURE__ */ jsxs12(Text, { children: [
18419
+ cloud.configured ? /* @__PURE__ */ jsxs12(Fragment5, { children: [
18420
+ /* @__PURE__ */ jsxs12(Text, { children: [
18421
+ " ",
18422
+ /* @__PURE__ */ jsx14(Text, { color: GREEN, children: statusDot(true) }),
18423
+ " Status: ",
18424
+ /* @__PURE__ */ jsx14(Text, { color: GREEN, children: "connected" })
18425
+ ] }),
18426
+ /* @__PURE__ */ jsxs12(Text, { children: [
18427
+ " ",
18428
+ "Endpoint: ",
18429
+ /* @__PURE__ */ jsx14(Text, { color: GREEN, children: cloud.endpoint })
18430
+ ] }),
18431
+ /* @__PURE__ */ jsxs12(Text, { children: [
18432
+ " ",
18433
+ "API key: ",
18434
+ /* @__PURE__ */ jsx14(Text, { color: DIM, children: cloud.apiKey })
18435
+ ] })
18436
+ ] }) : /* @__PURE__ */ jsxs12(Text, { children: [
17839
18437
  " ",
17840
- "Encryption: ",
17841
- /* @__PURE__ */ jsx14(Text, { color: memory.encryption.includes("AES") ? "#22C55E" : "#6B4C9A", children: memory.encryption })
18438
+ /* @__PURE__ */ jsx14(Text, { color: DIM, children: statusDot(false) }),
18439
+ " ",
18440
+ /* @__PURE__ */ jsx14(Text, { color: DIM, children: "Not configured \u2014 run /exe-cloud" })
17842
18441
  ] }),
17843
- /* @__PURE__ */ jsxs12(Text, { children: [
17844
- " ",
17845
- "Embedding: ",
17846
- /* @__PURE__ */ jsx14(Text, { color: "#22C55E", children: memory.embeddingModel })
18442
+ /* @__PURE__ */ jsx14(Text, { children: " " }),
18443
+ /* @__PURE__ */ jsxs12(Text, { bold: true, backgroundColor: sectionBg(2), color: sectionColor(2), children: [
18444
+ sectionMarker(2),
18445
+ "License"
17847
18446
  ] }),
18447
+ /* @__PURE__ */ jsx14(Text, { children: " " }),
17848
18448
  /* @__PURE__ */ jsxs12(Text, { children: [
17849
18449
  " ",
17850
- "Cloud sync: ",
17851
- /* @__PURE__ */ jsx14(Text, { color: memory.cloudSync === "enabled" ? "#22C55E" : "#6B4C9A", children: memory.cloudSync })
18450
+ "Plan: ",
18451
+ /* @__PURE__ */ jsx14(Text, { color: license.plan === "FREE" ? YELLOW : GREEN, children: license.plan })
17852
18452
  ] }),
17853
- /* @__PURE__ */ jsxs12(Text, { children: [
18453
+ license.expiresAt && /* @__PURE__ */ jsxs12(Text, { children: [
17854
18454
  " ",
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"
18455
+ "Expires: ",
18456
+ /* @__PURE__ */ jsx14(Text, { color: GREEN, children: license.expiresAt.split("T")[0] })
17862
18457
  ] }),
17863
- /* @__PURE__ */ jsx14(Text, { children: " " }),
17864
18458
  /* @__PURE__ */ jsxs12(Text, { children: [
17865
18459
  " ",
17866
- "Consolidation: ",
17867
- /* @__PURE__ */ jsx14(Text, { color: runtime.consolidation ? "#22C55E" : "#6B4C9A", children: runtime.consolidation ? "enabled" : "disabled" }),
17868
- " (",
17869
- runtime.consolidationModel,
17870
- ")"
18460
+ "Devices: ",
18461
+ /* @__PURE__ */ jsx14(Text, { color: GREEN, children: formatLimit(license.deviceLimit) })
17871
18462
  ] }),
17872
18463
  /* @__PURE__ */ jsxs12(Text, { children: [
17873
18464
  " ",
17874
- "Skill learning: ",
17875
- /* @__PURE__ */ jsx14(Text, { color: runtime.skillLearning ? "#22C55E" : "#6B4C9A", children: runtime.skillLearning ? "enabled" : "disabled" }),
17876
- " (threshold: ",
17877
- runtime.skillThreshold,
17878
- ")"
18465
+ "Employees: ",
18466
+ /* @__PURE__ */ jsx14(Text, { color: GREEN, children: formatLimit(license.employeeLimit) })
17879
18467
  ] }),
17880
18468
  /* @__PURE__ */ jsxs12(Text, { children: [
17881
18469
  " ",
17882
- "Failover chain: ",
17883
- /* @__PURE__ */ jsx14(Text, { color: "#22C55E", children: runtime.failoverChain.join(" \u2192 ") })
18470
+ "Memory limit: ",
18471
+ /* @__PURE__ */ jsx14(Text, { color: GREEN, children: formatLimit(license.memoryLimit) })
17884
18472
  ] }),
17885
18473
  /* @__PURE__ */ jsx14(Text, { children: " " }),
17886
18474
  /* @__PURE__ */ jsxs12(Text, { bold: true, backgroundColor: sectionBg(3), color: sectionColor(3), children: [
17887
18475
  sectionMarker(3),
17888
- "Employee Models"
18476
+ "System"
17889
18477
  ] }),
17890
18478
  /* @__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: [
18479
+ /* @__PURE__ */ jsxs12(Text, { children: [
17895
18480
  " ",
17896
- "No employees configured"
17897
- ] }) : employeeModels.map((e) => /* @__PURE__ */ jsxs12(Text, { children: [
18481
+ /* @__PURE__ */ jsx14(Text, { color: system.daemon === "running" ? GREEN : DIM, children: statusDot(system.daemon === "running") }),
18482
+ " Daemon: ",
18483
+ /* @__PURE__ */ jsx14(Text, { color: system.daemon === "running" ? GREEN : DIM, children: system.daemon })
18484
+ ] }),
18485
+ /* @__PURE__ */ jsxs12(Text, { children: [
17898
18486
  " ",
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"
18487
+ "Version: ",
18488
+ /* @__PURE__ */ jsx14(Text, { color: GREEN, children: system.version })
17912
18489
  ] }),
17913
- /* @__PURE__ */ jsx14(Text, { children: " " }),
17914
18490
  /* @__PURE__ */ jsxs12(Text, { children: [
17915
18491
  " ",
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
- ] })
18492
+ "Encryption: ",
18493
+ /* @__PURE__ */ jsx14(Text, { color: system.encryption.includes("AES") ? GREEN : DIM, children: system.encryption })
17922
18494
  ] }),
17923
18495
  /* @__PURE__ */ jsxs12(Text, { children: [
17924
18496
  " ",
17925
- "License: ",
17926
- /* @__PURE__ */ jsx14(Text, { color: license.valid ? "#22C55E" : "#6B4C9A", children: license.detail })
18497
+ "Embedding: ",
18498
+ /* @__PURE__ */ jsx14(Text, { color: GREEN, children: system.embeddingModel })
17927
18499
  ] }),
17928
18500
  /* @__PURE__ */ jsxs12(Text, { children: [
17929
18501
  " ",
17930
- "Cloud sync: ",
17931
- /* @__PURE__ */ jsx14(Text, { color: cloudSync.configured ? "#22C55E" : "#6B4C9A", children: cloudSync.detail })
18502
+ "DB path: ",
18503
+ /* @__PURE__ */ jsx14(Text, { color: DIM, children: system.dbPath })
18504
+ ] }),
18505
+ /* @__PURE__ */ jsx14(Text, { children: " " }),
18506
+ /* @__PURE__ */ jsxs12(Text, { bold: true, backgroundColor: sectionBg(4), color: sectionColor(4), children: [
18507
+ sectionMarker(4),
18508
+ "Gateway"
18509
+ ] }),
18510
+ /* @__PURE__ */ jsx14(Text, { children: " " }),
18511
+ anyGateway ? gateway.adapters.map((a) => /* @__PURE__ */ jsxs12(Text, { children: [
18512
+ " ",
18513
+ /* @__PURE__ */ jsx14(Text, { color: statusColor(a.configured), children: statusDot(a.configured) }),
18514
+ " ",
18515
+ a.name,
18516
+ ": ",
18517
+ /* @__PURE__ */ jsx14(Text, { color: statusColor(a.configured), children: a.configured ? "configured" : "not configured" })
18518
+ ] }, a.name)) : /* @__PURE__ */ jsxs12(Text, { children: [
18519
+ " ",
18520
+ /* @__PURE__ */ jsx14(Text, { color: DIM, children: statusDot(false) }),
18521
+ " ",
18522
+ /* @__PURE__ */ jsx14(Text, { color: DIM, children: "No gateway configured" })
17932
18523
  ] })
17933
18524
  ] });
17934
18525
  }
17935
18526
 
17936
- // src/tui/App.tsx
18527
+ // src/tui/views/DebugPanel.tsx
18528
+ import React25, { useState as useState15, useEffect as useEffect17 } from "react";
18529
+ init_state_bus();
17937
18530
  import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
18531
+ var MAX_EVENTS = 50;
18532
+ var DISPLAY_EVENTS = 20;
18533
+ var EVENT_COLORS = {
18534
+ task_completed: "#22C55E",
18535
+ review_created: "#F59E0B",
18536
+ employee_status: "#3B82F6",
18537
+ memory_stored: "#8B5CF6",
18538
+ session_started: "#06B6D4",
18539
+ session_ended: "#EF4444",
18540
+ gateway_message: "#EC4899"
18541
+ };
18542
+ function formatEvent(e) {
18543
+ switch (e.type) {
18544
+ case "task_completed":
18545
+ return `${e.employee}: "${e.result.slice(0, 60)}"`;
18546
+ case "review_created":
18547
+ return `${e.employee} \u2192 ${e.reviewer}`;
18548
+ case "employee_status":
18549
+ return `${e.employee}: ${e.status}`;
18550
+ case "memory_stored":
18551
+ return `${e.agentId} [${e.project}]`;
18552
+ case "session_started":
18553
+ return `${e.employee} (${e.sessionId.slice(0, 8)})`;
18554
+ case "session_ended":
18555
+ return `${e.employee} (${e.sessionId.slice(0, 8)})`;
18556
+ case "gateway_message":
18557
+ return `${e.platform}/${e.senderId} \u2192 ${e.botId}`;
18558
+ }
18559
+ }
18560
+ function DebugPanel() {
18561
+ const [events, setEvents] = useState15([]);
18562
+ useEffect17(() => {
18563
+ let counter = 0;
18564
+ const handler = (event) => {
18565
+ setEvents((prev) => {
18566
+ const next = [...prev, { event, id: counter++ }];
18567
+ return next.slice(-MAX_EVENTS);
18568
+ });
18569
+ };
18570
+ orgBus.onAny(handler);
18571
+ return () => {
18572
+ orgBus.offAny(handler);
18573
+ };
18574
+ }, []);
18575
+ return /* @__PURE__ */ jsxs13(Box_default, { flexDirection: "column", borderStyle: "single", borderColor: "#6B4C9A", flexGrow: 1, paddingX: 1, children: [
18576
+ /* @__PURE__ */ jsx15(Text, { bold: true, color: "#F5D76E", children: "Debug \u2014 Live Events" }),
18577
+ /* @__PURE__ */ jsx15(Text, { color: "#3D3660", children: "\u2500".repeat(40) }),
18578
+ events.length === 0 ? /* @__PURE__ */ jsx15(Text, { color: "#3D3660", children: "Waiting for events..." }) : events.slice(-DISPLAY_EVENTS).map(({ event, id }) => {
18579
+ const time = event.timestamp?.slice(11, 19) ?? "??:??:??";
18580
+ const color = EVENT_COLORS[event.type] ?? "#6B4C9A";
18581
+ const summary = formatEvent(event);
18582
+ return /* @__PURE__ */ jsxs13(Text, { color, children: [
18583
+ "[",
18584
+ time,
18585
+ "] ",
18586
+ event.type,
18587
+ " \u2014 ",
18588
+ summary
18589
+ ] }, id);
18590
+ })
18591
+ ] });
18592
+ }
18593
+
18594
+ // src/tui/components/HelpOverlay.tsx
18595
+ import React26 from "react";
18596
+ import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
18597
+ function HelpOverlay({ tabName, shortcuts }) {
18598
+ return /* @__PURE__ */ jsxs14(
18599
+ Box_default,
18600
+ {
18601
+ flexDirection: "column",
18602
+ borderStyle: "double",
18603
+ borderColor: "#6B4C9A",
18604
+ paddingX: 2,
18605
+ paddingY: 1,
18606
+ width: 50,
18607
+ alignSelf: "center",
18608
+ children: [
18609
+ /* @__PURE__ */ jsxs14(Text, { bold: true, color: "#F5D76E", children: [
18610
+ "Keyboard Shortcuts \u2014 ",
18611
+ tabName
18612
+ ] }),
18613
+ /* @__PURE__ */ jsx16(Text, { children: " " }),
18614
+ shortcuts.map((s, i) => /* @__PURE__ */ jsxs14(Box_default, { gap: 1, children: [
18615
+ /* @__PURE__ */ jsx16(Text, { color: "#F5D76E", bold: true, children: s.key.padEnd(10) }),
18616
+ /* @__PURE__ */ jsx16(Text, { color: "#F0EDE8", children: s.description })
18617
+ ] }, i)),
18618
+ /* @__PURE__ */ jsx16(Text, { children: " " }),
18619
+ /* @__PURE__ */ jsx16(Text, { bold: true, color: "#F5D76E", children: "Global" }),
18620
+ /* @__PURE__ */ jsx16(Text, { children: " " }),
18621
+ /* @__PURE__ */ jsxs14(Box_default, { gap: 1, children: [
18622
+ /* @__PURE__ */ jsx16(Text, { color: "#F5D76E", bold: true, children: "1-7".padEnd(10) }),
18623
+ /* @__PURE__ */ jsx16(Text, { color: "#F0EDE8", children: "Switch tabs" })
18624
+ ] }),
18625
+ /* @__PURE__ */ jsxs14(Box_default, { gap: 1, children: [
18626
+ /* @__PURE__ */ jsx16(Text, { color: "#F5D76E", bold: true, children: "q".padEnd(10) }),
18627
+ /* @__PURE__ */ jsx16(Text, { color: "#F0EDE8", children: "Quit" })
18628
+ ] }),
18629
+ /* @__PURE__ */ jsxs14(Box_default, { gap: 1, children: [
18630
+ /* @__PURE__ */ jsx16(Text, { color: "#F5D76E", bold: true, children: "?".padEnd(10) }),
18631
+ /* @__PURE__ */ jsx16(Text, { color: "#F0EDE8", children: "Toggle this help" })
18632
+ ] }),
18633
+ /* @__PURE__ */ jsxs14(Box_default, { gap: 1, children: [
18634
+ /* @__PURE__ */ jsx16(Text, { color: "#F5D76E", bold: true, children: "Esc".padEnd(10) }),
18635
+ /* @__PURE__ */ jsx16(Text, { color: "#F0EDE8", children: "Back to sidebar" })
18636
+ ] }),
18637
+ /* @__PURE__ */ jsx16(Text, { children: " " }),
18638
+ /* @__PURE__ */ jsx16(Text, { color: "#6B4C9A", children: "Press ? to close" })
18639
+ ]
18640
+ }
18641
+ );
18642
+ }
18643
+
18644
+ // src/tui/App.tsx
18645
+ import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
18646
+ var TAB_SHORTCUTS = {
18647
+ "command-center": {
18648
+ name: "Command Center",
18649
+ shortcuts: [
18650
+ { key: "\u2191\u2193", description: "Navigate projects" },
18651
+ { key: "Enter", description: "Select project" },
18652
+ { key: "/", description: "Enter chat mode" },
18653
+ { key: "n", description: "New task" }
18654
+ ]
18655
+ },
18656
+ sessions: {
18657
+ name: "Sessions",
18658
+ shortcuts: [
18659
+ { key: "\u2191\u2193", description: "Navigate sessions" },
18660
+ { key: "Enter", description: "View session output" },
18661
+ { key: "k", description: "Kill session" },
18662
+ { key: "r", description: "Restart agent" }
18663
+ ]
18664
+ },
18665
+ tasks: {
18666
+ name: "Tasks",
18667
+ shortcuts: [
18668
+ { key: "\u2191\u2193", description: "Navigate tasks" },
18669
+ { key: "Enter", description: "View task detail" },
18670
+ { key: "n", description: "Create new task" },
18671
+ { key: "f", description: "Cycle status filter" },
18672
+ { key: "p", description: "Filter by priority" }
18673
+ ]
18674
+ },
18675
+ team: {
18676
+ name: "Team",
18677
+ shortcuts: [
18678
+ { key: "\u2191\u2193", description: "Navigate employees" },
18679
+ { key: "Enter", description: "View detail" },
18680
+ { key: "a", description: "Add employee" },
18681
+ { key: "s", description: "View sessions" }
18682
+ ]
18683
+ },
18684
+ gateway: {
18685
+ name: "Gateway",
18686
+ shortcuts: [
18687
+ { key: "\u2191\u2193", description: "Navigate sections" },
18688
+ { key: "f", description: "Cycle platform filter" },
18689
+ { key: "\u2192", description: "Next conversation" },
18690
+ { key: "r", description: "Reconnect" }
18691
+ ]
18692
+ },
18693
+ wiki: {
18694
+ name: "Wiki",
18695
+ shortcuts: [
18696
+ { key: "\u2190\u2192", description: "Switch panels" },
18697
+ { key: "\u2191\u2193", description: "Navigate items" },
18698
+ { key: "/", description: "Search memories" },
18699
+ { key: "Enter", description: "Select" }
18700
+ ]
18701
+ },
18702
+ settings: {
18703
+ name: "Settings",
18704
+ shortcuts: [
18705
+ { key: "\u2191\u2193", description: "Navigate sections" },
18706
+ { key: "Enter", description: "Edit setting" },
18707
+ { key: "r", description: "Refresh status" }
18708
+ ]
18709
+ }
18710
+ };
17938
18711
  var isDemo = process.argv.includes("--demo");
17939
18712
  process.stderr.write(`[exe-tui] Terminal: ${TERMINAL_TYPE}
17940
18713
  `);
@@ -17961,17 +18734,17 @@ if (!isDemo) {
17961
18734
  })();
17962
18735
  }
17963
18736
  function App2() {
17964
- const [section, setSection] = useState15("command-center");
18737
+ const [section, setSection] = useState16("command-center");
17965
18738
  const { exit } = use_app_default();
17966
- const [, forceUpdate] = useState15(0);
17967
- useEffect17(() => {
18739
+ const [, forceUpdate] = useState16(0);
18740
+ useEffect18(() => {
17968
18741
  const handleResize = () => forceUpdate((n) => n + 1);
17969
18742
  process.stdout.on("resize", handleResize);
17970
18743
  return () => {
17971
18744
  process.stdout.off("resize", handleResize);
17972
18745
  };
17973
18746
  }, []);
17974
- useMouseEvent(useCallback7((event) => {
18747
+ useMouseEvent(useCallback8((event) => {
17975
18748
  if (event.button !== 0) return;
17976
18749
  if (event.col <= 26) {
17977
18750
  const tabIdx = event.row - 4;
@@ -17983,22 +18756,33 @@ function App2() {
17983
18756
  setFocus("content");
17984
18757
  }
17985
18758
  }, []));
17986
- const [focus, setFocus] = useState15("sidebar");
17987
- const [focusedProject, setFocusedProject] = useState15(null);
17988
- const handleSelectProject = useCallback7((projectName) => {
18759
+ const [focus, setFocus] = useState16("sidebar");
18760
+ const [showDebug, setShowDebug] = useState16(false);
18761
+ const [showHelp, setShowHelp] = useState16(false);
18762
+ const [focusedProject, setFocusedProject] = useState16(null);
18763
+ const handleSelectProject = useCallback8((projectName) => {
17989
18764
  setFocusedProject(projectName);
17990
18765
  setSection("sessions");
17991
18766
  setFocus("content");
17992
18767
  }, []);
17993
- const handleBackToCommandCenter = useCallback7(() => {
18768
+ const handleBackToCommandCenter = useCallback8(() => {
17994
18769
  setSection("command-center");
17995
18770
  setFocus("sidebar");
17996
18771
  }, []);
17997
18772
  use_input_default((input, key) => {
18773
+ if (input === "?" || key.shift && input === "/") {
18774
+ setShowHelp((prev) => !prev);
18775
+ return;
18776
+ }
17998
18777
  const idx = parseInt(input, 10);
17999
18778
  if (idx >= 1 && idx <= SECTIONS.length) {
18000
18779
  setSection(SECTIONS[idx - 1].key);
18001
18780
  setFocus("sidebar");
18781
+ setShowHelp(false);
18782
+ return;
18783
+ }
18784
+ if (input === "d" && !key.ctrl) {
18785
+ setShowDebug((prev) => !prev);
18002
18786
  return;
18003
18787
  }
18004
18788
  if (input === "q" && !key.ctrl) {
@@ -18026,24 +18810,34 @@ function App2() {
18026
18810
  }
18027
18811
  }
18028
18812
  });
18029
- const consumeFocusedProject = useCallback7(() => {
18813
+ const consumeFocusedProject = useCallback8(() => {
18030
18814
  setFocusedProject(null);
18031
18815
  }, []);
18032
18816
  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") })
18817
+ "command-center": /* @__PURE__ */ jsx17(CommandCenterView, { onSelectProject: handleSelectProject, isFocused: focus === "content" && section === "command-center", onBack: () => setFocus("sidebar") }),
18818
+ sessions: /* @__PURE__ */ jsx17(SessionsView, { initialProject: focusedProject, onConsumeInitialProject: consumeFocusedProject, onBackToCommandCenter: handleBackToCommandCenter, onBack: () => setFocus("sidebar") }),
18819
+ tasks: /* @__PURE__ */ jsx17(TasksView, { onBack: () => setFocus("sidebar") }),
18820
+ team: /* @__PURE__ */ jsx17(TeamView, { onBack: () => setFocus("sidebar"), onViewSessions: (name) => {
18821
+ setFocusedProject(name);
18822
+ setSection("sessions");
18823
+ setFocus("content");
18824
+ } }),
18825
+ gateway: /* @__PURE__ */ jsx17(GatewayView, { onBack: () => setFocus("sidebar") }),
18826
+ wiki: /* @__PURE__ */ jsx17(WikiView, { onBack: () => setFocus("sidebar") }),
18827
+ settings: /* @__PURE__ */ jsx17(SettingsView, { onBack: () => setFocus("sidebar") })
18040
18828
  };
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]
18829
+ return /* @__PURE__ */ jsx17(ErrorBoundary2, { children: /* @__PURE__ */ jsx17(AlternateScreen, { children: /* @__PURE__ */ jsx17(DemoProvider, { demo: isDemo, children: /* @__PURE__ */ jsxs15(Box_default, { flexDirection: "column", flexGrow: 1, children: [
18830
+ /* @__PURE__ */ jsxs15(Box_default, { flexGrow: 1, children: [
18831
+ /* @__PURE__ */ jsx17(Sidebar, { active: section, onSelect: setSection, onQuit: exit, focused: focus === "sidebar" }),
18832
+ showHelp ? /* @__PURE__ */ jsx17(
18833
+ HelpOverlay,
18834
+ {
18835
+ tabName: TAB_SHORTCUTS[section].name,
18836
+ shortcuts: TAB_SHORTCUTS[section].shortcuts
18837
+ }
18838
+ ) : showDebug ? /* @__PURE__ */ jsx17(DebugPanel, {}) : views[section]
18045
18839
  ] }),
18046
- /* @__PURE__ */ jsx15(Footer, {})
18840
+ /* @__PURE__ */ jsx17(Footer, {})
18047
18841
  ] }) }) }) });
18048
18842
  }
18049
18843
  {
@@ -18061,4 +18855,4 @@ function App2() {
18061
18855
  stdin.unref = () => stdin;
18062
18856
  }
18063
18857
  }
18064
- render_default(/* @__PURE__ */ jsx15(App2, {}));
18858
+ render_default(/* @__PURE__ */ jsx17(App2, {}));