omnara 0.2.1 → 0.2.2

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.
package/dist/cli.js CHANGED
@@ -107,8 +107,22 @@ import { randomUUID } from "crypto";
107
107
  import fs2 from "fs";
108
108
  import path from "path";
109
109
  import os from "os";
110
+ function isDevelopmentMode() {
111
+ if (process.env.OMNARA_ENV === "development") {
112
+ return true;
113
+ }
114
+ const backendUrl = process.env.OMNARA_BACKEND_URL;
115
+ if (backendUrl && (backendUrl.includes("localhost") || backendUrl.includes("127.0.0.1"))) {
116
+ return true;
117
+ }
118
+ return false;
119
+ }
110
120
  function getLogDir() {
111
- return process.env.OMNARA_LOG_DIR ?? path.join(os.homedir(), ".omnara", "omnara-voice");
121
+ if (process.env.OMNARA_LOG_DIR) {
122
+ return process.env.OMNARA_LOG_DIR;
123
+ }
124
+ const dirName = isDevelopmentMode() ? ".omnara-dev" : ".omnara";
125
+ return path.join(os.homedir(), dirName, "omnara-voice");
112
126
  }
113
127
  function ensureLogDir() {
114
128
  const dir = getLogDir();
@@ -784,7 +798,21 @@ import fs3 from "fs/promises";
784
798
  import os2 from "os";
785
799
  import path3 from "path";
786
800
  import { setTimeout as delay } from "node:timers/promises";
787
- var DEFAULT_DIR = path3.join(os2.homedir(), ".omnara");
801
+ function isDevelopmentMode2() {
802
+ if (process.env.OMNARA_ENV === "development") {
803
+ return true;
804
+ }
805
+ const backendUrl = process.env.OMNARA_BACKEND_URL;
806
+ if (backendUrl && (backendUrl.includes("localhost") || backendUrl.includes("127.0.0.1"))) {
807
+ return true;
808
+ }
809
+ return false;
810
+ }
811
+ function getOmnaraHomeDir() {
812
+ const dirName = isDevelopmentMode2() ? ".omnara-dev" : ".omnara";
813
+ return path3.join(os2.homedir(), dirName);
814
+ }
815
+ var DEFAULT_DIR = getOmnaraHomeDir();
788
816
  var DEFAULT_CREDENTIALS_PATH = path3.join(DEFAULT_DIR, "creds.json");
789
817
  var DEFAULT_FILE_MODE = 384;
790
818
  var DEFAULT_DIR_MODE = 448;
@@ -1546,6 +1574,70 @@ var WorkerManager = class extends EventEmitter {
1546
1574
  record.messageQueue.close();
1547
1575
  return true;
1548
1576
  }
1577
+ /**
1578
+ * Route a user message to a worker by session ID.
1579
+ * This is used when users send messages directly to subagent sessions from the frontend.
1580
+ *
1581
+ * @param sessionId - The session ID of the worker
1582
+ * @param message - The message content to send
1583
+ * @returns true if message was sent, false if worker not found
1584
+ */
1585
+ async routeUserMessageToWorker(sessionId, message) {
1586
+ const record = this.workers.get(sessionId);
1587
+ if (!record) {
1588
+ console.error("[WorkerManager] Worker not found for session:", sessionId);
1589
+ return false;
1590
+ }
1591
+ console.log("[WorkerManager] Routing user message to worker:", {
1592
+ workerId: record.id,
1593
+ task: record.task,
1594
+ sessionId,
1595
+ textPreview: message.substring(0, 100)
1596
+ });
1597
+ if (record.status !== "running") {
1598
+ record.messageQueue = new AsyncQueue();
1599
+ record.status = "running";
1600
+ record.lastUpdateAt = Date.now();
1601
+ record.progress.push({
1602
+ timestamp: Date.now(),
1603
+ message: `Worker restarted with user message: ${message}`
1604
+ });
1605
+ this.emitUpdate(record);
1606
+ void this.runWorker(record, "");
1607
+ }
1608
+ if (record.messageQueue.isClosed()) {
1609
+ console.error("[WorkerManager] Message queue is closed after restart attempt");
1610
+ return false;
1611
+ }
1612
+ record.progress.push({
1613
+ timestamp: Date.now(),
1614
+ message: `[USER MESSAGE]: ${message}`
1615
+ });
1616
+ record.lastUpdateAt = Date.now();
1617
+ const userMessage = {
1618
+ session_id: record.sessionId,
1619
+ type: "user",
1620
+ message: {
1621
+ role: "user",
1622
+ content: message
1623
+ },
1624
+ parent_tool_use_id: null,
1625
+ isSynthetic: false
1626
+ // This is a real user message from frontend
1627
+ };
1628
+ try {
1629
+ record.messageQueue.push(userMessage);
1630
+ console.log("[WorkerManager] Successfully queued user message to worker:", {
1631
+ workerId: record.id,
1632
+ task: record.task,
1633
+ queueClosed: record.messageQueue.isClosed()
1634
+ });
1635
+ return true;
1636
+ } catch (error) {
1637
+ console.error("[WorkerManager] Failed to queue user message:", error);
1638
+ return false;
1639
+ }
1640
+ }
1549
1641
  /**
1550
1642
  * Send a message to a worker (running or completed).
1551
1643
  *
@@ -1707,7 +1799,7 @@ var WorkerManager = class extends EventEmitter {
1707
1799
  record.sessionId,
1708
1800
  record.messageQueue
1709
1801
  );
1710
- const query4 = client.createQuery(messageStream, {
1802
+ const queryOptions = {
1711
1803
  hooks: {
1712
1804
  PostToolUse: [
1713
1805
  {
@@ -1747,7 +1839,16 @@ var WorkerManager = class extends EventEmitter {
1747
1839
  }
1748
1840
  ]
1749
1841
  }
1750
- });
1842
+ };
1843
+ if (record.sdkSessionId) {
1844
+ queryOptions.resume = record.sdkSessionId;
1845
+ console.log("[WorkerManager] Resuming worker with SDK session ID:", {
1846
+ workerId: record.id,
1847
+ sdkSessionId: record.sdkSessionId,
1848
+ task: record.task
1849
+ });
1850
+ }
1851
+ const query4 = client.createQuery(messageStream, queryOptions);
1751
1852
  this.runningQueries.set(record.id, query4);
1752
1853
  const summarizedChunks = [];
1753
1854
  try {
@@ -1817,6 +1918,15 @@ Automatically committed changes from worker completion.`;
1817
1918
  }
1818
1919
  handleMessage(record, message, summarizedChunks) {
1819
1920
  switch (message.type) {
1921
+ case "system":
1922
+ if (message.subtype === "init" && message.session_id) {
1923
+ record.sdkSessionId = message.session_id;
1924
+ console.log("[WorkerManager] Captured SDK session ID for worker:", {
1925
+ workerId: record.id,
1926
+ sdkSessionId: record.sdkSessionId
1927
+ });
1928
+ }
1929
+ break;
1820
1930
  case "assistant": {
1821
1931
  const text = this.extractAssistantText(message);
1822
1932
  if (text) {
@@ -2412,6 +2522,8 @@ var AgentOrchestrator = class {
2412
2522
  // Track voice sessions to filter out their messages
2413
2523
  voiceSessionIds = /* @__PURE__ */ new Set();
2414
2524
  // Made non-private for relay-bridge access
2525
+ // Track worker sessions to filter out their messages (so they're handled by workers, not orchestrator)
2526
+ workerSessionIds = /* @__PURE__ */ new Set();
2415
2527
  started = false;
2416
2528
  constructor(dependencies) {
2417
2529
  const resolveSessionIdBySdk = (sdkSessionId) => this.findSessionIdBySdkSession(sdkSessionId);
@@ -2591,6 +2703,21 @@ var AgentOrchestrator = class {
2591
2703
  currentVoiceSessions: Array.from(this.voiceSessionIds)
2592
2704
  });
2593
2705
  }
2706
+ if (message.metadata?.session_type === "WORKER") {
2707
+ this.workerSessionIds.add(message.sessionId);
2708
+ console.log("[Orchestrator] Tracking worker session by type:", {
2709
+ sessionId: message.sessionId,
2710
+ metadata: message.metadata
2711
+ });
2712
+ }
2713
+ if (message.parentSessionId && message.metadata?.session_type !== "VOICE") {
2714
+ this.workerSessionIds.add(message.sessionId);
2715
+ console.log("[Orchestrator] Tracking worker session from init:", {
2716
+ sessionId: message.sessionId,
2717
+ parentSessionId: message.parentSessionId,
2718
+ currentWorkerSessions: Array.from(this.workerSessionIds)
2719
+ });
2720
+ }
2594
2721
  this.sendToConnection(connection, {
2595
2722
  type: "session_init_ack",
2596
2723
  sessionId: message.sessionId,
@@ -2598,18 +2725,31 @@ var AgentOrchestrator = class {
2598
2725
  });
2599
2726
  }
2600
2727
  handleChatMessage(connection, message) {
2728
+ console.log("[Orchestrator] Routing check:", {
2729
+ sessionId: message.sessionId,
2730
+ workerSessionIds: Array.from(this.workerSessionIds),
2731
+ isWorkerSession: message.sessionId ? this.workerSessionIds.has(message.sessionId) : false,
2732
+ voiceSessionIds: Array.from(this.voiceSessionIds),
2733
+ isVoiceSession: message.sessionId ? this.voiceSessionIds.has(message.sessionId) : false
2734
+ });
2601
2735
  console.log("[Orchestrator] handleChatMessage:", {
2602
2736
  sessionId: message.sessionId,
2603
2737
  senderId: message.senderId,
2604
2738
  senderKind: message.senderKind,
2605
2739
  mainCodingSessionId: this.mainCodingSessionId,
2606
2740
  isVoiceSession: message.sessionId ? this.voiceSessionIds.has(message.sessionId) : false,
2741
+ isWorkerSession: message.sessionId ? this.workerSessionIds.has(message.sessionId) : false,
2607
2742
  textPreview: message.text.substring(0, 100)
2608
2743
  });
2609
2744
  if (message.sessionId && this.voiceSessionIds.has(message.sessionId)) {
2610
2745
  console.log("[Orchestrator] Ignoring message to voice session:", message.sessionId);
2611
2746
  return;
2612
2747
  }
2748
+ if (message.sessionId && this.workerSessionIds.has(message.sessionId)) {
2749
+ console.log("[Orchestrator] Routing message to worker session:", message.sessionId);
2750
+ this.routeMessageToWorker(message.sessionId, message.text);
2751
+ return;
2752
+ }
2613
2753
  let session;
2614
2754
  let sessionId = message.sessionId;
2615
2755
  if (sessionId) {
@@ -2694,6 +2834,17 @@ var AgentOrchestrator = class {
2694
2834
  });
2695
2835
  }
2696
2836
  }
2837
+ /**
2838
+ * Route a user message directly to a worker's message queue.
2839
+ * This allows users to send messages to subagent sessions from the frontend.
2840
+ */
2841
+ routeMessageToWorker(sessionId, text) {
2842
+ console.log("[Orchestrator] Routing message to worker session:", {
2843
+ sessionId,
2844
+ textPreview: text.substring(0, 100)
2845
+ });
2846
+ void this.workerManager.routeUserMessageToWorker(sessionId, text);
2847
+ }
2697
2848
  createSession() {
2698
2849
  const sessionId = randomUUID3();
2699
2850
  return this.ensureSessionExists(sessionId);
@@ -2789,9 +2940,17 @@ var AgentOrchestrator = class {
2789
2940
  };
2790
2941
  }
2791
2942
  handleWorkerSessionStart(record) {
2943
+ this.workerSessionIds.add(record.sessionId);
2944
+ console.log("[Orchestrator] Tracking new worker session:", {
2945
+ workerId: record.id,
2946
+ sessionId: record.sessionId,
2947
+ task: record.task,
2948
+ currentWorkerSessions: Array.from(this.workerSessionIds)
2949
+ });
2792
2950
  const metadata = {
2793
2951
  ...record.metadata,
2794
- task: record.task
2952
+ task: record.task,
2953
+ session_type: "WORKER"
2795
2954
  };
2796
2955
  this.broadcastToAll({
2797
2956
  type: "session_init",
@@ -2892,6 +3051,13 @@ var AgentOrchestrator = class {
2892
3051
  }
2893
3052
  }
2894
3053
  handleWorkerCompletion(record) {
3054
+ console.log("[Orchestrator] Worker completed but keeping session tracked for routing:", {
3055
+ workerId: record.id,
3056
+ sessionId: record.sessionId,
3057
+ task: record.task,
3058
+ status: record.status,
3059
+ currentWorkerSessions: Array.from(this.workerSessionIds)
3060
+ });
2895
3061
  const sessionId = record.originSessionId;
2896
3062
  if (!sessionId) {
2897
3063
  return;
@@ -7841,6 +8007,21 @@ var RelayBridge = class extends EventEmitter2 {
7841
8007
  });
7842
8008
  }
7843
8009
  }
8010
+ for (const agent of control.agent_sessions ?? []) {
8011
+ if (agent.metadata?.session_type === "WORKER" || agent.parent_session_id) {
8012
+ this.connectionHandle?.dispatch({
8013
+ type: "session_init",
8014
+ sessionId: agent.session_id,
8015
+ parentSessionId: agent.parent_session_id ?? void 0,
8016
+ metadata: { session_type: "WORKER", ...agent.metadata }
8017
+ });
8018
+ console.log("[RelayBridge] Dispatched worker session init from bootstrap:", {
8019
+ sessionId: agent.session_id,
8020
+ sessionType: agent.session_type,
8021
+ parentSessionId: agent.parent_session_id
8022
+ });
8023
+ }
8024
+ }
7844
8025
  for (const agent of control.agent_sessions ?? []) {
7845
8026
  this.orchestrator.ensureSessionExists(agent.session_id);
7846
8027
  }
@@ -8472,7 +8653,21 @@ import path8 from "node:path";
8472
8653
  import fs6 from "node:fs/promises";
8473
8654
  import os3 from "node:os";
8474
8655
  import path7 from "node:path";
8475
- var OMNARA_HOME_DIR = path7.join(os3.homedir(), ".omnara");
8656
+ function isDevelopmentMode3() {
8657
+ if (process.env.OMNARA_ENV === "development") {
8658
+ return true;
8659
+ }
8660
+ const backendUrl = process.env.OMNARA_BACKEND_URL;
8661
+ if (backendUrl && (backendUrl.includes("localhost") || backendUrl.includes("127.0.0.1"))) {
8662
+ return true;
8663
+ }
8664
+ return false;
8665
+ }
8666
+ function getOmnaraHomeDir2() {
8667
+ const dirName = isDevelopmentMode3() ? ".omnara-dev" : ".omnara";
8668
+ return path7.join(os3.homedir(), dirName);
8669
+ }
8670
+ var OMNARA_HOME_DIR = getOmnaraHomeDir2();
8476
8671
  var DAEMON_CONFIG_PATH = path7.join(OMNARA_HOME_DIR, "daemon.json");
8477
8672
  var DAEMON_STATE_PATH = path7.join(OMNARA_HOME_DIR, "daemon-state.json");
8478
8673
  var DAEMON_LOG_DIR = path7.join(OMNARA_HOME_DIR, "logs", "daemon");
@@ -8886,6 +9081,16 @@ function extractErrorMessage11(error) {
8886
9081
  import chalk2 from "chalk";
8887
9082
  var exec = promisify(execCb);
8888
9083
  var DEFAULT_WAIT_MS = 1e4;
9084
+ function isDevelopmentMode4() {
9085
+ if (process.env.OMNARA_ENV === "development") {
9086
+ return true;
9087
+ }
9088
+ const backendUrl = process.env.OMNARA_BACKEND_URL;
9089
+ if (backendUrl && (backendUrl.includes("localhost") || backendUrl.includes("127.0.0.1"))) {
9090
+ return true;
9091
+ }
9092
+ return false;
9093
+ }
8889
9094
  async function ensureDaemonForSession(options = {}) {
8890
9095
  const logger2 = options.logger ?? logger;
8891
9096
  const client = new DaemonClient();
@@ -8992,28 +9197,47 @@ async function showDaemonStatus(options = {}) {
8992
9197
  }
8993
9198
  async function killAllDaemonProcesses(options = {}) {
8994
9199
  const logger2 = options.logger ?? logger;
9200
+ const isDevMode = isDevelopmentMode4();
9201
+ const envLabel = isDevMode ? "development" : "production";
9202
+ logger2.info(`Killing ${envLabel} daemon processes...`);
9203
+ const stateStore = new DaemonStateStore();
9204
+ const targets = [];
8995
9205
  try {
8996
- const { stdout } = await exec("ps -axo pid=,command=");
8997
- const targets = [];
8998
- for (const line of stdout.split("\n")) {
8999
- const trimmed = line.trim();
9000
- if (!trimmed) {
9001
- continue;
9002
- }
9003
- const [pidStr, ...cmdParts] = trimmed.split(/\s+/);
9004
- const pid = Number(pidStr);
9005
- if (!Number.isFinite(pid) || pid === process.pid) {
9006
- continue;
9206
+ const state = await stateStore.load();
9207
+ if (state && state.pid) {
9208
+ try {
9209
+ process.kill(state.pid, 0);
9210
+ targets.push(state.pid);
9211
+ logger2.debug(`Found ${envLabel} daemon PID from state file: ${state.pid}`);
9212
+ } catch {
9213
+ logger2.debug(`Daemon PID ${state.pid} from state file is not running`);
9007
9214
  }
9008
- const command = cmdParts.join(" ");
9009
- if (command.includes("omnara") && (command.includes("daemon run-service") || command.includes("daemon run-session"))) {
9010
- targets.push(pid);
9215
+ }
9216
+ try {
9217
+ const { stdout } = await exec("ps -axo pid=,command=");
9218
+ for (const line of stdout.split("\n")) {
9219
+ const trimmed = line.trim();
9220
+ if (!trimmed) {
9221
+ continue;
9222
+ }
9223
+ const [pidStr, ...cmdParts] = trimmed.split(/\s+/);
9224
+ const pid = Number(pidStr);
9225
+ if (!Number.isFinite(pid) || pid === process.pid) {
9226
+ continue;
9227
+ }
9228
+ const command = cmdParts.join(" ");
9229
+ if (command.includes("omnara") && command.includes("daemon run-session") && !targets.includes(pid)) {
9230
+ targets.push(pid);
9231
+ }
9011
9232
  }
9233
+ } catch (error) {
9234
+ logger2.warn(`Failed to enumerate session processes: ${extractErrorMessage12(error)}`);
9012
9235
  }
9013
9236
  if (targets.length === 0) {
9014
- logger2.info("No omnara daemon processes found.");
9237
+ logger2.info(`No ${envLabel} omnara daemon processes found.`);
9015
9238
  return;
9016
9239
  }
9240
+ logger2.info(`Found ${targets.length} process(es) to kill in ${envLabel} mode`);
9017
9241
  for (const pid of targets) {
9018
9242
  try {
9019
9243
  process.kill(pid, "SIGKILL");
@@ -9022,8 +9246,10 @@ async function killAllDaemonProcesses(options = {}) {
9022
9246
  logger2.warn(`Failed to kill ${pid}: ${extractErrorMessage12(error)}`);
9023
9247
  }
9024
9248
  }
9249
+ await stateStore.clear();
9250
+ logger2.debug(`Cleared ${envLabel} daemon state file`);
9025
9251
  } catch (error) {
9026
- logger2.error(`Failed to enumerate processes: ${extractErrorMessage12(error)}`);
9252
+ logger2.error(`Failed to kill ${envLabel} daemon processes: ${extractErrorMessage12(error)}`);
9027
9253
  }
9028
9254
  }
9029
9255
  async function registerDirectory(directoryPath, options) {
@@ -9634,7 +9860,7 @@ var FALLBACK_NAME = pkg.name ?? "omnara";
9634
9860
  var FALLBACK_VERSION = pkg.version ?? "0.0.0";
9635
9861
  var FALLBACK_DESCRIPTION = pkg.description ?? "Command-line interface for Omnara.";
9636
9862
  var PACKAGE_NAME = true ? "omnara" : FALLBACK_NAME;
9637
- var PACKAGE_VERSION = true ? "0.2.1" : FALLBACK_VERSION;
9863
+ var PACKAGE_VERSION = true ? "0.2.2" : FALLBACK_VERSION;
9638
9864
  var PACKAGE_DESCRIPTION = true ? "Command-line interface for Omnara." : FALLBACK_DESCRIPTION;
9639
9865
 
9640
9866
  // src/daemon/machine-supervisor.ts
@@ -10516,14 +10742,7 @@ async function performClaudeCodeCheck() {
10516
10742
  })) {
10517
10743
  if (message.type === "system") {
10518
10744
  if (message.subtype === "init") {
10519
- if (message.apiKeySource === "none") {
10520
- console.log(
10521
- chalk4.red(
10522
- "\nOmnara didn't detect a valid API key. Login via `claude /login` or pass in a valid ANTHROPIC_API_KEY as an environment variable.\n"
10523
- )
10524
- );
10525
- return false;
10526
- } else {
10745
+ if (message.apiKeySource !== "none") {
10527
10746
  console.log(
10528
10747
  chalk4.gray(`
10529
10748
  Omnara is using ${message.apiKeySource} for Claude Code access
@@ -10533,14 +10752,21 @@ Omnara is using ${message.apiKeySource} for Claude Code access
10533
10752
  }
10534
10753
  }
10535
10754
  }
10536
- if (message.type === "auth_status") {
10537
- console.log(message);
10755
+ if (message.type === "result" && message.subtype === "success") {
10756
+ console.log(
10757
+ chalk4.gray(`
10758
+ Omnara detected a Pro/Max subscription for Claude Code access
10759
+ `)
10760
+ );
10761
+ return true;
10762
+ } else {
10763
+ throw new Error("Failed to generate");
10538
10764
  }
10539
10765
  }
10540
10766
  } catch {
10541
10767
  console.log(
10542
10768
  chalk4.red(
10543
- "\nError: Omnara needs access to the Anthropic API. Omnara failed to query the Anthropic API, which means Claude Code likely isn't installed.\n"
10769
+ "\nError: Omnara needs access to the Anthropic API. Omnara failed to query the Anthropic API, which means Claude Code likely isn't authenticated or installed. Try to log in using `claude /login`\n"
10544
10770
  )
10545
10771
  );
10546
10772
  return false;