omnara 0.1.5 → 0.1.8

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
@@ -1,12 +1,12 @@
1
1
  // ESM shim for __filename and __dirname
2
- import { fileURLToPath } from 'url';
3
- import { dirname } from 'path';
4
- const __filename = fileURLToPath(import.meta.url);
5
- const __dirname = dirname(__filename);
2
+ import { fileURLToPath as __omnaraFileURLToPath } from 'url';
3
+ import pathShim from 'path';
4
+ const __filename = __omnaraFileURLToPath(import.meta.url);
5
+ const __dirname = pathShim.dirname(__filename);
6
6
 
7
7
  // src/cli.ts
8
- import path3 from "path";
9
- import { createRequire } from "node:module";
8
+ import path4 from "path";
9
+ import { createRequire as createRequire2 } from "node:module";
10
10
  import { Command as Command3 } from "commander";
11
11
 
12
12
  // src/commands/agent.ts
@@ -17,7 +17,9 @@ import { randomUUID as randomUUID3 } from "crypto";
17
17
 
18
18
  // src/orchestrator/ai-client.ts
19
19
  import fs from "fs";
20
- import { query } from "@anthropic-ai/claude-agent-sdk";
20
+ import {
21
+ query
22
+ } from "@anthropic-ai/claude-agent-sdk";
21
23
  var BASE_ALLOWED_ORCHESTRATOR_TOOLS = [
22
24
  "Glob",
23
25
  "Grep",
@@ -52,7 +54,7 @@ var AIClient = class {
52
54
  constructor(config) {
53
55
  const defaultOptions = {
54
56
  maxTurns: 100,
55
- model: "opus",
57
+ model: "sonnet",
56
58
  cwd: process.cwd(),
57
59
  includePartialMessages: false,
58
60
  allowedTools: [...BASE_ALLOWED_ORCHESTRATOR_TOOLS]
@@ -183,7 +185,11 @@ var Session = class {
183
185
  }
184
186
  subscribe(subscriber) {
185
187
  this.subscribers.set(subscriber.id, subscriber);
186
- this.safeSend(subscriber, { type: "session_joined", sessionId: this.id, messageId: randomUUID() });
188
+ this.safeSend(subscriber, {
189
+ type: "session_joined",
190
+ sessionId: this.id,
191
+ messageId: randomUUID()
192
+ });
187
193
  }
188
194
  unsubscribe(subscriberId) {
189
195
  this.subscribers.delete(subscriberId);
@@ -740,7 +746,47 @@ var WorkerManager = class extends EventEmitter {
740
746
  record.sessionId,
741
747
  record.messageQueue
742
748
  );
743
- const query2 = client.createQuery(messageStream);
749
+ const query2 = client.createQuery(messageStream, {
750
+ hooks: {
751
+ PostToolUse: [
752
+ {
753
+ // No matcher means it applies to all tools (Bash, Read, Write, etc.)
754
+ // If we wanted to only capture Bash results, we could add: matcher: 'Bash'
755
+ hooks: [
756
+ async (input2, toolUseID) => {
757
+ if (input2.hook_event_name !== "PostToolUse") {
758
+ return {};
759
+ }
760
+ const postToolInput = input2;
761
+ console.log("[WorkerManager] PostToolUse hook triggered:", {
762
+ toolName: postToolInput.tool_name,
763
+ toolUseID,
764
+ hasResponse: !!postToolInput.tool_response,
765
+ workerId: record.id,
766
+ task: record.task
767
+ });
768
+ if (postToolInput.tool_name === "Bash") {
769
+ console.log("[WorkerManager] Bash tool response structure:", {
770
+ responseType: typeof postToolInput.tool_response,
771
+ responseKeys: postToolInput.tool_response ? Object.keys(postToolInput.tool_response) : [],
772
+ fullResponse: postToolInput.tool_response
773
+ });
774
+ }
775
+ this.emit("message", {
776
+ record,
777
+ messageType: "tool_result",
778
+ toolUseId: toolUseID || randomUUID2(),
779
+ toolContent: postToolInput.tool_response,
780
+ isError: false
781
+ // PostToolUse is for successful executions
782
+ });
783
+ return {};
784
+ }
785
+ ]
786
+ }
787
+ ]
788
+ }
789
+ });
744
790
  this.runningQueries.set(record.id, query2);
745
791
  const summarizedChunks = [];
746
792
  try {
@@ -821,7 +867,9 @@ var WorkerManager = class extends EventEmitter {
821
867
  }
822
868
  }
823
869
  if (!hasToolUse && !record.messageQueue.isClosed()) {
824
- console.log("[WorkerManager] Assistant message without tool use - closing message queue to signal completion");
870
+ console.log(
871
+ "[WorkerManager] Assistant message without tool use - closing message queue to signal completion"
872
+ );
825
873
  record.messageQueue.close();
826
874
  }
827
875
  break;
@@ -951,7 +999,9 @@ Usage notes:
951
999
  - The worker's result is returned to you, not the user - you must summarize or forward relevant information
952
1000
  - Worker progress status is automatically included in your context - you can see their current state without additional tools`,
953
1001
  {
954
- task: z2.string().min(1, "task name is required").describe('A short, unique name for this task (e.g., "FizzBuzz Python", "Search codebase", "Fix bug #123"). Used to identify and track this worker.'),
1002
+ task: z2.string().min(1, "task name is required").describe(
1003
+ 'A short, unique name for this task (e.g., "FizzBuzz Python", "Search codebase", "Fix bug #123"). Used to identify and track this worker.'
1004
+ ),
955
1005
  prompt: z2.string().min(1, "prompt is required"),
956
1006
  metadata: z2.record(z2.any()).optional()
957
1007
  },
@@ -966,7 +1016,10 @@ Usage notes:
966
1016
  if (!parentSessionId && toolUseId) {
967
1017
  parentSessionId = context.resolveSessionIdByToolUse(toolUseId);
968
1018
  }
969
- console.warn("[WorkerTools] Main coding session not available, falling back to SDK resolution:", parentSessionId);
1019
+ console.warn(
1020
+ "[WorkerTools] Main coding session not available, falling back to SDK resolution:",
1021
+ parentSessionId
1022
+ );
970
1023
  }
971
1024
  console.log("[WorkerTools] Launching worker:", {
972
1025
  task: args.task,
@@ -1041,11 +1094,19 @@ User: "Wait, actually don't touch the database - just refactor the API layer ins
1041
1094
  Orchestrator: [message_worker: task="refactor database", message="User wants to change scope - refactor API layer instead, leave database alone", interrupt=true]`,
1042
1095
  {
1043
1096
  task: z2.string().min(1, "task name is required").describe("The task name of the worker to message"),
1044
- message: z2.string().min(1, "message is required").describe("The message to send to the worker. Be clear and specific about what has changed or what additional context is being provided."),
1045
- interrupt: z2.boolean().default(false).describe("If true, interrupt current generation immediately; if false (default), queue message for next turn")
1097
+ message: z2.string().min(1, "message is required").describe(
1098
+ "The message to send to the worker. Be clear and specific about what has changed or what additional context is being provided."
1099
+ ),
1100
+ interrupt: z2.boolean().default(false).describe(
1101
+ "If true, interrupt current generation immediately; if false (default), queue message for next turn"
1102
+ )
1046
1103
  },
1047
1104
  async (args) => {
1048
- const success = await workerManager.messageWorker(args.task, args.message, args.interrupt);
1105
+ const success = await workerManager.messageWorker(
1106
+ args.task,
1107
+ args.message,
1108
+ args.interrupt
1109
+ );
1049
1110
  if (!success) {
1050
1111
  const worker = workerManager.getWorkerByTask(args.task);
1051
1112
  if (!worker) {
@@ -1092,8 +1153,10 @@ Orchestrator: [message_worker: task="refactor database", message="User wants to
1092
1153
  });
1093
1154
  }
1094
1155
 
1095
- // src/orchestrator/orchestrator-prompt.md
1096
- var orchestrator_prompt_default = '# Role: Task Orchestrator\n\nYou are Omnara, an orchestrator agent responsible for coordinating work through delegation. Your primary job is to DELEGATE implementation work to background worker agents while using read-only task management tools for information gathering.\n\n## Core Principles\n\n**STAY RESPONSIVE - Your Most Important Job**:\n\nThe orchestrator must remain responsive to handle user interactions in a conversational environment. When you execute tool calls or perform work directly, you BLOCK your own context and cannot respond to new user messages.\n\n**Why this matters**:\n- Users need to interact with you conversationally while work is happening\n- Users may want to add new tasks, ask questions, or modify ongoing work\n- Blocking your context means the user has to wait for you to finish before they can interact\n- Workers run in parallel and don\'t block your ability to respond\n\n**Therefore**: Delegate almost everything. Even tasks that seem quick can block you from being responsive. Your job is to stay available for user interaction, not to do work yourself.\n\n**INFORMATION vs ACTION - The Key Distinction**:\n- **INFORMATION**: Use tools directly ONLY for quick lookups (1-2 tool calls max). If unsure, delegate.\n- **ACTION**: ALWAYS delegate to workers for implementation or complex research tasks (Write, Edit, Bash, complex analysis). Track high level todos via TodoWrite.\n\n**MAXIMIZE PARALLELIZATION - Speed Through Concurrency**:\n- Default assumption: Tasks are INDEPENDENT and should run in PARALLEL\n- Only group tasks together if one task absolutely REQUIRES the other\'s output\n- When user requests multiple things \u2192 Launch multiple workers (one per task)\n- More parallel workers = faster completion and better user experience\n- Don\'t make users wait for sequential execution when tasks could run simultaneously\n\n**Your workflow**:\n1. Understand what the user wants\n2. Optionally: Make 1-2 quick tool calls for context IF it helps (prefer skipping this and delegating)\n3. Launch worker(s) with complete context and clear instructions\n4. Return control immediately - stay responsive for user interaction\n\n**When to use tools yourself (RARE)**:\n- Single quick lookup (1-2 tool calls max, < 5 seconds)\n- Answering "what/where/how does X work?" questions IF it\'s a single file or simple pattern\n- If you\'re uncertain or it might take longer: delegate instead\n\n**When to delegate to workers (ALMOST ALWAYS)**:\n- ANY code modification, file writing, or command execution\n- Reading 2+ files or doing any analysis\n- Multi-step research or investigation tasks\n- Running tests, git operations, deployments\n- ANY task that takes more than a few seconds\n- When in doubt: delegate\n\n**Best practices**:\n- **MAXIMIZE PARALLELIZATION**: Launch separate workers for independent tasks - DO NOT group tasks together unless they truly depend on each other\n- **DEFAULT TO PARALLEL**: If tasks CAN run in parallel, they SHOULD run in parallel\n- Delegate liberally - keep your context free and responsive\n- Trust worker outputs - don\'t verify or double-check\n- Use TodoWrite for complex multi-step work (Any sequential worker tasks with 2+ distinct steps)\n- Reference code with `file_path:line_number` format\n\n## Using Your Tools Effectively\n\nYou have read-only tools available: **Read, Grep, Glob, WebFetch, WebSearch, TodoWrite**\n\n**Key tips**:\n- **Stay responsive**: Avoid blocking your context with work - delegate instead of using tools directly\n- **Limit tool use**: If you must use tools, limit to 1-2 calls maximum, then delegate\n- **Code references**: Use `file_path:line_number` format when explaining locations\n- **Prefer delegation**: When in doubt, delegate to a worker to keep your context responsive\n\n## Managing Todos with TodoWrite\n\nUse the TodoWrite tool to track and manage complex tasks. This is CRITICAL for giving users visibility into progress.\n\n### When to Use TodoWrite\n\n**Use TodoWrite for:**\n- Complex multi-step tasks requiring 2+ distinct sequential steps\n- Non-trivial tasks that need careful planning or multiple operations\n- When the user explicitly requests a todo list\n- When users provide multiple tasks (numbered list, comma-separated, etc.)\n- To track progress on ongoing work and give users visibility into what workers are doing\n\n**DO NOT use TodoWrite for:**\n- Single, straightforward tasks\n- Trivial tasks where tracking provides no benefit\n- Tasks completable in less than 2 trivial steps\n- Purely conversational or informational requests\n\n### Todo States\n\nTodos have three states:\n- **pending**: Task not yet started\n- **in_progress**: Currently working on this task (ONLY ONE todo should be in_progress at a time)\n- **completed**: Task finished successfully\n\n### Todo Descriptions\n\nEach todo needs two forms:\n- **content**: Imperative form - what needs to be done (e.g., "Implement authentication", "Run tests")\n- **activeForm**: Present continuous form - shown during execution (e.g., "Implementing authentication", "Running tests")\n\n### Best Practices\n\n**Todo Management**:\n- Update todo status in real-time as work progresses\n- Mark todos complete IMMEDIATELY after finishing (don\'t batch completions)\n- Exactly ONE todo should be in_progress at any time (not less, not more)\n- Complete current todos before starting new ones\n- Remove todos that are no longer relevant from the list entirely\n\n**Todo Completion Requirements**:\n- ONLY mark a todo as completed when you have FULLY accomplished it\n- If workers encounter errors, blockers, or cannot finish, keep the todo as in_progress\n- When blocked, create a new todo describing what needs to be resolved\n- Never mark a todo as completed if:\n - Workers reported failures or errors\n - Implementation is partial\n - Tests are failing\n - You couldn\'t complete the work for any reason\n\n**Todo Breakdown**:\n- Create specific, actionable items\n- Break complex tasks into smaller, manageable steps\n- Use clear, descriptive todo names\n- Always provide both content and activeForm\n\n### Examples\n\n**Example 1 - Multi-step implementation:**\n```\nUser: "Add authentication with Google OAuth to the API"\n\nYou: [TodoWrite with todos:]\n 1. content: "Research authentication implementation", activeForm: "Researching authentication implementation", status: in_progress\n 2. content: "Implement Google OAuth integration", activeForm: "Implementing Google OAuth integration", status: pending\n 3. content: "Add authentication middleware", activeForm: "Adding authentication middleware", status: pending\n 4. content: "Test authentication flow", activeForm: "Testing authentication flow", status: pending\n\n[Launch worker for step 1]\n... worker completes research ...\n[TodoWrite: mark todo 1 as completed, mark todo 2 as in_progress]\n[Launch worker for step 2]\n... and so on ...\n```\n\n**Example 2 - User provides list:**\n```\nUser: "I need you to: 1) refactor the database layer, 2) add error handling, 3) write tests"\n\nYou: [TodoWrite with todos:]\n 1. content: "Refactor database layer", activeForm: "Refactoring database layer", status: pending\n 2. content: "Add error handling to database layer", activeForm: "Adding error handling", status: pending\n 3. content: "Write tests for database layer", activeForm: "Writing tests", status: pending\n\n[Launch workers for each task - can be parallel or sequential depending on dependencies]\n```\n\n**Example 3 - DO NOT use todos for simple tasks:**\n```\nUser: "What authentication method does the API use?"\n\nYou: [No todos needed - single information lookup]\n [Launch worker or use Grep to find the answer]\n```\n\n### Todo Lifecycle\n\nWhen using todos with workers:\n1. Create todos before launching workers (gives user visibility)\n2. Mark the corresponding todo as `in_progress` when launching a worker\n3. When worker completes successfully \u2192 mark todo as `completed`\n4. When worker fails or encounters issues \u2192 keep as `in_progress`, possibly add new todo for resolution\n5. Only ONE todo should be `in_progress` at a time\n\n## How You Get Triggered\n\nYou will generate responses in two scenarios:\n\n1. **User sends a message** - Normal user interaction. You should acknowledge and launch workers if needed.\n\n2. **Worker completes** - When a worker finishes (success, error, or cancel), a [WORKER STATUS UPDATE] message is automatically added to the conversation, and you are triggered to generate a response. You should:\n - Look at the status update message to see ALL worker states\n - The completed worker is marked with >>> and "JUST COMPLETED"\n - Provide an INCREMENTAL summary of only the NEW completion\n - DO NOT re-summarize workers you\'ve already reported on\n\nThink of it like notifications: you\'re "woken up" when something changes, the status update message shows you what changed, and you report the change.\n\n## When to Launch Workers\n\nLaunch workers for ALMOST EVERYTHING to stay responsive:\n\n- **Writing or modifying code** - "Add error handling to the login function" \u2192 Delegate immediately\n- **Running commands or tests** - "Run the test suite and report any failures" \u2192 Delegate immediately\n- **Git operations** - "Create a commit with these changes" \u2192 Delegate immediately\n- **Multi-step implementation tasks** - "Implement a new feature for user notifications" \u2192 Delegate immediately\n- **Complex analysis requiring multiple files** - "Analyze the entire authentication flow" \u2192 Delegate immediately\n- **Complex sequential tasks** - "Research and implement an OAuth provider integration" \u2192 Delegate in sequence\n- **Research tasks** - "Find all API endpoints and document their parameters" \u2192 Delegate immediately\n- **Parallel work** - Launch multiple workers when tasks can happen simultaneously (DEFAULT: tasks are parallel unless proven otherwise)\n- **Independent changes** - "Fix bug X and add feature Y" \u2192 Launch 2 separate workers, don\'t combine\n- **Most questions beyond simple lookups** - "How does the auth system work?" \u2192 Delegate to worker to research\n\n**When launching workers:**\n- **Skip context gathering** - Let the worker handle it. Delegate immediately to stay responsive.\n- **Add a Todo** to track the worker\'s task (use TodoWrite for 2+ sequential steps)\n- **Trust workers** to read files, explore code, and gather their own context\n- **Provide clear instructions** - What to do, what to return, what the goal is\n\n**ONLY use tools directly for:**\n- Single, trivial information requests (1 grep, 1 file read - if truly quick)\n- If you\'re uncertain or it might block you: delegate instead\n\n## When to Message Existing Workers (Resteering)\n\nUse the **message_worker** tool to send additional context or requirements to running workers:\n\n**When to message an existing worker:**\n- User provides clarifications or additions to an in-progress task\n- User adds requirements that logically belong to the current task\n- User provides context that would help the running worker\n- The new request is clearly related to what the worker has been working on and would benefit from shared context\n\n**When to launch a NEW worker instead:**\n- The request is completely unrelated to existing tasks\n- The user explicitly wants a separate task\n- The work would conflict with or duplicate an existing worker\'s goal\n- Clean context would be beneficial to a subsequent task\n\n**Choosing interrupt vs queue:**\n- **interrupt=false (default)**: Queue the message to be delivered after the worker\'s current turn\n - Use for: Adding requirements, providing context, clarifications\n - The worker continues current work, then receives your message\n- **interrupt=true**: Stop current work immediately and redirect\n - Use ONLY when: User explicitly says to stop/interrupt, or the new requirement significantly contradicts the current direction\n - The worker\'s current generation is stopped and your message is delivered immediately\n\n**After Messaging a Worker:**\n- **DO NOT** use get_workers to check the status after messaging a worker\n- The worker will automatically notify you when it completes (you\'ll be triggered with a [WORKER STATUS UPDATE])\n- Just acknowledge that you sent the message and wait for the completion notification\n\n## How to Delegate Effectively\n\nWhen launching workers, provide:\n\n**Clear, autonomous instructions**: Tell the worker exactly what to do and what information to return. Workers cannot ask you for clarification.\n\n**Complete context**: Include all relevant details the worker needs to complete the task independently. Use Read/Grep/Glob tools first to gather context, then provide it to the worker.\n\n**Expected output format**: Specify what information you need back from the worker.\n\n**Gather context before delegating**: For implementation tasks, use read-only tools first to understand the current state, then provide that context to workers.\n\n**Example excellent delegation (with context gathering)**:\n```\nUser: "Add rate limiting to the API"\n\n1. [Read: src/api/server.ts] - See current API setup\n2. [Grep: pattern="middleware", output_mode="content"] - Check existing middleware\n\n3. launch_worker with prompt: "Add rate limiting middleware to the API. Current setup:\n - Express server in src/api/server.ts using middleware pattern\n - Existing middleware: helmet, cors, body-parser\n - Routes defined in src/api/routes/\n\n Add rate limiting using express-rate-limit. Apply globally with 100 requests per 15 minutes.\n Document the configuration in comments."\n```\n\n## Multiple Workers\n\n**CRITICAL: MAXIMIZE PARALLELIZATION**\n\nLaunch workers concurrently whenever possible by calling launch_worker multiple times in a single response. DO NOT group independent tasks into a single worker.\n\n**The Parallelization Rule**:\n- If tasks are independent and don\'t strictly depend on each other\'s results \u2192 Launch SEPARATE workers\n- Only combine tasks into one worker if the second task absolutely REQUIRES the first task\'s output\n- When in doubt, launch separate workers - err on the side of more parallelization\n\n**Examples of PARALLEL tasks** (launch separate workers):\n- "Add logging to the API and update the documentation" \u2192 2 workers (independent changes)\n- "Fix the auth bug and add unit tests" \u2192 2 workers (can test independently)\n- "Refactor component A and refactor component B" \u2192 2 workers (independent refactors)\n- "Update the database schema and update the API endpoints" \u2192 2 workers (likely independent unless explicitly linked)\n- "Check the tests and review the API docs" \u2192 2 workers (completely independent)\n\n**Examples of SEQUENTIAL tasks** (must be one after another):\n- "Research the best logging library, then implement it" \u2192 Worker 1 researches, then Worker 2 implements with that info\n- "Run the tests, then fix any failures found" \u2192 Worker 1 runs tests, then Worker 2 fixes based on results\n- "Analyze the performance bottleneck, then optimize it" \u2192 Worker 1 analyzes, Worker 2 uses those findings\n\n**Good Example** (maximizing parallelization):\n```\nUser: "Add error handling to the API, update the docs, and write tests"\n\nResponse:\n- Launch worker 1: "Add comprehensive error handling to the API"\n- Launch worker 2: "Update documentation to reflect new error handling patterns"\n- Launch worker 3: "Write tests for error handling scenarios"\n```\n\n**Bad Example** (unnecessary grouping):\n```\nUser: "Add error handling to the API, update the docs, and write tests"\n\nResponse:\n- Launch worker 1: "Add error handling, update docs, and write tests"\n \u274C This is BAD - these are independent tasks that could run in parallel\n```\n\n**Edge Case - Minor Dependencies**: Even if there\'s a soft dependency (e.g., docs should reflect the code), prefer parallel execution. Workers can read the latest code state, so documentation can be written based on the new code even if both workers run simultaneously.\n\n## Worker Status Tracking\n\n**Status in Message History**: When workers complete, a special status update message is added to the conversation history. This message shows:\n- ALL current worker statuses\n- Which worker just completed (marked with >>> and "JUST COMPLETED" note)\n- Which workers are still running\n- **FULL PROGRESS LOG** for completed workers - showing everything the worker did\n\n**CRITICAL: Read the Full Progress Log**\n\nWhen a worker completes, you receive the COMPLETE history of everything it did:\n- All status updates the worker sent\n- All orchestrator messages you sent to the worker (marked with [ORCHESTRATOR MESSAGE])\n- The entire sequence of work from start to finish\n\n**Before creating new workers:**\n1. **CHECK THE PROGRESS LOG** - See what the completed worker actually did\n2. **Look for [ORCHESTRATOR MESSAGE] entries** - These show you already resteered this worker\n3. **DO NOT create duplicate workers** - If you already messaged a worker to do something, it\'s done or in the log\n4. **Trust the progress log** - It shows you the complete picture of what was accomplished\n\n**DO NOT use get_workers tool**: All worker information is already in your conversation history via status update messages. Only use get_workers if you have a specific edge case reason. Specifically:\n- **Never** call get_workers immediately after launching a worker\n- **Never** call get_workers after messaging a worker with message_worker\n- **Never** poll workers - you will be automatically notified when they complete\n\n**Automatic Notifications**: When a worker completes, a status update message is automatically added to the conversation, and you are triggered to generate a response. This happens automatically - you don\'t need to check or poll.\n\n## Your Response Pattern\n\n### When User Makes Initial Request:\n1. **Acknowledge the request** - Briefly confirm what you\'ll do\n2. **Launch worker(s)** - Delegate with clear instructions using meaningful task names\n3. **Inform about tracking** - Let user know workers are running and they\'ll be notified upon completion\n\n### When Workers Complete (Automatic Trigger):\n1. **Check the "Current Worker Status" section** - Review what just completed and what\'s still running\n2. **Identify NEW completions** - Only summarize workers that just finished. DO NOT repeat summaries for workers you already reported on.\n3. **Provide incremental summary** - Report ONLY the new information:\n - Which worker(s) just completed\n - Their results (success/error)\n - Next steps if applicable\n4. **Avoid repetition** - If you\'ve already told the user about a worker\'s completion in a previous message, don\'t mention it again unless directly relevant to a new development\n\n### Incremental Summary Guidelines:\n\n**CRITICAL: DO NOT RE-SUMMARIZE ALREADY-REPORTED COMPLETIONS**\n\nYou will be automatically triggered when workers complete. Each time this happens, you receive a [WORKER STATUS UPDATE] message showing ALL workers (both completed and running). However, you should ONLY report on the worker that JUST completed (marked with >>> and "JUST COMPLETED").\n\n**Rules:**\n- **ONLY report the NEW completion** - The worker marked with >>> in the status update\n- **DO NOT re-explain or re-summarize** previous completions you\'ve already told the user about\n- **DO NOT provide recaps** of what previous workers did unless the user explicitly asks\n- **Keep responses minimal** - Just acknowledge the new completion briefly\n- **Trust your previous summaries** - You already explained things once, don\'t repeat\n\n**Example responses:**\n- **First completion**: "The [task name] worker completed. [Brief 1-2 sentence result summary]"\n- **Second completion**: "The [task name] worker also finished. [Brief result]." \u2190 DO NOT re-mention first worker\n- **Third completion**: "The [task name] worker is done. All tasks complete." \u2190 DO NOT re-mention first two workers\n\n**When ALL workers complete:**\n- If you\'ve been reporting each completion incrementally, just say: "All tasks complete."\n- DO NOT provide a comprehensive summary of everything unless user asks\n\n## Remember\n\n**Your primary goal: STAY RESPONSIVE**\n- Your context must remain unblocked for user interaction\n- Tool calls and work block your ability to respond to users\n- Delegate liberally - it\'s not just good practice, it\'s architecturally required\n- Users need a responsive conversational partner, not a busy worker\n\n**Your role:**\n- You are a COORDINATOR, not an IMPLEMENTER\n- Workers are your hands - you are the brain\n- Delegation is not optional - it\'s required for responsiveness\n- The faster you delegate, the faster work gets done AND the more responsive you stay\n- Never apologize for delegating - it\'s your job and keeps you responsive\n\n**About Worker Status:**\n- ALL worker statuses are in your conversation history via [WORKER STATUS UPDATE] messages\n- The completed worker is marked with >>> and "JUST COMPLETED (triggered this generation)"\n- DO NOT use get_workers tool unless absolutely necessary (you already have the info in message history)\n- You\'ll be automatically triggered when workers complete with a status update message\n\n**About Summaries:**\n- Provide INCREMENTAL summaries - only report NEW completions\n- DO NOT repeat information about workers you already told the user about\n- Focus on what changed since your last message\n- Avoid comprehensive recaps unless explicitly requested\n\n**When in doubt: DELEGATE**. Even if you think you can handle it quickly - delegate to stay responsive.\n';
1156
+ // src/orchestrator/orchestrator-prompt.ts
1157
+ import { readFileSync } from "fs";
1158
+ var orchestratorPromptUrl = new URL("./orchestrator-prompt.md", import.meta.url);
1159
+ var ORCHESTRATOR_SYSTEM_PROMPT = readFileSync(orchestratorPromptUrl, "utf-8");
1097
1160
 
1098
1161
  // src/orchestrator/agent-orchestrator.ts
1099
1162
  var WORKER_LAUNCH_TOOL = "mcp__worker__launch_worker";
@@ -1131,11 +1194,16 @@ var AgentOrchestrator = class {
1131
1194
  });
1132
1195
  const mainClientFactory = dependencies?.createMainClient ?? (() => new AIClient({
1133
1196
  defaults: {
1134
- allowedTools: [...BASE_ALLOWED_ORCHESTRATOR_TOOLS, WORKER_LAUNCH_TOOL, WORKER_GET_TOOL, WORKER_MESSAGE_TOOL],
1197
+ allowedTools: [
1198
+ ...BASE_ALLOWED_ORCHESTRATOR_TOOLS,
1199
+ WORKER_LAUNCH_TOOL,
1200
+ WORKER_GET_TOOL,
1201
+ WORKER_MESSAGE_TOOL
1202
+ ],
1135
1203
  mcpServers: {
1136
1204
  worker: workerMcpServer
1137
1205
  },
1138
- systemPrompt: orchestrator_prompt_default
1206
+ systemPrompt: ORCHESTRATOR_SYSTEM_PROMPT
1139
1207
  }
1140
1208
  }));
1141
1209
  this.aiClient = mainClientFactory();
@@ -1530,7 +1598,8 @@ var AgentOrchestrator = class {
1530
1598
  toolName: event.toolName,
1531
1599
  workerId: event.record.id,
1532
1600
  task: event.record.task,
1533
- targetSessionId
1601
+ targetSessionId,
1602
+ toolUseId: event.toolUseId
1534
1603
  });
1535
1604
  this.broadcastToAll({
1536
1605
  type: "tool_call",
@@ -1547,6 +1616,13 @@ var AgentOrchestrator = class {
1547
1616
  if (!event.toolUseId) {
1548
1617
  return;
1549
1618
  }
1619
+ console.log("[Orchestrator] Broadcasting worker tool_result:", {
1620
+ toolName: event.toolName,
1621
+ workerId: event.record.id,
1622
+ task: event.record.task,
1623
+ targetSessionId,
1624
+ toolUseId: event.toolUseId
1625
+ });
1550
1626
  this.broadcastToAll({
1551
1627
  type: "tool_result",
1552
1628
  sessionId: targetSessionId,
@@ -1598,7 +1674,12 @@ var AgentOrchestrator = class {
1598
1674
  metadata,
1599
1675
  messageId: randomUUID3()
1600
1676
  });
1601
- session.logInbound({ type: "worker_result", workerId: record.id, workerTask: record.task, summary });
1677
+ session.logInbound({
1678
+ type: "worker_result",
1679
+ workerId: record.id,
1680
+ workerTask: record.task,
1681
+ summary
1682
+ });
1602
1683
  session.announceInboundMessage(summary, "worker", record.id, record.task);
1603
1684
  const statusMessage = this.buildWorkerStatusMessage(record.id);
1604
1685
  session.enqueueUserMessage(statusMessage, {
@@ -1784,8 +1865,8 @@ var ApiClient = class {
1784
1865
  constructor(options) {
1785
1866
  this.baseUrl = normalizeBaseUrl(options.baseUrl);
1786
1867
  }
1787
- async getJson(path4, options) {
1788
- const url = resolveUrl(this.baseUrl, path4);
1868
+ async getJson(path5, options) {
1869
+ const url = resolveUrl(this.baseUrl, path5);
1789
1870
  const headers = this.buildHeaders(options);
1790
1871
  const response = await fetch(url, {
1791
1872
  method: "GET",
@@ -1794,8 +1875,8 @@ var ApiClient = class {
1794
1875
  });
1795
1876
  return await this.handleResponse(response);
1796
1877
  }
1797
- async postJson(path4, body, options) {
1798
- const url = resolveUrl(this.baseUrl, path4);
1878
+ async postJson(path5, body, options) {
1879
+ const url = resolveUrl(this.baseUrl, path5);
1799
1880
  const headers = this.buildHeaders(options, { includeJson: true });
1800
1881
  const response = await fetch(url, {
1801
1882
  method: "POST",
@@ -1853,12 +1934,12 @@ function normalizeBaseUrl(raw) {
1853
1934
  }
1854
1935
  return raw.endsWith("/") ? raw : `${raw}/`;
1855
1936
  }
1856
- function resolveUrl(base, path4) {
1937
+ function resolveUrl(base, path5) {
1857
1938
  const baseUrl = new URL(base);
1858
- if (!path4.startsWith("/")) {
1859
- path4 = `/${path4}`;
1939
+ if (!path5.startsWith("/")) {
1940
+ path5 = `/${path5}`;
1860
1941
  }
1861
- const resolved = new URL(path4, baseUrl);
1942
+ const resolved = new URL(path5, baseUrl);
1862
1943
  return resolved.toString();
1863
1944
  }
1864
1945
  function extractDetail(payload) {
@@ -2011,9 +2092,6 @@ function deriveAccessExpiry(session) {
2011
2092
  return void 0;
2012
2093
  }
2013
2094
 
2014
- // src/auth/login-flow.ts
2015
- import { spawn } from "child_process";
2016
-
2017
2095
  // src/utils/prompt.ts
2018
2096
  import readline from "readline/promises";
2019
2097
  import { stdin as input, stdout as output } from "process";
@@ -2165,7 +2243,11 @@ var LoopbackListener = class _LoopbackListener {
2165
2243
  const sessionId = url.searchParams.get("session");
2166
2244
  const state = url.searchParams.get("state");
2167
2245
  if (!code) {
2168
- this.respond(response, 400, buildMessageHtml("Missing authorization code. Return to your CLI and try again."));
2246
+ this.respond(
2247
+ response,
2248
+ 400,
2249
+ buildMessageHtml("Missing authorization code. Return to your CLI and try again.")
2250
+ );
2169
2251
  return;
2170
2252
  }
2171
2253
  this.respond(response, 200, buildSuccessHtml());
@@ -2185,7 +2267,7 @@ var LoopbackListener = class _LoopbackListener {
2185
2267
  }
2186
2268
  };
2187
2269
  function buildSuccessHtml() {
2188
- const redirectTarget = "https://voice.omnara.com/dashboard";
2270
+ const redirectTarget = "https://www.omnara.com/dashboard";
2189
2271
  return `<!doctype html>
2190
2272
  <html lang="en">
2191
2273
  <head>
@@ -2240,6 +2322,18 @@ function buildMessageHtml(message) {
2240
2322
  </html>`;
2241
2323
  }
2242
2324
 
2325
+ // src/utils/browser.ts
2326
+ import { spawn } from "child_process";
2327
+ function tryOpenBrowser(url) {
2328
+ const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
2329
+ const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
2330
+ try {
2331
+ const child = spawn(command, args, { stdio: "ignore", detached: true });
2332
+ child.unref();
2333
+ } catch {
2334
+ }
2335
+ }
2336
+
2243
2337
  // src/auth/login-flow.ts
2244
2338
  async function performVerificationLogin({
2245
2339
  backendUrlOption,
@@ -2339,8 +2433,8 @@ async function performVerificationLogin({
2339
2433
  }
2340
2434
  return latestCredentials;
2341
2435
  }
2342
- var DEFAULT_BACKEND_URL = "https://omnara-voice.onrender.com/";
2343
- var DEFAULT_FRONTEND_URL = "https://voice.omnara.com/";
2436
+ var DEFAULT_BACKEND_URL = "https://api.omnara.com/";
2437
+ var DEFAULT_FRONTEND_URL = "https://www.omnara.com/";
2344
2438
  function resolveBackendUrl(option) {
2345
2439
  const candidate = option ?? process.env.OMNARA_BACKEND_URL ?? DEFAULT_BACKEND_URL;
2346
2440
  return normalizeBaseUrl(candidate);
@@ -2393,15 +2487,6 @@ function printSessionInstructions(session) {
2393
2487
  console.log(` ${session.verificationUriComplete}`);
2394
2488
  console.log("Approve the request, then return to the CLI.");
2395
2489
  }
2396
- function tryOpenBrowser(url) {
2397
- const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
2398
- const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
2399
- try {
2400
- const child = spawn(command, args, { stdio: "ignore", detached: true });
2401
- child.unref();
2402
- } catch {
2403
- }
2404
- }
2405
2490
  function extractErrorMessage4(error) {
2406
2491
  if (error instanceof Error && error.message) {
2407
2492
  return error.message;
@@ -4736,6 +4821,7 @@ var RelayBridge = class extends EventEmitter2 {
4736
4821
  authProviderPromise = null;
4737
4822
  userSessionId = null;
4738
4823
  currentAgentSessionId = null;
4824
+ browserOpened = false;
4739
4825
  constructor(orchestrator, options) {
4740
4826
  super();
4741
4827
  this.orchestrator = orchestrator;
@@ -4779,6 +4865,7 @@ var RelayBridge = class extends EventEmitter2 {
4779
4865
  async stop() {
4780
4866
  this.stopped = true;
4781
4867
  this.currentAgentSessionId = null;
4868
+ this.browserOpened = false;
4782
4869
  if (this.pingTimer) {
4783
4870
  clearInterval(this.pingTimer);
4784
4871
  this.pingTimer = null;
@@ -4818,6 +4905,13 @@ var RelayBridge = class extends EventEmitter2 {
4818
4905
  this.heartbeatIntervalMs = registration.relayHeartbeatIntervalSec * 1e3;
4819
4906
  this.startPingTimer();
4820
4907
  await this.sendPrimarySessionInit(ws);
4908
+ if (this.frontendUrl && !this.browserOpened) {
4909
+ const dashboardUrl = new URL("dashboard", this.frontendUrl);
4910
+ dashboardUrl.searchParams.set("session", agentSessionId);
4911
+ tryOpenBrowser(dashboardUrl.toString());
4912
+ this.logger.log(`Opened dashboard at ${dashboardUrl.toString()}`);
4913
+ this.browserOpened = true;
4914
+ }
4821
4915
  this.flushQueue();
4822
4916
  await this.handleWebsocketLifecycle(ws);
4823
4917
  }
@@ -4836,7 +4930,7 @@ var RelayBridge = class extends EventEmitter2 {
4836
4930
  if (!this.currentAgentSessionId) {
4837
4931
  this.currentAgentSessionId = randomUUID4();
4838
4932
  }
4839
- const session = this.orchestrator.ensureSessionExists(this.currentAgentSessionId);
4933
+ this.orchestrator.ensureSessionExists(this.currentAgentSessionId);
4840
4934
  this.orchestrator.setMainCodingSessionId(this.currentAgentSessionId);
4841
4935
  return this.currentAgentSessionId;
4842
4936
  }
@@ -5316,16 +5410,16 @@ var RelayBridge = class extends EventEmitter2 {
5316
5410
  }
5317
5411
  }
5318
5412
  };
5319
- function buildRelayWebsocketUrl(baseUrl, path4) {
5413
+ function buildRelayWebsocketUrl(baseUrl, path5) {
5320
5414
  const url = new URL(baseUrl);
5321
5415
  url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
5322
5416
  url.search = "";
5323
5417
  url.hash = "";
5324
- if (path4.startsWith("/")) {
5325
- url.pathname = path4;
5418
+ if (path5.startsWith("/")) {
5419
+ url.pathname = path5;
5326
5420
  } else {
5327
5421
  const basePath = url.pathname.endsWith("/") ? url.pathname : `${url.pathname}/`;
5328
- url.pathname = `${basePath}${path4}`;
5422
+ url.pathname = `${basePath}${path5}`;
5329
5423
  }
5330
5424
  return url.toString();
5331
5425
  }
@@ -5536,13 +5630,17 @@ function handleOutboundMessage(message, onPrompt, onSessionChange, onTurnComplet
5536
5630
  }
5537
5631
  switch (message.type) {
5538
5632
  case "session_created":
5539
- console.log(`
5540
- ${formatSessionPrefix(message.sessionId)} [server] Session created: ${message.sessionId}`);
5633
+ console.log(
5634
+ `
5635
+ ${formatSessionPrefix(message.sessionId)} [server] Session created: ${message.sessionId}`
5636
+ );
5541
5637
  onSessionChange(message.sessionId);
5542
5638
  break;
5543
5639
  case "session_joined":
5544
- console.log(`
5545
- ${formatSessionPrefix(message.sessionId)} [server] Joined session: ${message.sessionId}`);
5640
+ console.log(
5641
+ `
5642
+ ${formatSessionPrefix(message.sessionId)} [server] Joined session: ${message.sessionId}`
5643
+ );
5546
5644
  onSessionChange(message.sessionId);
5547
5645
  break;
5548
5646
  case "session_init":
@@ -5620,10 +5718,8 @@ ${formatSessionPrefix(message.sessionId)} [system] ${message.subtype}`);
5620
5718
  }
5621
5719
  break;
5622
5720
  case "error":
5623
- console.error(
5624
- `
5625
- ${formatSessionPrefix(message.sessionId)} [error] ${message.message}`
5626
- );
5721
+ console.error(`
5722
+ ${formatSessionPrefix(message.sessionId)} [error] ${message.message}`);
5627
5723
  onTurnComplete();
5628
5724
  break;
5629
5725
  case "pong":
@@ -5666,7 +5762,7 @@ Commands:
5666
5762
 
5667
5763
  // src/voice/voice-service.ts
5668
5764
  async function launchVoiceForSession(client, accessToken, options) {
5669
- const path4 = `/api/v1/voice/sessions/${options.sessionId}/launch`;
5765
+ const path5 = `/api/v1/voice/sessions/${options.sessionId}/launch`;
5670
5766
  const requestBody = {};
5671
5767
  if (options.force) {
5672
5768
  requestBody.force = true;
@@ -5675,7 +5771,7 @@ async function launchVoiceForSession(client, accessToken, options) {
5675
5771
  requestBody.room_name = options.roomName;
5676
5772
  }
5677
5773
  const response = await client.postJson(
5678
- path4,
5774
+ path5,
5679
5775
  requestBody,
5680
5776
  { accessToken }
5681
5777
  );
@@ -5693,6 +5789,291 @@ async function launchVoiceForSession(client, accessToken, options) {
5693
5789
  };
5694
5790
  }
5695
5791
 
5792
+ // src/utils/claude-code-check.ts
5793
+ import { execSync } from "child_process";
5794
+ import chalk from "chalk";
5795
+ function checkClaudeCodeInstallation() {
5796
+ try {
5797
+ execSync("claude --version", {
5798
+ stdio: "ignore",
5799
+ encoding: "utf-8"
5800
+ });
5801
+ return true;
5802
+ } catch {
5803
+ return false;
5804
+ }
5805
+ }
5806
+ function displayClaudeCodeWarning() {
5807
+ console.log(chalk.yellow("\nWarning: Claude Code is not installed or not found in PATH"));
5808
+ console.log(
5809
+ chalk.red("\nIMPORTANT: Claude Code is REQUIRED for authentication with your account.")
5810
+ );
5811
+ console.log(chalk.white("\nClaude Code provides:"));
5812
+ console.log(chalk.gray(" - AI-powered code assistance"));
5813
+ console.log(chalk.gray(" - Intelligent code completion"));
5814
+ console.log(chalk.gray(" - Natural language to code translation"));
5815
+ console.log(chalk.gray(" - User authentication for API access"));
5816
+ console.log(chalk.white("\nTo install Claude Code:"));
5817
+ console.log(chalk.cyan(" 1. Visit: https://claude.ai/download"));
5818
+ console.log(chalk.cyan(" 2. Download and install Claude Code for your platform"));
5819
+ console.log(chalk.cyan(' 3. Make sure the "claude" command is available in your PATH'));
5820
+ console.log(chalk.yellow("\nNote: Without Claude Code, you cannot authenticate and access"));
5821
+ console.log(chalk.yellow("personalized features that require your user credentials.\n"));
5822
+ }
5823
+ function performClaudeCodeCheck(options = {}) {
5824
+ const { silent = false, exitOnMissing = false } = options;
5825
+ if (process.env.OMNARA_SKIP_CLAUDE_CHECK === "true") {
5826
+ return true;
5827
+ }
5828
+ const isInstalled = checkClaudeCodeInstallation();
5829
+ if (!isInstalled && !silent) {
5830
+ displayClaudeCodeWarning();
5831
+ if (exitOnMissing) {
5832
+ console.error(chalk.red("Exiting: Claude Code is required for this operation."));
5833
+ process.exit(1);
5834
+ }
5835
+ }
5836
+ return isInstalled;
5837
+ }
5838
+ function getClaudeCodeVersion() {
5839
+ try {
5840
+ const version = execSync("claude --version", {
5841
+ encoding: "utf-8",
5842
+ stdio: ["ignore", "pipe", "ignore"]
5843
+ }).trim();
5844
+ return version;
5845
+ } catch {
5846
+ return null;
5847
+ }
5848
+ }
5849
+
5850
+ // src/utils/logger.ts
5851
+ import path3 from "path";
5852
+ function isProduction() {
5853
+ if (process.env.NODE_ENV === "production") {
5854
+ return true;
5855
+ }
5856
+ try {
5857
+ const currentFile = typeof __filename !== "undefined" ? __filename : "";
5858
+ const isInDist = currentFile.includes(path3.sep + "dist" + path3.sep);
5859
+ if (isInDist && process.env.NODE_ENV !== "development") {
5860
+ return true;
5861
+ }
5862
+ } catch {
5863
+ }
5864
+ const logLevel = process.env.OMNARA_LOG_LEVEL?.toLowerCase();
5865
+ if (logLevel === "none" || logLevel === "silent") {
5866
+ return true;
5867
+ }
5868
+ return false;
5869
+ }
5870
+ function getLogLevel() {
5871
+ const envLevel = process.env.OMNARA_LOG_LEVEL?.toLowerCase();
5872
+ switch (envLevel) {
5873
+ case "none":
5874
+ case "silent":
5875
+ return 0 /* NONE */;
5876
+ case "error":
5877
+ return 1 /* ERROR */;
5878
+ case "warn":
5879
+ case "warning":
5880
+ return 2 /* WARN */;
5881
+ case "info":
5882
+ return 3 /* INFO */;
5883
+ case "debug":
5884
+ return 4 /* DEBUG */;
5885
+ case "verbose":
5886
+ case "trace":
5887
+ return 5 /* VERBOSE */;
5888
+ default:
5889
+ return isProduction() ? 1 /* ERROR */ : 3 /* INFO */;
5890
+ }
5891
+ }
5892
+ var ControlledLogger = class {
5893
+ logLevel;
5894
+ forceOutput;
5895
+ _console;
5896
+ // Console properties we need to implement
5897
+ Console;
5898
+ constructor(options) {
5899
+ this.logLevel = options?.logLevel ?? getLogLevel();
5900
+ this.forceOutput = options?.forceOutput ?? false;
5901
+ this._console = console;
5902
+ this.Console = console.Console;
5903
+ }
5904
+ /**
5905
+ * Standard log output (INFO level)
5906
+ */
5907
+ log(...args) {
5908
+ if (this.shouldLog(3 /* INFO */)) {
5909
+ console.log(...args);
5910
+ }
5911
+ }
5912
+ /**
5913
+ * Error output (always shown unless NONE)
5914
+ */
5915
+ error(...args) {
5916
+ if (this.shouldLog(1 /* ERROR */)) {
5917
+ console.error(...args);
5918
+ }
5919
+ }
5920
+ /**
5921
+ * Warning output
5922
+ */
5923
+ warn(...args) {
5924
+ if (this.shouldLog(2 /* WARN */)) {
5925
+ console.warn(...args);
5926
+ }
5927
+ }
5928
+ /**
5929
+ * Info level output
5930
+ */
5931
+ info(...args) {
5932
+ if (this.shouldLog(3 /* INFO */)) {
5933
+ console.info(...args);
5934
+ }
5935
+ }
5936
+ /**
5937
+ * Debug output (only in development)
5938
+ */
5939
+ debug(...args) {
5940
+ if (this.shouldLog(4 /* DEBUG */)) {
5941
+ console.debug(...args);
5942
+ }
5943
+ }
5944
+ /**
5945
+ * Verbose/trace output
5946
+ */
5947
+ trace(...args) {
5948
+ if (this.shouldLog(5 /* VERBOSE */)) {
5949
+ console.log("[TRACE]", ...args);
5950
+ }
5951
+ }
5952
+ /**
5953
+ * Important messages that should always be shown to user
5954
+ * (authentication status, connection info, etc.)
5955
+ */
5956
+ important(...args) {
5957
+ if (this.logLevel !== 0 /* NONE */ || this.forceOutput) {
5958
+ console.log(...args);
5959
+ }
5960
+ }
5961
+ /**
5962
+ * Success messages that should typically be shown
5963
+ */
5964
+ success(...args) {
5965
+ if (this.shouldLog(2 /* WARN */) || this.forceOutput) {
5966
+ console.log(...args);
5967
+ }
5968
+ }
5969
+ /**
5970
+ * Check if a message should be logged at the given level
5971
+ */
5972
+ shouldLog(level) {
5973
+ return this.forceOutput || this.logLevel >= level;
5974
+ }
5975
+ /**
5976
+ * Update the log level dynamically
5977
+ */
5978
+ setLogLevel(level) {
5979
+ this.logLevel = level;
5980
+ }
5981
+ /**
5982
+ * Get current log level
5983
+ */
5984
+ getLogLevel() {
5985
+ return this.logLevel;
5986
+ }
5987
+ // Console methods we delegate or control based on log level
5988
+ assert(condition, ...data) {
5989
+ if (this.shouldLog(1 /* ERROR */)) {
5990
+ this._console.assert(condition, ...data);
5991
+ }
5992
+ }
5993
+ clear() {
5994
+ this._console.clear();
5995
+ }
5996
+ count(label) {
5997
+ if (this.shouldLog(4 /* DEBUG */)) {
5998
+ this._console.count(label);
5999
+ }
6000
+ }
6001
+ countReset(label) {
6002
+ if (this.shouldLog(4 /* DEBUG */)) {
6003
+ this._console.countReset(label);
6004
+ }
6005
+ }
6006
+ dir(item, options) {
6007
+ if (this.shouldLog(4 /* DEBUG */)) {
6008
+ this._console.dir(item, options);
6009
+ }
6010
+ }
6011
+ dirxml(...data) {
6012
+ if (this.shouldLog(4 /* DEBUG */)) {
6013
+ this._console.dirxml(...data);
6014
+ }
6015
+ }
6016
+ group(...data) {
6017
+ if (this.shouldLog(4 /* DEBUG */)) {
6018
+ this._console.group(...data);
6019
+ }
6020
+ }
6021
+ groupCollapsed(...data) {
6022
+ if (this.shouldLog(4 /* DEBUG */)) {
6023
+ this._console.groupCollapsed(...data);
6024
+ }
6025
+ }
6026
+ groupEnd() {
6027
+ if (this.shouldLog(4 /* DEBUG */)) {
6028
+ this._console.groupEnd();
6029
+ }
6030
+ }
6031
+ table(tabularData, properties) {
6032
+ if (this.shouldLog(3 /* INFO */)) {
6033
+ this._console.table(tabularData, properties);
6034
+ }
6035
+ }
6036
+ time(label) {
6037
+ if (this.shouldLog(4 /* DEBUG */)) {
6038
+ this._console.time(label);
6039
+ }
6040
+ }
6041
+ timeEnd(label) {
6042
+ if (this.shouldLog(4 /* DEBUG */)) {
6043
+ this._console.timeEnd(label);
6044
+ }
6045
+ }
6046
+ timeLog(label, ...data) {
6047
+ if (this.shouldLog(4 /* DEBUG */)) {
6048
+ this._console.timeLog(label, ...data);
6049
+ }
6050
+ }
6051
+ timeStamp(label) {
6052
+ if (this.shouldLog(4 /* DEBUG */)) {
6053
+ this._console.timeStamp(label);
6054
+ }
6055
+ }
6056
+ profile(label) {
6057
+ if (this.shouldLog(4 /* DEBUG */)) {
6058
+ const consoleWithProfile = this._console;
6059
+ consoleWithProfile.profile?.(label);
6060
+ }
6061
+ }
6062
+ profileEnd(label) {
6063
+ if (this.shouldLog(4 /* DEBUG */)) {
6064
+ const consoleWithProfileEnd = this._console;
6065
+ consoleWithProfileEnd.profileEnd?.(label);
6066
+ }
6067
+ }
6068
+ };
6069
+ var logger = new ControlledLogger();
6070
+ function extractErrorMessage6(error) {
6071
+ if (error instanceof Error) {
6072
+ return error.message;
6073
+ }
6074
+ return String(error);
6075
+ }
6076
+
5696
6077
  // src/commands/agent.ts
5697
6078
  function buildAgentCommand() {
5698
6079
  const agent = new Command("agent").description(
@@ -5710,6 +6091,12 @@ function buildAgentCommand() {
5710
6091
  return agent;
5711
6092
  }
5712
6093
  async function runAgentOrchestrator(options = {}) {
6094
+ const claudeVersion = getClaudeCodeVersion();
6095
+ if (claudeVersion) {
6096
+ logger.info(`\u2713 Claude Code detected: ${claudeVersion}`);
6097
+ } else {
6098
+ logger.info("\u2139 Running without Claude Code integration");
6099
+ }
5713
6100
  const orchestrator = new AgentOrchestrator();
5714
6101
  await orchestrator.start();
5715
6102
  const backendUrl = resolveBackendUrl(options.backendUrl);
@@ -5731,7 +6118,7 @@ async function runAgentOrchestrator(options = {}) {
5731
6118
  try {
5732
6119
  await bridge.start();
5733
6120
  } catch (error) {
5734
- console.error(`Relay orchestrator terminated: ${extractErrorMessage6(error)}`);
6121
+ logger.error(`Relay orchestrator terminated: ${extractErrorMessage6(error)}`);
5735
6122
  process.exitCode = process.exitCode || 1;
5736
6123
  } finally {
5737
6124
  await stop();
@@ -5745,10 +6132,10 @@ function createSignalHandler(onStop) {
5745
6132
  return;
5746
6133
  }
5747
6134
  stopping = true;
5748
- console.log(`
6135
+ logger.important(`
5749
6136
  Received ${signal}. Shutting down agent orchestrator...`);
5750
6137
  void onStop().catch((error) => {
5751
- console.error("Error while shutting down agent orchestrator:", error);
6138
+ logger.error("Error while shutting down agent orchestrator:", error);
5752
6139
  });
5753
6140
  };
5754
6141
  const remove = () => {
@@ -5757,12 +6144,6 @@ Received ${signal}. Shutting down agent orchestrator...`);
5757
6144
  };
5758
6145
  return { handler, remove };
5759
6146
  }
5760
- function extractErrorMessage6(error) {
5761
- if (error instanceof Error) {
5762
- return error.message;
5763
- }
5764
- return String(error);
5765
- }
5766
6147
  async function runRepl(options) {
5767
6148
  const backendUrl = resolveBackendUrl(options.backendUrl);
5768
6149
  const frontendUrl = resolveFrontendUrl(options.frontendUrl);
@@ -5780,7 +6161,7 @@ async function runRepl(options) {
5780
6161
  clientKind: "cli"
5781
6162
  });
5782
6163
  const wsUrl = buildRelayWebsocketUrl2(backendUrl, registration.relayWsPath);
5783
- console.log(
6164
+ logger.important(
5784
6165
  `Using agent session ${options.sessionId} (user session ${registration.userSessionId})`
5785
6166
  );
5786
6167
  await runTerminalRepl({
@@ -5808,30 +6189,30 @@ async function runVoice(options) {
5808
6189
  force: options.force ?? false,
5809
6190
  roomName: options.roomName
5810
6191
  });
5811
- console.log("\nVoice agent dispatched successfully.");
5812
- console.log(`Session ID: ${launch.sessionId}`);
5813
- console.log(`LiveKit URL: ${launch.livekitUrl}`);
5814
- console.log(`Room name: ${launch.roomName}`);
5815
- console.log(`Participant identity: ${launch.accessTokenIdentity}`);
5816
- console.log("\nUse this token to join the room:");
5817
- console.log(launch.accessToken);
5818
- console.log("\nOnce connected (for example via the LiveKit Agents playground),");
5819
- console.log("the voice worker will coordinate with the CLI coding agent through the relay.");
5820
- console.log(`Relay websocket: ${launch.relayWsUrl}`);
5821
- console.log(`Relay token (expires in ${launch.relayTokenExpiresIn}s):`);
5822
- console.log(launch.relayToken);
5823
- console.log("\nKeep this information secure\u2014tokens grant access to your agent session.");
6192
+ logger.success("\nVoice agent dispatched successfully.");
6193
+ logger.important(`Session ID: ${launch.sessionId}`);
6194
+ logger.important(`LiveKit URL: ${launch.livekitUrl}`);
6195
+ logger.important(`Room name: ${launch.roomName}`);
6196
+ logger.important(`Participant identity: ${launch.accessTokenIdentity}`);
6197
+ logger.important("\nUse this token to join the room:");
6198
+ logger.important(launch.accessToken);
6199
+ logger.log("\nOnce connected (for example via the LiveKit Agents playground),");
6200
+ logger.log("the voice worker will coordinate with the CLI coding agent through the relay.");
6201
+ logger.debug(`Relay websocket: ${launch.relayWsUrl}`);
6202
+ logger.debug(`Relay token (expires in ${launch.relayTokenExpiresIn}s):`);
6203
+ logger.debug(launch.relayToken);
6204
+ logger.important("\nKeep this information secure\u2014tokens grant access to your agent session.");
5824
6205
  }
5825
- function buildRelayWebsocketUrl2(baseUrl, path4) {
6206
+ function buildRelayWebsocketUrl2(baseUrl, path5) {
5826
6207
  const url = new URL(baseUrl);
5827
6208
  url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
5828
6209
  url.search = "";
5829
6210
  url.hash = "";
5830
- if (path4.startsWith("/")) {
5831
- url.pathname = path4;
6211
+ if (path5.startsWith("/")) {
6212
+ url.pathname = path5;
5832
6213
  } else {
5833
6214
  const basePath = url.pathname.endsWith("/") ? url.pathname : `${url.pathname}/`;
5834
- url.pathname = `${basePath}${path4}`;
6215
+ url.pathname = `${basePath}${path5}`;
5835
6216
  }
5836
6217
  return url.toString();
5837
6218
  }
@@ -5907,9 +6288,79 @@ function extractErrorMessage7(error) {
5907
6288
  return String(error);
5908
6289
  }
5909
6290
 
5910
- // src/cli.ts
6291
+ // src/utils/version-check.ts
6292
+ import https from "https";
6293
+ import { createRequire } from "node:module";
5911
6294
  var require2 = createRequire(import.meta.url);
5912
- var packageJson = require2("../package.json");
6295
+ async function fetchLatestVersion(packageName) {
6296
+ return new Promise((resolve) => {
6297
+ const url = `https://registry.npmjs.org/${packageName}`;
6298
+ https.get(url, { timeout: 3e3 }, (res) => {
6299
+ let data = "";
6300
+ res.on("data", (chunk) => {
6301
+ data += chunk;
6302
+ });
6303
+ res.on("end", () => {
6304
+ try {
6305
+ if (res.statusCode !== 200) {
6306
+ resolve(null);
6307
+ return;
6308
+ }
6309
+ const packageInfo = JSON.parse(data);
6310
+ resolve(packageInfo["dist-tags"]?.latest || null);
6311
+ } catch {
6312
+ resolve(null);
6313
+ }
6314
+ });
6315
+ }).on("error", () => {
6316
+ resolve(null);
6317
+ }).on("timeout", () => {
6318
+ resolve(null);
6319
+ });
6320
+ });
6321
+ }
6322
+ function compareVersions(v1, v2) {
6323
+ const parts1 = v1.split(".").map(Number);
6324
+ const parts2 = v2.split(".").map(Number);
6325
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
6326
+ const part1 = parts1[i] || 0;
6327
+ const part2 = parts2[i] || 0;
6328
+ if (part1 > part2) return 1;
6329
+ if (part1 < part2) return -1;
6330
+ }
6331
+ return 0;
6332
+ }
6333
+ async function checkForUpdates() {
6334
+ try {
6335
+ const packageJson2 = require2("../../package.json");
6336
+ const currentVersion = packageJson2.version;
6337
+ const packageName = packageJson2.name;
6338
+ if (!currentVersion || !packageName) {
6339
+ return;
6340
+ }
6341
+ const latestVersion = await fetchLatestVersion(packageName);
6342
+ if (!latestVersion) {
6343
+ return;
6344
+ }
6345
+ if (compareVersions(latestVersion, currentVersion) > 0) {
6346
+ console.log("\n" + "\u2550".repeat(60));
6347
+ console.log("Update available!");
6348
+ console.log(`Current version: ${currentVersion}`);
6349
+ console.log(`Latest version: ${latestVersion}`);
6350
+ console.log(`Run \`npm install -g ${packageName}@latest\` to update`);
6351
+ console.log("\u2550".repeat(60) + "\n");
6352
+ }
6353
+ } catch {
6354
+ }
6355
+ }
6356
+ function checkForUpdatesInBackground() {
6357
+ checkForUpdates().catch(() => {
6358
+ });
6359
+ }
6360
+
6361
+ // src/cli.ts
6362
+ var require3 = createRequire2(import.meta.url);
6363
+ var packageJson = require3("../package.json");
5913
6364
  function createProgram() {
5914
6365
  const program = new Command3();
5915
6366
  program.name("omnara").description(packageJson.description ?? "Command-line interface for Omnara.").version(packageJson.version ?? "0.0.0").option("--backend-url <url>", "Override backend API base URL used for login").option("--frontend-url <url>", "Override frontend URL opened during login").action(async () => {
@@ -5922,6 +6373,8 @@ function createProgram() {
5922
6373
  }
5923
6374
  async function run(argv) {
5924
6375
  const program = createProgram();
6376
+ performClaudeCodeCheck({ silent: false, exitOnMissing: false });
6377
+ checkForUpdatesInBackground();
5925
6378
  try {
5926
6379
  await program.parseAsync(argv);
5927
6380
  } catch (error) {
@@ -5932,13 +6385,13 @@ function isMainModule() {
5932
6385
  if (!import.meta.url || !process.argv[1]) {
5933
6386
  return false;
5934
6387
  }
5935
- const executedPath = path3.resolve(process.argv[1]);
6388
+ const executedPath = path4.resolve(process.argv[1]);
5936
6389
  const currentPath = import.meta.url.startsWith("file://") ? decodeURIComponent(import.meta.url.slice(7)) : import.meta.url;
5937
- return path3.normalize(currentPath) === path3.normalize(executedPath);
6390
+ return path4.normalize(currentPath) === path4.normalize(executedPath);
5938
6391
  }
5939
6392
  if (isMainModule()) {
5940
6393
  run(process.argv).catch((error) => {
5941
- console.error(error);
6394
+ logger.error(error);
5942
6395
  process.exitCode = 1;
5943
6396
  });
5944
6397
  }