@askexenow/exe-os 0.8.0

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 (131) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +139 -0
  3. package/dist/bin/backfill-responses.js +1912 -0
  4. package/dist/bin/backfill-vectors.js +1642 -0
  5. package/dist/bin/cleanup-stale-review-tasks.js +1339 -0
  6. package/dist/bin/cli.js +18800 -0
  7. package/dist/bin/exe-agent.js +1858 -0
  8. package/dist/bin/exe-assign.js +1957 -0
  9. package/dist/bin/exe-boot.js +6460 -0
  10. package/dist/bin/exe-call.js +197 -0
  11. package/dist/bin/exe-cloud.js +850 -0
  12. package/dist/bin/exe-dispatch.js +1146 -0
  13. package/dist/bin/exe-doctor.js +1657 -0
  14. package/dist/bin/exe-export-behaviors.js +1494 -0
  15. package/dist/bin/exe-forget.js +1627 -0
  16. package/dist/bin/exe-gateway.js +7732 -0
  17. package/dist/bin/exe-healthcheck.js +207 -0
  18. package/dist/bin/exe-heartbeat.js +1647 -0
  19. package/dist/bin/exe-kill.js +1479 -0
  20. package/dist/bin/exe-launch-agent.js +1704 -0
  21. package/dist/bin/exe-link.js +192 -0
  22. package/dist/bin/exe-new-employee.js +852 -0
  23. package/dist/bin/exe-pending-messages.js +1446 -0
  24. package/dist/bin/exe-pending-notifications.js +1321 -0
  25. package/dist/bin/exe-pending-reviews.js +1468 -0
  26. package/dist/bin/exe-repo-drift.js +95 -0
  27. package/dist/bin/exe-review.js +1590 -0
  28. package/dist/bin/exe-search.js +2651 -0
  29. package/dist/bin/exe-session-cleanup.js +3173 -0
  30. package/dist/bin/exe-settings.js +354 -0
  31. package/dist/bin/exe-status.js +1532 -0
  32. package/dist/bin/exe-team.js +1324 -0
  33. package/dist/bin/git-sweep.js +2185 -0
  34. package/dist/bin/graph-backfill.js +1968 -0
  35. package/dist/bin/graph-export.js +1604 -0
  36. package/dist/bin/install.js +656 -0
  37. package/dist/bin/list-providers.js +140 -0
  38. package/dist/bin/scan-tasks.js +1820 -0
  39. package/dist/bin/setup.js +951 -0
  40. package/dist/bin/shard-migrate.js +1494 -0
  41. package/dist/bin/update.js +95 -0
  42. package/dist/bin/wiki-sync.js +1514 -0
  43. package/dist/gateway/index.js +8848 -0
  44. package/dist/hooks/bug-report-worker.js +2743 -0
  45. package/dist/hooks/commit-complete.js +2108 -0
  46. package/dist/hooks/error-recall.js +2861 -0
  47. package/dist/hooks/exe-heartbeat-hook.js +232 -0
  48. package/dist/hooks/ingest-worker.js +4793 -0
  49. package/dist/hooks/ingest.js +684 -0
  50. package/dist/hooks/instructions-loaded.js +1880 -0
  51. package/dist/hooks/notification.js +1726 -0
  52. package/dist/hooks/post-compact.js +1751 -0
  53. package/dist/hooks/pre-compact.js +1746 -0
  54. package/dist/hooks/pre-tool-use.js +2191 -0
  55. package/dist/hooks/prompt-ingest-worker.js +2126 -0
  56. package/dist/hooks/prompt-submit.js +4693 -0
  57. package/dist/hooks/response-ingest-worker.js +1936 -0
  58. package/dist/hooks/session-end.js +1752 -0
  59. package/dist/hooks/session-start.js +2795 -0
  60. package/dist/hooks/stop.js +1835 -0
  61. package/dist/hooks/subagent-stop.js +1726 -0
  62. package/dist/hooks/summary-worker.js +2661 -0
  63. package/dist/index.js +11834 -0
  64. package/dist/lib/cloud-sync.js +495 -0
  65. package/dist/lib/config.js +222 -0
  66. package/dist/lib/consolidation.js +476 -0
  67. package/dist/lib/crypto.js +51 -0
  68. package/dist/lib/database.js +730 -0
  69. package/dist/lib/device-registry.js +900 -0
  70. package/dist/lib/embedder.js +632 -0
  71. package/dist/lib/employee-templates.js +543 -0
  72. package/dist/lib/employees.js +177 -0
  73. package/dist/lib/error-detector.js +156 -0
  74. package/dist/lib/exe-daemon-client.js +451 -0
  75. package/dist/lib/exe-daemon.js +8285 -0
  76. package/dist/lib/file-grep.js +199 -0
  77. package/dist/lib/hybrid-search.js +1819 -0
  78. package/dist/lib/identity-templates.js +320 -0
  79. package/dist/lib/identity.js +223 -0
  80. package/dist/lib/keychain.js +145 -0
  81. package/dist/lib/license.js +377 -0
  82. package/dist/lib/messaging.js +1376 -0
  83. package/dist/lib/reminders.js +63 -0
  84. package/dist/lib/schedules.js +1396 -0
  85. package/dist/lib/session-registry.js +52 -0
  86. package/dist/lib/skill-learning.js +477 -0
  87. package/dist/lib/status-brief.js +235 -0
  88. package/dist/lib/store.js +1551 -0
  89. package/dist/lib/task-router.js +62 -0
  90. package/dist/lib/tasks.js +2456 -0
  91. package/dist/lib/tmux-routing.js +2836 -0
  92. package/dist/lib/tmux-status.js +261 -0
  93. package/dist/lib/tmux-transport.js +83 -0
  94. package/dist/lib/transport.js +128 -0
  95. package/dist/lib/ws-auth.js +19 -0
  96. package/dist/lib/ws-client.js +160 -0
  97. package/dist/mcp/server.js +10538 -0
  98. package/dist/mcp/tools/complete-reminder.js +67 -0
  99. package/dist/mcp/tools/create-reminder.js +52 -0
  100. package/dist/mcp/tools/create-task.js +1853 -0
  101. package/dist/mcp/tools/deactivate-behavior.js +263 -0
  102. package/dist/mcp/tools/list-reminders.js +62 -0
  103. package/dist/mcp/tools/list-tasks.js +463 -0
  104. package/dist/mcp/tools/send-message.js +1382 -0
  105. package/dist/mcp/tools/update-task.js +1692 -0
  106. package/dist/runtime/index.js +6809 -0
  107. package/dist/tui/App.js +17479 -0
  108. package/package.json +104 -0
  109. package/src/commands/exe/assign.md +17 -0
  110. package/src/commands/exe/build-adv.md +381 -0
  111. package/src/commands/exe/call.md +133 -0
  112. package/src/commands/exe/cloud.md +17 -0
  113. package/src/commands/exe/employee-heartbeat.md +44 -0
  114. package/src/commands/exe/forget.md +15 -0
  115. package/src/commands/exe/heartbeat.md +92 -0
  116. package/src/commands/exe/intercom.md +81 -0
  117. package/src/commands/exe/kill.md +34 -0
  118. package/src/commands/exe/launch.md +52 -0
  119. package/src/commands/exe/link.md +17 -0
  120. package/src/commands/exe/logs.md +22 -0
  121. package/src/commands/exe/new-employee.md +12 -0
  122. package/src/commands/exe/review.md +14 -0
  123. package/src/commands/exe/schedule.md +108 -0
  124. package/src/commands/exe/search.md +13 -0
  125. package/src/commands/exe/sessions.md +25 -0
  126. package/src/commands/exe/settings.md +13 -0
  127. package/src/commands/exe/setup.md +171 -0
  128. package/src/commands/exe/status.md +15 -0
  129. package/src/commands/exe/team.md +11 -0
  130. package/src/commands/exe/update.md +11 -0
  131. package/src/commands/exe.md +181 -0
@@ -0,0 +1,2743 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
6
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
7
+ }) : x)(function(x) {
8
+ if (typeof require !== "undefined") return require.apply(this, arguments);
9
+ throw Error('Dynamic require of "' + x + '" is not supported');
10
+ });
11
+ var __esm = (fn, res) => function __init() {
12
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
13
+ };
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
+
28
+ // src/lib/database.ts
29
+ import { createClient } from "@libsql/client";
30
+ async function initDatabase(config) {
31
+ if (_client) {
32
+ _client.close();
33
+ _client = null;
34
+ }
35
+ const opts = {
36
+ url: `file:${config.dbPath}`
37
+ };
38
+ if (config.encryptionKey) {
39
+ opts.encryptionKey = config.encryptionKey;
40
+ }
41
+ _client = createClient(opts);
42
+ }
43
+ function getClient() {
44
+ if (!_client) {
45
+ throw new Error("Database client not initialized. Call initDatabase() first.");
46
+ }
47
+ return _client;
48
+ }
49
+ async function ensureSchema() {
50
+ const client = getClient();
51
+ await client.execute("PRAGMA journal_mode = WAL");
52
+ await client.execute("PRAGMA busy_timeout = 5000");
53
+ try {
54
+ await client.execute("PRAGMA libsql_vector_search_ef = 128");
55
+ } catch {
56
+ }
57
+ await client.executeMultiple(`
58
+ CREATE TABLE IF NOT EXISTS memories (
59
+ id TEXT PRIMARY KEY,
60
+ agent_id TEXT NOT NULL,
61
+ agent_role TEXT NOT NULL,
62
+ session_id TEXT NOT NULL,
63
+ timestamp TEXT NOT NULL,
64
+ tool_name TEXT NOT NULL,
65
+ project_name TEXT NOT NULL,
66
+ has_error INTEGER NOT NULL DEFAULT 0,
67
+ raw_text TEXT NOT NULL,
68
+ vector F32_BLOB(1024),
69
+ version INTEGER NOT NULL DEFAULT 0
70
+ );
71
+
72
+ CREATE INDEX IF NOT EXISTS idx_memories_agent
73
+ ON memories(agent_id);
74
+
75
+ CREATE INDEX IF NOT EXISTS idx_memories_timestamp
76
+ ON memories(timestamp);
77
+
78
+ CREATE INDEX IF NOT EXISTS idx_memories_session
79
+ ON memories(session_id);
80
+
81
+ CREATE INDEX IF NOT EXISTS idx_memories_project
82
+ ON memories(project_name);
83
+
84
+ CREATE INDEX IF NOT EXISTS idx_memories_tool
85
+ ON memories(tool_name);
86
+
87
+ CREATE INDEX IF NOT EXISTS idx_memories_version
88
+ ON memories(version);
89
+
90
+ CREATE INDEX IF NOT EXISTS idx_memories_agent_project
91
+ ON memories(agent_id, project_name);
92
+ `);
93
+ await client.executeMultiple(`
94
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
95
+ raw_text,
96
+ content='memories',
97
+ content_rowid='rowid'
98
+ );
99
+
100
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
101
+ INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
102
+ END;
103
+
104
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
105
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
106
+ END;
107
+
108
+ CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
109
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
110
+ INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
111
+ END;
112
+ `);
113
+ await client.executeMultiple(`
114
+ CREATE TABLE IF NOT EXISTS sync_meta (
115
+ key TEXT PRIMARY KEY,
116
+ value TEXT NOT NULL
117
+ );
118
+ `);
119
+ await client.executeMultiple(`
120
+ CREATE TABLE IF NOT EXISTS tasks (
121
+ id TEXT PRIMARY KEY,
122
+ title TEXT NOT NULL,
123
+ assigned_to TEXT NOT NULL,
124
+ assigned_by TEXT NOT NULL,
125
+ project_name TEXT NOT NULL,
126
+ priority TEXT NOT NULL DEFAULT 'p1',
127
+ status TEXT NOT NULL DEFAULT 'open',
128
+ task_file TEXT,
129
+ created_at TEXT NOT NULL,
130
+ updated_at TEXT NOT NULL
131
+ );
132
+
133
+ CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
134
+ ON tasks(assigned_to, status);
135
+ `);
136
+ await client.executeMultiple(`
137
+ CREATE TABLE IF NOT EXISTS behaviors (
138
+ id TEXT PRIMARY KEY,
139
+ agent_id TEXT NOT NULL,
140
+ project_name TEXT,
141
+ domain TEXT,
142
+ content TEXT NOT NULL,
143
+ active INTEGER NOT NULL DEFAULT 1,
144
+ created_at TEXT NOT NULL,
145
+ updated_at TEXT NOT NULL
146
+ );
147
+
148
+ CREATE INDEX IF NOT EXISTS idx_behaviors_agent
149
+ ON behaviors(agent_id, active);
150
+ `);
151
+ try {
152
+ const existing = await client.execute({
153
+ sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = 'exe'",
154
+ args: []
155
+ });
156
+ if (Number(existing.rows[0]?.cnt) === 0) {
157
+ await client.executeMultiple(`
158
+ INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
159
+ VALUES
160
+ (hex(randomblob(16)), 'exe', NULL, 'workflow', 'Don''t ask "keep going?" \u2014 just keep executing phases/plans autonomously', 1, '2026-03-25T00:00:00Z', '2026-03-25T00:00:00Z');
161
+ INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
162
+ VALUES
163
+ (hex(randomblob(16)), 'exe', NULL, 'tool-use', 'Always use create_task MCP tool, never write .md files directly for task creation', 1, '2026-03-25T00:00:00Z', '2026-03-25T00:00:00Z');
164
+ INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
165
+ VALUES
166
+ (hex(randomblob(16)), 'exe', NULL, 'workflow', 'Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission', 1, '2026-03-25T00:00:00Z', '2026-03-25T00:00:00Z');
167
+ `);
168
+ }
169
+ } catch {
170
+ }
171
+ try {
172
+ await client.execute({
173
+ sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
174
+ args: []
175
+ });
176
+ } catch {
177
+ }
178
+ try {
179
+ await client.execute({
180
+ sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
181
+ args: []
182
+ });
183
+ } catch {
184
+ }
185
+ try {
186
+ await client.execute({
187
+ sql: `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`,
188
+ args: []
189
+ });
190
+ } catch {
191
+ }
192
+ try {
193
+ await client.execute({
194
+ sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
195
+ ON tasks(parent_task_id)
196
+ WHERE parent_task_id IS NOT NULL`,
197
+ args: []
198
+ });
199
+ } catch {
200
+ }
201
+ try {
202
+ await client.execute({
203
+ sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
204
+ args: []
205
+ });
206
+ } catch {
207
+ }
208
+ try {
209
+ await client.execute({
210
+ sql: `ALTER TABLE tasks ADD COLUMN reviewer TEXT`,
211
+ args: []
212
+ });
213
+ } catch {
214
+ }
215
+ try {
216
+ await client.execute({
217
+ sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
218
+ args: []
219
+ });
220
+ } catch {
221
+ }
222
+ try {
223
+ await client.execute({
224
+ sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
225
+ args: []
226
+ });
227
+ } catch {
228
+ }
229
+ try {
230
+ await client.execute({
231
+ sql: `ALTER TABLE tasks ADD COLUMN assigned_tmux TEXT`,
232
+ args: []
233
+ });
234
+ } catch {
235
+ }
236
+ try {
237
+ await client.execute({
238
+ sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
239
+ args: []
240
+ });
241
+ } catch {
242
+ }
243
+ try {
244
+ await client.execute({
245
+ sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
246
+ args: []
247
+ });
248
+ } catch {
249
+ }
250
+ try {
251
+ await client.execute({
252
+ sql: `ALTER TABLE memories ADD COLUMN author_device_id TEXT`,
253
+ args: []
254
+ });
255
+ } catch {
256
+ }
257
+ try {
258
+ await client.execute({
259
+ sql: `ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'`,
260
+ args: []
261
+ });
262
+ } catch {
263
+ }
264
+ await client.executeMultiple(`
265
+ CREATE TABLE IF NOT EXISTS consolidations (
266
+ id TEXT PRIMARY KEY,
267
+ consolidated_memory_id TEXT NOT NULL,
268
+ source_memory_id TEXT NOT NULL,
269
+ created_at TEXT NOT NULL
270
+ );
271
+
272
+ CREATE INDEX IF NOT EXISTS idx_consolidations_source
273
+ ON consolidations(source_memory_id);
274
+
275
+ CREATE INDEX IF NOT EXISTS idx_consolidations_consolidated
276
+ ON consolidations(consolidated_memory_id);
277
+ `);
278
+ await client.executeMultiple(`
279
+ CREATE TABLE IF NOT EXISTS reminders (
280
+ id TEXT PRIMARY KEY,
281
+ text TEXT NOT NULL,
282
+ created_at TEXT NOT NULL,
283
+ due_date TEXT,
284
+ completed_at TEXT
285
+ );
286
+ `);
287
+ await client.executeMultiple(`
288
+ CREATE TABLE IF NOT EXISTS notifications (
289
+ id TEXT PRIMARY KEY,
290
+ agent_id TEXT NOT NULL,
291
+ agent_role TEXT NOT NULL,
292
+ event TEXT NOT NULL,
293
+ project TEXT NOT NULL,
294
+ summary TEXT NOT NULL,
295
+ task_file TEXT,
296
+ read INTEGER NOT NULL DEFAULT 0,
297
+ created_at TEXT NOT NULL
298
+ );
299
+
300
+ CREATE INDEX IF NOT EXISTS idx_notifications_read
301
+ ON notifications(read);
302
+
303
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent
304
+ ON notifications(agent_id);
305
+
306
+ CREATE INDEX IF NOT EXISTS idx_notifications_task_file
307
+ ON notifications(task_file);
308
+ `);
309
+ await client.executeMultiple(`
310
+ CREATE TABLE IF NOT EXISTS schedules (
311
+ id TEXT PRIMARY KEY,
312
+ cron TEXT NOT NULL,
313
+ description TEXT NOT NULL,
314
+ job_type TEXT NOT NULL DEFAULT 'report',
315
+ prompt TEXT,
316
+ assigned_to TEXT,
317
+ project_name TEXT,
318
+ active INTEGER NOT NULL DEFAULT 1,
319
+ use_crontab INTEGER NOT NULL DEFAULT 0,
320
+ created_at TEXT NOT NULL
321
+ );
322
+ `);
323
+ await client.executeMultiple(`
324
+ CREATE TABLE IF NOT EXISTS device_registry (
325
+ device_id TEXT PRIMARY KEY,
326
+ friendly_name TEXT NOT NULL,
327
+ hostname TEXT NOT NULL,
328
+ projects TEXT NOT NULL DEFAULT '[]',
329
+ agents TEXT NOT NULL DEFAULT '[]',
330
+ connected INTEGER DEFAULT 0,
331
+ last_seen TEXT NOT NULL
332
+ );
333
+ `);
334
+ await client.executeMultiple(`
335
+ CREATE TABLE IF NOT EXISTS messages (
336
+ id TEXT PRIMARY KEY,
337
+ from_agent TEXT NOT NULL,
338
+ from_device TEXT NOT NULL DEFAULT 'local',
339
+ target_agent TEXT NOT NULL,
340
+ target_project TEXT,
341
+ target_device TEXT NOT NULL DEFAULT 'local',
342
+ content TEXT NOT NULL,
343
+ priority TEXT DEFAULT 'normal',
344
+ status TEXT DEFAULT 'pending',
345
+ server_seq INTEGER,
346
+ retry_count INTEGER DEFAULT 0,
347
+ created_at TEXT NOT NULL,
348
+ delivered_at TEXT,
349
+ processed_at TEXT,
350
+ failed_at TEXT,
351
+ failure_reason TEXT
352
+ );
353
+
354
+ CREATE INDEX IF NOT EXISTS idx_messages_target
355
+ ON messages(target_agent, status);
356
+
357
+ CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
358
+ ON messages(target_agent, from_agent, server_seq);
359
+ `);
360
+ try {
361
+ await client.execute({
362
+ sql: `UPDATE memories SET project_name = 'exe-create' WHERE project_name = 'web'`,
363
+ args: []
364
+ });
365
+ await client.execute({
366
+ sql: `UPDATE memories SET project_name = 'exe-os' WHERE project_name = 'worker'`,
367
+ args: []
368
+ });
369
+ await client.execute({
370
+ sql: `UPDATE tasks SET project_name = 'exe-create' WHERE project_name = 'web'`,
371
+ args: []
372
+ });
373
+ await client.execute({
374
+ sql: `UPDATE tasks SET project_name = 'exe-os' WHERE project_name = 'worker'`,
375
+ args: []
376
+ });
377
+ } catch {
378
+ }
379
+ await client.executeMultiple(`
380
+ CREATE TABLE IF NOT EXISTS trajectories (
381
+ id TEXT PRIMARY KEY,
382
+ task_id TEXT NOT NULL,
383
+ agent_id TEXT NOT NULL,
384
+ project_name TEXT NOT NULL,
385
+ task_title TEXT NOT NULL,
386
+ signature TEXT NOT NULL,
387
+ signature_hash TEXT NOT NULL,
388
+ tool_count INTEGER NOT NULL,
389
+ skill_id TEXT,
390
+ created_at TEXT NOT NULL
391
+ );
392
+
393
+ CREATE INDEX IF NOT EXISTS idx_trajectories_hash
394
+ ON trajectories(signature_hash);
395
+
396
+ CREATE INDEX IF NOT EXISTS idx_trajectories_agent
397
+ ON trajectories(agent_id);
398
+ `);
399
+ try {
400
+ await client.execute("ALTER TABLE trajectories ADD COLUMN skill_id TEXT");
401
+ } catch {
402
+ }
403
+ await client.executeMultiple(`
404
+ CREATE TABLE IF NOT EXISTS consolidations (
405
+ id TEXT PRIMARY KEY,
406
+ consolidated_memory_id TEXT NOT NULL,
407
+ source_memory_id TEXT NOT NULL,
408
+ created_at TEXT NOT NULL
409
+ );
410
+
411
+ CREATE INDEX IF NOT EXISTS idx_consolidations_source
412
+ ON consolidations(source_memory_id);
413
+ `);
414
+ await client.executeMultiple(`
415
+ CREATE TABLE IF NOT EXISTS audit_trail (
416
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
417
+ timestamp TEXT NOT NULL,
418
+ session_id TEXT NOT NULL,
419
+ agent_id TEXT NOT NULL,
420
+ tool TEXT NOT NULL,
421
+ input TEXT,
422
+ decision TEXT NOT NULL,
423
+ reason TEXT,
424
+ is_customer_facing INTEGER NOT NULL DEFAULT 0
425
+ );
426
+
427
+ CREATE INDEX IF NOT EXISTS idx_audit_trail_agent
428
+ ON audit_trail(agent_id, timestamp);
429
+
430
+ CREATE INDEX IF NOT EXISTS idx_audit_trail_session
431
+ ON audit_trail(session_id);
432
+ `);
433
+ try {
434
+ await client.execute({
435
+ sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
436
+ args: []
437
+ });
438
+ } catch {
439
+ }
440
+ try {
441
+ await client.execute({
442
+ sql: `ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5`,
443
+ args: []
444
+ });
445
+ } catch {
446
+ }
447
+ try {
448
+ await client.execute({
449
+ sql: `ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'`,
450
+ args: []
451
+ });
452
+ } catch {
453
+ }
454
+ try {
455
+ await client.execute({
456
+ sql: `ALTER TABLE memories ADD COLUMN confidence REAL DEFAULT 0.7`,
457
+ args: []
458
+ });
459
+ } catch {
460
+ }
461
+ try {
462
+ await client.execute({
463
+ sql: `ALTER TABLE memories ADD COLUMN last_accessed TEXT`,
464
+ args: []
465
+ });
466
+ } catch {
467
+ }
468
+ try {
469
+ await client.execute({
470
+ sql: `UPDATE memories SET last_accessed = timestamp WHERE last_accessed IS NULL`,
471
+ args: []
472
+ });
473
+ } catch {
474
+ }
475
+ try {
476
+ await client.execute({
477
+ sql: `ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0`,
478
+ args: []
479
+ });
480
+ } catch {
481
+ }
482
+ try {
483
+ await client.execute({
484
+ sql: `ALTER TABLE memories ADD COLUMN graph_extracted INTEGER DEFAULT 0`,
485
+ args: []
486
+ });
487
+ } catch {
488
+ }
489
+ for (const col of [
490
+ "ALTER TABLE memories ADD COLUMN content_hash TEXT",
491
+ "ALTER TABLE memories ADD COLUMN graph_extracted_hash TEXT"
492
+ ]) {
493
+ try {
494
+ await client.execute(col);
495
+ } catch {
496
+ }
497
+ }
498
+ await client.executeMultiple(`
499
+ CREATE TABLE IF NOT EXISTS entities (
500
+ id TEXT PRIMARY KEY,
501
+ name TEXT NOT NULL,
502
+ type TEXT NOT NULL,
503
+ first_seen TEXT NOT NULL,
504
+ last_seen TEXT NOT NULL,
505
+ properties TEXT DEFAULT '{}',
506
+ UNIQUE(name, type)
507
+ );
508
+
509
+ CREATE TABLE IF NOT EXISTS relationships (
510
+ id TEXT PRIMARY KEY,
511
+ source_entity_id TEXT NOT NULL,
512
+ target_entity_id TEXT NOT NULL,
513
+ type TEXT NOT NULL,
514
+ weight REAL DEFAULT 1.0,
515
+ timestamp TEXT NOT NULL,
516
+ properties TEXT DEFAULT '{}',
517
+ UNIQUE(source_entity_id, target_entity_id, type)
518
+ );
519
+
520
+ CREATE TABLE IF NOT EXISTS entity_memories (
521
+ entity_id TEXT NOT NULL,
522
+ memory_id TEXT NOT NULL,
523
+ PRIMARY KEY (entity_id, memory_id)
524
+ );
525
+
526
+ CREATE TABLE IF NOT EXISTS relationship_memories (
527
+ relationship_id TEXT NOT NULL,
528
+ memory_id TEXT NOT NULL,
529
+ PRIMARY KEY (relationship_id, memory_id)
530
+ );
531
+
532
+ CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
533
+ CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
534
+ CREATE INDEX IF NOT EXISTS idx_relationships_source ON relationships(source_entity_id);
535
+ CREATE INDEX IF NOT EXISTS idx_relationships_target ON relationships(target_entity_id);
536
+
537
+ CREATE TABLE IF NOT EXISTS hyperedges (
538
+ id TEXT PRIMARY KEY,
539
+ label TEXT NOT NULL,
540
+ relation TEXT NOT NULL,
541
+ confidence REAL DEFAULT 1.0,
542
+ timestamp TEXT NOT NULL
543
+ );
544
+
545
+ CREATE TABLE IF NOT EXISTS hyperedge_nodes (
546
+ hyperedge_id TEXT NOT NULL,
547
+ entity_id TEXT NOT NULL,
548
+ PRIMARY KEY (hyperedge_id, entity_id)
549
+ );
550
+ `);
551
+ await client.executeMultiple(`
552
+ CREATE TABLE IF NOT EXISTS entity_aliases (
553
+ alias TEXT NOT NULL PRIMARY KEY,
554
+ canonical_entity_id TEXT NOT NULL
555
+ );
556
+ CREATE INDEX IF NOT EXISTS idx_entity_aliases_canonical ON entity_aliases(canonical_entity_id);
557
+ `);
558
+ for (const col of [
559
+ "ALTER TABLE relationships ADD COLUMN confidence REAL DEFAULT 1.0",
560
+ "ALTER TABLE relationships ADD COLUMN confidence_label TEXT DEFAULT 'extracted'"
561
+ ]) {
562
+ try {
563
+ await client.execute(col);
564
+ } catch {
565
+ }
566
+ }
567
+ try {
568
+ await client.execute(
569
+ `CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)`
570
+ );
571
+ } catch {
572
+ }
573
+ await client.executeMultiple(`
574
+ CREATE TABLE IF NOT EXISTS identity (
575
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
576
+ agent_id TEXT NOT NULL UNIQUE,
577
+ content_hash TEXT NOT NULL,
578
+ updated_at TEXT NOT NULL,
579
+ updated_by TEXT NOT NULL
580
+ );
581
+
582
+ CREATE INDEX IF NOT EXISTS idx_identity_agent ON identity(agent_id);
583
+ `);
584
+ await client.executeMultiple(`
585
+ CREATE TABLE IF NOT EXISTS chat_history (
586
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
587
+ session_id TEXT NOT NULL,
588
+ role TEXT NOT NULL,
589
+ content TEXT NOT NULL,
590
+ tool_name TEXT,
591
+ tool_id TEXT,
592
+ is_error INTEGER NOT NULL DEFAULT 0,
593
+ timestamp INTEGER NOT NULL
594
+ );
595
+
596
+ CREATE INDEX IF NOT EXISTS idx_chat_history_session
597
+ ON chat_history(session_id, id);
598
+ `);
599
+ await client.executeMultiple(`
600
+ CREATE TABLE IF NOT EXISTS workspaces (
601
+ id TEXT PRIMARY KEY,
602
+ slug TEXT NOT NULL UNIQUE,
603
+ name TEXT NOT NULL,
604
+ owner_agent_id TEXT,
605
+ created_at TEXT NOT NULL,
606
+ metadata TEXT
607
+ );
608
+
609
+ CREATE INDEX IF NOT EXISTS idx_workspaces_slug
610
+ ON workspaces(slug);
611
+ `);
612
+ await client.executeMultiple(`
613
+ CREATE TABLE IF NOT EXISTS documents (
614
+ id TEXT PRIMARY KEY,
615
+ workspace_id TEXT NOT NULL,
616
+ filename TEXT NOT NULL,
617
+ mime TEXT,
618
+ source_type TEXT,
619
+ user_id TEXT,
620
+ uploaded_at TEXT NOT NULL,
621
+ metadata TEXT,
622
+ FOREIGN KEY (workspace_id) REFERENCES workspaces(id)
623
+ );
624
+
625
+ CREATE INDEX IF NOT EXISTS idx_documents_workspace
626
+ ON documents(workspace_id);
627
+
628
+ CREATE INDEX IF NOT EXISTS idx_documents_user
629
+ ON documents(user_id);
630
+ `);
631
+ for (const column of [
632
+ "workspace_id TEXT",
633
+ "document_id TEXT",
634
+ "user_id TEXT",
635
+ "char_offset INTEGER",
636
+ "page_number INTEGER"
637
+ ]) {
638
+ try {
639
+ await client.execute({
640
+ sql: `ALTER TABLE memories ADD COLUMN ${column}`,
641
+ args: []
642
+ });
643
+ } catch {
644
+ }
645
+ }
646
+ await client.executeMultiple(`
647
+ CREATE INDEX IF NOT EXISTS idx_memories_workspace
648
+ ON memories(workspace_id);
649
+
650
+ CREATE INDEX IF NOT EXISTS idx_memories_document
651
+ ON memories(document_id);
652
+
653
+ CREATE INDEX IF NOT EXISTS idx_memories_user
654
+ ON memories(user_id);
655
+ `);
656
+ await client.executeMultiple(`
657
+ CREATE TABLE IF NOT EXISTS session_kills (
658
+ id TEXT PRIMARY KEY,
659
+ session_name TEXT NOT NULL,
660
+ agent_id TEXT NOT NULL,
661
+ killed_at TIMESTAMP NOT NULL,
662
+ reason TEXT NOT NULL,
663
+ ticks_idle INTEGER,
664
+ estimated_tokens_saved INTEGER
665
+ );
666
+
667
+ CREATE INDEX IF NOT EXISTS idx_session_kills_killed_at
668
+ ON session_kills(killed_at);
669
+
670
+ CREATE INDEX IF NOT EXISTS idx_session_kills_agent
671
+ ON session_kills(agent_id);
672
+ `);
673
+ await client.executeMultiple(`
674
+ CREATE TABLE IF NOT EXISTS conversations (
675
+ id TEXT PRIMARY KEY,
676
+ platform TEXT NOT NULL,
677
+ external_id TEXT,
678
+ sender_id TEXT NOT NULL,
679
+ sender_name TEXT,
680
+ sender_phone TEXT,
681
+ sender_email TEXT,
682
+ recipient_id TEXT,
683
+ channel_id TEXT NOT NULL,
684
+ thread_id TEXT,
685
+ reply_to_id TEXT,
686
+ content_text TEXT,
687
+ content_media TEXT,
688
+ content_metadata TEXT,
689
+ agent_response TEXT,
690
+ agent_name TEXT,
691
+ timestamp TEXT NOT NULL,
692
+ ingested_at TEXT NOT NULL
693
+ );
694
+
695
+ CREATE INDEX IF NOT EXISTS idx_conversations_platform
696
+ ON conversations(platform);
697
+
698
+ CREATE INDEX IF NOT EXISTS idx_conversations_sender
699
+ ON conversations(sender_id);
700
+
701
+ CREATE INDEX IF NOT EXISTS idx_conversations_timestamp
702
+ ON conversations(timestamp);
703
+
704
+ CREATE INDEX IF NOT EXISTS idx_conversations_thread
705
+ ON conversations(thread_id);
706
+
707
+ CREATE INDEX IF NOT EXISTS idx_conversations_channel
708
+ ON conversations(channel_id);
709
+ `);
710
+ await client.executeMultiple(`
711
+ CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
712
+ content_text,
713
+ sender_name,
714
+ agent_response,
715
+ content='conversations',
716
+ content_rowid='rowid'
717
+ );
718
+
719
+ CREATE TRIGGER IF NOT EXISTS conversations_fts_ai AFTER INSERT ON conversations BEGIN
720
+ INSERT INTO conversations_fts(rowid, content_text, sender_name, agent_response)
721
+ VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
722
+ END;
723
+
724
+ CREATE TRIGGER IF NOT EXISTS conversations_fts_ad AFTER DELETE ON conversations BEGIN
725
+ INSERT INTO conversations_fts(conversations_fts, rowid, content_text, sender_name, agent_response)
726
+ VALUES('delete', old.rowid, old.content_text, old.sender_name, old.agent_response);
727
+ END;
728
+
729
+ CREATE TRIGGER IF NOT EXISTS conversations_fts_au AFTER UPDATE ON conversations BEGIN
730
+ INSERT INTO conversations_fts(conversations_fts, rowid, content_text, sender_name, agent_response)
731
+ VALUES('delete', old.rowid, old.content_text, old.sender_name, old.agent_response);
732
+ INSERT INTO conversations_fts(rowid, content_text, sender_name, agent_response)
733
+ VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
734
+ END;
735
+ `);
736
+ }
737
+ var _client, initTurso;
738
+ var init_database = __esm({
739
+ "src/lib/database.ts"() {
740
+ "use strict";
741
+ _client = null;
742
+ initTurso = initDatabase;
743
+ }
744
+ });
745
+
746
+ // src/lib/config.ts
747
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
748
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
749
+ import path2 from "path";
750
+ import os from "os";
751
+ function resolveDataDir() {
752
+ if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
753
+ if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
754
+ const newDir = path2.join(os.homedir(), ".exe-os");
755
+ const legacyDir = path2.join(os.homedir(), ".exe-mem");
756
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
757
+ try {
758
+ renameSync(legacyDir, newDir);
759
+ process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
760
+ `);
761
+ } catch {
762
+ return legacyDir;
763
+ }
764
+ }
765
+ return newDir;
766
+ }
767
+ function migrateLegacyConfig(raw) {
768
+ if ("r2" in raw) {
769
+ process.stderr.write(
770
+ "[exe-os] Warning: config.json contains deprecated 'r2' field from v1.0. R2 sync has been replaced in v1.1. The 'r2' field will be ignored.\n"
771
+ );
772
+ delete raw.r2;
773
+ }
774
+ if ("syncIntervalMs" in raw) {
775
+ delete raw.syncIntervalMs;
776
+ }
777
+ return raw;
778
+ }
779
+ function migrateConfig(raw) {
780
+ const fromVersion = typeof raw.config_version === "number" ? raw.config_version : 0;
781
+ let currentVersion = fromVersion;
782
+ let migrated = false;
783
+ if (currentVersion > CURRENT_CONFIG_VERSION) {
784
+ return { config: raw, migrated: false, fromVersion };
785
+ }
786
+ for (const migration of CONFIG_MIGRATIONS) {
787
+ if (currentVersion === migration.from && migration.to <= CURRENT_CONFIG_VERSION) {
788
+ raw = migration.migrate(raw);
789
+ currentVersion = migration.to;
790
+ migrated = true;
791
+ }
792
+ }
793
+ return { config: raw, migrated, fromVersion };
794
+ }
795
+ function normalizeScalingRoadmap(raw) {
796
+ const defaultAuto = DEFAULT_CONFIG.scalingRoadmap.rerankerAutoTrigger;
797
+ const userRoadmap = raw.scalingRoadmap ?? {};
798
+ const userAuto = userRoadmap.rerankerAutoTrigger ?? {};
799
+ if (userAuto.enabled === void 0 && raw.rerankerEnabled !== void 0) {
800
+ userAuto.enabled = raw.rerankerEnabled;
801
+ }
802
+ raw.scalingRoadmap = {
803
+ ...userRoadmap,
804
+ rerankerAutoTrigger: { ...defaultAuto, ...userAuto }
805
+ };
806
+ }
807
+ function normalizeSessionLifecycle(raw) {
808
+ const defaultSL = DEFAULT_CONFIG.sessionLifecycle;
809
+ const userSL = raw.sessionLifecycle ?? {};
810
+ raw.sessionLifecycle = { ...defaultSL, ...userSL };
811
+ }
812
+ async function loadConfig() {
813
+ const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
814
+ await mkdir2(dir, { recursive: true });
815
+ const configPath = path2.join(dir, "config.json");
816
+ if (!existsSync2(configPath)) {
817
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
818
+ }
819
+ const raw = await readFile2(configPath, "utf-8");
820
+ try {
821
+ let parsed = JSON.parse(raw);
822
+ parsed = migrateLegacyConfig(parsed);
823
+ const { config: migratedCfg, migrated, fromVersion } = migrateConfig(parsed);
824
+ if (migrated) {
825
+ process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
826
+ `);
827
+ try {
828
+ await writeFile2(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
829
+ } catch {
830
+ }
831
+ }
832
+ normalizeScalingRoadmap(migratedCfg);
833
+ normalizeSessionLifecycle(migratedCfg);
834
+ const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
835
+ if (config.dbPath.startsWith("~")) {
836
+ config.dbPath = config.dbPath.replace(/^~/, os.homedir());
837
+ }
838
+ return config;
839
+ } catch {
840
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
841
+ }
842
+ }
843
+ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
844
+ var init_config = __esm({
845
+ "src/lib/config.ts"() {
846
+ "use strict";
847
+ EXE_AI_DIR = resolveDataDir();
848
+ DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
849
+ MODELS_DIR = path2.join(EXE_AI_DIR, "models");
850
+ CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
851
+ LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
852
+ CURRENT_CONFIG_VERSION = 1;
853
+ DEFAULT_CONFIG = {
854
+ config_version: CURRENT_CONFIG_VERSION,
855
+ dbPath: DB_PATH,
856
+ modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
857
+ embeddingDim: 1024,
858
+ batchSize: 20,
859
+ flushIntervalMs: 1e4,
860
+ autoIngestion: true,
861
+ autoRetrieval: true,
862
+ searchMode: "hybrid",
863
+ hookSearchMode: "hybrid",
864
+ fileGrepEnabled: true,
865
+ splashEffect: true,
866
+ consolidationEnabled: true,
867
+ consolidationIntervalMs: 6 * 60 * 60 * 1e3,
868
+ consolidationModel: "claude-haiku-4-5-20251001",
869
+ consolidationMaxCallsPerRun: 20,
870
+ selfQueryRouter: true,
871
+ selfQueryModel: "claude-haiku-4-5-20251001",
872
+ rerankerEnabled: true,
873
+ scalingRoadmap: {
874
+ rerankerAutoTrigger: {
875
+ enabled: true,
876
+ broadQueryMinCardinality: 5e4,
877
+ fetchTopK: 150,
878
+ returnTopK: 5
879
+ }
880
+ },
881
+ graphRagEnabled: true,
882
+ wikiEnabled: false,
883
+ wikiUrl: "",
884
+ wikiApiKey: "",
885
+ wikiSyncIntervalMs: 30 * 60 * 1e3,
886
+ wikiWorkspaceMapping: {
887
+ exe: "Executive",
888
+ yoshi: "Engineering",
889
+ mari: "Marketing",
890
+ tom: "Engineering",
891
+ sasha: "Production"
892
+ },
893
+ wikiAutoUpdate: true,
894
+ wikiAutoUpdateThreshold: 0.5,
895
+ wikiAutoUpdateCreateNew: true,
896
+ skillLearning: true,
897
+ skillThreshold: 3,
898
+ skillModel: "claude-haiku-4-5-20251001",
899
+ exeHeartbeat: {
900
+ enabled: true,
901
+ intervalSeconds: 60,
902
+ staleInProgressThresholdHours: 2
903
+ },
904
+ sessionLifecycle: {
905
+ idleKillEnabled: true,
906
+ idleKillTicksRequired: 3,
907
+ idleKillIntercomAckWindowMs: 1e4,
908
+ maxAutoInstances: 10
909
+ }
910
+ };
911
+ CONFIG_MIGRATIONS = [
912
+ {
913
+ from: 0,
914
+ to: 1,
915
+ migrate: (cfg) => {
916
+ cfg.config_version = 1;
917
+ return cfg;
918
+ }
919
+ }
920
+ ];
921
+ }
922
+ });
923
+
924
+ // src/lib/shard-manager.ts
925
+ var shard_manager_exports = {};
926
+ __export(shard_manager_exports, {
927
+ disposeShards: () => disposeShards,
928
+ ensureShardSchema: () => ensureShardSchema,
929
+ getReadyShardClient: () => getReadyShardClient,
930
+ getShardClient: () => getShardClient,
931
+ getShardsDir: () => getShardsDir,
932
+ initShardManager: () => initShardManager,
933
+ isShardingEnabled: () => isShardingEnabled,
934
+ listShards: () => listShards,
935
+ shardExists: () => shardExists
936
+ });
937
+ import path3 from "path";
938
+ import { existsSync as existsSync3, mkdirSync } from "fs";
939
+ import { createClient as createClient2 } from "@libsql/client";
940
+ function initShardManager(encryptionKey) {
941
+ _encryptionKey = encryptionKey;
942
+ if (!existsSync3(SHARDS_DIR)) {
943
+ mkdirSync(SHARDS_DIR, { recursive: true });
944
+ }
945
+ _shardingEnabled = true;
946
+ }
947
+ function isShardingEnabled() {
948
+ return _shardingEnabled;
949
+ }
950
+ function getShardsDir() {
951
+ return SHARDS_DIR;
952
+ }
953
+ function getShardClient(projectName) {
954
+ if (!_encryptionKey) {
955
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
956
+ }
957
+ const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
958
+ if (!safeName) {
959
+ throw new Error(`Invalid project name for shard: "${projectName}"`);
960
+ }
961
+ const cached = _shards.get(safeName);
962
+ if (cached) return cached;
963
+ const dbPath = path3.join(SHARDS_DIR, `${safeName}.db`);
964
+ const client = createClient2({
965
+ url: `file:${dbPath}`,
966
+ encryptionKey: _encryptionKey
967
+ });
968
+ _shards.set(safeName, client);
969
+ return client;
970
+ }
971
+ function shardExists(projectName) {
972
+ const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
973
+ return existsSync3(path3.join(SHARDS_DIR, `${safeName}.db`));
974
+ }
975
+ function listShards() {
976
+ if (!existsSync3(SHARDS_DIR)) return [];
977
+ const { readdirSync: readdirSync3 } = __require("fs");
978
+ return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
979
+ }
980
+ async function ensureShardSchema(client) {
981
+ await client.execute("PRAGMA journal_mode = WAL");
982
+ await client.execute("PRAGMA busy_timeout = 5000");
983
+ try {
984
+ await client.execute("PRAGMA libsql_vector_search_ef = 128");
985
+ } catch {
986
+ }
987
+ await client.executeMultiple(`
988
+ CREATE TABLE IF NOT EXISTS memories (
989
+ id TEXT PRIMARY KEY,
990
+ agent_id TEXT NOT NULL,
991
+ agent_role TEXT NOT NULL,
992
+ session_id TEXT NOT NULL,
993
+ timestamp TEXT NOT NULL,
994
+ tool_name TEXT NOT NULL,
995
+ project_name TEXT NOT NULL,
996
+ has_error INTEGER NOT NULL DEFAULT 0,
997
+ raw_text TEXT NOT NULL,
998
+ vector F32_BLOB(1024),
999
+ version INTEGER NOT NULL DEFAULT 0
1000
+ );
1001
+
1002
+ CREATE INDEX IF NOT EXISTS idx_memories_agent ON memories(agent_id);
1003
+ CREATE INDEX IF NOT EXISTS idx_memories_timestamp ON memories(timestamp);
1004
+ CREATE INDEX IF NOT EXISTS idx_memories_agent_project ON memories(agent_id, project_name);
1005
+ `);
1006
+ await client.executeMultiple(`
1007
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
1008
+ raw_text,
1009
+ content='memories',
1010
+ content_rowid='rowid'
1011
+ );
1012
+
1013
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
1014
+ INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
1015
+ END;
1016
+
1017
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
1018
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
1019
+ END;
1020
+
1021
+ CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
1022
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
1023
+ INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
1024
+ END;
1025
+ `);
1026
+ for (const col of [
1027
+ "ALTER TABLE memories ADD COLUMN task_id TEXT",
1028
+ "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
1029
+ "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
1030
+ "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
1031
+ "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
1032
+ "ALTER TABLE memories ADD COLUMN graph_extracted INTEGER DEFAULT 0",
1033
+ "ALTER TABLE memories ADD COLUMN content_hash TEXT",
1034
+ "ALTER TABLE memories ADD COLUMN graph_extracted_hash TEXT",
1035
+ "ALTER TABLE memories ADD COLUMN confidence REAL DEFAULT 0.7",
1036
+ "ALTER TABLE memories ADD COLUMN last_accessed TEXT",
1037
+ // Wiki linkage columns (must match database.ts)
1038
+ "ALTER TABLE memories ADD COLUMN workspace_id TEXT",
1039
+ "ALTER TABLE memories ADD COLUMN document_id TEXT",
1040
+ "ALTER TABLE memories ADD COLUMN user_id TEXT",
1041
+ "ALTER TABLE memories ADD COLUMN char_offset INTEGER",
1042
+ "ALTER TABLE memories ADD COLUMN page_number INTEGER"
1043
+ ]) {
1044
+ try {
1045
+ await client.execute(col);
1046
+ } catch {
1047
+ }
1048
+ }
1049
+ try {
1050
+ await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
1051
+ } catch {
1052
+ }
1053
+ for (const idx of [
1054
+ "CREATE INDEX IF NOT EXISTS idx_memories_workspace ON memories(workspace_id)",
1055
+ "CREATE INDEX IF NOT EXISTS idx_memories_document ON memories(document_id)",
1056
+ "CREATE INDEX IF NOT EXISTS idx_memories_user ON memories(user_id)"
1057
+ ]) {
1058
+ try {
1059
+ await client.execute(idx);
1060
+ } catch {
1061
+ }
1062
+ }
1063
+ await client.executeMultiple(`
1064
+ CREATE TABLE IF NOT EXISTS entities (
1065
+ id TEXT PRIMARY KEY,
1066
+ name TEXT NOT NULL,
1067
+ type TEXT NOT NULL,
1068
+ first_seen TEXT NOT NULL,
1069
+ last_seen TEXT NOT NULL,
1070
+ properties TEXT DEFAULT '{}',
1071
+ UNIQUE(name, type)
1072
+ );
1073
+
1074
+ CREATE TABLE IF NOT EXISTS relationships (
1075
+ id TEXT PRIMARY KEY,
1076
+ source_entity_id TEXT NOT NULL,
1077
+ target_entity_id TEXT NOT NULL,
1078
+ type TEXT NOT NULL,
1079
+ weight REAL DEFAULT 1.0,
1080
+ timestamp TEXT NOT NULL,
1081
+ properties TEXT DEFAULT '{}',
1082
+ UNIQUE(source_entity_id, target_entity_id, type)
1083
+ );
1084
+
1085
+ CREATE TABLE IF NOT EXISTS entity_memories (
1086
+ entity_id TEXT NOT NULL,
1087
+ memory_id TEXT NOT NULL,
1088
+ PRIMARY KEY (entity_id, memory_id)
1089
+ );
1090
+
1091
+ CREATE TABLE IF NOT EXISTS relationship_memories (
1092
+ relationship_id TEXT NOT NULL,
1093
+ memory_id TEXT NOT NULL,
1094
+ PRIMARY KEY (relationship_id, memory_id)
1095
+ );
1096
+
1097
+ CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
1098
+ CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
1099
+ CREATE INDEX IF NOT EXISTS idx_relationships_source ON relationships(source_entity_id);
1100
+ CREATE INDEX IF NOT EXISTS idx_relationships_target ON relationships(target_entity_id);
1101
+ CREATE INDEX IF NOT EXISTS idx_relationships_type ON relationships(type);
1102
+
1103
+ CREATE TABLE IF NOT EXISTS hyperedges (
1104
+ id TEXT PRIMARY KEY,
1105
+ label TEXT NOT NULL,
1106
+ relation TEXT NOT NULL,
1107
+ confidence REAL DEFAULT 1.0,
1108
+ timestamp TEXT NOT NULL
1109
+ );
1110
+
1111
+ CREATE TABLE IF NOT EXISTS hyperedge_nodes (
1112
+ hyperedge_id TEXT NOT NULL,
1113
+ entity_id TEXT NOT NULL,
1114
+ PRIMARY KEY (hyperedge_id, entity_id)
1115
+ );
1116
+ `);
1117
+ for (const col of [
1118
+ "ALTER TABLE relationships ADD COLUMN confidence REAL DEFAULT 1.0",
1119
+ "ALTER TABLE relationships ADD COLUMN confidence_label TEXT DEFAULT 'extracted'"
1120
+ ]) {
1121
+ try {
1122
+ await client.execute(col);
1123
+ } catch {
1124
+ }
1125
+ }
1126
+ }
1127
+ async function getReadyShardClient(projectName) {
1128
+ const client = getShardClient(projectName);
1129
+ await ensureShardSchema(client);
1130
+ return client;
1131
+ }
1132
+ function disposeShards() {
1133
+ for (const [, client] of _shards) {
1134
+ client.close();
1135
+ }
1136
+ _shards.clear();
1137
+ _shardingEnabled = false;
1138
+ _encryptionKey = null;
1139
+ }
1140
+ var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
1141
+ var init_shard_manager = __esm({
1142
+ "src/lib/shard-manager.ts"() {
1143
+ "use strict";
1144
+ init_config();
1145
+ SHARDS_DIR = path3.join(EXE_AI_DIR, "shards");
1146
+ _shards = /* @__PURE__ */ new Map();
1147
+ _encryptionKey = null;
1148
+ _shardingEnabled = false;
1149
+ }
1150
+ });
1151
+
1152
+ // src/lib/notifications.ts
1153
+ import crypto2 from "crypto";
1154
+ import path4 from "path";
1155
+ import os2 from "os";
1156
+ import {
1157
+ readFileSync as readFileSync2,
1158
+ readdirSync,
1159
+ unlinkSync,
1160
+ existsSync as existsSync4,
1161
+ rmdirSync
1162
+ } from "fs";
1163
+ var init_notifications = __esm({
1164
+ "src/lib/notifications.ts"() {
1165
+ "use strict";
1166
+ init_database();
1167
+ }
1168
+ });
1169
+
1170
+ // src/lib/tasks-crud.ts
1171
+ import crypto3 from "crypto";
1172
+ import path5 from "path";
1173
+ import { execSync } from "child_process";
1174
+ import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
1175
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
1176
+ function extractParentFromContext(contextBody) {
1177
+ if (!contextBody) return null;
1178
+ const match = contextBody.match(
1179
+ /Parent task:\s*([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i
1180
+ );
1181
+ return match ? match[1].toLowerCase() : null;
1182
+ }
1183
+ function slugify(title) {
1184
+ return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
1185
+ }
1186
+ async function resolveTask(client, identifier) {
1187
+ let result = await client.execute({
1188
+ sql: "SELECT * FROM tasks WHERE id = ?",
1189
+ args: [identifier]
1190
+ });
1191
+ if (result.rows.length === 1) return result.rows[0];
1192
+ result = await client.execute({
1193
+ sql: "SELECT * FROM tasks WHERE task_file LIKE ?",
1194
+ args: [`%${identifier}%`]
1195
+ });
1196
+ if (result.rows.length === 1) return result.rows[0];
1197
+ if (result.rows.length > 1) {
1198
+ const exact = result.rows.filter(
1199
+ (r) => String(r.task_file).endsWith(`/${identifier}.md`)
1200
+ );
1201
+ if (exact.length === 1) return exact[0];
1202
+ const candidates = exact.length > 1 ? exact : result.rows;
1203
+ const active = candidates.filter(
1204
+ (r) => !["done", "cancelled"].includes(String(r.status))
1205
+ );
1206
+ if (active.length === 1) return active[0];
1207
+ const matches = (active.length > 1 ? active : candidates).map((r) => `${String(r.task_file)} (${String(r.status)}, ${String(r.id)})`).join(", ");
1208
+ throw new Error(
1209
+ `Multiple tasks match "${identifier}": ${matches}. Use a UUID to disambiguate.`
1210
+ );
1211
+ }
1212
+ result = await client.execute({
1213
+ sql: "SELECT * FROM tasks WHERE title LIKE ?",
1214
+ args: [`%${identifier}%`]
1215
+ });
1216
+ if (result.rows.length === 1) return result.rows[0];
1217
+ if (result.rows.length > 1) {
1218
+ const active = result.rows.filter(
1219
+ (r) => !["done", "cancelled"].includes(String(r.status))
1220
+ );
1221
+ if (active.length === 1) return active[0];
1222
+ const matches = (active.length > 1 ? active : result.rows).map((r) => `"${String(r.title)}" (${String(r.status)}, ${String(r.id)})`).join(", ");
1223
+ throw new Error(
1224
+ `Multiple tasks match "${identifier}": ${matches}. Use a UUID to disambiguate.`
1225
+ );
1226
+ }
1227
+ throw new Error(`Task not found: ${identifier}`);
1228
+ }
1229
+ async function createTaskCore(input) {
1230
+ const client = getClient();
1231
+ const id = crypto3.randomUUID();
1232
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1233
+ const slug = slugify(input.title);
1234
+ const taskFile = input.taskFile ?? `exe/${input.assignedTo}/${slug}.md`;
1235
+ let blockedById = null;
1236
+ const initialStatus = input.blockedBy ? "blocked" : "open";
1237
+ if (input.blockedBy) {
1238
+ const blocker = await resolveTask(client, input.blockedBy);
1239
+ blockedById = String(blocker.id);
1240
+ }
1241
+ let parentTaskId = null;
1242
+ let parentRef = input.parentTaskId;
1243
+ if (!parentRef) {
1244
+ const extracted = extractParentFromContext(input.context);
1245
+ if (extracted) {
1246
+ parentRef = extracted;
1247
+ process.stderr.write(
1248
+ "[create_task] auto-populated parent_task_id from context body \u2014 dispatchers should pass parent_task_id explicitly\n"
1249
+ );
1250
+ }
1251
+ }
1252
+ if (parentRef) {
1253
+ try {
1254
+ const parent = await resolveTask(client, parentRef);
1255
+ parentTaskId = String(parent.id);
1256
+ } catch (err) {
1257
+ if (!input.parentTaskId) {
1258
+ throw new Error(
1259
+ `create_task: parent reference "${parentRef}" in context body does not resolve to an existing task`
1260
+ );
1261
+ }
1262
+ throw err;
1263
+ }
1264
+ }
1265
+ let warning;
1266
+ const dupCheck = await client.execute({
1267
+ sql: "SELECT id FROM tasks WHERE title = ? AND assigned_to = ? AND status IN ('open', 'in_progress', 'blocked')",
1268
+ args: [input.title, input.assignedTo]
1269
+ });
1270
+ if (dupCheck.rows.length > 0) {
1271
+ warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
1272
+ }
1273
+ if (input.baseDir) {
1274
+ try {
1275
+ await mkdir3(path5.join(input.baseDir, "exe", "output"), { recursive: true });
1276
+ await mkdir3(path5.join(input.baseDir, "exe", "research"), { recursive: true });
1277
+ await ensureArchitectureDoc(input.baseDir, input.projectName);
1278
+ await ensureGitignoreExe(input.baseDir);
1279
+ } catch {
1280
+ }
1281
+ }
1282
+ await client.execute({
1283
+ sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, created_at, updated_at)
1284
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1285
+ args: [
1286
+ id,
1287
+ input.title,
1288
+ input.assignedTo,
1289
+ input.assignedBy,
1290
+ input.projectName,
1291
+ input.priority,
1292
+ initialStatus,
1293
+ taskFile,
1294
+ blockedById,
1295
+ parentTaskId,
1296
+ input.reviewer ?? null,
1297
+ input.context,
1298
+ now,
1299
+ now
1300
+ ]
1301
+ });
1302
+ return {
1303
+ id,
1304
+ title: input.title,
1305
+ assignedTo: input.assignedTo,
1306
+ assignedBy: input.assignedBy,
1307
+ projectName: input.projectName,
1308
+ priority: input.priority,
1309
+ status: initialStatus,
1310
+ taskFile,
1311
+ createdAt: now,
1312
+ updatedAt: now,
1313
+ warning
1314
+ };
1315
+ }
1316
+ async function ensureArchitectureDoc(baseDir, projectName) {
1317
+ const archPath = path5.join(baseDir, "exe", "ARCHITECTURE.md");
1318
+ try {
1319
+ if (existsSync5(archPath)) return;
1320
+ const template = [
1321
+ `# ${projectName} \u2014 System Architecture`,
1322
+ "",
1323
+ "> Employees: read this before every task. Update it when you change system structure.",
1324
+ `> Last updated: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`,
1325
+ "",
1326
+ "## Overview",
1327
+ "",
1328
+ "<!-- Describe what this system does, its main components, and how they connect. -->",
1329
+ "",
1330
+ "## Key Components",
1331
+ "",
1332
+ "<!-- List the major modules, services, or subsystems. -->",
1333
+ "",
1334
+ "## Data Flow",
1335
+ "",
1336
+ "<!-- How does data move through the system? What writes where? -->",
1337
+ "",
1338
+ "## Invariants",
1339
+ "",
1340
+ "<!-- Rules that must never be violated. What breaks if these are wrong? -->",
1341
+ "",
1342
+ "## Dependencies",
1343
+ "",
1344
+ "<!-- What depends on what? If I change X, what else is affected? -->",
1345
+ ""
1346
+ ].join("\n");
1347
+ await writeFile3(archPath, template, "utf-8");
1348
+ } catch {
1349
+ }
1350
+ }
1351
+ async function ensureGitignoreExe(baseDir) {
1352
+ const gitignorePath = path5.join(baseDir, ".gitignore");
1353
+ try {
1354
+ if (existsSync5(gitignorePath)) {
1355
+ const content = readFileSync3(gitignorePath, "utf-8");
1356
+ if (/^\/?exe\/?$/m.test(content)) return;
1357
+ await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
1358
+ } else {
1359
+ await writeFile3(gitignorePath, "# Employee task assignments (private)\n/exe/\n", "utf-8");
1360
+ }
1361
+ } catch {
1362
+ }
1363
+ }
1364
+ var init_tasks_crud = __esm({
1365
+ "src/lib/tasks-crud.ts"() {
1366
+ "use strict";
1367
+ init_database();
1368
+ }
1369
+ });
1370
+
1371
+ // src/lib/employees.ts
1372
+ import { readFile as readFile3, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
1373
+ import { existsSync as existsSync6, symlinkSync, readlinkSync } from "fs";
1374
+ import { execSync as execSync2 } from "child_process";
1375
+ import path6 from "path";
1376
+ var EMPLOYEES_PATH;
1377
+ var init_employees = __esm({
1378
+ "src/lib/employees.ts"() {
1379
+ "use strict";
1380
+ init_config();
1381
+ EMPLOYEES_PATH = path6.join(EXE_AI_DIR, "exe-employees.json");
1382
+ }
1383
+ });
1384
+
1385
+ // src/lib/session-registry.ts
1386
+ import { readFileSync as readFileSync4, writeFileSync, mkdirSync as mkdirSync2, existsSync as existsSync7 } from "fs";
1387
+ import path7 from "path";
1388
+ import os3 from "os";
1389
+ function registerSession(entry) {
1390
+ const dir = path7.dirname(REGISTRY_PATH);
1391
+ if (!existsSync7(dir)) {
1392
+ mkdirSync2(dir, { recursive: true });
1393
+ }
1394
+ const sessions = listSessions();
1395
+ const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
1396
+ if (idx >= 0) {
1397
+ sessions[idx] = entry;
1398
+ } else {
1399
+ sessions.push(entry);
1400
+ }
1401
+ writeFileSync(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
1402
+ }
1403
+ function listSessions() {
1404
+ try {
1405
+ const raw = readFileSync4(REGISTRY_PATH, "utf8");
1406
+ return JSON.parse(raw);
1407
+ } catch {
1408
+ return [];
1409
+ }
1410
+ }
1411
+ var REGISTRY_PATH;
1412
+ var init_session_registry = __esm({
1413
+ "src/lib/session-registry.ts"() {
1414
+ "use strict";
1415
+ REGISTRY_PATH = path7.join(os3.homedir(), ".exe-os", "session-registry.json");
1416
+ }
1417
+ });
1418
+
1419
+ // src/lib/session-key.ts
1420
+ import { execSync as execSync3 } from "child_process";
1421
+ function getSessionKey() {
1422
+ if (_cached) return _cached;
1423
+ let pid = process.ppid;
1424
+ for (let i = 0; i < 10; i++) {
1425
+ try {
1426
+ const info = execSync3(`ps -p ${pid} -o ppid=,comm=`, {
1427
+ encoding: "utf8",
1428
+ timeout: 2e3
1429
+ }).trim();
1430
+ const match = info.match(/^\s*(\d+)\s+(.+)$/);
1431
+ if (!match) break;
1432
+ const [, ppid, cmd] = match;
1433
+ if (cmd === "claude" || cmd.endsWith("/claude")) {
1434
+ _cached = String(pid);
1435
+ return _cached;
1436
+ }
1437
+ pid = parseInt(ppid, 10);
1438
+ if (pid <= 1) break;
1439
+ } catch {
1440
+ break;
1441
+ }
1442
+ }
1443
+ _cached = process.env.CLAUDE_CODE_SSE_PORT ?? String(process.ppid);
1444
+ return _cached;
1445
+ }
1446
+ var _cached;
1447
+ var init_session_key = __esm({
1448
+ "src/lib/session-key.ts"() {
1449
+ "use strict";
1450
+ _cached = null;
1451
+ }
1452
+ });
1453
+
1454
+ // src/lib/tmux-transport.ts
1455
+ var tmux_transport_exports = {};
1456
+ __export(tmux_transport_exports, {
1457
+ TmuxTransport: () => TmuxTransport
1458
+ });
1459
+ import { execFileSync } from "child_process";
1460
+ var QUIET, TmuxTransport;
1461
+ var init_tmux_transport = __esm({
1462
+ "src/lib/tmux-transport.ts"() {
1463
+ "use strict";
1464
+ QUIET = {
1465
+ encoding: "utf8",
1466
+ stdio: ["pipe", "pipe", "pipe"]
1467
+ };
1468
+ TmuxTransport = class {
1469
+ getMySession() {
1470
+ try {
1471
+ return execFileSync("tmux", ["display-message", "-p", "#{session_name}"], QUIET).trim() || null;
1472
+ } catch {
1473
+ return null;
1474
+ }
1475
+ }
1476
+ listSessions() {
1477
+ try {
1478
+ return execFileSync("tmux", ["list-sessions", "-F", "#{session_name}"], QUIET).trim().split("\n").filter(Boolean);
1479
+ } catch {
1480
+ return [];
1481
+ }
1482
+ }
1483
+ isAlive(target) {
1484
+ try {
1485
+ const sessions = this.listSessions();
1486
+ if (!sessions.includes(target)) return false;
1487
+ const paneStatus = execFileSync(
1488
+ "tmux",
1489
+ ["list-panes", "-t", target, "-F", "#{pane_dead}"],
1490
+ QUIET
1491
+ ).trim();
1492
+ return paneStatus !== "1";
1493
+ } catch {
1494
+ return false;
1495
+ }
1496
+ }
1497
+ sendKeys(target, keys) {
1498
+ execFileSync("tmux", ["send-keys", "-t", target, keys, "Enter"], QUIET);
1499
+ }
1500
+ capturePane(target, lines) {
1501
+ const args = ["capture-pane", "-t", target, "-p"];
1502
+ if (lines) args.push("-S", `-${lines}`);
1503
+ return execFileSync("tmux", args, { ...QUIET, timeout: 3e3 });
1504
+ }
1505
+ isPaneInCopyMode(target) {
1506
+ try {
1507
+ const result = execFileSync(
1508
+ "tmux",
1509
+ ["display-message", "-p", "-t", target, "#{pane_in_mode}"],
1510
+ { ...QUIET, timeout: 3e3 }
1511
+ ).trim();
1512
+ return result === "1";
1513
+ } catch {
1514
+ return false;
1515
+ }
1516
+ }
1517
+ spawn(name, config) {
1518
+ try {
1519
+ const args = ["new-session", "-d", "-s", name];
1520
+ if (config.cwd) args.push("-c", config.cwd);
1521
+ args.push(config.command);
1522
+ execFileSync("tmux", args);
1523
+ return { sessionName: name };
1524
+ } catch (e) {
1525
+ return { sessionName: name, error: `spawn failed: ${e}` };
1526
+ }
1527
+ }
1528
+ kill(target) {
1529
+ try {
1530
+ execFileSync("tmux", ["kill-session", "-t", target], QUIET);
1531
+ } catch {
1532
+ }
1533
+ }
1534
+ pipeLog(target, logFile) {
1535
+ try {
1536
+ const safePath = logFile.replace(/'/g, "'\\''");
1537
+ execFileSync("tmux", ["pipe-pane", "-t", target, `cat >> '${safePath}'`], QUIET);
1538
+ } catch {
1539
+ }
1540
+ }
1541
+ };
1542
+ }
1543
+ });
1544
+
1545
+ // src/lib/transport.ts
1546
+ function getTransport() {
1547
+ if (!_transport) {
1548
+ const { TmuxTransport: TmuxTransport2 } = (init_tmux_transport(), __toCommonJS(tmux_transport_exports));
1549
+ _transport = new TmuxTransport2();
1550
+ }
1551
+ return _transport;
1552
+ }
1553
+ var _transport;
1554
+ var init_transport = __esm({
1555
+ "src/lib/transport.ts"() {
1556
+ "use strict";
1557
+ _transport = null;
1558
+ }
1559
+ });
1560
+
1561
+ // src/lib/cc-agent-support.ts
1562
+ import { execSync as execSync4 } from "child_process";
1563
+ function _resetCcAgentSupportCache() {
1564
+ _cachedSupport = null;
1565
+ }
1566
+ function claudeSupportsAgentFlag() {
1567
+ if (_cachedSupport !== null) return _cachedSupport;
1568
+ try {
1569
+ const helpOutput = execSync4("claude --help 2>&1", {
1570
+ encoding: "utf-8",
1571
+ timeout: 5e3
1572
+ });
1573
+ _cachedSupport = /(^|\s)--agent(\b|=)/.test(helpOutput);
1574
+ } catch {
1575
+ _cachedSupport = false;
1576
+ }
1577
+ return _cachedSupport;
1578
+ }
1579
+ var _cachedSupport;
1580
+ var init_cc_agent_support = __esm({
1581
+ "src/lib/cc-agent-support.ts"() {
1582
+ "use strict";
1583
+ _cachedSupport = null;
1584
+ }
1585
+ });
1586
+
1587
+ // src/lib/mcp-prefix.ts
1588
+ function expandDualPrefixTools(shortNames) {
1589
+ const out = [];
1590
+ for (const name of shortNames) {
1591
+ for (const prefix of MCP_TOOL_PREFIXES) {
1592
+ out.push(prefix + name);
1593
+ }
1594
+ }
1595
+ return out;
1596
+ }
1597
+ var MCP_PRIMARY_KEY, MCP_LEGACY_KEY, MCP_TOOL_PREFIXES;
1598
+ var init_mcp_prefix = __esm({
1599
+ "src/lib/mcp-prefix.ts"() {
1600
+ "use strict";
1601
+ MCP_PRIMARY_KEY = "exe-os";
1602
+ MCP_LEGACY_KEY = "exe-mem";
1603
+ MCP_TOOL_PREFIXES = [
1604
+ `mcp__${MCP_PRIMARY_KEY}__`,
1605
+ `mcp__${MCP_LEGACY_KEY}__`
1606
+ ];
1607
+ }
1608
+ });
1609
+
1610
+ // src/lib/provider-table.ts
1611
+ function detectActiveProvider(env = process.env) {
1612
+ const baseUrl = env.ANTHROPIC_BASE_URL;
1613
+ if (!baseUrl) return DEFAULT_PROVIDER;
1614
+ for (const [name, cfg] of Object.entries(PROVIDER_TABLE)) {
1615
+ if (cfg.baseUrl === baseUrl) return name;
1616
+ }
1617
+ return DEFAULT_PROVIDER;
1618
+ }
1619
+ var PROVIDER_TABLE, DEFAULT_PROVIDER;
1620
+ var init_provider_table = __esm({
1621
+ "src/lib/provider-table.ts"() {
1622
+ "use strict";
1623
+ PROVIDER_TABLE = {
1624
+ opencode: {
1625
+ baseUrl: "https://opencode.ai/zen/go",
1626
+ apiKeyEnv: "OPENCODE_API_KEY",
1627
+ defaultModel: "minimax-m2.7"
1628
+ }
1629
+ };
1630
+ DEFAULT_PROVIDER = "default";
1631
+ }
1632
+ });
1633
+
1634
+ // src/lib/intercom-queue.ts
1635
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, renameSync as renameSync2, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
1636
+ import path8 from "path";
1637
+ import os4 from "os";
1638
+ function ensureDir() {
1639
+ const dir = path8.dirname(QUEUE_PATH);
1640
+ if (!existsSync8(dir)) mkdirSync3(dir, { recursive: true });
1641
+ }
1642
+ function readQueue() {
1643
+ try {
1644
+ if (!existsSync8(QUEUE_PATH)) return [];
1645
+ return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
1646
+ } catch {
1647
+ return [];
1648
+ }
1649
+ }
1650
+ function writeQueue(queue) {
1651
+ ensureDir();
1652
+ const tmp = `${QUEUE_PATH}.tmp`;
1653
+ writeFileSync2(tmp, JSON.stringify(queue, null, 2));
1654
+ renameSync2(tmp, QUEUE_PATH);
1655
+ }
1656
+ function queueIntercom(targetSession, reason) {
1657
+ const queue = readQueue();
1658
+ const existing = queue.find((q) => q.targetSession === targetSession);
1659
+ if (existing) {
1660
+ existing.attempts++;
1661
+ existing.queuedAt = (/* @__PURE__ */ new Date()).toISOString();
1662
+ existing.reason = reason;
1663
+ } else {
1664
+ queue.push({
1665
+ targetSession,
1666
+ queuedAt: (/* @__PURE__ */ new Date()).toISOString(),
1667
+ attempts: 0,
1668
+ reason
1669
+ });
1670
+ }
1671
+ writeQueue(queue);
1672
+ }
1673
+ var QUEUE_PATH, INTERCOM_LOG;
1674
+ var init_intercom_queue = __esm({
1675
+ "src/lib/intercom-queue.ts"() {
1676
+ "use strict";
1677
+ QUEUE_PATH = path8.join(os4.homedir(), ".exe-os", "intercom-queue.json");
1678
+ INTERCOM_LOG = path8.join(os4.homedir(), ".exe-os", "intercom.log");
1679
+ }
1680
+ });
1681
+
1682
+ // src/lib/license.ts
1683
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
1684
+ import { randomUUID } from "crypto";
1685
+ import path9 from "path";
1686
+ import { jwtVerify, importSPKI } from "jose";
1687
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
1688
+ var init_license = __esm({
1689
+ "src/lib/license.ts"() {
1690
+ "use strict";
1691
+ init_config();
1692
+ LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
1693
+ CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
1694
+ DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
1695
+ PLAN_LIMITS = {
1696
+ free: { devices: 1, employees: 1, memories: 5e3 },
1697
+ pro: { devices: 2, employees: 5, memories: 1e5 },
1698
+ team: { devices: 10, employees: 20, memories: 1e6 },
1699
+ agency: { devices: 50, employees: 100, memories: 1e7 },
1700
+ enterprise: { devices: -1, employees: -1, memories: -1 }
1701
+ };
1702
+ }
1703
+ });
1704
+
1705
+ // src/lib/plan-limits.ts
1706
+ import { readFileSync as readFileSync7, existsSync as existsSync10 } from "fs";
1707
+ import path10 from "path";
1708
+ function getLicenseSync() {
1709
+ try {
1710
+ if (!existsSync10(CACHE_PATH2)) return freeLicense();
1711
+ const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
1712
+ if (!raw.token || typeof raw.token !== "string") return freeLicense();
1713
+ const parts = raw.token.split(".");
1714
+ if (parts.length !== 3) return freeLicense();
1715
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
1716
+ const plan = payload.plan ?? "free";
1717
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
1718
+ return {
1719
+ valid: true,
1720
+ plan,
1721
+ email: payload.sub ?? "",
1722
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
1723
+ deviceLimit: limits.devices,
1724
+ employeeLimit: limits.employees,
1725
+ memoryLimit: limits.memories
1726
+ };
1727
+ } catch {
1728
+ return freeLicense();
1729
+ }
1730
+ }
1731
+ function freeLicense() {
1732
+ const limits = PLAN_LIMITS.free;
1733
+ return {
1734
+ valid: true,
1735
+ plan: "free",
1736
+ email: "",
1737
+ expiresAt: null,
1738
+ deviceLimit: limits.devices,
1739
+ employeeLimit: limits.employees,
1740
+ memoryLimit: limits.memories
1741
+ };
1742
+ }
1743
+ function assertEmployeeLimitSync(rosterPath) {
1744
+ const license = getLicenseSync();
1745
+ if (license.employeeLimit < 0) return;
1746
+ const filePath = rosterPath ?? EMPLOYEES_PATH;
1747
+ let count = 0;
1748
+ try {
1749
+ if (existsSync10(filePath)) {
1750
+ const raw = readFileSync7(filePath, "utf8");
1751
+ const employees = JSON.parse(raw);
1752
+ count = Array.isArray(employees) ? employees.length : 0;
1753
+ }
1754
+ } catch {
1755
+ throw new PlanLimitError(
1756
+ `Cannot verify employee count: roster unreadable at ${filePath}. Refusing to proceed. Check file permissions or upgrade plan.`
1757
+ );
1758
+ }
1759
+ if (count >= license.employeeLimit) {
1760
+ throw new PlanLimitError(
1761
+ `Employee limit reached: ${count}/${license.employeeLimit} employees on the ${license.plan} plan. Upgrade at https://askexe.com to add more.`
1762
+ );
1763
+ }
1764
+ }
1765
+ var PlanLimitError, CACHE_PATH2;
1766
+ var init_plan_limits = __esm({
1767
+ "src/lib/plan-limits.ts"() {
1768
+ "use strict";
1769
+ init_database();
1770
+ init_employees();
1771
+ init_license();
1772
+ init_config();
1773
+ PlanLimitError = class extends Error {
1774
+ constructor(message) {
1775
+ super(message);
1776
+ this.name = "PlanLimitError";
1777
+ }
1778
+ };
1779
+ CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
1780
+ }
1781
+ });
1782
+
1783
+ // src/lib/worktree.ts
1784
+ import { execSync as execSync5 } from "child_process";
1785
+ import { existsSync as existsSync11, readFileSync as readFileSync8, appendFileSync, mkdirSync as mkdirSync5, realpathSync } from "fs";
1786
+ import path11 from "path";
1787
+ function getGitRoot(dir) {
1788
+ try {
1789
+ const root = execSync5("git rev-parse --show-toplevel", {
1790
+ cwd: dir,
1791
+ encoding: "utf-8",
1792
+ timeout: GIT_TIMEOUT_MS,
1793
+ stdio: ["pipe", "pipe", "pipe"]
1794
+ }).trim();
1795
+ return realpath(root);
1796
+ } catch {
1797
+ return null;
1798
+ }
1799
+ }
1800
+ function worktreePath(repoRoot, employeeName, instance) {
1801
+ const label = instanceLabel(employeeName, instance);
1802
+ return path11.join(repoRoot, ".worktrees", label);
1803
+ }
1804
+ function worktreeBranch(employeeName, instance) {
1805
+ return `${instanceLabel(employeeName, instance)}-work`;
1806
+ }
1807
+ function instanceLabel(name, instance) {
1808
+ return instance != null && instance > 0 ? `${name}${instance}` : name;
1809
+ }
1810
+ function ensureWorktree(projectDir, employeeName, instance) {
1811
+ const repoRoot = getGitRoot(projectDir);
1812
+ if (!repoRoot) return null;
1813
+ const wtPath = worktreePath(repoRoot, employeeName, instance);
1814
+ const branch = worktreeBranch(employeeName, instance);
1815
+ if (existsSync11(path11.join(wtPath, ".git"))) {
1816
+ return wtPath;
1817
+ }
1818
+ const worktreesDir = path11.join(repoRoot, ".worktrees");
1819
+ mkdirSync5(worktreesDir, { recursive: true });
1820
+ ensureGitignoreEntry(repoRoot, "/.worktrees/");
1821
+ try {
1822
+ execSync5("git worktree prune", {
1823
+ cwd: repoRoot,
1824
+ encoding: "utf-8",
1825
+ timeout: GIT_TIMEOUT_MS,
1826
+ stdio: ["pipe", "pipe", "pipe"]
1827
+ });
1828
+ } catch {
1829
+ }
1830
+ const branchExists = branchExistsLocally(repoRoot, branch);
1831
+ try {
1832
+ if (branchExists) {
1833
+ execSync5(`git worktree add "${wtPath}" "${branch}"`, {
1834
+ cwd: repoRoot,
1835
+ encoding: "utf-8",
1836
+ timeout: GIT_TIMEOUT_MS,
1837
+ stdio: ["pipe", "pipe", "pipe"]
1838
+ });
1839
+ } else {
1840
+ execSync5(`git worktree add "${wtPath}" -b "${branch}" HEAD`, {
1841
+ cwd: repoRoot,
1842
+ encoding: "utf-8",
1843
+ timeout: GIT_TIMEOUT_MS,
1844
+ stdio: ["pipe", "pipe", "pipe"]
1845
+ });
1846
+ }
1847
+ return wtPath;
1848
+ } catch (err) {
1849
+ process.stderr.write(
1850
+ `[worktree] Failed to create worktree for ${employeeName}: ${err instanceof Error ? err.message : String(err)}
1851
+ `
1852
+ );
1853
+ return null;
1854
+ }
1855
+ }
1856
+ function branchExistsLocally(repoRoot, branch) {
1857
+ try {
1858
+ execSync5(`git rev-parse --verify "refs/heads/${branch}"`, {
1859
+ cwd: repoRoot,
1860
+ encoding: "utf-8",
1861
+ timeout: GIT_TIMEOUT_MS,
1862
+ stdio: ["pipe", "pipe", "pipe"]
1863
+ });
1864
+ return true;
1865
+ } catch {
1866
+ return false;
1867
+ }
1868
+ }
1869
+ function realpath(p) {
1870
+ try {
1871
+ return realpathSync(p);
1872
+ } catch {
1873
+ return p;
1874
+ }
1875
+ }
1876
+ function ensureGitignoreEntry(repoRoot, entry) {
1877
+ try {
1878
+ const gitignorePath = path11.join(repoRoot, ".gitignore");
1879
+ if (existsSync11(gitignorePath)) {
1880
+ const content = readFileSync8(gitignorePath, "utf-8");
1881
+ if (content.includes(entry)) return;
1882
+ appendFileSync(gitignorePath, `
1883
+ # Agent worktrees (exe-os)
1884
+ ${entry}
1885
+ `);
1886
+ } else {
1887
+ }
1888
+ } catch {
1889
+ }
1890
+ }
1891
+ var GIT_TIMEOUT_MS;
1892
+ var init_worktree = __esm({
1893
+ "src/lib/worktree.ts"() {
1894
+ "use strict";
1895
+ GIT_TIMEOUT_MS = 1e4;
1896
+ }
1897
+ });
1898
+
1899
+ // src/lib/tmux-routing.ts
1900
+ import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
1901
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync4, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync as appendFileSync2 } from "fs";
1902
+ import path12 from "path";
1903
+ import os5 from "os";
1904
+ import { fileURLToPath } from "url";
1905
+ function resolveBehaviorsExporterScript() {
1906
+ try {
1907
+ const thisFile = fileURLToPath(import.meta.url);
1908
+ const scriptPath = path12.join(
1909
+ path12.dirname(thisFile),
1910
+ "..",
1911
+ "bin",
1912
+ "exe-export-behaviors.js"
1913
+ );
1914
+ return existsSync12(scriptPath) ? scriptPath : null;
1915
+ } catch {
1916
+ return null;
1917
+ }
1918
+ }
1919
+ function exportBehaviorsSync(agentId, projectName, sessionKey) {
1920
+ const script = resolveBehaviorsExporterScript();
1921
+ if (!script) return null;
1922
+ try {
1923
+ const output = execFileSync2(
1924
+ process.execPath,
1925
+ [script, agentId, projectName, sessionKey],
1926
+ { encoding: "utf-8", timeout: BEHAVIORS_EXPORT_TIMEOUT_MS }
1927
+ ).trim();
1928
+ return output.length > 0 ? output : null;
1929
+ } catch (err) {
1930
+ process.stderr.write(
1931
+ `[tmux-routing] behaviors export failed for ${agentId}: ${err instanceof Error ? err.message : String(err)}
1932
+ `
1933
+ );
1934
+ return null;
1935
+ }
1936
+ }
1937
+ function getMySession() {
1938
+ return getTransport().getMySession();
1939
+ }
1940
+ function employeeSessionName(employee, exeSession, instance) {
1941
+ const suffix = instance != null && instance > 0 ? String(instance) : "";
1942
+ return `${employee}${suffix}-${exeSession}`;
1943
+ }
1944
+ function extractRootExe(name) {
1945
+ const match = name.match(/(exe\d+)$/);
1946
+ return match?.[1] ?? null;
1947
+ }
1948
+ function getParentExe(sessionKey) {
1949
+ try {
1950
+ const data = JSON.parse(readFileSync9(path12.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
1951
+ return data.parentExe || null;
1952
+ } catch {
1953
+ return null;
1954
+ }
1955
+ }
1956
+ function resolveExeSession() {
1957
+ const mySession = getMySession();
1958
+ if (!mySession) return null;
1959
+ try {
1960
+ const key = getSessionKey();
1961
+ const parentExe = getParentExe(key);
1962
+ if (parentExe) {
1963
+ return extractRootExe(parentExe) ?? parentExe;
1964
+ }
1965
+ } catch {
1966
+ }
1967
+ return extractRootExe(mySession) ?? mySession;
1968
+ }
1969
+ function isEmployeeAlive(sessionName) {
1970
+ return getTransport().isAlive(sessionName);
1971
+ }
1972
+ function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
1973
+ const base = employeeSessionName(employeeName, exeSession);
1974
+ if (!isAlive(base)) return 0;
1975
+ for (let i = 2; i <= maxInstances; i++) {
1976
+ const candidate = employeeSessionName(employeeName, exeSession, i);
1977
+ if (!isAlive(candidate)) return i;
1978
+ }
1979
+ return null;
1980
+ }
1981
+ function readDebounceState() {
1982
+ try {
1983
+ if (!existsSync12(DEBOUNCE_FILE)) return {};
1984
+ return JSON.parse(readFileSync9(DEBOUNCE_FILE, "utf8"));
1985
+ } catch {
1986
+ return {};
1987
+ }
1988
+ }
1989
+ function writeDebounceState(state) {
1990
+ try {
1991
+ if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
1992
+ writeFileSync4(DEBOUNCE_FILE, JSON.stringify(state));
1993
+ } catch {
1994
+ }
1995
+ }
1996
+ function isDebounced(targetSession) {
1997
+ const state = readDebounceState();
1998
+ const lastSent = state[targetSession] ?? 0;
1999
+ return Date.now() - lastSent < INTERCOM_DEBOUNCE_MS;
2000
+ }
2001
+ function recordDebounce(targetSession) {
2002
+ const state = readDebounceState();
2003
+ state[targetSession] = Date.now();
2004
+ const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
2005
+ for (const key of Object.keys(state)) {
2006
+ if ((state[key] ?? 0) < cutoff) delete state[key];
2007
+ }
2008
+ writeDebounceState(state);
2009
+ }
2010
+ function logIntercom(msg) {
2011
+ const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
2012
+ `;
2013
+ process.stderr.write(`[intercom] ${msg}
2014
+ `);
2015
+ try {
2016
+ appendFileSync2(INTERCOM_LOG2, line);
2017
+ } catch {
2018
+ }
2019
+ }
2020
+ function getSessionState(sessionName) {
2021
+ const transport = getTransport();
2022
+ if (!transport.isAlive(sessionName)) return "offline";
2023
+ try {
2024
+ const pane = transport.capturePane(sessionName, 5);
2025
+ if (/Running…/.test(pane)) return "tool";
2026
+ if (BUSY_PATTERN.test(pane)) return "thinking";
2027
+ return "idle";
2028
+ } catch {
2029
+ return "offline";
2030
+ }
2031
+ }
2032
+ function isSessionBusy(sessionName) {
2033
+ const state = getSessionState(sessionName);
2034
+ return state === "thinking" || state === "tool";
2035
+ }
2036
+ function isExeSession(sessionName) {
2037
+ return /^exe\d*$/.test(sessionName);
2038
+ }
2039
+ function sendIntercom(targetSession) {
2040
+ const transport = getTransport();
2041
+ if (isExeSession(targetSession)) {
2042
+ logIntercom(`SKIP_EXE \u2192 ${targetSession} (exe sessions use prompt-submit hook)`);
2043
+ return "skipped_exe";
2044
+ }
2045
+ if (isDebounced(targetSession)) {
2046
+ logIntercom(`DEBOUNCE \u2192 ${targetSession} (cross-process file debounce)`);
2047
+ return "debounced";
2048
+ }
2049
+ try {
2050
+ const sessions = transport.listSessions();
2051
+ if (!sessions.includes(targetSession)) {
2052
+ logIntercom(`SKIP \u2192 ${targetSession} (session not found)`);
2053
+ return "failed";
2054
+ }
2055
+ if (isSessionBusy(targetSession)) {
2056
+ queueIntercom(targetSession, "session busy at send time");
2057
+ recordDebounce(targetSession);
2058
+ logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
2059
+ return "queued";
2060
+ }
2061
+ if (transport.isPaneInCopyMode(targetSession)) {
2062
+ logIntercom(`COPY_MODE \u2192 ${targetSession} (exiting copy mode first)`);
2063
+ transport.sendKeys(targetSession, "q");
2064
+ }
2065
+ transport.sendKeys(targetSession, "/exe-intercom");
2066
+ recordDebounce(targetSession);
2067
+ for (let i = 0; i < INTERCOM_POLL_MAX_ATTEMPTS; i++) {
2068
+ try {
2069
+ execSync6(`sleep ${INTERCOM_POLL_INTERVAL_S}`);
2070
+ } catch {
2071
+ }
2072
+ const state = getSessionState(targetSession);
2073
+ if (state === "thinking" || state === "tool") {
2074
+ logIntercom(`ACKNOWLEDGED \u2192 ${targetSession} (state=${state}, poll=${i + 1})`);
2075
+ return "acknowledged";
2076
+ }
2077
+ }
2078
+ logIntercom(`DELIVERED \u2192 ${targetSession} (no state transition after ${INTERCOM_POLL_MAX_ATTEMPTS}s)`);
2079
+ return "delivered";
2080
+ } catch {
2081
+ logIntercom(`FAIL \u2192 ${targetSession}`);
2082
+ return "failed";
2083
+ }
2084
+ }
2085
+ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
2086
+ if (employeeName === "exe") {
2087
+ return { status: "failed", sessionName: "", error: "exe is the COO, not a dispatchable employee" };
2088
+ }
2089
+ try {
2090
+ assertEmployeeLimitSync();
2091
+ } catch (err) {
2092
+ if (err instanceof PlanLimitError) {
2093
+ return { status: "failed", sessionName: "", error: err.message };
2094
+ }
2095
+ }
2096
+ if (/-exe\d*$/.test(employeeName)) {
2097
+ const bare = employeeName.replace(/-exe\d*$/, "").replace(/\d+$/, "");
2098
+ return {
2099
+ status: "failed",
2100
+ sessionName: "",
2101
+ error: `Error: pass employee name ('${bare}'), not session name ('${employeeName}')`
2102
+ };
2103
+ }
2104
+ let effectiveInstance = opts?.instance;
2105
+ if (effectiveInstance === void 0 && opts?.autoInstance) {
2106
+ const free = findFreeInstance(
2107
+ employeeName,
2108
+ exeSession,
2109
+ opts.maxAutoInstances ?? 10
2110
+ );
2111
+ if (free === null) {
2112
+ return {
2113
+ status: "failed",
2114
+ sessionName: employeeSessionName(employeeName, exeSession),
2115
+ error: `All ${opts.maxAutoInstances ?? 10} instances of ${employeeName} are alive \u2014 cap reached`
2116
+ };
2117
+ }
2118
+ effectiveInstance = free === 0 ? void 0 : free;
2119
+ }
2120
+ const sessionName = employeeSessionName(employeeName, exeSession, effectiveInstance);
2121
+ if (isEmployeeAlive(sessionName)) {
2122
+ const result2 = sendIntercom(sessionName);
2123
+ if (result2 === "acknowledged" || result2 === "skipped_exe" || result2 === "debounced" || result2 === "queued") {
2124
+ return { status: "intercom_sent", sessionName };
2125
+ }
2126
+ if (result2 === "delivered") {
2127
+ return { status: "intercom_unprocessed", sessionName };
2128
+ }
2129
+ return { status: "failed", sessionName, error: "intercom delivery failed" };
2130
+ }
2131
+ const spawnOpts = { ...opts, instance: effectiveInstance };
2132
+ const wtPath = ensureWorktree(projectDir, employeeName, effectiveInstance);
2133
+ if (wtPath) {
2134
+ spawnOpts.cwd = wtPath;
2135
+ }
2136
+ const result = spawnEmployee(employeeName, exeSession, projectDir, spawnOpts);
2137
+ if (result.error) {
2138
+ return { status: "failed", sessionName, error: result.error };
2139
+ }
2140
+ return { status: "spawned", sessionName };
2141
+ }
2142
+ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2143
+ const transport = getTransport();
2144
+ const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
2145
+ const instanceLabel2 = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
2146
+ const logDir = path12.join(os5.homedir(), ".exe-os", "session-logs");
2147
+ const logFile = path12.join(logDir, `${instanceLabel2}-${Date.now()}.log`);
2148
+ if (!existsSync12(logDir)) {
2149
+ mkdirSync6(logDir, { recursive: true });
2150
+ }
2151
+ transport.kill(sessionName);
2152
+ let cleanupSuffix = "";
2153
+ try {
2154
+ const thisFile = fileURLToPath(import.meta.url);
2155
+ const cleanupScript = path12.join(path12.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
2156
+ if (existsSync12(cleanupScript)) {
2157
+ cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
2158
+ }
2159
+ } catch {
2160
+ }
2161
+ try {
2162
+ const claudeJsonPath = path12.join(os5.homedir(), ".claude.json");
2163
+ let claudeJson = {};
2164
+ try {
2165
+ claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
2166
+ } catch {
2167
+ }
2168
+ if (!claudeJson.projects) claudeJson.projects = {};
2169
+ const projects = claudeJson.projects;
2170
+ const trustDir = opts?.cwd ?? projectDir;
2171
+ if (!projects[trustDir]) projects[trustDir] = {};
2172
+ projects[trustDir].hasTrustDialogAccepted = true;
2173
+ writeFileSync4(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
2174
+ } catch {
2175
+ }
2176
+ try {
2177
+ const settingsDir = path12.join(os5.homedir(), ".claude", "projects");
2178
+ const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
2179
+ const projSettingsDir = path12.join(settingsDir, normalizedKey);
2180
+ const settingsPath = path12.join(projSettingsDir, "settings.json");
2181
+ let settings = {};
2182
+ try {
2183
+ settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
2184
+ } catch {
2185
+ }
2186
+ const perms = settings.permissions ?? {};
2187
+ const allow = perms.allow ?? [];
2188
+ const toolNames = [
2189
+ "recall_my_memory",
2190
+ "store_memory",
2191
+ "create_task",
2192
+ "update_task",
2193
+ "list_tasks",
2194
+ "get_task",
2195
+ "ask_team_memory",
2196
+ "store_behavior",
2197
+ "get_identity",
2198
+ "send_message"
2199
+ ];
2200
+ const requiredTools = expandDualPrefixTools(toolNames);
2201
+ let changed = false;
2202
+ for (const tool of requiredTools) {
2203
+ if (!allow.includes(tool)) {
2204
+ allow.push(tool);
2205
+ changed = true;
2206
+ }
2207
+ }
2208
+ if (changed) {
2209
+ perms.allow = allow;
2210
+ settings.permissions = perms;
2211
+ mkdirSync6(projSettingsDir, { recursive: true });
2212
+ writeFileSync4(settingsPath, JSON.stringify(settings, null, 2) + "\n");
2213
+ }
2214
+ } catch {
2215
+ }
2216
+ const spawnCwd = opts?.cwd ?? projectDir;
2217
+ const useExeAgent = !!(opts?.model && opts?.provider);
2218
+ const ccProvider = useExeAgent ? DEFAULT_PROVIDER : detectActiveProvider();
2219
+ const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
2220
+ let identityFlag = "";
2221
+ let behaviorsFlag = "";
2222
+ let legacyFallbackWarned = false;
2223
+ if (!useExeAgent && !useBinSymlink) {
2224
+ const identityPath = path12.join(
2225
+ os5.homedir(),
2226
+ ".exe-os",
2227
+ "identity",
2228
+ `${employeeName}.md`
2229
+ );
2230
+ _resetCcAgentSupportCache();
2231
+ const hasAgentFlag = claudeSupportsAgentFlag();
2232
+ if (hasAgentFlag) {
2233
+ identityFlag = ` --agent ${employeeName}`;
2234
+ } else if (existsSync12(identityPath)) {
2235
+ identityFlag = ` --append-system-prompt-file ${identityPath}`;
2236
+ legacyFallbackWarned = true;
2237
+ }
2238
+ const behaviorsFile = exportBehaviorsSync(
2239
+ employeeName,
2240
+ path12.basename(spawnCwd),
2241
+ sessionName
2242
+ );
2243
+ if (behaviorsFile) {
2244
+ behaviorsFlag = ` --append-system-prompt-file ${behaviorsFile}`;
2245
+ }
2246
+ }
2247
+ if (legacyFallbackWarned) {
2248
+ process.stderr.write(
2249
+ `[tmux-routing] claude --agent not supported by installed CC. Falling back to --append-system-prompt-file for ${employeeName}. Upgrade Claude Code to enable native --agent launch.
2250
+ `
2251
+ );
2252
+ }
2253
+ let sessionContextFlag = "";
2254
+ try {
2255
+ const ctxDir = path12.join(os5.homedir(), ".exe-os", "session-cache");
2256
+ mkdirSync6(ctxDir, { recursive: true });
2257
+ const ctxFile = path12.join(ctxDir, `session-context-${sessionName}.md`);
2258
+ const ctxContent = [
2259
+ `## Session Context`,
2260
+ `You are running in tmux session: ${sessionName}.`,
2261
+ `Your parent exe session is ${exeSession}.`,
2262
+ `Your employees (if any) use the -${exeSession} suffix (e.g., tom-${exeSession}).`
2263
+ ].join("\n");
2264
+ writeFileSync4(ctxFile, ctxContent);
2265
+ sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
2266
+ } catch {
2267
+ }
2268
+ let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName}`;
2269
+ if (ccProvider !== DEFAULT_PROVIDER) {
2270
+ const cfg = PROVIDER_TABLE[ccProvider];
2271
+ if (cfg?.apiKeyEnv) {
2272
+ const keyVal = process.env[cfg.apiKeyEnv];
2273
+ if (keyVal) {
2274
+ envPrefix = `${envPrefix} ${cfg.apiKeyEnv}=${keyVal}`;
2275
+ }
2276
+ }
2277
+ }
2278
+ let spawnCommand;
2279
+ if (useExeAgent) {
2280
+ spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
2281
+ } else if (useBinSymlink) {
2282
+ const binName = `${employeeName}-${ccProvider}`;
2283
+ process.stderr.write(
2284
+ `[tmux-routing] provider cascade: ${ccProvider} \u2192 spawning ${binName}
2285
+ `
2286
+ );
2287
+ spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
2288
+ } else {
2289
+ spawnCommand = `${envPrefix} claude --dangerously-skip-permissions${identityFlag}${behaviorsFlag}${sessionContextFlag}${cleanupSuffix}`;
2290
+ }
2291
+ const spawnResult = transport.spawn(sessionName, {
2292
+ cwd: spawnCwd,
2293
+ command: spawnCommand
2294
+ });
2295
+ if (spawnResult.error) {
2296
+ return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
2297
+ }
2298
+ transport.pipeLog(sessionName, logFile);
2299
+ try {
2300
+ const mySession = getMySession();
2301
+ const dispatchInfo = path12.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
2302
+ writeFileSync4(dispatchInfo, JSON.stringify({
2303
+ dispatchedBy: mySession,
2304
+ rootExe: exeSession,
2305
+ provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
2306
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2307
+ }));
2308
+ } catch {
2309
+ }
2310
+ let booted = false;
2311
+ for (let i = 0; i < 30; i++) {
2312
+ try {
2313
+ execSync6("sleep 1");
2314
+ } catch {
2315
+ }
2316
+ try {
2317
+ const pane = transport.capturePane(sessionName);
2318
+ if (useExeAgent) {
2319
+ if (pane.includes("[exe-agent]") || pane.includes("online")) {
2320
+ booted = true;
2321
+ break;
2322
+ }
2323
+ } else {
2324
+ if (pane.includes("Claude Code") || pane.includes("\u276F")) {
2325
+ booted = true;
2326
+ break;
2327
+ }
2328
+ }
2329
+ } catch {
2330
+ }
2331
+ }
2332
+ if (!booted) {
2333
+ return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 30s` };
2334
+ }
2335
+ if (!useExeAgent) {
2336
+ try {
2337
+ transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
2338
+ } catch {
2339
+ }
2340
+ }
2341
+ registerSession({
2342
+ windowName: sessionName,
2343
+ agentId: employeeName,
2344
+ projectDir: spawnCwd,
2345
+ parentExe: exeSession,
2346
+ pid: 0,
2347
+ registeredAt: (/* @__PURE__ */ new Date()).toISOString()
2348
+ });
2349
+ return { sessionName };
2350
+ }
2351
+ var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN, INTERCOM_POLL_INTERVAL_S, INTERCOM_POLL_MAX_ATTEMPTS;
2352
+ var init_tmux_routing = __esm({
2353
+ "src/lib/tmux-routing.ts"() {
2354
+ "use strict";
2355
+ init_session_registry();
2356
+ init_session_key();
2357
+ init_transport();
2358
+ init_cc_agent_support();
2359
+ init_mcp_prefix();
2360
+ init_provider_table();
2361
+ init_intercom_queue();
2362
+ init_plan_limits();
2363
+ init_worktree();
2364
+ SESSION_CACHE = path12.join(os5.homedir(), ".exe-os", "session-cache");
2365
+ BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
2366
+ INTERCOM_DEBOUNCE_MS = 3e4;
2367
+ INTERCOM_LOG2 = path12.join(os5.homedir(), ".exe-os", "intercom.log");
2368
+ DEBOUNCE_FILE = path12.join(SESSION_CACHE, "intercom-debounce.json");
2369
+ DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
2370
+ BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
2371
+ INTERCOM_POLL_INTERVAL_S = 1;
2372
+ INTERCOM_POLL_MAX_ATTEMPTS = 8;
2373
+ }
2374
+ });
2375
+
2376
+ // src/lib/tasks-review.ts
2377
+ import path13 from "path";
2378
+ import { existsSync as existsSync13, readdirSync as readdirSync2, unlinkSync as unlinkSync2 } from "fs";
2379
+ var init_tasks_review = __esm({
2380
+ "src/lib/tasks-review.ts"() {
2381
+ "use strict";
2382
+ init_database();
2383
+ init_config();
2384
+ init_employees();
2385
+ init_notifications();
2386
+ init_tasks_crud();
2387
+ init_tmux_routing();
2388
+ init_session_key();
2389
+ }
2390
+ });
2391
+
2392
+ // src/lib/tasks-chain.ts
2393
+ import path14 from "path";
2394
+ import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
2395
+ var init_tasks_chain = __esm({
2396
+ "src/lib/tasks-chain.ts"() {
2397
+ "use strict";
2398
+ init_database();
2399
+ init_notifications();
2400
+ }
2401
+ });
2402
+
2403
+ // src/lib/project-name.ts
2404
+ import { execSync as execSync7 } from "child_process";
2405
+ import path15 from "path";
2406
+ function getProjectName(cwd) {
2407
+ const dir = cwd ?? process.cwd();
2408
+ if (_cached2 && _cachedCwd === dir) return _cached2;
2409
+ try {
2410
+ const repoRoot = execSync7("git rev-parse --show-toplevel", {
2411
+ cwd: dir,
2412
+ encoding: "utf8",
2413
+ timeout: 2e3,
2414
+ stdio: ["pipe", "pipe", "pipe"]
2415
+ }).trim();
2416
+ _cached2 = path15.basename(repoRoot);
2417
+ _cachedCwd = dir;
2418
+ return _cached2;
2419
+ } catch {
2420
+ _cached2 = path15.basename(dir);
2421
+ _cachedCwd = dir;
2422
+ return _cached2;
2423
+ }
2424
+ }
2425
+ var _cached2, _cachedCwd;
2426
+ var init_project_name = __esm({
2427
+ "src/lib/project-name.ts"() {
2428
+ "use strict";
2429
+ _cached2 = null;
2430
+ _cachedCwd = null;
2431
+ }
2432
+ });
2433
+
2434
+ // src/lib/session-scope.ts
2435
+ var session_scope_exports = {};
2436
+ __export(session_scope_exports, {
2437
+ assertSessionScope: () => assertSessionScope,
2438
+ findSessionForProject: () => findSessionForProject,
2439
+ getSessionProject: () => getSessionProject
2440
+ });
2441
+ function getSessionProject(sessionName) {
2442
+ const sessions = listSessions();
2443
+ const entry = sessions.find((s) => s.windowName === sessionName);
2444
+ if (!entry) return null;
2445
+ const parts = entry.projectDir.split("/").filter(Boolean);
2446
+ return parts[parts.length - 1] ?? null;
2447
+ }
2448
+ function findSessionForProject(projectName) {
2449
+ const sessions = listSessions();
2450
+ for (const s of sessions) {
2451
+ const proj = s.projectDir.split("/").filter(Boolean).pop();
2452
+ if (proj === projectName && s.agentId === "exe") return s;
2453
+ }
2454
+ return null;
2455
+ }
2456
+ function assertSessionScope(actionType, targetProject) {
2457
+ try {
2458
+ const currentProject = getProjectName();
2459
+ const exeSession = resolveExeSession();
2460
+ if (!exeSession) {
2461
+ return { allowed: true, reason: "no_session" };
2462
+ }
2463
+ if (currentProject === targetProject) {
2464
+ return {
2465
+ allowed: true,
2466
+ reason: "same_session",
2467
+ currentProject,
2468
+ targetProject
2469
+ };
2470
+ }
2471
+ process.stderr.write(
2472
+ `[session-scope] Cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
2473
+ `
2474
+ );
2475
+ return {
2476
+ allowed: true,
2477
+ // v1: warn-only, don't block
2478
+ reason: "cross_session_granted",
2479
+ currentProject,
2480
+ targetProject,
2481
+ targetSession: findSessionForProject(targetProject)?.windowName
2482
+ };
2483
+ } catch {
2484
+ return { allowed: true, reason: "no_session" };
2485
+ }
2486
+ }
2487
+ var init_session_scope = __esm({
2488
+ "src/lib/session-scope.ts"() {
2489
+ "use strict";
2490
+ init_session_registry();
2491
+ init_project_name();
2492
+ init_tmux_routing();
2493
+ }
2494
+ });
2495
+
2496
+ // src/lib/tasks-notify.ts
2497
+ async function dispatchTaskToEmployee(input) {
2498
+ if (input.assignedTo === "exe") return { dispatched: "skipped" };
2499
+ let crossProject = false;
2500
+ if (input.projectName) {
2501
+ try {
2502
+ const { assertSessionScope: assertSessionScope2 } = (init_session_scope(), __toCommonJS(session_scope_exports));
2503
+ const check = assertSessionScope2("dispatch_task", input.projectName);
2504
+ if (check.reason === "cross_session_granted") {
2505
+ crossProject = true;
2506
+ }
2507
+ } catch {
2508
+ }
2509
+ }
2510
+ try {
2511
+ const transport = getTransport();
2512
+ const exeSession = resolveExeSession();
2513
+ if (!exeSession) return { dispatched: "session_missing" };
2514
+ const sessionName = employeeSessionName(input.assignedTo, exeSession);
2515
+ if (transport.isAlive(sessionName)) {
2516
+ const result = sendIntercom(sessionName);
2517
+ const dispatched = result === "acknowledged" || result === "debounced" || result === "queued" ? "verified" : result === "delivered" ? "sent_unverified" : "session_dead";
2518
+ return { dispatched, session: sessionName, crossProject };
2519
+ } else {
2520
+ const projectDir = input.projectDir ?? process.cwd();
2521
+ const result = ensureEmployee(input.assignedTo, exeSession, projectDir);
2522
+ if (result.status === "failed") {
2523
+ process.stderr.write(
2524
+ `[dispatch] Failed to spawn ${input.assignedTo}: ${result.error}
2525
+ `
2526
+ );
2527
+ return { dispatched: "session_missing" };
2528
+ }
2529
+ return { dispatched: "spawned", session: result.sessionName, crossProject };
2530
+ }
2531
+ } catch {
2532
+ return { dispatched: "session_missing" };
2533
+ }
2534
+ }
2535
+ var init_tasks_notify = __esm({
2536
+ "src/lib/tasks-notify.ts"() {
2537
+ "use strict";
2538
+ init_tmux_routing();
2539
+ init_session_key();
2540
+ init_notifications();
2541
+ init_transport();
2542
+ }
2543
+ });
2544
+
2545
+ // src/lib/tasks.ts
2546
+ import path16 from "path";
2547
+ import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync7, unlinkSync as unlinkSync3 } from "fs";
2548
+ async function createTask(input) {
2549
+ const result = await createTaskCore(input);
2550
+ if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
2551
+ dispatchTaskToEmployee({
2552
+ assignedTo: input.assignedTo,
2553
+ title: input.title,
2554
+ priority: input.priority,
2555
+ taskFile: result.taskFile,
2556
+ initialStatus: result.status,
2557
+ projectName: input.projectName
2558
+ });
2559
+ }
2560
+ return result;
2561
+ }
2562
+ var init_tasks = __esm({
2563
+ "src/lib/tasks.ts"() {
2564
+ "use strict";
2565
+ init_database();
2566
+ init_config();
2567
+ init_notifications();
2568
+ init_tasks_crud();
2569
+ init_tasks_review();
2570
+ init_tasks_crud();
2571
+ init_tasks_chain();
2572
+ init_tasks_review();
2573
+ init_tasks_notify();
2574
+ }
2575
+ });
2576
+
2577
+ // src/lib/store.ts
2578
+ init_database();
2579
+
2580
+ // src/lib/keychain.ts
2581
+ import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
2582
+ import { existsSync } from "fs";
2583
+ import path from "path";
2584
+ import crypto from "crypto";
2585
+ var SERVICE = "exe-mem";
2586
+ var ACCOUNT = "master-key";
2587
+ function getKeyDir() {
2588
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(process.env.HOME ?? "/tmp", ".exe-os");
2589
+ }
2590
+ function getKeyPath() {
2591
+ return path.join(getKeyDir(), "master.key");
2592
+ }
2593
+ async function tryKeytar() {
2594
+ try {
2595
+ return await import("keytar");
2596
+ } catch {
2597
+ return null;
2598
+ }
2599
+ }
2600
+ async function getMasterKey() {
2601
+ const keytar = await tryKeytar();
2602
+ if (keytar) {
2603
+ try {
2604
+ const stored = await keytar.getPassword(SERVICE, ACCOUNT);
2605
+ if (stored) {
2606
+ return Buffer.from(stored, "base64");
2607
+ }
2608
+ } catch {
2609
+ }
2610
+ }
2611
+ const keyPath = getKeyPath();
2612
+ if (!existsSync(keyPath)) {
2613
+ return null;
2614
+ }
2615
+ try {
2616
+ const content = await readFile(keyPath, "utf-8");
2617
+ return Buffer.from(content.trim(), "base64");
2618
+ } catch {
2619
+ return null;
2620
+ }
2621
+ }
2622
+
2623
+ // src/lib/store.ts
2624
+ init_config();
2625
+ var _pendingRecords = [];
2626
+ var _batchSize = 20;
2627
+ var _flushIntervalMs = 1e4;
2628
+ var _flushTimer = null;
2629
+ var _flushing = false;
2630
+ var _nextVersion = 1;
2631
+ async function initStore(options) {
2632
+ if (_flushTimer !== null) {
2633
+ clearInterval(_flushTimer);
2634
+ _flushTimer = null;
2635
+ }
2636
+ _pendingRecords = [];
2637
+ _flushing = false;
2638
+ _batchSize = options?.batchSize ?? 20;
2639
+ _flushIntervalMs = options?.flushIntervalMs ?? 1e4;
2640
+ let dbPath = options?.dbPath;
2641
+ if (!dbPath) {
2642
+ const config = await loadConfig();
2643
+ dbPath = config.dbPath;
2644
+ }
2645
+ let masterKey = options?.masterKey ?? null;
2646
+ if (!masterKey) {
2647
+ masterKey = await getMasterKey();
2648
+ if (!masterKey) {
2649
+ throw new Error(
2650
+ "No encryption key found. Run /exe-setup to generate one."
2651
+ );
2652
+ }
2653
+ }
2654
+ const hexKey = masterKey.toString("hex");
2655
+ await initTurso({
2656
+ dbPath,
2657
+ encryptionKey: hexKey
2658
+ });
2659
+ await ensureSchema();
2660
+ try {
2661
+ const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
2662
+ initShardManager2(hexKey);
2663
+ } catch {
2664
+ }
2665
+ const client = getClient();
2666
+ const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
2667
+ _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
2668
+ }
2669
+
2670
+ // src/adapters/claude/hooks/bug-report-worker.ts
2671
+ init_database();
2672
+ init_tasks();
2673
+ async function main() {
2674
+ const toolName = process.env.BUG_TOOL_NAME ?? "unknown";
2675
+ const errorText = process.env.BUG_ERROR_TEXT ?? "";
2676
+ const toolInput = process.env.BUG_TOOL_INPUT ?? "{}";
2677
+ const fingerprint = process.env.BUG_FINGERPRINT ?? "";
2678
+ const agentId = process.env.BUG_AGENT_ID ?? "unknown";
2679
+ const agentRole = process.env.BUG_AGENT_ROLE ?? "employee";
2680
+ const projectName = process.env.BUG_PROJECT_NAME ?? "unknown";
2681
+ await initStore();
2682
+ const fpPrefix = fingerprint.slice(0, 8);
2683
+ const client = getClient();
2684
+ const existing = await client.execute({
2685
+ sql: `SELECT id FROM tasks
2686
+ WHERE assigned_to = 'yoshi'
2687
+ AND status IN ('open', 'in_progress')
2688
+ AND title LIKE '[auto-bug]%'
2689
+ AND task_file LIKE ?
2690
+ LIMIT 1`,
2691
+ args: [`%${fpPrefix}%`]
2692
+ });
2693
+ if (existing.rows.length > 0) {
2694
+ process.stderr.write(`[bug-report-worker] Duplicate found for fingerprint ${fingerprint}, skipping
2695
+ `);
2696
+ return;
2697
+ }
2698
+ const errorSummary = errorText.split("\n").find((line) => line.trim().length > 0)?.trim().slice(0, 60) ?? "unknown error";
2699
+ const context = [
2700
+ "## Auto-detected system bug",
2701
+ "",
2702
+ `**Detected by:** ${agentId} (${agentRole})`,
2703
+ `**Tool:** ${toolName}`,
2704
+ `**Timestamp:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
2705
+ `**Fingerprint:** ${fingerprint}`,
2706
+ "",
2707
+ "## Error output",
2708
+ "",
2709
+ "```",
2710
+ errorText.slice(0, 1e3),
2711
+ "```",
2712
+ "",
2713
+ "## Tool input (reproduction context)",
2714
+ "",
2715
+ "```json",
2716
+ toolInput.slice(0, 500),
2717
+ "```",
2718
+ "",
2719
+ "## Triage notes",
2720
+ "",
2721
+ "- Classification: system bug (auto-detected)",
2722
+ "- Review this error \u2014 fix if real, close if false positive",
2723
+ "- If false positive: add the error pattern to USER_ERROR_PATTERNS in error-detector.ts"
2724
+ ].join("\n");
2725
+ await createTask({
2726
+ title: `[auto-bug] ${toolName}: ${errorSummary}`,
2727
+ assignedTo: "yoshi",
2728
+ assignedBy: "system",
2729
+ projectName,
2730
+ priority: "p1",
2731
+ context,
2732
+ baseDir: process.cwd(),
2733
+ skipDispatch: true,
2734
+ reviewer: "exe"
2735
+ });
2736
+ process.stderr.write(`[bug-report-worker] Created auto-bug task for ${toolName}: ${errorSummary}
2737
+ `);
2738
+ }
2739
+ main().catch((err) => {
2740
+ process.stderr.write("[bug-report-worker] FATAL: " + (err instanceof Error ? err.message : String(err)) + "\n");
2741
+ }).finally(() => {
2742
+ process.exit(0);
2743
+ });