omnikey-cli 1.0.18 → 1.0.20

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
 
@@ -105,9 +105,27 @@ async function runToolLoop(initialResult, session, sessionId, send, log, tools,
105
105
  });
106
106
  await onUsage(result);
107
107
  }
108
+ // If we exhausted the iteration cap and the model still wants to call tools,
109
+ // force a final text response by calling again without tools.
110
+ if (result.finish_reason === 'tool_calls') {
111
+ log.warn('Tool loop hit MAX_TOOL_ITERATIONS; forcing final conclusion', { sessionId });
112
+ session.history.push(result.assistantMessage);
113
+ session.history.push({
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.',
116
+ });
117
+ result = await ai_client_1.aiClient.complete(aiModel, session.history, {
118
+ tools: undefined,
119
+ temperature: 0.2,
120
+ });
121
+ await onUsage(result);
122
+ }
108
123
  log.info('Finished reasoning and tool calls: ', {
109
124
  reason: result.finish_reason,
110
125
  });
126
+ if (result.assistantMessage) {
127
+ session.history.push(result.assistantMessage);
128
+ }
111
129
  return result;
112
130
  }
113
131
  function buildAvailableTools() {
@@ -239,7 +257,12 @@ async function authenticateFromAuthHeader(authHeader, log) {
239
257
  }
240
258
  }
241
259
  function createUserContent(content, hasStoredPrompt) {
242
- return hasStoredPrompt ? content.replace(/@omniAgent/g, '').trim() : content;
260
+ return hasStoredPrompt
261
+ ? content
262
+ .toLowerCase()
263
+ .replace(/@omniagent/g, '')
264
+ .trim()
265
+ : content;
243
266
  }
244
267
  async function runAgentTurn(sessionId, subscription, clientMessage, send, log) {
245
268
  const { sessionState: session, hasStoredPrompt } = await getOrCreateSession(sessionId, subscription, clientMessage.platform, log);
@@ -278,6 +301,10 @@ async function runAgentTurn(sessionId, subscription, clientMessage, send, log) {
278
301
  });
279
302
  const isAssistance = isTerminalOutput || isErrorFlag;
280
303
  if (!clientMessage?.is_web_call) {
304
+ // Terminal output and command errors are always user-role messages — they
305
+ // represent environment feedback that the agent must reason about next.
306
+ // Pushing them as 'assistant' would create two consecutive assistant turns
307
+ // which breaks most LLM APIs and prevents the model from processing the output.
281
308
  session.history.push({
282
309
  role: 'user',
283
310
  content: isAssistance
@@ -291,7 +318,7 @@ async function runAgentTurn(sessionId, subscription, clientMessage, send, log) {
291
318
  const tools = isFinalTurn ? undefined : buildAvailableTools();
292
319
  const recordUsage = async (result) => {
293
320
  const usage = result.usage;
294
- if (!usage || !subscription.id)
321
+ if (!usage || !subscription.id || config_1.config.isSelfHosted)
295
322
  return;
296
323
  try {
297
324
  await subscriptionUsage_1.SubscriptionUsage.create({
@@ -352,6 +379,13 @@ async function runAgentTurn(sessionId, subscription, clientMessage, send, log) {
352
379
  sessionMessages.delete(sessionId);
353
380
  return;
354
381
  }
382
+ await runAgentTurn(sessionId, subscription, {
383
+ sender: 'agent',
384
+ session_id: sessionId,
385
+ content: '',
386
+ is_web_call: true,
387
+ }, send, logger_1.logger);
388
+ return;
355
389
  }
356
390
  // Ensure that a proper <final_answer> block is produced for the
357
391
  // desktop clients once we reach the final turn. If the model did
@@ -395,6 +429,31 @@ async function runAgentTurn(sessionId, subscription, clientMessage, send, log) {
395
429
  });
396
430
  sessionMessages.delete(sessionId);
397
431
  }
432
+ else if (content) {
433
+ // Fallback: the LLM returned content without any recognized tag and it
434
+ // is not the final turn (e.g. plain-text conclusion after terminal
435
+ // output). Treat it as a final answer so the client is never left
436
+ // hanging.
437
+ log.info('Agent returned untagged content on a non-final turn; treating as final answer', {
438
+ sessionId,
439
+ subscriptionId: subscription.id,
440
+ turn: session.turns,
441
+ });
442
+ session.history.push({ role: 'assistant', content });
443
+ send({
444
+ session_id: sessionId,
445
+ sender: 'agent',
446
+ content: `<final_answer>\n${content}\n</final_answer>`,
447
+ });
448
+ sessionMessages.delete(sessionId);
449
+ }
450
+ else {
451
+ log.warn('Agent returned empty content with no recognized tags; sending error', {
452
+ sessionId,
453
+ });
454
+ sendFinalAnswer(send, sessionId, 'The agent returned an empty response. Please try again.', true);
455
+ sessionMessages.delete(sessionId);
456
+ }
398
457
  }
399
458
  catch (err) {
400
459
  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 = '14';
68
- const shortVersion = '1.0.13';
67
+ const bundleVersion = '16';
68
+ const shortVersion = '1.0.15';
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"
@@ -93,7 +93,7 @@ app.get('/macos/appcast', (req, res) => {
93
93
  // ── Windows distribution endpoints ───────────────────────────────────────────
94
94
  // These should match the values in windows/OmniKey.Windows.csproj
95
95
  // <Version> and windows/build_release_zip.ps1 $APP_VERSION.
96
- const WIN_VERSION = '1.2';
96
+ const WIN_VERSION = '1.3';
97
97
  const WIN_ZIP_FILENAME = 'OmniKeyAI-windows-win-x64.zip';
98
98
  const WIN_ZIP_PATH = path_1.default.join(process.cwd(), 'windows', WIN_ZIP_FILENAME);
99
99
  // Serves the pre-built ZIP produced by windows/build_release_zip.ps1.
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.18",
7
+ "version": "1.0.20",
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",