omnikey-cli 1.0.20 → 1.0.22

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.
@@ -110,9 +110,20 @@ async function runToolLoop(initialResult, session, sessionId, send, log, tools,
110
110
  if (result.finish_reason === 'tool_calls') {
111
111
  log.warn('Tool loop hit MAX_TOOL_ITERATIONS; forcing final conclusion', { sessionId });
112
112
  session.history.push(result.assistantMessage);
113
+ // The API requires a tool_result for every tool_use in the preceding
114
+ // assistant message. Add synthetic results for any unexecuted calls so
115
+ // the history remains valid before we send the follow-up user message.
116
+ for (const tc of result.tool_calls ?? []) {
117
+ session.history.push({
118
+ role: 'tool',
119
+ tool_call_id: tc.id,
120
+ tool_name: tc.name,
121
+ content: 'Tool call limit reached. Result unavailable.',
122
+ });
123
+ }
113
124
  session.history.push({
114
125
  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.',
126
+ 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
127
  });
117
128
  result = await ai_client_1.aiClient.complete(aiModel, session.history, {
118
129
  tools: undefined,
@@ -123,9 +134,6 @@ async function runToolLoop(initialResult, session, sessionId, send, log, tools,
123
134
  log.info('Finished reasoning and tool calls: ', {
124
135
  reason: result.finish_reason,
125
136
  });
126
- if (result.assistantMessage) {
127
- session.history.push(result.assistantMessage);
128
- }
129
137
  return result;
130
138
  }
131
139
  function buildAvailableTools() {
@@ -298,6 +306,7 @@ async function runAgentTurn(sessionId, subscription, clientMessage, send, log) {
298
306
  isError: isErrorFlag,
299
307
  rawContentLength: (clientMessage.content || '').length,
300
308
  userContentLength: userContent.length,
309
+ isRecursiveCall: clientMessage.is_web_call,
301
310
  });
302
311
  const isAssistance = isTerminalOutput || isErrorFlag;
303
312
  if (!clientMessage?.is_web_call) {
@@ -371,21 +380,60 @@ async function runAgentTurn(sessionId, subscription, clientMessage, send, log) {
371
380
  subscriptionId: subscription.id,
372
381
  turn: session.turns,
373
382
  });
374
- result = await runToolLoop(result, session, sessionId, send, log, buildAvailableTools(), recordUsage);
375
- content = result.content.trim();
376
- if (!content) {
377
- log.warn('Agent returned empty content after tool loop; sending generic error.');
378
- sendFinalAnswer(send, sessionId, 'The agent returned an empty response. Please try again.', true);
379
- sessionMessages.delete(sessionId);
383
+ const toolLoopResult = await runToolLoop(result, session, sessionId, send, log, buildAvailableTools(), recordUsage);
384
+ const toolLoopContent = toolLoopResult.content.trim();
385
+ const toolLoopHasShell = toolLoopContent.includes('<shell_script>');
386
+ const toolLoopHasFinal = toolLoopContent.includes('<final_answer>');
387
+ const webToolFailed = session.history.some((msg) => msg.role === 'tool' && typeof msg.content === 'string' && msg.content.startsWith('Error'));
388
+ if (toolLoopHasShell || (toolLoopHasFinal && !webToolFailed)) {
389
+ // The tool loop already produced a shell script — use it directly.
390
+ // This avoids a redundant AI call and handles the case where the model
391
+ // emits a <shell_script> immediately after its web tool calls.
392
+ log.info('Tool loop produced shell script; processing inline', { sessionId });
393
+ content = toolLoopContent;
394
+ result = toolLoopResult;
395
+ // Fall through to the <shell_script> handling below.
396
+ }
397
+ else {
398
+ // The tool loop returned either plain text or a <final_answer>.
399
+ // We always make one more AI turn here so the model has a chance to
400
+ // correct itself — specifically when web tools failed (404 / error) the
401
+ // model tends to wrap a "please run this manually" message in
402
+ // <final_answer>. The directive below tells it to use <shell_script> as
403
+ // a fallback instead of asking the user to run commands.
404
+ if (toolLoopResult.assistantMessage) {
405
+ session.history.push(toolLoopResult.assistantMessage);
406
+ }
407
+ session.history.push({
408
+ role: 'user',
409
+ content: webToolFailed
410
+ ? [
411
+ 'IMPORTANT: The web search tool failed and is unavailable. Do NOT attempt any further web calls or ask the user to run commands manually.',
412
+ 'You MUST retrieve any needed data by generating a <shell_script> that runs terminal commands (curl, grep, cat, etc.).',
413
+ 'The shell script output will be returned to you automatically.',
414
+ '',
415
+ 'Respond with exactly one of:',
416
+ '- <shell_script>...</shell_script> — to fetch or retrieve data via terminal commands',
417
+ '- <final_answer>...</final_answer> — only if you already have enough information',
418
+ 'No plain text. No web tool calls. No other format.',
419
+ ].join('\n')
420
+ : [
421
+ 'Web research is complete. The results are in the conversation above.',
422
+ '',
423
+ 'Now respond with exactly one of:',
424
+ '- <shell_script>...</shell_script> — to run terminal commands (output will be returned to you automatically)',
425
+ '- <final_answer>...</final_answer> — only if you genuinely have enough information',
426
+ 'No plain text. No other format.',
427
+ ].join('\n'),
428
+ });
429
+ await runAgentTurn(sessionId, subscription, {
430
+ sender: 'agent',
431
+ session_id: sessionId,
432
+ content: '',
433
+ is_web_call: true,
434
+ }, send, logger_1.logger);
380
435
  return;
381
436
  }
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;
389
437
  }
390
438
  // Ensure that a proper <final_answer> block is produced for the
391
439
  // desktop clients once we reach the final turn. If the model did
@@ -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 = '18';
68
+ const shortVersion = '1.0.17';
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.3';
96
+ const WIN_VERSION = '1.4';
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.20",
7
+ "version": "1.0.22",
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",