omnikey-cli 1.0.19 → 1.0.21

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.
@@ -26,14 +26,16 @@ ${hasTaskInstructions
26
26
  - If a request needs BOTH machine data AND web search: emit a \`<shell_script>\` first → wait for \`TERMINAL OUTPUT:\` → then call the web tool with concrete values. Never use placeholders like "my IP" in a web query.
27
27
 
28
28
  **Incoming message tags:**
29
- - \`TERMINAL OUTPUT:\` — stdout/stderr from a prior script. Use it to continue reasoning or emit a follow-up.
29
+ - \`TERMINAL OUTPUT:\` — stdout/stderr from a prior script. Analyze it immediately and respond with EITHER a follow-up \`<shell_script>\` (if more data is needed) OR a \`<final_answer>\` (if you have enough to conclude). You MUST pick one — never respond with plain text.
30
30
  - \`COMMAND ERROR:\` — script failed. Diagnose and emit a corrected \`<shell_script>\` or explain in \`<final_answer>\`.
31
31
  - No prefix — direct user message; treat as the primary request.
32
32
 
33
33
  **Response format — every response must be exactly one of:**
34
- 1. \`<shell_script>...</shell_script>\` — to run commands.
34
+ 1. \`<shell_script>...</shell_script>\` — to run commands and gather more data.
35
35
  2. A \`web_search\` or \`web_fetch\` tool call — to fetch web context (use native tool calling, not XML tags).
36
- 3. \`<final_answer>...</final_answer>\` — when done.
36
+ 3. \`<final_answer>...</final_answer>\` — your conclusion once you have enough information.
37
+
38
+ **Critical rule:** After receiving \`TERMINAL OUTPUT:\` you MUST immediately produce either \`<shell_script>\` or \`<final_answer>\`. Never output raw text, markdown, or any other format. If the terminal output contains enough information to answer the user's request, output \`<final_answer>\` right away.
37
39
 
38
40
  No plain text, reasoning, or other tags outside these blocks. Never wrap in additional XML/JSON.
39
41
 
@@ -112,7 +112,7 @@ async function runToolLoop(initialResult, session, sessionId, send, log, tools,
112
112
  session.history.push(result.assistantMessage);
113
113
  session.history.push({
114
114
  role: 'user',
115
- content: 'You have reached the maximum number of tool calls. Based on all the information gathered so far, provide a single, final, concise answer. Do not call any more tools.',
115
+ content: 'You have reached the maximum number of tool calls. Do NOT make any further tool calls or web searches. You MUST now provide a final answer directly. If you still need to gather information from the system, generate a `<shell_scripts>` block instead of making tool calls.',
116
116
  });
117
117
  result = await ai_client_1.aiClient.complete(aiModel, session.history, {
118
118
  tools: undefined,
@@ -254,7 +254,12 @@ async function authenticateFromAuthHeader(authHeader, log) {
254
254
  }
255
255
  }
256
256
  function createUserContent(content, hasStoredPrompt) {
257
- return hasStoredPrompt ? content.replace(/@omniAgent/g, '').trim() : content;
257
+ return hasStoredPrompt
258
+ ? content
259
+ .toLowerCase()
260
+ .replace(/@omniagent/g, '')
261
+ .trim()
262
+ : content;
258
263
  }
259
264
  async function runAgentTurn(sessionId, subscription, clientMessage, send, log) {
260
265
  const { sessionState: session, hasStoredPrompt } = await getOrCreateSession(sessionId, subscription, clientMessage.platform, log);
@@ -290,9 +295,14 @@ async function runAgentTurn(sessionId, subscription, clientMessage, send, log) {
290
295
  isError: isErrorFlag,
291
296
  rawContentLength: (clientMessage.content || '').length,
292
297
  userContentLength: userContent.length,
298
+ isRecursiveCall: clientMessage.is_web_call,
293
299
  });
294
300
  const isAssistance = isTerminalOutput || isErrorFlag;
295
301
  if (!clientMessage?.is_web_call) {
302
+ // Terminal output and command errors are always user-role messages — they
303
+ // represent environment feedback that the agent must reason about next.
304
+ // Pushing them as 'assistant' would create two consecutive assistant turns
305
+ // which breaks most LLM APIs and prevents the model from processing the output.
296
306
  session.history.push({
297
307
  role: 'user',
298
308
  content: isAssistance
@@ -306,7 +316,7 @@ async function runAgentTurn(sessionId, subscription, clientMessage, send, log) {
306
316
  const tools = isFinalTurn ? undefined : buildAvailableTools();
307
317
  const recordUsage = async (result) => {
308
318
  const usage = result.usage;
309
- if (!usage || !subscription.id)
319
+ if (!usage || !subscription.id || config_1.config.isSelfHosted)
310
320
  return;
311
321
  try {
312
322
  await subscriptionUsage_1.SubscriptionUsage.create({
@@ -359,12 +369,58 @@ async function runAgentTurn(sessionId, subscription, clientMessage, send, log) {
359
369
  subscriptionId: subscription.id,
360
370
  turn: session.turns,
361
371
  });
362
- result = await runToolLoop(result, session, sessionId, send, log, buildAvailableTools(), recordUsage);
363
- content = result.content.trim();
364
- if (!content) {
365
- log.warn('Agent returned empty content after tool loop; sending generic error.');
366
- sendFinalAnswer(send, sessionId, 'The agent returned an empty response. Please try again.', true);
367
- sessionMessages.delete(sessionId);
372
+ const toolLoopResult = await runToolLoop(result, session, sessionId, send, log, buildAvailableTools(), recordUsage);
373
+ const toolLoopContent = toolLoopResult.content.trim();
374
+ const toolLoopHasShell = toolLoopContent.includes('<shell_script>');
375
+ const toolLoopHasFinal = toolLoopContent.includes('<final_answer>');
376
+ const webToolFailed = session.history.some((msg) => msg.role === 'tool' && typeof msg.content === 'string' && msg.content.startsWith('Error'));
377
+ if (toolLoopHasShell || (toolLoopHasFinal && !webToolFailed)) {
378
+ // The tool loop already produced a shell script — use it directly.
379
+ // This avoids a redundant AI call and handles the case where the model
380
+ // emits a <shell_script> immediately after its web tool calls.
381
+ log.info('Tool loop produced shell script; processing inline', { sessionId });
382
+ content = toolLoopContent;
383
+ result = toolLoopResult;
384
+ // Fall through to the <shell_script> handling below.
385
+ }
386
+ else {
387
+ // The tool loop returned either plain text or a <final_answer>.
388
+ // We always make one more AI turn here so the model has a chance to
389
+ // correct itself — specifically when web tools failed (404 / error) the
390
+ // model tends to wrap a "please run this manually" message in
391
+ // <final_answer>. The directive below tells it to use <shell_script> as
392
+ // a fallback instead of asking the user to run commands.
393
+ if (toolLoopResult.assistantMessage) {
394
+ session.history.push(toolLoopResult.assistantMessage);
395
+ }
396
+ session.history.push({
397
+ role: 'user',
398
+ content: webToolFailed
399
+ ? [
400
+ 'IMPORTANT: The web search tool failed and is unavailable. Do NOT attempt any further web calls or ask the user to run commands manually.',
401
+ 'You MUST retrieve any needed data by generating a <shell_script> that runs terminal commands (curl, grep, cat, etc.).',
402
+ 'The shell script output will be returned to you automatically.',
403
+ '',
404
+ 'Respond with exactly one of:',
405
+ '- <shell_script>...</shell_script> — to fetch or retrieve data via terminal commands',
406
+ '- <final_answer>...</final_answer> — only if you already have enough information',
407
+ 'No plain text. No web tool calls. No other format.',
408
+ ].join('\n')
409
+ : [
410
+ 'Web research is complete. The results are in the conversation above.',
411
+ '',
412
+ 'Now respond with exactly one of:',
413
+ '- <shell_script>...</shell_script> — to run terminal commands (output will be returned to you automatically)',
414
+ '- <final_answer>...</final_answer> — only if you genuinely have enough information',
415
+ 'No plain text. No other format.',
416
+ ].join('\n'),
417
+ });
418
+ await runAgentTurn(sessionId, subscription, {
419
+ sender: 'agent',
420
+ session_id: sessionId,
421
+ content: '',
422
+ is_web_call: true,
423
+ }, send, logger_1.logger);
368
424
  return;
369
425
  }
370
426
  }
@@ -410,6 +466,31 @@ async function runAgentTurn(sessionId, subscription, clientMessage, send, log) {
410
466
  });
411
467
  sessionMessages.delete(sessionId);
412
468
  }
469
+ else if (content) {
470
+ // Fallback: the LLM returned content without any recognized tag and it
471
+ // is not the final turn (e.g. plain-text conclusion after terminal
472
+ // output). Treat it as a final answer so the client is never left
473
+ // hanging.
474
+ log.info('Agent returned untagged content on a non-final turn; treating as final answer', {
475
+ sessionId,
476
+ subscriptionId: subscription.id,
477
+ turn: session.turns,
478
+ });
479
+ session.history.push({ role: 'assistant', content });
480
+ send({
481
+ session_id: sessionId,
482
+ sender: 'agent',
483
+ content: `<final_answer>\n${content}\n</final_answer>`,
484
+ });
485
+ sessionMessages.delete(sessionId);
486
+ }
487
+ else {
488
+ log.warn('Agent returned empty content with no recognized tags; sending error', {
489
+ sessionId,
490
+ });
491
+ sendFinalAnswer(send, sessionId, 'The agent returned an empty response. Please try again.', true);
492
+ sessionMessages.delete(sessionId);
493
+ }
413
494
  }
414
495
  catch (err) {
415
496
  log.error('Agent LLM call failed', { error: err });
@@ -118,7 +118,7 @@ async function enhanceText(logger, text, cmd, subscription) {
118
118
  const { rawResponse, usage, model } = result;
119
119
  // Record token usage for this subscription and model, if usage
120
120
  // data is available and we know which subscription made the call.
121
- if (usage && subscription.id) {
121
+ if (usage && subscription.id && !config_1.config.isSelfHosted) {
122
122
  try {
123
123
  await subscriptionUsage_1.SubscriptionUsage.create({
124
124
  subscriptionId: subscription.id,
@@ -197,7 +197,7 @@ async function streamEnhanceResponse(res, text, cmd) {
197
197
  return;
198
198
  }
199
199
  const { usage, model } = result;
200
- if (usage && subscription.id) {
200
+ if (usage && subscription.id && !config_1.config.isSelfHosted) {
201
201
  try {
202
202
  await subscriptionUsage_1.SubscriptionUsage.create({
203
203
  subscriptionId: subscription.id,
@@ -64,8 +64,8 @@ app.get('/macos/appcast', (req, res) => {
64
64
  const appcastUrl = `${baseUrl}/macos/appcast`;
65
65
  // These should match the values embedded into the macOS app
66
66
  // Info.plist in macOS/build_release_dmg.sh.
67
- const bundleVersion = '16';
68
- const shortVersion = '1.0.15';
67
+ const bundleVersion = '17';
68
+ const shortVersion = '1.0.16';
69
69
  const xml = `<?xml version="1.0" encoding="utf-8"?>
70
70
  <rss version="2.0"
71
71
  xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle"
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public",
5
5
  "registry": "https://registry.npmjs.org/"
6
6
  },
7
- "version": "1.0.19",
7
+ "version": "1.0.21",
8
8
  "description": "CLI for onboarding users to Omnikey AI and configuring OPENAI_API_KEY. Use Yarn for install/build.",
9
9
  "engines": {
10
10
  "node": ">=14.0.0",