@askexenow/exe-os 0.8.40 → 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 (78) 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 +1376 -659
  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 +2549 -1784
  9. package/dist/bin/exe-call.js +39 -1
  10. package/dist/bin/exe-cloud.js +12 -2
  11. package/dist/bin/exe-dispatch.js +39 -2
  12. package/dist/bin/exe-doctor.js +791 -634
  13. package/dist/bin/exe-export-behaviors.js +792 -637
  14. package/dist/bin/exe-forget.js +145 -0
  15. package/dist/bin/exe-gateway.js +2501 -1846
  16. package/dist/bin/exe-heartbeat.js +147 -1
  17. package/dist/bin/exe-kill.js +795 -640
  18. package/dist/bin/exe-launch-agent.js +2168 -2008
  19. package/dist/bin/exe-link.js +44 -12
  20. package/dist/bin/exe-new-employee.js +6 -2
  21. package/dist/bin/exe-pending-messages.js +146 -1
  22. package/dist/bin/exe-pending-notifications.js +788 -631
  23. package/dist/bin/exe-pending-reviews.js +176 -1
  24. package/dist/bin/exe-rename.js +23 -0
  25. package/dist/bin/exe-review.js +490 -327
  26. package/dist/bin/exe-search.js +157 -4
  27. package/dist/bin/exe-session-cleanup.js +2487 -403
  28. package/dist/bin/exe-settings.js +2 -1
  29. package/dist/bin/exe-status.js +474 -317
  30. package/dist/bin/exe-team.js +474 -317
  31. package/dist/bin/git-sweep.js +2691 -151
  32. package/dist/bin/graph-backfill.js +794 -637
  33. package/dist/bin/graph-export.js +798 -641
  34. package/dist/bin/scan-tasks.js +2951 -44
  35. package/dist/bin/setup.js +50 -26
  36. package/dist/bin/shard-migrate.js +792 -637
  37. package/dist/bin/wiki-sync.js +794 -637
  38. package/dist/gateway/index.js +2542 -1887
  39. package/dist/hooks/bug-report-worker.js +2118 -576
  40. package/dist/hooks/commit-complete.js +2690 -150
  41. package/dist/hooks/error-recall.js +157 -4
  42. package/dist/hooks/ingest-worker.js +1455 -803
  43. package/dist/hooks/instructions-loaded.js +151 -0
  44. package/dist/hooks/notification.js +153 -2
  45. package/dist/hooks/post-compact.js +164 -0
  46. package/dist/hooks/pre-compact.js +3073 -101
  47. package/dist/hooks/pre-tool-use.js +151 -0
  48. package/dist/hooks/prompt-ingest-worker.js +1670 -1509
  49. package/dist/hooks/prompt-submit.js +2650 -1074
  50. package/dist/hooks/response-ingest-worker.js +154 -6
  51. package/dist/hooks/session-end.js +153 -2
  52. package/dist/hooks/session-start.js +157 -4
  53. package/dist/hooks/stop.js +151 -0
  54. package/dist/hooks/subagent-stop.js +155 -2
  55. package/dist/hooks/summary-worker.js +190 -21
  56. package/dist/index.js +326 -102
  57. package/dist/lib/cloud-sync.js +31 -10
  58. package/dist/lib/config.js +2 -0
  59. package/dist/lib/consolidation.js +69 -2
  60. package/dist/lib/database.js +19 -0
  61. package/dist/lib/device-registry.js +19 -0
  62. package/dist/lib/embedder.js +3 -1
  63. package/dist/lib/employee-templates.js +20 -1
  64. package/dist/lib/exe-daemon.js +285 -18
  65. package/dist/lib/hybrid-search.js +157 -4
  66. package/dist/lib/messaging.js +39 -2
  67. package/dist/lib/schedules.js +792 -637
  68. package/dist/lib/store.js +796 -636
  69. package/dist/lib/tasks.js +1485 -918
  70. package/dist/lib/tmux-routing.js +194 -10
  71. package/dist/mcp/server.js +1643 -924
  72. package/dist/mcp/tools/create-task.js +2283 -829
  73. package/dist/mcp/tools/list-tasks.js +2788 -159
  74. package/dist/mcp/tools/send-message.js +39 -2
  75. package/dist/mcp/tools/update-task.js +79 -0
  76. package/dist/runtime/index.js +280 -68
  77. package/dist/tui/App.js +1485 -645
  78. 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,
@@ -954,6 +1040,7 @@ var config_exports = {};
954
1040
  __export(config_exports, {
955
1041
  CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
956
1042
  CONFIG_PATH: () => CONFIG_PATH,
1043
+ COO_AGENT_NAME: () => COO_AGENT_NAME,
957
1044
  CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
958
1045
  DB_PATH: () => DB_PATH,
959
1046
  EXE_AI_DIR: () => EXE_AI_DIR,
@@ -1109,7 +1196,7 @@ async function loadConfigFrom(configPath) {
1109
1196
  return { ...DEFAULT_CONFIG };
1110
1197
  }
1111
1198
  }
1112
- var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
1199
+ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, COO_AGENT_NAME, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
1113
1200
  var init_config = __esm({
1114
1201
  "src/lib/config.ts"() {
1115
1202
  "use strict";
@@ -1117,6 +1204,7 @@ var init_config = __esm({
1117
1204
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
1118
1205
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
1119
1206
  CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
1207
+ COO_AGENT_NAME = "exe";
1120
1208
  LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
1121
1209
  CURRENT_CONFIG_VERSION = 1;
1122
1210
  DEFAULT_CONFIG = {
@@ -2709,6 +2797,71 @@ var init_agent_loop = __esm({
2709
2797
  }
2710
2798
  });
2711
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
+
2712
2865
  // src/gateway/providers/anthropic.ts
2713
2866
  import Anthropic from "@anthropic-ai/sdk";
2714
2867
  var AnthropicProvider;
@@ -2813,7 +2966,7 @@ var init_anthropic = __esm({
2813
2966
 
2814
2967
  // src/gateway/providers/openai-compat.ts
2815
2968
  import OpenAI from "openai";
2816
- import { randomUUID as randomUUID2 } from "crypto";
2969
+ import { randomUUID as randomUUID3 } from "crypto";
2817
2970
  var OpenAICompatProvider;
2818
2971
  var init_openai_compat = __esm({
2819
2972
  "src/gateway/providers/openai-compat.ts"() {
@@ -2930,7 +3083,7 @@ var init_openai_compat = __esm({
2930
3083
  }
2931
3084
  content.push({
2932
3085
  type: "tool_use",
2933
- id: call.id ?? randomUUID2(),
3086
+ id: call.id ?? randomUUID3(),
2934
3087
  name: fn.name,
2935
3088
  input
2936
3089
  });
@@ -4015,7 +4168,7 @@ var init_employees = __esm({
4015
4168
  });
4016
4169
 
4017
4170
  // src/lib/task-router.ts
4018
- import { randomUUID as randomUUID3 } from "crypto";
4171
+ import { randomUUID as randomUUID4 } from "crypto";
4019
4172
  function resolveBloomRouting(complexity, config = DEFAULT_BLOOM_CONFIG) {
4020
4173
  const tier = config.complexityToTier[complexity];
4021
4174
  const rule = config.tierRules[tier];
@@ -4547,6 +4700,61 @@ var init_session_kill_telemetry = __esm({
4547
4700
  }
4548
4701
  });
4549
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
+
4550
4758
  // src/lib/tasks-crud.ts
4551
4759
  import crypto3 from "crypto";
4552
4760
  import path15 from "path";
@@ -4690,9 +4898,15 @@ async function createTaskCore(input) {
4690
4898
  }
4691
4899
  }
4692
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
+ }
4693
4907
  await client.execute({
4694
- 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)
4695
- 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4696
4910
  args: [
4697
4911
  id,
4698
4912
  input.title,
@@ -4711,6 +4925,7 @@ async function createTaskCore(input) {
4711
4925
  input.budgetFallbackModel ?? null,
4712
4926
  0,
4713
4927
  null,
4928
+ sessionScope,
4714
4929
  now,
4715
4930
  now
4716
4931
  ]
@@ -4755,9 +4970,18 @@ async function listTasks(input) {
4755
4970
  conditions.push("priority = ?");
4756
4971
  args.push(input.priority);
4757
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
+ }
4758
4982
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
4759
4983
  const result = await client.execute({
4760
- 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`,
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`,
4761
4985
  args
4762
4986
  });
4763
4987
  return result.rows.map((r) => ({
@@ -4978,6 +5202,34 @@ async function listPendingReviews(limit) {
4978
5202
  });
4979
5203
  return result.rows;
4980
5204
  }
5205
+ async function cleanupOrphanedReviews() {
5206
+ const client = getClient();
5207
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5208
+ const r1 = await client.execute({
5209
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
5210
+ WHERE status = 'needs_review'
5211
+ AND assigned_by = 'system'
5212
+ AND title LIKE 'Review:%'
5213
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
5214
+ args: [now]
5215
+ });
5216
+ const staleThreshold = new Date(Date.now() - 60 * 60 * 1e3).toISOString();
5217
+ const r2 = await client.execute({
5218
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
5219
+ WHERE status = 'needs_review'
5220
+ AND result IS NOT NULL
5221
+ AND updated_at < ?`,
5222
+ args: [now, staleThreshold]
5223
+ });
5224
+ const total = r1.rowsAffected + r2.rowsAffected;
5225
+ if (total > 0) {
5226
+ process.stderr.write(
5227
+ `[cleanup] Closed ${total} orphaned review(s): ${r1.rowsAffected} cascade + ${r2.rowsAffected} stale
5228
+ `
5229
+ );
5230
+ }
5231
+ return total;
5232
+ }
4981
5233
  function getReviewChecklist(role, agent, taskSlug) {
4982
5234
  const roleLower = role.toLowerCase();
4983
5235
  if (roleLower.includes("engineer") || roleLower === "principal engineer") {
@@ -5091,6 +5343,7 @@ var init_tasks_review = __esm({
5091
5343
  init_tasks_crud();
5092
5344
  init_tmux_routing();
5093
5345
  init_session_key();
5346
+ init_state_bus();
5094
5347
  }
5095
5348
  });
5096
5349
 
@@ -5255,13 +5508,12 @@ function assertSessionScope(actionType, targetProject) {
5255
5508
  };
5256
5509
  }
5257
5510
  process.stderr.write(
5258
- `[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}"
5259
5512
  `
5260
5513
  );
5261
5514
  return {
5262
- allowed: true,
5263
- // v1: warn-only, don't block
5264
- reason: "cross_session_granted",
5515
+ allowed: false,
5516
+ reason: "cross_session_denied",
5265
5517
  currentProject,
5266
5518
  targetProject,
5267
5519
  targetSession: findSessionForProject(targetProject)?.windowName
@@ -5287,8 +5539,9 @@ async function dispatchTaskToEmployee(input) {
5287
5539
  try {
5288
5540
  const { assertSessionScope: assertSessionScope2 } = (init_session_scope(), __toCommonJS(session_scope_exports));
5289
5541
  const check = assertSessionScope2("dispatch_task", input.projectName);
5290
- if (check.reason === "cross_session_granted") {
5542
+ if (check.reason === "cross_session_denied") {
5291
5543
  crossProject = true;
5544
+ return { dispatched: "skipped", crossProject: true };
5292
5545
  }
5293
5546
  } catch {
5294
5547
  }
@@ -5657,6 +5910,7 @@ var init_skill_learning = __esm({
5657
5910
  // src/lib/tasks.ts
5658
5911
  var tasks_exports = {};
5659
5912
  __export(tasks_exports, {
5913
+ cleanupOrphanedReviews: () => cleanupOrphanedReviews,
5660
5914
  countNewPendingReviewsSince: () => countNewPendingReviewsSince,
5661
5915
  countPendingReviews: () => countPendingReviews,
5662
5916
  createTask: () => createTask,
@@ -5722,6 +5976,21 @@ async function updateTask(input) {
5722
5976
  });
5723
5977
  } catch {
5724
5978
  }
5979
+ try {
5980
+ const client = getClient();
5981
+ const cascaded = await client.execute({
5982
+ sql: `UPDATE tasks SET status = 'done', updated_at = ?
5983
+ WHERE parent_task_id = ? AND status = 'needs_review'`,
5984
+ args: [now, taskId]
5985
+ });
5986
+ if (cascaded.rowsAffected > 0) {
5987
+ process.stderr.write(
5988
+ `[cascade] Closed ${cascaded.rowsAffected} orphaned review task(s) for parent ${taskId}
5989
+ `
5990
+ );
5991
+ }
5992
+ } catch {
5993
+ }
5725
5994
  }
5726
5995
  const isTerminal = input.status === "done" || input.status === "needs_review";
5727
5996
  if (isTerminal) {
@@ -5735,6 +6004,13 @@ async function updateTask(input) {
5735
6004
  await cascadeUnblock(taskId, input.baseDir, now);
5736
6005
  } catch {
5737
6006
  }
6007
+ orgBus.emit({
6008
+ type: "task_completed",
6009
+ taskId,
6010
+ employee: String(row.assigned_to),
6011
+ result: input.result ?? "",
6012
+ timestamp: now
6013
+ });
5738
6014
  if (row.parent_task_id) {
5739
6015
  try {
5740
6016
  await checkSubtaskCompletion(String(row.parent_task_id), String(row.project_name));
@@ -5802,6 +6078,7 @@ var init_tasks = __esm({
5802
6078
  init_database();
5803
6079
  init_config();
5804
6080
  init_notifications();
6081
+ init_state_bus();
5805
6082
  init_tasks_crud();
5806
6083
  init_tasks_review();
5807
6084
  init_tasks_crud();
@@ -6192,8 +6469,28 @@ function getMySession() {
6192
6469
  return getTransport().getMySession();
6193
6470
  }
6194
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
+ }
6195
6486
  const suffix = instance != null && instance > 0 ? String(instance) : "";
6196
- 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;
6197
6494
  }
6198
6495
  function parseParentExe(sessionName, agentId) {
6199
6496
  const escaped = agentId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -6433,6 +6730,22 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
6433
6730
  error: `Error: pass employee name ('${bare}'), not session name ('${employeeName}')`
6434
6731
  };
6435
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
+ }
6436
6749
  let effectiveInstance = opts?.instance;
6437
6750
  if (effectiveInstance === void 0 && opts?.autoInstance) {
6438
6751
  const free = findFreeInstance(
@@ -6679,7 +6992,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6679
6992
  releaseSpawnLock(sessionName);
6680
6993
  return { sessionName };
6681
6994
  }
6682
- 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;
6683
6996
  var init_tmux_routing = __esm({
6684
6997
  "src/lib/tmux-routing.ts"() {
6685
6998
  "use strict";
@@ -6694,6 +7007,7 @@ var init_tmux_routing = __esm({
6694
7007
  SPAWN_LOCK_DIR = path20.join(os6.homedir(), ".exe-os", "spawn-locks");
6695
7008
  SESSION_CACHE = path20.join(os6.homedir(), ".exe-os", "session-cache");
6696
7009
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
7010
+ VALID_SESSION_NAME = /^[a-z]+-exe\d+$|^[a-z]+\d+-exe\d+$/;
6697
7011
  VERIFY_PANE_LINES = 200;
6698
7012
  INTERCOM_DEBOUNCE_MS = 3e4;
6699
7013
  INTERCOM_LOG2 = path20.join(os6.homedir(), ".exe-os", "intercom.log");
@@ -7862,6 +8176,11 @@ async function initStore(options) {
7862
8176
  "version-query"
7863
8177
  );
7864
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
+ }
7865
8184
  }
7866
8185
  function classifyTier(record) {
7867
8186
  if (record.tool_name === "commit_to_long_term_memory" && (record.importance ?? 0) >= 8) return 1;
@@ -7903,6 +8222,12 @@ async function writeMemory(record) {
7903
8222
  supersedes_id: record.supersedes_id ?? null
7904
8223
  };
7905
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
+ });
7906
8231
  const MAX_PENDING = 1e3;
7907
8232
  if (_pendingRecords.length > MAX_PENDING) {
7908
8233
  const dropped = _pendingRecords.length - MAX_PENDING;
@@ -8248,6 +8573,7 @@ var init_store = __esm({
8248
8573
  init_database();
8249
8574
  init_keychain();
8250
8575
  init_config();
8576
+ init_state_bus();
8251
8577
  INIT_MAX_RETRIES = 3;
8252
8578
  INIT_RETRY_DELAY_MS = 1e3;
8253
8579
  _pendingRecords = [];
@@ -8260,7 +8586,7 @@ var init_store = __esm({
8260
8586
  });
8261
8587
 
8262
8588
  // src/tui/App.tsx
8263
- 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";
8264
8590
 
8265
8591
  // src/tui/ink/render.js
8266
8592
  import { Stream } from "stream";
@@ -14160,61 +14486,64 @@ function Footer() {
14160
14486
  return () => clearInterval(timer);
14161
14487
  }, []);
14162
14488
  async function loadFooterData() {
14163
- try {
14164
- const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
14165
- const client = getClient2();
14166
- if (client) {
14167
- const result = await client.execute("SELECT COUNT(*) as cnt FROM memories");
14168
- 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 {
14169
14499
  }
14170
- } catch {
14171
- }
14172
- try {
14173
- const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
14174
- const client = getClient2();
14175
- if (client) {
14176
- const result = await client.execute(
14177
- "SELECT COUNT(*) as cnt FROM tasks WHERE status IN ('open','in_progress')"
14178
- );
14179
- 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 {
14180
14510
  }
14181
- } catch {
14182
- }
14183
- try {
14184
- const { existsSync: existsSync14 } = await import("fs");
14185
- const { join } = await import("path");
14186
- const home = process.env.HOME ?? "";
14187
- const pidPath = join(home, ".exe-os", "exed.pid");
14188
- setDaemon(existsSync14(pidPath) ? "running" : "stopped");
14189
- } catch {
14190
- setDaemon("unknown");
14191
- }
14192
- try {
14193
- const { checkLicense: checkLicense2 } = await Promise.resolve().then(() => (init_license(), license_exports));
14194
- const license = await checkLicense2();
14195
- setPlan(license.plan);
14196
- } catch {
14197
- setPlan("free");
14198
- }
14199
- try {
14200
- const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
14201
- if (inTmux2()) {
14202
- const allSessions = listTmuxSessions2();
14203
- setSessions(allSessions.length);
14204
- if (!currentSession) {
14205
- try {
14206
- const { execSync: execSync10 } = await import("child_process");
14207
- const name = execSync10("tmux display-message -p '#{session_name}' 2>/dev/null", {
14208
- encoding: "utf8",
14209
- timeout: 2e3
14210
- }).trim();
14211
- if (name) setCurrentSession(name);
14212
- } 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
+ }
14213
14542
  }
14214
14543
  }
14544
+ } catch {
14215
14545
  }
14216
- } catch {
14217
- }
14546
+ });
14218
14547
  }
14219
14548
  return /* @__PURE__ */ jsxs3(Box_default, { flexDirection: "column", children: [
14220
14549
  /* @__PURE__ */ jsx5(Text, { color: "#3D3660", children: "\u2500".repeat(process.stdout.columns || 120) }),
@@ -14255,6 +14584,8 @@ function Footer() {
14255
14584
  ] }),
14256
14585
  /* @__PURE__ */ jsx5(Text, { color: "#6B4C9A", children: "1-7 navigate" }),
14257
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" }),
14258
14589
  /* @__PURE__ */ jsx5(Text, { color: "#6B4C9A", children: "q quit" })
14259
14590
  ] })
14260
14591
  ] })
@@ -14875,6 +15206,147 @@ function handleEvent(event, addMessage) {
14875
15206
  }
14876
15207
  }
14877
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
+
14878
15350
  // src/tui/views/CommandCenter.tsx
14879
15351
  import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
14880
15352
  function CommandCenterView({
@@ -14998,7 +15470,7 @@ function CommandCenterView({
14998
15470
  setAgentConfig({
14999
15471
  provider,
15000
15472
  model,
15001
- 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."),
15002
15474
  tools: registry,
15003
15475
  hooks: createDefaultHooks2(),
15004
15476
  permissions,
@@ -15119,6 +15591,7 @@ function CommandCenterView({
15119
15591
  employeeCount: p.employees.length,
15120
15592
  activeCount: p.employees.filter((e) => e.status === "active").length,
15121
15593
  memoryCount: p.employees.length * 4e3,
15594
+ openTaskCount: Math.floor(p.employees.length * 1.5),
15122
15595
  status: p.employees.some((e) => e.status === "active") ? "active" : "idle",
15123
15596
  type: p.projectName.startsWith("exe-") ? "code" : "automation",
15124
15597
  recentTasks: DEMO_RECENT_TASKS[p.projectName] ?? []
@@ -15130,6 +15603,7 @@ function CommandCenterView({
15130
15603
  employeeCount: 0,
15131
15604
  activeCount: 0,
15132
15605
  memoryCount: 0,
15606
+ openTaskCount: 0,
15133
15607
  status: "offline",
15134
15608
  type: "reference",
15135
15609
  recentTasks: []
@@ -15144,182 +15618,122 @@ function CommandCenterView({
15144
15618
  return () => clearInterval(timer);
15145
15619
  }, []);
15146
15620
  async function loadData() {
15147
- try {
15148
- const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session_registry(), session_registry_exports));
15149
- const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
15150
- const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
15151
- const { existsSync: existsSync14 } = await import("fs");
15152
- const { join } = await import("path");
15153
- const registry = listSessions2();
15154
- const tmuxSessions = inTmux2() ? new Set(listTmuxSessions2()) : /* @__PURE__ */ new Set();
15155
- const roster = await loadEmployees2();
15156
- const exeSessions = /* @__PURE__ */ new Map();
15157
- for (const entry of registry) {
15158
- if (entry.agentId === "exe" && tmuxSessions.has(entry.windowName)) {
15159
- exeSessions.set(entry.windowName, entry.projectDir);
15160
- }
15161
- }
15162
- for (const s of tmuxSessions) {
15163
- if (/^exe\d+$/.test(s) && !exeSessions.has(s)) exeSessions.set(s, "");
15164
- }
15165
- let projectMemoryCounts = /* @__PURE__ */ new Map();
15166
- let projectRecentTasks = /* @__PURE__ */ new Map();
15621
+ const { withTrace: withTrace2 } = await Promise.resolve().then(() => (init_telemetry(), telemetry_exports));
15622
+ return withTrace2("tui.commandCenter.loadData", async () => {
15167
15623
  try {
15168
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");
15169
15630
  const client = getClient2();
15170
- if (client) {
15171
- const memResult = await client.execute(
15172
- "SELECT project_name, COUNT(*) as cnt FROM memories GROUP BY project_name"
15173
- );
15174
- for (const row of memResult.rows) {
15175
- projectMemoryCounts.set(String(row.project_name), Number(row.cnt));
15176
- }
15177
- for (const [, projectDir] of exeSessions) {
15178
- const projectName = projectDir.split("/").filter(Boolean).pop() ?? "";
15179
- if (!projectName) continue;
15180
- const taskResult = await client.execute({
15181
- sql: "SELECT title FROM tasks WHERE project_name = ? AND status = 'done' ORDER BY updated_at DESC LIMIT 3",
15182
- args: [projectName]
15183
- });
15184
- const tasks = taskResult.rows.map((r) => String(r.title));
15185
- if (tasks.length > 0) projectRecentTasks.set(projectName, tasks);
15186
- }
15631
+ if (!client) {
15632
+ setDbError(true);
15633
+ return;
15187
15634
  }
15188
- } catch {
15189
- }
15190
- const projectList = [];
15191
- for (const [exeSession, projectDir] of exeSessions) {
15192
- 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();
15193
15669
  const employeeNames = roster.map((e) => e.name).filter((n) => n !== "exe");
15194
- let activeCount = tmuxSessions.has(exeSession) ? 1 : 0;
15195
- for (const empName of employeeNames) {
15196
- 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
+ }
15197
15678
  }
15198
- const totalCount = 1 + employeeNames.length;
15199
- const memoryCount = projectMemoryCounts.get(projectName) ?? 0;
15200
- const hasGit = projectDir ? existsSync14(join(projectDir, ".git")) : false;
15201
- const hasActivity = memoryCount > 0;
15202
- let type = "automation";
15203
- if (hasGit && hasActivity) type = "code";
15204
- else if (hasGit && !hasActivity) type = "reference";
15205
- projectList.push({
15206
- projectName,
15207
- exeSession,
15208
- projectDir,
15209
- employeeCount: totalCount,
15210
- activeCount,
15211
- memoryCount,
15212
- status: activeCount > 0 ? "active" : "idle",
15213
- type,
15214
- recentTasks: projectRecentTasks.get(projectName) ?? []
15215
- });
15216
- }
15217
- const knownDirs = [
15218
- process.env.HOME ? join(process.env.HOME, "openclaw") : null,
15219
- process.env.HOME ? join(process.env.HOME, "agno") : null
15220
- ].filter(Boolean);
15221
- const activeProjectNames = new Set(projectList.map((p) => p.projectName));
15222
- for (const dir of knownDirs) {
15223
- const name = dir.split("/").filter(Boolean).pop() ?? "";
15224
- if (activeProjectNames.has(name) || !existsSync14(dir) || !existsSync14(join(dir, ".git"))) continue;
15225
- if ((projectMemoryCounts.get(name) ?? 0) > 0) continue;
15226
- projectList.push({
15227
- projectName: name,
15228
- exeSession: "",
15229
- projectDir: dir,
15230
- employeeCount: 0,
15231
- activeCount: 0,
15232
- memoryCount: 0,
15233
- status: "offline",
15234
- type: "reference",
15235
- recentTasks: []
15236
- });
15237
- }
15238
- const dbActiveProjectNames = new Set(projectList.map((p) => p.projectName));
15239
- try {
15240
- const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
15241
- const client = getClient2();
15242
- if (client) {
15243
- const dbProjects = await client.execute(
15244
- `SELECT DISTINCT project_name FROM memories WHERE project_name IS NOT NULL AND project_name != ''
15245
- UNION
15246
- SELECT DISTINCT project_name FROM tasks WHERE project_name IS NOT NULL AND project_name != ''
15247
- ORDER BY project_name`
15248
- );
15249
- for (const row of dbProjects.rows) {
15250
- const name = String(row.project_name);
15251
- if (dbActiveProjectNames.has(name)) continue;
15252
- const memCount = projectMemoryCounts.get(name) ?? 0;
15253
- const agentResult = await client.execute({
15254
- sql: "SELECT COUNT(DISTINCT agent_id) as cnt FROM memories WHERE project_name = ?",
15255
- args: [name]
15256
- });
15257
- const agentCount = Number(agentResult.rows[0]?.cnt ?? 0);
15258
- projectList.push({
15259
- projectName: name,
15260
- exeSession: "",
15261
- projectDir: "",
15262
- employeeCount: agentCount,
15263
- activeCount: 0,
15264
- memoryCount: memCount,
15265
- status: "offline",
15266
- type: "code",
15267
- recentTasks: projectRecentTasks.get(name) ?? []
15268
- });
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
+ }
15269
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
+ });
15270
15707
  }
15271
- } catch {
15272
- }
15273
- if (projectList.filter((p) => p.type !== "reference").length === 0) {
15274
- projectList.unshift({
15275
- projectName: "(no active sessions)",
15276
- exeSession: "",
15277
- projectDir: "",
15278
- employeeCount: 0,
15279
- activeCount: 0,
15280
- memoryCount: 0,
15281
- status: "offline",
15282
- type: "code",
15283
- recentTasks: []
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;
15284
15712
  });
15285
- }
15286
- setProjects(projectList);
15287
- try {
15288
- const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
15289
- const client = getClient2();
15290
- if (client) {
15291
- const result = await client.execute("SELECT COUNT(*) as cnt FROM memories");
15292
- setHealth((h) => ({ ...h, memories: Number(result.rows[0]?.cnt ?? 0) }));
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 {
15293
15720
  }
15294
- } catch {
15295
- }
15296
- try {
15297
- const pidPath = join(process.env.HOME ?? "", ".exe-os", "exed.pid");
15298
- setHealth((h) => ({ ...h, daemon: existsSync14(pidPath) ? "running" : "stopped" }));
15299
- } catch {
15300
- }
15301
- try {
15302
- const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
15303
- const client = getClient2();
15304
- if (client) {
15305
- const activityResult = await client.execute(
15306
- "SELECT agent_id, tool_name, project_name, created_at, text FROM memories ORDER BY created_at DESC LIMIT 20"
15307
- );
15308
- if (activityResult.rows.length > 0) {
15309
- setActivity(activityResult.rows.slice(0, 8).map((r) => ({
15310
- time: new Date(String(r.created_at)).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false }),
15311
- agent: String(r.agent_id ?? "system"),
15312
- action: String(r.text ?? "").slice(0, 60)
15313
- })));
15314
- }
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
+ })));
15315
15730
  }
15316
15731
  } catch {
15732
+ setDbError(true);
15733
+ } finally {
15734
+ setLoading(false);
15317
15735
  }
15318
- } catch {
15319
- setDbError(true);
15320
- } finally {
15321
- setLoading(false);
15322
- }
15736
+ });
15323
15737
  }
15324
15738
  const daemonColor = health.daemon === "running" ? "green" : health.daemon === "stopped" ? "red" : "gray";
15325
15739
  const handleChatSubmit = useCallback4((value) => {
@@ -15411,7 +15825,7 @@ function CommandCenterView({
15411
15825
  ] }),
15412
15826
  /* @__PURE__ */ jsx7(Text, { color: "#6B4C9A", children: "\u2191\u2193 navigate | c to chat | Enter to open | Escape to detach" }),
15413
15827
  /* @__PURE__ */ jsx7(Text, { children: " " }),
15414
- 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." }) : (() => {
15415
15829
  const sections = [];
15416
15830
  let currentProjects = [];
15417
15831
  let sectionKey = 0;
@@ -15441,9 +15855,9 @@ function CommandCenterView({
15441
15855
  ] })
15442
15856
  ] }),
15443
15857
  /* @__PURE__ */ jsxs5(Text, { color: isSelected ? "#F0EDE8" : "#6B4C9A", children: [
15444
- entry.employeeCount,
15858
+ entry.openTaskCount,
15445
15859
  " ",
15446
- entry.employeeCount === 1 ? "employee" : "employees",
15860
+ entry.openTaskCount === 1 ? "task" : "tasks",
15447
15861
  " \xB7 ",
15448
15862
  entry.memoryCount.toLocaleString(),
15449
15863
  " memories"
@@ -15777,6 +16191,7 @@ function SessionsView({
15777
16191
  const [viewingEmployee, setViewingEmployee] = useState9(null);
15778
16192
  const [viewingProject, setViewingProject] = useState9(null);
15779
16193
  const [loading, setLoading] = useState9(!demo);
16194
+ const [sessionError, setSessionError] = useState9(null);
15780
16195
  const [tmuxAvailable, setTmuxAvailable] = useState9(true);
15781
16196
  const orch = useOrchestrator(!demo);
15782
16197
  const [carouselEmployees, setCarouselEmployees] = useState9([]);
@@ -15952,111 +16367,116 @@ function SessionsView({
15952
16367
  return ACTIVE_PANE_PATTERN.test(lines.join("\n")) ? "active" : "idle";
15953
16368
  }
15954
16369
  async function loadSessions() {
15955
- try {
15956
- const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session_registry(), session_registry_exports));
15957
- const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2, capturePaneLines: capturePaneLines2, parseActivity: parseActivity2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
15958
- const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
15959
- const { execSync: execSync10 } = await import("child_process");
15960
- if (!inTmux2()) {
15961
- setTmuxAvailable(false);
15962
- setProjects([]);
15963
- return;
15964
- }
15965
- setTmuxAvailable(true);
15966
- const attachedMap = /* @__PURE__ */ new Map();
16370
+ const { withTrace: withTrace2 } = await Promise.resolve().then(() => (init_telemetry(), telemetry_exports));
16371
+ return withTrace2("tui.sessions.loadSessions", async () => {
15967
16372
  try {
15968
- const out = execSync10("tmux list-sessions -F '#{session_name}:#{session_attached}' 2>/dev/null", {
15969
- encoding: "utf8",
15970
- timeout: 3e3
15971
- });
15972
- for (const line of out.trim().split("\n").filter(Boolean)) {
15973
- const sepIdx = line.lastIndexOf(":");
15974
- if (sepIdx > 0) {
15975
- attachedMap.set(line.slice(0, sepIdx), line.slice(sepIdx + 1) === "1");
15976
- }
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;
15977
16381
  }
15978
- } catch {
15979
- }
15980
- const registry = listSessions2();
15981
- const tmuxSessions = new Set(listTmuxSessions2());
15982
- const roster = await loadEmployees2();
15983
- const exeSessions = /* @__PURE__ */ new Map();
15984
- for (const entry of registry) {
15985
- if (entry.agentId === "exe" && tmuxSessions.has(entry.windowName)) {
15986
- 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 {
15987
16396
  }
15988
- }
15989
- for (const s of tmuxSessions) {
15990
- if (/^exe\d+$/.test(s) && !exeSessions.has(s)) {
15991
- 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
+ }
15992
16405
  }
15993
- }
15994
- const projectList = [];
15995
- for (const [exeSession, projectDir] of exeSessions) {
15996
- const projectName = projectDir.split("/").filter(Boolean).pop() ?? exeSession;
15997
- const exeHasSession = tmuxSessions.has(exeSession);
15998
- let exeStatus = "offline";
15999
- let exeActivity = "";
16000
- if (exeHasSession) {
16001
- const exeLines = capturePaneLines2(exeSession, 10);
16002
- exeStatus = statusFromPaneLines(exeLines);
16003
- 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
+ }
16004
16410
  }
16005
- const employeeEntries = roster.filter((e) => e.name !== "exe").map((emp) => {
16006
- const sessionName = `${emp.name}-${exeSession}`;
16007
- const hasSession = tmuxSessions.has(sessionName);
16008
- const isCrashed = !hasSession && orch.crashedSessions.includes(emp.name);
16009
- if (isCrashed) {
16010
- return {
16011
- name: emp.name,
16012
- role: emp.role,
16013
- status: "crashed",
16014
- sessionName,
16015
- activity: "Dead session \u2014 has open tasks",
16016
- attached: false
16017
- };
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) : "";
16018
16421
  }
16019
- 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);
16020
16447
  return {
16021
16448
  name: emp.name,
16022
16449
  role: emp.role,
16023
- status: "offline",
16450
+ status: statusFromPaneLines(lines),
16024
16451
  sessionName,
16025
- activity: "",
16026
- attached: false
16452
+ activity: lines.length > 0 ? parseActivity2(lines) : "",
16453
+ attached: attachedMap.get(sessionName) ?? false
16027
16454
  };
16028
- }
16029
- const lines = capturePaneLines2(sessionName, 10);
16030
- return {
16031
- name: emp.name,
16032
- role: emp.role,
16033
- status: statusFromPaneLines(lines),
16034
- sessionName,
16035
- activity: lines.length > 0 ? parseActivity2(lines) : "",
16036
- attached: attachedMap.get(sessionName) ?? false
16037
- };
16038
- });
16039
- const employees = [
16040
- {
16041
- name: "exe",
16042
- role: "COO",
16043
- status: exeStatus,
16044
- sessionName: exeSession,
16045
- activity: exeActivity,
16046
- attached: attachedMap.get(exeSession) ?? false
16047
- },
16048
- ...employeeEntries
16049
- ];
16050
- projectList.push({ projectName, exeSession, projectDir, employees });
16051
- }
16052
- if (projectList.length === 0) {
16053
- 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);
16054
16478
  }
16055
- setProjects(projectList);
16056
- } catch {
16057
- } finally {
16058
- setLoading(false);
16059
- }
16479
+ });
16060
16480
  }
16061
16481
  if (viewingEmployee) {
16062
16482
  const inCarousel = carouselEmployees.length > 0;
@@ -16138,7 +16558,10 @@ function SessionsView({
16138
16558
  ] })
16139
16559
  ] }),
16140
16560
  /* @__PURE__ */ jsx9(Text, { children: " " }),
16141
- 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) => {
16142
16565
  const isSelected = i === selectedIdx;
16143
16566
  const marker = isSelected ? "\u25B8 " : " ";
16144
16567
  if (item.type === "project") {
@@ -16239,7 +16662,7 @@ function TasksView({ onBack }) {
16239
16662
  const demo = useDemo();
16240
16663
  const [allTasks, setAllTasks] = useState10([]);
16241
16664
  const [loading, setLoading] = useState10(!demo);
16242
- const [dbError, setDbError] = useState10(false);
16665
+ const [dbError, setDbError] = useState10(null);
16243
16666
  const [selectedTask, setSelectedTask] = useState10(0);
16244
16667
  const [showDetail, setShowDetail] = useState10(false);
16245
16668
  const [statusFilter, setStatusFilter] = useState10("all");
@@ -16311,40 +16734,43 @@ function TasksView({ onBack }) {
16311
16734
  }
16312
16735
  });
16313
16736
  async function loadTasks() {
16314
- try {
16315
- const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
16316
- const client = getClient2();
16317
- if (client) {
16318
- const result = await client.execute(
16319
- `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
16320
16745
  FROM tasks
16321
16746
  ORDER BY
16322
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,
16323
16748
  CASE priority WHEN 'p0' THEN 0 WHEN 'p1' THEN 1 WHEN 'p2' THEN 2 ELSE 3 END,
16324
16749
  created_at DESC`
16325
- );
16326
- setAllTasks(
16327
- result.rows.map((r) => ({
16328
- id: String(r.id ?? ""),
16329
- priority: String(r.priority ?? "p2").toUpperCase(),
16330
- title: String(r.title ?? ""),
16331
- assignee: String(r.assigned_to ?? ""),
16332
- assignedBy: String(r.assigned_by ?? ""),
16333
- status: String(r.status ?? "open"),
16334
- project: String(r.project_name ?? ""),
16335
- createdAt: String(r.created_at ?? ""),
16336
- result: String(r.result ?? "")
16337
- }))
16338
- );
16339
- setDbError(false);
16340
- } else {
16341
- 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);
16342
16772
  }
16343
- } catch {
16344
- setDbError(true);
16345
- } finally {
16346
- setLoading(false);
16347
- }
16773
+ });
16348
16774
  }
16349
16775
  const selected = taskRows[selectedTask]?.task;
16350
16776
  if (showDetail && selected) {
@@ -16411,7 +16837,10 @@ function TasksView({ onBack }) {
16411
16837
  filterLabel
16412
16838
  ] }),
16413
16839
  /* @__PURE__ */ jsx10(Text, { children: " " }),
16414
- 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) => {
16415
16844
  if (row.type === "header") {
16416
16845
  return /* @__PURE__ */ jsxs8(Box_default, { marginTop: i > 0 ? 1 : 0, children: [
16417
16846
  /* @__PURE__ */ jsx10(Text, { bold: true, color: "#F0EDE8", children: row.project }),
@@ -16865,6 +17294,31 @@ function GatewayView({ onBack }) {
16865
17294
  /* @__PURE__ */ jsx11(Text, { color: "#6B4C9A", children: "Loading gateway status..." })
16866
17295
  ] });
16867
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
+ }
16868
17322
  if (!gateway.running && gateway.adapters.length === 0 && !gateway.gatewayUrl) {
16869
17323
  return /* @__PURE__ */ jsxs9(Box_default, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: [
16870
17324
  /* @__PURE__ */ jsx11(Box_default, { borderStyle: "single", borderColor: "#3D3660", paddingX: 1, alignSelf: "flex-start", children: /* @__PURE__ */ jsx11(Text, { bold: true, children: "Gateway Monitor" }) }),
@@ -17053,14 +17507,30 @@ function getAgentStatus(agentId) {
17053
17507
  // src/tui/views/Team.tsx
17054
17508
  import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
17055
17509
  var DEPRECATED_PROJECTS = /* @__PURE__ */ new Set(["exe-ai-employees"]);
17056
- 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 }) {
17057
17526
  const demo = useDemo();
17058
17527
  const [members, setMembers] = useState12([]);
17059
17528
  const [externals, setExternals] = useState12([]);
17060
17529
  const [loading, setLoading] = useState12(!demo);
17061
- const [dbError, setDbError] = useState12(false);
17530
+ const [dbError, setDbError] = useState12(null);
17062
17531
  const [selectedIdx, setSelectedIdx] = useState12(0);
17063
17532
  const [showDetail, setShowDetail] = useState12(false);
17533
+ const [showAddHint, setShowAddHint] = useState12(false);
17064
17534
  const orch = useOrchestrator(!demo);
17065
17535
  const orchEmployeeMap = new Map(orch.employees.map((e) => [e.name, e]));
17066
17536
  const allItems = [...members, ...externals.map((e) => ({ ...e, activity: "", memoryCount: 0 }))];
@@ -17075,7 +17545,7 @@ function TeamView({ onBack }) {
17075
17545
  const timer = setInterval(loadTeam, 5e3);
17076
17546
  return () => clearInterval(timer);
17077
17547
  }, []);
17078
- use_input_default((_input, key) => {
17548
+ use_input_default((input, key) => {
17079
17549
  if (showDetail) {
17080
17550
  if (key.escape) setShowDetail(false);
17081
17551
  return;
@@ -17084,91 +17554,103 @@ function TeamView({ onBack }) {
17084
17554
  onBack?.();
17085
17555
  return;
17086
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
+ }
17087
17566
  if (key.upArrow && selectedIdx > 0) setSelectedIdx(selectedIdx - 1);
17088
17567
  if (key.downArrow && selectedIdx < allItems.length - 1) setSelectedIdx(selectedIdx + 1);
17089
17568
  if (key.return && allItems.length > 0) setShowDetail(true);
17090
17569
  });
17091
17570
  async function loadTeam() {
17092
- try {
17093
- const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
17094
- const roster = await loadEmployees2();
17095
- let memoryCounts = /* @__PURE__ */ new Map();
17096
- let projectsByEmployee = /* @__PURE__ */ new Map();
17097
- let currentTaskByEmployee = /* @__PURE__ */ new Map();
17571
+ const { withTrace: withTrace2 } = await Promise.resolve().then(() => (init_telemetry(), telemetry_exports));
17572
+ return withTrace2("tui.team.loadTeam", async () => {
17098
17573
  try {
17099
- const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
17100
- const client = getClient2();
17101
- if (client) {
17102
- const memResult = await client.execute("SELECT agent_id, COUNT(*) as cnt FROM memories GROUP BY agent_id");
17103
- for (const row of memResult.rows) {
17104
- memoryCounts.set(String(row.agent_id), Number(row.cnt));
17105
- }
17106
- for (const emp of roster) {
17107
- const projResult = await client.execute({
17108
- 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,
17109
17590
  MAX(CASE WHEN status = 'in_progress' THEN 1 WHEN status = 'open' THEN 2 ELSE 3 END) as urgency
17110
17591
  FROM tasks WHERE assigned_to = ? AND status IN ('open','in_progress','done')
17111
17592
  GROUP BY project_name ORDER BY urgency ASC LIMIT 5`,
17112
- args: [emp.name]
17113
- });
17114
- const projects = projResult.rows.filter((r) => !DEPRECATED_PROJECTS.has(String(r.project_name))).map((r) => {
17115
- const urgency = Number(r.urgency);
17116
- let pStatus = "idle";
17117
- if (urgency === 1) pStatus = "active";
17118
- else if (urgency === 2) pStatus = "has_tasks";
17119
- return { name: String(r.project_name), status: pStatus };
17120
- });
17121
- if (projects.length > 0) projectsByEmployee.set(emp.name, projects);
17122
- }
17123
- for (const emp of roster) {
17124
- const taskResult = await client.execute({
17125
- sql: "SELECT title FROM tasks WHERE assigned_to = ? AND status = 'in_progress' ORDER BY updated_at DESC LIMIT 1",
17126
- args: [emp.name]
17127
- });
17128
- if (taskResult.rows.length > 0) {
17129
- 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
+ }
17130
17612
  }
17131
17613
  }
17614
+ } catch {
17132
17615
  }
17133
- } catch {
17134
- }
17135
- const teamData = roster.map((emp) => {
17136
- const agentSt = getAgentStatus(emp.name);
17137
- return {
17138
- name: emp.name,
17139
- role: emp.role,
17140
- status: agentSt.label,
17141
- activity: agentSt.label === "active" ? "Processing..." : "",
17142
- memoryCount: memoryCounts.get(emp.name) ?? 0,
17143
- projects: projectsByEmployee.get(emp.name),
17144
- currentTask: currentTaskByEmployee.get(emp.name),
17145
- sessionName: agentSt.session
17146
- };
17147
- });
17148
- setMembers(teamData);
17149
- setDbError(false);
17150
- try {
17151
- const { existsSync: existsSync14, readFileSync: readFileSync11 } = await import("fs");
17152
- const { join } = await import("path");
17153
- const home = process.env.HOME ?? "";
17154
- const gatewayConfig = join(home, ".exe-os", "gateway.json");
17155
- if (existsSync14(gatewayConfig)) {
17156
- const raw = JSON.parse(readFileSync11(gatewayConfig, "utf8"));
17157
- if (raw.agents && raw.agents.length > 0) {
17158
- setExternals(raw.agents.map((a) => ({
17159
- name: a.name,
17160
- role: a.role ?? "external agent",
17161
- status: "offline"
17162
- })));
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
+ }
17163
17645
  }
17646
+ } catch {
17164
17647
  }
17165
- } catch {
17648
+ } catch (err) {
17649
+ setDbError(err instanceof Error ? err.message : "Unknown error");
17650
+ } finally {
17651
+ setLoading(false);
17166
17652
  }
17167
- } catch {
17168
- setDbError(true);
17169
- } finally {
17170
- setLoading(false);
17171
- }
17653
+ });
17172
17654
  }
17173
17655
  const totalCount = members.length + externals.length;
17174
17656
  const selected = allItems[selectedIdx];
@@ -17185,7 +17667,7 @@ function TeamView({ onBack }) {
17185
17667
  /* @__PURE__ */ jsx12(Text, { children: " " }),
17186
17668
  /* @__PURE__ */ jsxs10(Text, { children: [
17187
17669
  "Role: ",
17188
- /* @__PURE__ */ jsx12(Text, { bold: true, children: selected.role })
17670
+ /* @__PURE__ */ jsx12(Text, { bold: true, color: roleBadgeColor(selected.role), children: selected.role })
17189
17671
  ] }),
17190
17672
  /* @__PURE__ */ jsxs10(Text, { children: [
17191
17673
  "Status: ",
@@ -17214,8 +17696,9 @@ function TeamView({ onBack }) {
17214
17696
  /* @__PURE__ */ jsx12(Box_default, { borderStyle: "single", borderColor: "#3D3660", paddingX: 1, alignSelf: "flex-start", children: /* @__PURE__ */ jsx12(Text, { bold: true, children: "Team Roster" }) }),
17215
17697
  /* @__PURE__ */ jsxs10(Text, { color: "#6B4C9A", children: [
17216
17698
  totalCount,
17217
- " agents | up/down navigate | Enter for details"
17699
+ " agents | \u2191\u2193 navigate | Enter details | a add | s sessions"
17218
17700
  ] }),
17701
+ showAddHint && /* @__PURE__ */ jsx12(Text, { color: "#F5D76E", children: "Run /exe-new-employee <name> from CLI to add an employee." }),
17219
17702
  !demo && orch.pendingReviews > 0 && /* @__PURE__ */ jsxs10(Text, { color: "#6B4C9A", children: [
17220
17703
  orch.pendingReviews,
17221
17704
  " review(s) pending exe attention"
@@ -17223,7 +17706,10 @@ function TeamView({ onBack }) {
17223
17706
  /* @__PURE__ */ jsx12(Text, { children: " " }),
17224
17707
  /* @__PURE__ */ jsx12(Text, { bold: true, children: "INTERNAL" }),
17225
17708
  /* @__PURE__ */ jsx12(Text, { children: " " }),
17226
- 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) => {
17227
17713
  const isSelected = i === selectedIdx;
17228
17714
  const orchEmp = orchEmployeeMap.get(m.name);
17229
17715
  return /* @__PURE__ */ jsxs10(
@@ -17239,7 +17725,7 @@ function TeamView({ onBack }) {
17239
17725
  /* @__PURE__ */ jsxs10(Box_default, { gap: 1, children: [
17240
17726
  /* @__PURE__ */ jsx12(StatusDot, { status: m.status, showLabel: false }),
17241
17727
  /* @__PURE__ */ jsx12(Text, { bold: true, color: isSelected ? "#F5D76E" : "#F0EDE8", children: m.name }),
17242
- /* @__PURE__ */ jsxs10(Text, { color: isSelected ? "#F5D76E" : "#6B4C9A", children: [
17728
+ /* @__PURE__ */ jsxs10(Text, { color: isSelected ? "#F5D76E" : roleBadgeColor(m.role), children: [
17243
17729
  "(",
17244
17730
  m.role,
17245
17731
  ")"
@@ -17312,6 +17798,33 @@ import TextInput2 from "ink-text-input";
17312
17798
  import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
17313
17799
  var PANELS = ["Workspaces", "Documents", "Chat"];
17314
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
+ }
17315
17828
  function WikiView({ onBack }) {
17316
17829
  const demo = useDemo();
17317
17830
  const [activePanel, setActivePanel] = useState13("Workspaces");
@@ -17322,6 +17835,12 @@ function WikiView({ onBack }) {
17322
17835
  const [chatHistory, setChatHistory] = useState13([]);
17323
17836
  const [chatInput, setChatInput] = useState13("");
17324
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);
17325
17844
  const [loading, setLoading] = useState13(true);
17326
17845
  const [connected, setConnected] = useState13(false);
17327
17846
  const [wikiUrl, setWikiUrl] = useState13("");
@@ -17415,6 +17934,55 @@ function WikiView({ onBack }) {
17415
17934
  setSending(false);
17416
17935
  }
17417
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]);
17418
17986
  async function selectWorkspace(idx) {
17419
17987
  setSelectedWorkspaceIdx(idx);
17420
17988
  const ws = workspaces[idx];
@@ -17435,6 +18003,32 @@ function WikiView({ onBack }) {
17435
18003
  }
17436
18004
  }
17437
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
+ }
17438
18032
  if (chatFocused) {
17439
18033
  if (key.escape) {
17440
18034
  setChatFocused(false);
@@ -17446,6 +18040,13 @@ function WikiView({ onBack }) {
17446
18040
  }
17447
18041
  return;
17448
18042
  }
18043
+ if (input === "/" && activePanel !== "Chat") {
18044
+ setSearchMode(true);
18045
+ setSearchQuery("");
18046
+ setSearchResults(demo ? DEMO_SEARCH_RESULTS : []);
18047
+ setExpandedResultIdx(null);
18048
+ return;
18049
+ }
17449
18050
  if (key.leftArrow) {
17450
18051
  const panelIdx = PANELS.indexOf(activePanel);
17451
18052
  if (panelIdx === 0) {
@@ -17523,9 +18124,53 @@ function WikiView({ onBack }) {
17523
18124
  /* @__PURE__ */ jsx13(Text, { bold: true, children: selectedWs.name })
17524
18125
  ] }) : null
17525
18126
  ] }),
17526
- /* @__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" }),
17527
18128
  /* @__PURE__ */ jsx13(Text, { children: " " }),
17528
- /* @__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: [
17529
18174
  /* @__PURE__ */ jsxs11(Box_default, { flexDirection: "column", width: "25%", children: [
17530
18175
  /* @__PURE__ */ jsxs11(Text, { bold: true, backgroundColor: panelHeaderBg("Workspaces"), color: panelHeaderColor("Workspaces"), children: [
17531
18176
  activePanel === "Workspaces" ? "\u25B8 " : " ",
@@ -17609,50 +18254,41 @@ function WikiView({ onBack }) {
17609
18254
  }
17610
18255
 
17611
18256
  // src/tui/views/Settings.tsx
17612
- import React24, { useState as useState14, useEffect as useEffect16 } from "react";
18257
+ import React24, { useState as useState14, useEffect as useEffect16, useCallback as useCallback7 } from "react";
17613
18258
  import { Fragment as Fragment5, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
17614
- 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";
17615
18267
  function SettingsView({ onBack }) {
17616
18268
  const [providers, setProviders] = useState14([]);
17617
- const [memory, setMemory] = useState14({
17618
- encryption: "checking...",
17619
- embeddingModel: "checking...",
17620
- cloudSync: "checking...",
17621
- searchMode: "checking..."
17622
- });
17623
- const [runtime, setRuntime] = useState14({
17624
- consolidation: true,
17625
- consolidationModel: "...",
17626
- skillLearning: true,
17627
- skillThreshold: 3,
17628
- skillModel: "...",
17629
- failoverChain: ["anthropic", "opencode", "gemini", "openai"]
17630
- });
17631
- 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: [] });
17632
18273
  const [selectedSection, setSelectedSection] = useState14(0);
17633
18274
  const [loading, setLoading] = useState14(true);
17634
- const [claudeCode, setClaudeCode] = useState14({ installed: false, hooksWired: false });
17635
- const [license, setLicense] = useState14({ valid: false, detail: "checking..." });
17636
- const [cloudSync, setCloudSync] = useState14({ configured: false, detail: "checking..." });
17637
- useEffect16(() => {
17638
- loadSettings().finally(() => setLoading(false));
17639
- }, []);
17640
- use_input_default((_input, key) => {
17641
- if (key.leftArrow) {
17642
- onBack?.();
17643
- return;
17644
- }
17645
- if (key.upArrow && selectedSection > 0) setSelectedSection(selectedSection - 1);
17646
- if (key.downArrow && selectedSection < SECTION_NAMES.length - 1) setSelectedSection(selectedSection + 1);
17647
- });
17648
- async function loadSettings() {
17649
- const providerList = [
17650
- { name: "Anthropic", configured: !!process.env.ANTHROPIC_API_KEY, detail: process.env.ANTHROPIC_API_KEY ? "ANTHROPIC_API_KEY set" : "not configured" },
17651
- { name: "OpenCode", configured: !!process.env.OPENCODE_API_KEY, detail: process.env.OPENCODE_API_KEY ? "OPENCODE_API_KEY set" : "not configured" },
17652
- { name: "Gemini", configured: !!process.env.GEMINI_API_KEY, detail: process.env.GEMINI_API_KEY ? "GEMINI_API_KEY set" : "not configured" },
17653
- { name: "OpenAI", configured: !!process.env.OPENAI_API_KEY, detail: process.env.OPENAI_API_KEY ? "OPENAI_API_KEY set" : "not configured" },
17654
- { 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"]
17655
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
+ });
17656
18292
  try {
17657
18293
  const { execSync: execSync10 } = await import("child_process");
17658
18294
  execSync10("curl -s --max-time 1 http://localhost:11434/api/tags", { timeout: 2e3 });
@@ -17664,112 +18300,101 @@ function SettingsView({ onBack }) {
17664
18300
  try {
17665
18301
  const { existsSync: existsSync14 } = await import("fs");
17666
18302
  const { join } = await import("path");
17667
- const home = process.env.HOME ?? "";
17668
- const hasKey = existsSync14(join(home, ".exe-os", "master.key"));
17669
18303
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
17670
18304
  const cfg = await loadConfig2();
17671
- setMemory({
17672
- encryption: hasKey ? "SQLCipher AES-256" : "not configured",
17673
- embeddingModel: cfg.modelFile ?? "unknown",
17674
- cloudSync: cfg.cloud ? "enabled" : "disabled",
17675
- searchMode: cfg.searchMode ?? "hybrid"
17676
- });
17677
- const rawCfg = cfg;
17678
- const chain = Array.isArray(rawCfg.failoverChain) ? rawCfg.failoverChain : ["anthropic", "opencode", "gemini", "openai"];
17679
- setRuntime({
17680
- consolidation: cfg.consolidationEnabled,
17681
- consolidationModel: cfg.consolidationModel,
17682
- skillLearning: cfg.skillLearning,
17683
- skillThreshold: cfg.skillThreshold,
17684
- skillModel: cfg.skillModel,
17685
- failoverChain: chain
17686
- });
17687
- } catch {
17688
- }
17689
- try {
17690
- const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
17691
- const roster = await loadEmployees2();
17692
- const { existsSync: existsSync14, readFileSync: readFileSync11 } = await import("fs");
17693
- const { join } = await import("path");
17694
18305
  const home = process.env.HOME ?? "";
17695
- const configPath = join(home, ".exe-os", "config.json");
17696
- let empConfig = {};
17697
- if (existsSync14(configPath)) {
17698
- try {
17699
- const raw = JSON.parse(readFileSync11(configPath, "utf8"));
17700
- if (raw.employees && typeof raw.employees === "object") {
17701
- empConfig = raw.employees;
17702
- }
17703
- } catch {
17704
- }
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: "" });
17705
18315
  }
17706
- setEmployeeModels(roster.filter((e) => e.name !== "exe").map((e) => ({
17707
- name: e.name,
17708
- model: empConfig[e.name]?.model ?? "claude-sonnet-4-6",
17709
- provider: empConfig[e.name]?.provider ?? "anthropic"
17710
- })));
17711
- } catch {
17712
- }
17713
- try {
17714
- const { existsSync: existsSync14, readFileSync: readFileSync11 } = await import("fs");
17715
- const { join } = await import("path");
17716
- const home = process.env.HOME ?? "";
17717
- const ccSettingsPath = join(home, ".claude", "settings.json");
17718
- const installed = existsSync14(ccSettingsPath);
17719
- let hooksWired = false;
17720
- if (installed) {
17721
- try {
17722
- const settings = JSON.parse(readFileSync11(ccSettingsPath, "utf8"));
17723
- const hooks = settings.hooks;
17724
- if (hooks) {
17725
- hooksWired = Object.values(hooks).flat().some((h) => h.command?.includes("exe-os") || h.command?.includes("exe-mem"));
17726
- }
17727
- } catch {
17728
- }
18316
+ const pidPath = join(home, ".exe-os", "exed.pid");
18317
+ let daemon = "unknown";
18318
+ try {
18319
+ daemon = existsSync14(pidPath) ? "running" : "stopped";
18320
+ } catch {
17729
18321
  }
17730
- setClaudeCode({ installed, hooksWired });
17731
- } catch {
17732
- }
17733
- try {
17734
- const { existsSync: existsSync14, readFileSync: readFileSync11 } = await import("fs");
17735
- const { join } = await import("path");
17736
- const home = process.env.HOME ?? "";
17737
- const licensePath = join(home, ".exe-os", "license.json");
17738
- 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 {
17739
18331
  try {
17740
- const lic = JSON.parse(readFileSync11(licensePath, "utf8"));
17741
- 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;
17742
18336
  } catch {
17743
- setLicense({ valid: false, detail: "invalid license file" });
17744
18337
  }
17745
- } else {
17746
- setLicense({ valid: false, detail: "no license" });
17747
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
+ });
17748
18346
  } catch {
17749
18347
  }
17750
18348
  try {
17751
- const { existsSync: existsSync14 } = await import("fs");
17752
- const { join } = await import("path");
17753
- const home = process.env.HOME ?? "";
17754
- const cloudPath = join(home, ".exe-os", "cloud.json");
17755
- if (existsSync14(cloudPath)) {
17756
- setCloudSync({ configured: true, detail: "configured" });
17757
- } else {
17758
- setCloudSync({ configured: false, detail: "not configured" });
17759
- }
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
+ });
17760
18359
  } catch {
18360
+ setLicense({ valid: false, plan: "FREE", expiresAt: null, deviceLimit: 1, employeeLimit: 1, memoryLimit: 5e3 });
17761
18361
  }
17762
- }
17763
- const statusColor = (ok) => ok ? "#22C55E" : "#6B4C9A";
17764
- const sectionColor = (idx) => selectedSection === idx ? "#F5D76E" : void 0;
17765
- 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;
17766
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();
17767
18392
  return /* @__PURE__ */ jsxs12(Box_default, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: [
17768
18393
  /* @__PURE__ */ jsx14(Box_default, { borderStyle: "single", borderColor: "#3D3660", paddingX: 1, alignSelf: "flex-start", children: /* @__PURE__ */ jsx14(Text, { bold: true, children: "Settings" }) }),
17769
- /* @__PURE__ */ jsx14(Text, { color: "#6B4C9A", children: "\u2191\u2193 navigate sections" }),
18394
+ /* @__PURE__ */ jsx14(Text, { color: DIM, children: "\u2191\u2193 navigate sections r refresh" }),
17770
18395
  /* @__PURE__ */ jsx14(Text, { children: " " }),
17771
18396
  loading && /* @__PURE__ */ jsxs12(Fragment5, { children: [
17772
- /* @__PURE__ */ jsx14(Text, { color: "#6B4C9A", children: "Loading settings..." }),
18397
+ /* @__PURE__ */ jsx14(Text, { color: DIM, children: "Loading settings..." }),
17773
18398
  /* @__PURE__ */ jsx14(Text, { children: " " })
17774
18399
  ] }),
17775
18400
  /* @__PURE__ */ jsxs12(Text, { bold: true, backgroundColor: sectionBg(0), color: sectionColor(0), children: [
@@ -17779,6 +18404,8 @@ function SettingsView({ onBack }) {
17779
18404
  /* @__PURE__ */ jsx14(Text, { children: " " }),
17780
18405
  providers.map((p) => /* @__PURE__ */ jsxs12(Text, { children: [
17781
18406
  " ",
18407
+ /* @__PURE__ */ jsx14(Text, { color: statusColor(p.configured), children: statusDot(p.configured) }),
18408
+ " ",
17782
18409
  p.name,
17783
18410
  ": ",
17784
18411
  /* @__PURE__ */ jsx14(Text, { color: statusColor(p.configured), children: p.detail })
@@ -17786,109 +18413,301 @@ function SettingsView({ onBack }) {
17786
18413
  /* @__PURE__ */ jsx14(Text, { children: " " }),
17787
18414
  /* @__PURE__ */ jsxs12(Text, { bold: true, backgroundColor: sectionBg(1), color: sectionColor(1), children: [
17788
18415
  sectionMarker(1),
17789
- "Memory"
18416
+ "Cloud Sync"
17790
18417
  ] }),
17791
18418
  /* @__PURE__ */ jsx14(Text, { children: " " }),
17792
- /* @__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: [
17793
18437
  " ",
17794
- "Encryption: ",
17795
- /* @__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" })
17796
18441
  ] }),
17797
- /* @__PURE__ */ jsxs12(Text, { children: [
17798
- " ",
17799
- "Embedding: ",
17800
- /* @__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"
17801
18446
  ] }),
18447
+ /* @__PURE__ */ jsx14(Text, { children: " " }),
17802
18448
  /* @__PURE__ */ jsxs12(Text, { children: [
17803
18449
  " ",
17804
- "Cloud sync: ",
17805
- /* @__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 })
17806
18452
  ] }),
17807
- /* @__PURE__ */ jsxs12(Text, { children: [
18453
+ license.expiresAt && /* @__PURE__ */ jsxs12(Text, { children: [
17808
18454
  " ",
17809
- "Search mode: ",
17810
- /* @__PURE__ */ jsx14(Text, { color: "#22C55E", children: memory.searchMode })
17811
- ] }),
17812
- /* @__PURE__ */ jsx14(Text, { children: " " }),
17813
- /* @__PURE__ */ jsxs12(Text, { bold: true, backgroundColor: sectionBg(2), color: sectionColor(2), children: [
17814
- sectionMarker(2),
17815
- "Runtime"
18455
+ "Expires: ",
18456
+ /* @__PURE__ */ jsx14(Text, { color: GREEN, children: license.expiresAt.split("T")[0] })
17816
18457
  ] }),
17817
- /* @__PURE__ */ jsx14(Text, { children: " " }),
17818
18458
  /* @__PURE__ */ jsxs12(Text, { children: [
17819
18459
  " ",
17820
- "Consolidation: ",
17821
- /* @__PURE__ */ jsx14(Text, { color: runtime.consolidation ? "#22C55E" : "#6B4C9A", children: runtime.consolidation ? "enabled" : "disabled" }),
17822
- " (",
17823
- runtime.consolidationModel,
17824
- ")"
18460
+ "Devices: ",
18461
+ /* @__PURE__ */ jsx14(Text, { color: GREEN, children: formatLimit(license.deviceLimit) })
17825
18462
  ] }),
17826
18463
  /* @__PURE__ */ jsxs12(Text, { children: [
17827
18464
  " ",
17828
- "Skill learning: ",
17829
- /* @__PURE__ */ jsx14(Text, { color: runtime.skillLearning ? "#22C55E" : "#6B4C9A", children: runtime.skillLearning ? "enabled" : "disabled" }),
17830
- " (threshold: ",
17831
- runtime.skillThreshold,
17832
- ")"
18465
+ "Employees: ",
18466
+ /* @__PURE__ */ jsx14(Text, { color: GREEN, children: formatLimit(license.employeeLimit) })
17833
18467
  ] }),
17834
18468
  /* @__PURE__ */ jsxs12(Text, { children: [
17835
18469
  " ",
17836
- "Failover chain: ",
17837
- /* @__PURE__ */ jsx14(Text, { color: "#22C55E", children: runtime.failoverChain.join(" \u2192 ") })
18470
+ "Memory limit: ",
18471
+ /* @__PURE__ */ jsx14(Text, { color: GREEN, children: formatLimit(license.memoryLimit) })
17838
18472
  ] }),
17839
18473
  /* @__PURE__ */ jsx14(Text, { children: " " }),
17840
18474
  /* @__PURE__ */ jsxs12(Text, { bold: true, backgroundColor: sectionBg(3), color: sectionColor(3), children: [
17841
18475
  sectionMarker(3),
17842
- "Employee Models"
18476
+ "System"
17843
18477
  ] }),
17844
18478
  /* @__PURE__ */ jsx14(Text, { children: " " }),
17845
- loading ? /* @__PURE__ */ jsxs12(Text, { color: "#6B4C9A", children: [
17846
- " ",
17847
- "Loading..."
17848
- ] }) : employeeModels.length === 0 ? /* @__PURE__ */ jsxs12(Text, { color: "#6B4C9A", children: [
18479
+ /* @__PURE__ */ jsxs12(Text, { children: [
17849
18480
  " ",
17850
- "No employees configured"
17851
- ] }) : 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: [
17852
18486
  " ",
17853
- e.name,
17854
- ": ",
17855
- /* @__PURE__ */ jsx14(Text, { color: "#22C55E", children: e.model }),
17856
- " ",
17857
- /* @__PURE__ */ jsxs12(Text, { color: "#6B4C9A", children: [
17858
- "via ",
17859
- e.provider
17860
- ] })
17861
- ] }, e.name)),
17862
- /* @__PURE__ */ jsx14(Text, { children: " " }),
17863
- /* @__PURE__ */ jsxs12(Text, { bold: true, backgroundColor: sectionBg(4), color: sectionColor(4), children: [
17864
- sectionMarker(4),
17865
- "Integrations"
18487
+ "Version: ",
18488
+ /* @__PURE__ */ jsx14(Text, { color: GREEN, children: system.version })
17866
18489
  ] }),
17867
- /* @__PURE__ */ jsx14(Text, { children: " " }),
17868
18490
  /* @__PURE__ */ jsxs12(Text, { children: [
17869
18491
  " ",
17870
- "Claude Code: ",
17871
- /* @__PURE__ */ jsx14(Text, { color: claudeCode.installed ? "#22C55E" : "#6B4C9A", children: claudeCode.installed ? "installed" : "not found" }),
17872
- claudeCode.installed && /* @__PURE__ */ jsxs12(Text, { color: claudeCode.hooksWired ? "#22C55E" : "#6B4C9A", children: [
17873
- " \u2014 hooks ",
17874
- claudeCode.hooksWired ? "wired" : "not wired"
17875
- ] })
18492
+ "Encryption: ",
18493
+ /* @__PURE__ */ jsx14(Text, { color: system.encryption.includes("AES") ? GREEN : DIM, children: system.encryption })
17876
18494
  ] }),
17877
18495
  /* @__PURE__ */ jsxs12(Text, { children: [
17878
18496
  " ",
17879
- "License: ",
17880
- /* @__PURE__ */ jsx14(Text, { color: license.valid ? "#22C55E" : "#6B4C9A", children: license.detail })
18497
+ "Embedding: ",
18498
+ /* @__PURE__ */ jsx14(Text, { color: GREEN, children: system.embeddingModel })
17881
18499
  ] }),
17882
18500
  /* @__PURE__ */ jsxs12(Text, { children: [
17883
18501
  " ",
17884
- "Cloud sync: ",
17885
- /* @__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" })
17886
18523
  ] })
17887
18524
  ] });
17888
18525
  }
17889
18526
 
17890
- // 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();
17891
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
+ };
17892
18711
  var isDemo = process.argv.includes("--demo");
17893
18712
  process.stderr.write(`[exe-tui] Terminal: ${TERMINAL_TYPE}
17894
18713
  `);
@@ -17915,17 +18734,17 @@ if (!isDemo) {
17915
18734
  })();
17916
18735
  }
17917
18736
  function App2() {
17918
- const [section, setSection] = useState15("command-center");
18737
+ const [section, setSection] = useState16("command-center");
17919
18738
  const { exit } = use_app_default();
17920
- const [, forceUpdate] = useState15(0);
17921
- useEffect17(() => {
18739
+ const [, forceUpdate] = useState16(0);
18740
+ useEffect18(() => {
17922
18741
  const handleResize = () => forceUpdate((n) => n + 1);
17923
18742
  process.stdout.on("resize", handleResize);
17924
18743
  return () => {
17925
18744
  process.stdout.off("resize", handleResize);
17926
18745
  };
17927
18746
  }, []);
17928
- useMouseEvent(useCallback7((event) => {
18747
+ useMouseEvent(useCallback8((event) => {
17929
18748
  if (event.button !== 0) return;
17930
18749
  if (event.col <= 26) {
17931
18750
  const tabIdx = event.row - 4;
@@ -17937,22 +18756,33 @@ function App2() {
17937
18756
  setFocus("content");
17938
18757
  }
17939
18758
  }, []));
17940
- const [focus, setFocus] = useState15("sidebar");
17941
- const [focusedProject, setFocusedProject] = useState15(null);
17942
- 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) => {
17943
18764
  setFocusedProject(projectName);
17944
18765
  setSection("sessions");
17945
18766
  setFocus("content");
17946
18767
  }, []);
17947
- const handleBackToCommandCenter = useCallback7(() => {
18768
+ const handleBackToCommandCenter = useCallback8(() => {
17948
18769
  setSection("command-center");
17949
18770
  setFocus("sidebar");
17950
18771
  }, []);
17951
18772
  use_input_default((input, key) => {
18773
+ if (input === "?" || key.shift && input === "/") {
18774
+ setShowHelp((prev) => !prev);
18775
+ return;
18776
+ }
17952
18777
  const idx = parseInt(input, 10);
17953
18778
  if (idx >= 1 && idx <= SECTIONS.length) {
17954
18779
  setSection(SECTIONS[idx - 1].key);
17955
18780
  setFocus("sidebar");
18781
+ setShowHelp(false);
18782
+ return;
18783
+ }
18784
+ if (input === "d" && !key.ctrl) {
18785
+ setShowDebug((prev) => !prev);
17956
18786
  return;
17957
18787
  }
17958
18788
  if (input === "q" && !key.ctrl) {
@@ -17980,24 +18810,34 @@ function App2() {
17980
18810
  }
17981
18811
  }
17982
18812
  });
17983
- const consumeFocusedProject = useCallback7(() => {
18813
+ const consumeFocusedProject = useCallback8(() => {
17984
18814
  setFocusedProject(null);
17985
18815
  }, []);
17986
18816
  const views = {
17987
- "command-center": /* @__PURE__ */ jsx15(CommandCenterView, { onSelectProject: handleSelectProject, isFocused: focus === "content" && section === "command-center", onBack: () => setFocus("sidebar") }),
17988
- sessions: /* @__PURE__ */ jsx15(SessionsView, { initialProject: focusedProject, onConsumeInitialProject: consumeFocusedProject, onBackToCommandCenter: handleBackToCommandCenter, onBack: () => setFocus("sidebar") }),
17989
- tasks: /* @__PURE__ */ jsx15(TasksView, { onBack: () => setFocus("sidebar") }),
17990
- team: /* @__PURE__ */ jsx15(TeamView, { onBack: () => setFocus("sidebar") }),
17991
- gateway: /* @__PURE__ */ jsx15(GatewayView, { onBack: () => setFocus("sidebar") }),
17992
- wiki: /* @__PURE__ */ jsx15(WikiView, { onBack: () => setFocus("sidebar") }),
17993
- 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") })
17994
18828
  };
17995
- return /* @__PURE__ */ jsx15(ErrorBoundary2, { children: /* @__PURE__ */ jsx15(AlternateScreen, { children: /* @__PURE__ */ jsx15(DemoProvider, { demo: isDemo, children: /* @__PURE__ */ jsxs13(Box_default, { flexDirection: "column", flexGrow: 1, children: [
17996
- /* @__PURE__ */ jsxs13(Box_default, { flexGrow: 1, children: [
17997
- /* @__PURE__ */ jsx15(Sidebar, { active: section, onSelect: setSection, onQuit: exit, focused: focus === "sidebar" }),
17998
- 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]
17999
18839
  ] }),
18000
- /* @__PURE__ */ jsx15(Footer, {})
18840
+ /* @__PURE__ */ jsx17(Footer, {})
18001
18841
  ] }) }) }) });
18002
18842
  }
18003
18843
  {
@@ -18015,4 +18855,4 @@ function App2() {
18015
18855
  stdin.unref = () => stdin;
18016
18856
  }
18017
18857
  }
18018
- render_default(/* @__PURE__ */ jsx15(App2, {}));
18858
+ render_default(/* @__PURE__ */ jsx17(App2, {}));