@rtrvr-ai/rover 1.2.1 → 1.3.0

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.
@@ -1458,6 +1458,18 @@ async function processActionResponse({ request, response, tabId, prevSteps, thou
1458
1458
  if (Array.isArray(functionCalls) && functionCalls.length > 0) {
1459
1459
  const askUserCall = functionCalls.find((call) => String(call?.name || "").trim().toLowerCase() === "ask_user");
1460
1460
  if (askUserCall) {
1461
+ const deferredSiblingCalls = functionCalls.filter((call) => call && call !== askUserCall && typeof call.name === "string" && call.name.trim()).map((call) => ({
1462
+ name: String(call.name || "unknown"),
1463
+ args: call.args || {},
1464
+ response: {
1465
+ status: "Failure",
1466
+ error: "Skipped because ask_user was called. Wait for user answers before executing other tools.",
1467
+ allowFallback: true,
1468
+ output: {
1469
+ status: "deferred_after_ask_user"
1470
+ }
1471
+ }
1472
+ }));
1461
1473
  const questions = normalizeAskUserQuestions(askUserCall?.args?.questions_to_ask);
1462
1474
  if (!questions.length) {
1463
1475
  prevSteps.push({
@@ -1474,7 +1486,8 @@ async function processActionResponse({ request, response, tabId, prevSteps, thou
1474
1486
  error: "Missing/invalid 'questions_to_ask'",
1475
1487
  allowFallback: false
1476
1488
  }
1477
- }
1489
+ },
1490
+ ...deferredSiblingCalls
1478
1491
  ]
1479
1492
  });
1480
1493
  limitPrevSteps(prevSteps);
@@ -1489,7 +1502,11 @@ async function processActionResponse({ request, response, tabId, prevSteps, thou
1489
1502
  {
1490
1503
  name: "ask_user",
1491
1504
  args: {
1492
- questions_to_ask: questions.map((question) => ({ key: question.key, query: question.query }))
1505
+ questions_to_ask: questions.map((question) => ({
1506
+ key: question.key,
1507
+ query: question.query,
1508
+ ...question.required === false ? { required: false } : {}
1509
+ }))
1493
1510
  },
1494
1511
  response: {
1495
1512
  status: "Success",
@@ -1499,7 +1516,8 @@ async function processActionResponse({ request, response, tabId, prevSteps, thou
1499
1516
  questions
1500
1517
  }
1501
1518
  }
1502
- }
1519
+ },
1520
+ ...deferredSiblingCalls
1503
1521
  ]
1504
1522
  });
1505
1523
  limitPrevSteps(prevSteps);
@@ -1608,12 +1626,16 @@ function normalizeAskUserQuestions(rawQuestions) {
1608
1626
  if (seenKeys.has(rawKey))
1609
1627
  continue;
1610
1628
  seenKeys.add(rawKey);
1629
+ const hasRequired = typeof item.required === "boolean";
1630
+ const hasOptional = typeof item.optional === "boolean";
1631
+ const required = hasRequired ? !!item.required : hasOptional ? !item.optional : true;
1611
1632
  out.push({
1612
1633
  key: rawKey,
1613
1634
  query,
1614
1635
  ...typeof item.question === "string" && item.question.trim() ? { question: String(item.question).trim() } : {},
1615
1636
  ...typeof item.id === "string" && item.id.trim() ? { id: String(item.id).trim() } : {},
1616
- ...Array.isArray(item.choices) ? { choices: item.choices } : {}
1637
+ ...Array.isArray(item.choices) ? { choices: item.choices } : {},
1638
+ required
1617
1639
  });
1618
1640
  }
1619
1641
  return out.slice(0, 6);
@@ -4574,12 +4596,16 @@ function normalizePlannerQuestion(input, index) {
4574
4596
  const key = keyCandidate || `clarification_${index + 1}`;
4575
4597
  if (!queryCandidate)
4576
4598
  return void 0;
4599
+ const hasRequired = typeof input.required === "boolean";
4600
+ const hasOptional = typeof input.optional === "boolean";
4601
+ const required = hasRequired ? !!input.required : hasOptional ? !input.optional : true;
4577
4602
  return {
4578
4603
  key,
4579
4604
  query: queryCandidate,
4580
4605
  ...typeof input.id === "string" && input.id.trim() ? { id: input.id.trim() } : {},
4581
4606
  ...typeof input.question === "string" && input.question.trim() ? { question: input.question.trim() } : {},
4582
- ...Array.isArray(input.choices) ? { choices: input.choices } : {}
4607
+ ...Array.isArray(input.choices) ? { choices: input.choices } : {},
4608
+ required
4583
4609
  };
4584
4610
  }
4585
4611
  function normalizePlannerQuestions(input) {
@@ -5078,36 +5104,41 @@ function extractLatestPrevStepsFromPlanner(toolResults) {
5078
5104
  }
5079
5105
  return void 0;
5080
5106
  }
5081
- async function waitForNewTabReady(logicalTabId, timeoutMs = 1e4) {
5082
- if (!bridgeRpc)
5083
- return false;
5107
+ async function waitForNewTabReady(openedTab, timeoutMs = 1e4) {
5108
+ if (!bridgeRpc) {
5109
+ return { ready: false, attached: false, external: !!openedTab.external };
5110
+ }
5084
5111
  const pollInterval = 500;
5085
5112
  const start = Date.now();
5086
5113
  while (Date.now() - start < timeoutMs) {
5087
5114
  try {
5088
5115
  const tabs = await bridgeRpc("listSessionTabs");
5089
5116
  if (Array.isArray(tabs)) {
5090
- const target = tabs.find((t) => Number(t?.logicalTabId) === logicalTabId);
5117
+ const target = tabs.find((t) => Number(t?.logicalTabId) === openedTab.logicalTabId);
5091
5118
  if (target?.external) {
5092
- return true;
5119
+ return { ready: true, attached: false, external: true };
5093
5120
  }
5094
5121
  if (target?.runtimeId) {
5095
5122
  await new Promise((resolve) => setTimeout(resolve, 1e3));
5096
- return true;
5123
+ return { ready: true, attached: true, external: false };
5097
5124
  }
5098
5125
  }
5099
5126
  } catch {
5100
5127
  }
5101
5128
  await new Promise((resolve) => setTimeout(resolve, pollInterval));
5102
5129
  }
5103
- return false;
5130
+ return { ready: false, attached: false, external: !!openedTab.external };
5104
5131
  }
5105
5132
  function detectOpenedTabFromToolResult(result) {
5106
5133
  if (!result || typeof result !== "object")
5107
5134
  return void 0;
5108
5135
  const output = result.output ?? result;
5109
5136
  if (output?.openedInNewTab && typeof output?.logicalTabId === "number") {
5110
- return output.logicalTabId;
5137
+ return {
5138
+ logicalTabId: output.logicalTabId,
5139
+ url: typeof output?.url === "string" ? output.url : void 0,
5140
+ external: output?.external === true
5141
+ };
5111
5142
  }
5112
5143
  return void 0;
5113
5144
  }
@@ -5240,11 +5271,13 @@ function clearTaskScopedContextAfterCompletion() {
5240
5271
  pendingAskUser = void 0;
5241
5272
  }
5242
5273
  async function maybeWaitForNewTab(result) {
5243
- const logicalTabId = detectOpenedTabFromToolResult(result);
5244
- if (logicalTabId) {
5274
+ const openedTab = detectOpenedTabFromToolResult(result);
5275
+ if (openedTab) {
5245
5276
  postStatus("Waiting for new tab to load...", void 0, "execute");
5246
- await waitForNewTabReady(logicalTabId);
5277
+ const readyState = await waitForNewTabReady(openedTab);
5278
+ return { openedTab, readyState };
5247
5279
  }
5280
+ return {};
5248
5281
  }
5249
5282
  async function handleUserMessage(text, options) {
5250
5283
  if (!config)
@@ -5274,11 +5307,22 @@ async function handleUserMessage(text, options) {
5274
5307
  applyAgentPrevSteps([
5275
5308
  ...agentPrevSteps,
5276
5309
  {
5277
- thought: "User provided clarification answers.",
5278
- data: JSON.stringify({
5279
- ask_user_answers: normalizedAnswers.answersByKey,
5280
- raw_user_reply: normalizedAnswers.rawText
5281
- })
5310
+ functions: [{
5311
+ name: "ask_user",
5312
+ args: {
5313
+ questions_to_ask: pendingAskUser.questions.map((q) => ({
5314
+ key: q.key,
5315
+ query: q.query
5316
+ }))
5317
+ },
5318
+ response: {
5319
+ status: "Success",
5320
+ output: {
5321
+ ask_user_answers: normalizedAnswers.answersByKey,
5322
+ raw_user_reply: normalizedAnswers.rawText
5323
+ }
5324
+ }
5325
+ }]
5282
5326
  }
5283
5327
  ], { snapshot: false });
5284
5328
  pendingAskUser = void 0;
@@ -5418,7 +5462,44 @@ async function handleUserMessage(text, options) {
5418
5462
  return { route: result.route, taskComplete: true };
5419
5463
  }
5420
5464
  if (result.directToolResult) {
5421
- await maybeWaitForNewTab(result.directToolResult);
5465
+ const newTabWait = await maybeWaitForNewTab(result.directToolResult);
5466
+ if (newTabWait.openedTab && newTabWait.readyState && !newTabWait.readyState.ready && !newTabWait.readyState.external) {
5467
+ const fallbackUrl = String(newTabWait.openedTab.url || "").trim();
5468
+ if (fallbackUrl && bridgeRpc) {
5469
+ postStatus("New tab did not attach; continuing in current tab", void 0, "execute");
5470
+ try {
5471
+ const tabCtx = await bridgeRpc("getTabContext");
5472
+ const localLogicalTabId = Number(tabCtx?.logicalTabId ?? tabCtx?.id);
5473
+ if (Number.isFinite(localLogicalTabId) && localLogicalTabId > 0) {
5474
+ await bridgeRpc("executeTool", {
5475
+ call: {
5476
+ name: "switch_tab",
5477
+ args: { tab_id: localLogicalTabId }
5478
+ },
5479
+ payload: {
5480
+ forceLocal: true,
5481
+ reason: "opened_tab_unattached_reset_active"
5482
+ }
5483
+ });
5484
+ }
5485
+ const fallbackResult = await bridgeRpc("executeTool", {
5486
+ call: {
5487
+ name: "goto_url",
5488
+ args: {
5489
+ tab_id: 0,
5490
+ url: fallbackUrl
5491
+ }
5492
+ },
5493
+ payload: {
5494
+ forceLocal: true,
5495
+ reason: "opened_tab_unattached_fallback"
5496
+ }
5497
+ });
5498
+ maybePostNavigationGuardrailFromToolResult(fallbackResult);
5499
+ } catch {
5500
+ }
5501
+ }
5502
+ }
5422
5503
  postStatus("Verifying result", void 0, "verify");
5423
5504
  maybePostNavigationGuardrailFromToolResult(result.directToolResult);
5424
5505
  applyAgentPrevSteps(result.directToolResult.prevSteps, { snapshot: false });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rtrvr-ai/rover",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "dist/rover.js",