@rtrvr-ai/rover 1.3.1 → 1.3.2

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/rover.js CHANGED
@@ -11385,10 +11385,9 @@ var Bridge = class {
11385
11385
  }
11386
11386
  const clickTargetUrl = this.getClickTargetUrl(args);
11387
11387
  if (clickTargetUrl) {
11388
- this.notifyAgentNavigation(clickTargetUrl);
11389
- this.notifyCrossHostNavigation(clickTargetUrl);
11390
- } else {
11391
- this.notifyAgentNavigation(window.location.href);
11388
+ const intent = this.buildNavigationIntent(clickTargetUrl);
11389
+ this.notifyAgentNavigation(intent);
11390
+ this.notifyCrossHostNavigation(intent);
11392
11391
  }
11393
11392
  }
11394
11393
  if (toolName === SystemToolNames.describe_images) {
@@ -11527,8 +11526,9 @@ var Bridge = class {
11527
11526
  }
11528
11527
  return this.domainScopeBlockedResponse(targetUrl, reason);
11529
11528
  }
11530
- this.notifyAgentNavigation(targetUrl);
11531
- this.notifyCrossHostNavigation(targetUrl);
11529
+ const intent = this.buildNavigationIntent(targetUrl);
11530
+ this.notifyAgentNavigation(intent);
11531
+ this.notifyCrossHostNavigation(intent);
11532
11532
  window.location.href = targetUrl;
11533
11533
  return { success: true, output: { url: targetUrl, navigation: "same_tab" } };
11534
11534
  }
@@ -11557,23 +11557,24 @@ var Bridge = class {
11557
11557
  }
11558
11558
  return this.domainScopeBlockedResponse(targetUrl, reason);
11559
11559
  }
11560
- this.notifyAgentNavigation(targetUrl);
11561
- this.notifyCrossHostNavigation(targetUrl);
11560
+ const intent = this.buildNavigationIntent(targetUrl);
11561
+ this.notifyAgentNavigation(intent);
11562
+ this.notifyCrossHostNavigation(intent);
11562
11563
  window.location.href = targetUrl;
11563
11564
  return { success: true, output: { url: targetUrl, navigation: "same_tab" } };
11564
11565
  }
11565
11566
  case SystemToolNames.go_back: {
11566
- this.notifyAgentNavigation(window.location.href);
11567
+ this.notifyAgentNavigation(this.buildNavigationIntent(window.location.href, { isCrossHost: false }));
11567
11568
  window.history.back();
11568
11569
  return { success: true };
11569
11570
  }
11570
11571
  case SystemToolNames.go_forward: {
11571
- this.notifyAgentNavigation(window.location.href);
11572
+ this.notifyAgentNavigation(this.buildNavigationIntent(window.location.href, { isCrossHost: false }));
11572
11573
  window.history.forward();
11573
11574
  return { success: true };
11574
11575
  }
11575
11576
  case SystemToolNames.refresh_page: {
11576
- this.notifyAgentNavigation(window.location.href);
11577
+ this.notifyAgentNavigation(this.buildNavigationIntent(window.location.href, { isCrossHost: false }));
11577
11578
  window.location.reload();
11578
11579
  return { success: true };
11579
11580
  }
@@ -11585,8 +11586,9 @@ var Bridge = class {
11585
11586
  if (!targetUrl)
11586
11587
  return { success: false, error: `open_new_tab: invalid url "${rawUrl}"`, allowFallback: true };
11587
11588
  if (this.shouldConvertOpenNewTabToSameTab(targetUrl)) {
11588
- this.notifyAgentNavigation(targetUrl);
11589
- this.notifyCrossHostNavigation(targetUrl);
11589
+ const intent = this.buildNavigationIntent(targetUrl);
11590
+ this.notifyAgentNavigation(intent);
11591
+ this.notifyCrossHostNavigation(intent);
11590
11592
  window.location.assign(targetUrl);
11591
11593
  return {
11592
11594
  success: true,
@@ -11681,16 +11683,33 @@ var Bridge = class {
11681
11683
  return false;
11682
11684
  return isUrlAllowedByDomains(targetUrl, this.allowedDomains);
11683
11685
  }
11684
- notifyAgentNavigation(targetUrl) {
11686
+ buildNavigationIntent(targetUrl, options) {
11687
+ let handoffId = "";
11685
11688
  try {
11686
- this.onBeforeAgentNavigation?.(targetUrl);
11689
+ handoffId = crypto.randomUUID();
11687
11690
  } catch {
11691
+ handoffId = `handoff_${Date.now()}_${Math.floor(Math.random() * 1e5)}`;
11688
11692
  }
11693
+ return {
11694
+ handoffId,
11695
+ targetUrl,
11696
+ sourceRuntimeId: this.runtimeId,
11697
+ sourceLogicalTabId: this.actionGateContext.localLogicalTabId,
11698
+ runId: options?.runId,
11699
+ isCrossHost: options?.isCrossHost ?? this.isCrossHostInScopeNavigation(targetUrl),
11700
+ ts: Date.now()
11701
+ };
11689
11702
  }
11690
- notifyCrossHostNavigation(targetUrl) {
11691
- if (this.isCrossHostInScopeNavigation(targetUrl)) {
11703
+ notifyAgentNavigation(event) {
11704
+ try {
11705
+ this.onBeforeAgentNavigation?.(event);
11706
+ } catch {
11707
+ }
11708
+ }
11709
+ notifyCrossHostNavigation(event) {
11710
+ if (event.isCrossHost) {
11692
11711
  try {
11693
- this.onBeforeCrossHostNavigation?.(targetUrl);
11712
+ this.onBeforeCrossHostNavigation?.(event);
11694
11713
  } catch {
11695
11714
  }
11696
11715
  }
@@ -14084,12 +14103,29 @@ function mountWidget(opts) {
14084
14103
  display: flex;
14085
14104
  justify-content: flex-end;
14086
14105
  align-items: center;
14106
+ gap: 8px;
14087
14107
  position: sticky;
14088
14108
  bottom: 0;
14089
14109
  padding-top: 2px;
14090
14110
  background: linear-gradient(to bottom, rgba(255, 247, 242, 0), rgba(255, 247, 242, 0.94) 40%);
14091
14111
  }
14092
14112
 
14113
+ .questionPromptCancel {
14114
+ border: 1px solid var(--rv-border-strong);
14115
+ background: rgba(255, 255, 255, 0.88);
14116
+ color: var(--rv-text-secondary);
14117
+ border-radius: var(--rv-radius-sm);
14118
+ font-size: 12px;
14119
+ font-weight: 700;
14120
+ letter-spacing: 0.01em;
14121
+ padding: 6px 10px;
14122
+ cursor: pointer;
14123
+ }
14124
+
14125
+ .questionPromptCancel:hover {
14126
+ background: rgba(242, 246, 250, 0.95);
14127
+ }
14128
+
14093
14129
  .questionPromptSubmit {
14094
14130
  border: 1px solid var(--rv-accent-border);
14095
14131
  background: var(--rv-accent-soft);
@@ -14860,6 +14896,11 @@ function mountWidget(opts) {
14860
14896
  questionPromptForm.appendChild(questionPromptList);
14861
14897
  const questionPromptActions = document.createElement("div");
14862
14898
  questionPromptActions.className = "questionPromptActions";
14899
+ const questionPromptCancel = document.createElement("button");
14900
+ questionPromptCancel.type = "button";
14901
+ questionPromptCancel.className = "questionPromptCancel";
14902
+ questionPromptCancel.textContent = "Cancel";
14903
+ questionPromptActions.appendChild(questionPromptCancel);
14863
14904
  const questionPromptSubmit = document.createElement("button");
14864
14905
  questionPromptSubmit.type = "submit";
14865
14906
  questionPromptSubmit.className = "questionPromptSubmit";
@@ -15219,6 +15260,7 @@ function mountWidget(opts) {
15219
15260
  function syncComposerDisabledState() {
15220
15261
  const disabled = currentMode2 === "observer" && !canComposeInObserver;
15221
15262
  inputEl.disabled = disabled;
15263
+ questionPromptCancel.disabled = disabled;
15222
15264
  questionPromptSubmit.disabled = disabled;
15223
15265
  for (const node of Array.from(questionPromptForm.querySelectorAll(".questionPromptInput"))) {
15224
15266
  node.disabled = disabled;
@@ -15616,6 +15658,7 @@ function mountWidget(opts) {
15616
15658
  if (!value) {
15617
15659
  input.classList.remove("invalid");
15618
15660
  delete questionDraftAnswers[question.key];
15661
+ rawLines.push(`${question.key}: (no answer provided)`);
15619
15662
  continue;
15620
15663
  }
15621
15664
  input.classList.remove("invalid");
@@ -15628,7 +15671,7 @@ function mountWidget(opts) {
15628
15671
  return;
15629
15672
  }
15630
15673
  const keys = currentQuestionPrompt.questions.map((question) => question.key);
15631
- const rawText = rawLines.join("\n");
15674
+ const rawText = rawLines.length ? rawLines.join("\n") : keys.map((key) => `${key}: (no answer provided)`).join("\n");
15632
15675
  opts.onSend(rawText, {
15633
15676
  askUserAnswers: {
15634
15677
  answersByKey,
@@ -15636,7 +15679,15 @@ function mountWidget(opts) {
15636
15679
  keys
15637
15680
  }
15638
15681
  });
15639
- setQuestionPrompt(void 0);
15682
+ });
15683
+ questionPromptCancel.addEventListener("click", () => {
15684
+ if (inputEl.disabled)
15685
+ return;
15686
+ if (opts.onCancelQuestionFlow) {
15687
+ opts.onCancelQuestionFlow();
15688
+ return;
15689
+ }
15690
+ opts.onCancelRun?.();
15640
15691
  });
15641
15692
  inputEl.addEventListener("keydown", (e) => {
15642
15693
  if (e.key === "Enter" && !e.shiftKey && !e.isComposing) {
@@ -15751,7 +15802,9 @@ var SHARED_VERSION = 2;
15751
15802
  var SHARED_KEY_PREFIX = "rover:shared:";
15752
15803
  var SHARED_CHANNEL_PREFIX = "rover:channel:";
15753
15804
  var STALE_DETACHED_EXTERNAL_TAB_MS = 2 * 6e4;
15754
- var STALE_DETACHED_TAB_MS = 10 * 6e4;
15805
+ var STALE_DETACHED_TAB_MS = 9e4;
15806
+ var STALE_NAVIGATION_HANDOFF_TAB_MS = 45e3;
15807
+ var STALE_PENDING_ATTACH_TAB_MS = 2e4;
15755
15808
  var STALE_RUNTIME_TAB_MS = 45e3;
15756
15809
  function now() {
15757
15810
  return Date.now();
@@ -15773,13 +15826,6 @@ function normalizeUrl2(raw) {
15773
15826
  return text;
15774
15827
  }
15775
15828
  }
15776
- function extractHostname2(url) {
15777
- try {
15778
- return new URL(url).hostname.toLowerCase();
15779
- } catch {
15780
- return "";
15781
- }
15782
- }
15783
15829
  function sanitizeSharedTask(raw) {
15784
15830
  if (!raw || typeof raw !== "object")
15785
15831
  return void 0;
@@ -15866,6 +15912,12 @@ function sanitizeSharedWorkerContext(raw) {
15866
15912
  choices: Array.isArray(question?.choices) ? question.choices : void 0
15867
15913
  };
15868
15914
  }).filter((question) => !!question).slice(0, 6) : [];
15915
+ const pendingStepRefRaw = raw.pendingAskUser?.stepRef;
15916
+ const pendingStepRef = pendingStepRefRaw && Number.isFinite(Number(pendingStepRefRaw.stepIndex)) && Number(pendingStepRefRaw.stepIndex) >= 0 && Number.isFinite(Number(pendingStepRefRaw.functionIndex)) && Number(pendingStepRefRaw.functionIndex) >= 0 ? {
15917
+ stepIndex: Number(pendingStepRefRaw.stepIndex),
15918
+ functionIndex: Number(pendingStepRefRaw.functionIndex),
15919
+ ...typeof pendingStepRefRaw.accTreeId === "string" && pendingStepRefRaw.accTreeId.trim() ? { accTreeId: pendingStepRefRaw.accTreeId.trim() } : {}
15920
+ } : void 0;
15869
15921
  const normalizeEntries = (items) => items.slice(-80).map((item) => ({
15870
15922
  role: typeof item?.role === "string" ? item.role : "assistant",
15871
15923
  content: typeof item?.content === "string" ? item.content : ""
@@ -15873,15 +15925,20 @@ function sanitizeSharedWorkerContext(raw) {
15873
15925
  const history2 = Array.isArray(raw.history) ? normalizeEntries(raw.history) : [];
15874
15926
  const plannerHistory = cloneUnknownArrayTail(raw.plannerHistory, 40);
15875
15927
  const agentPrevSteps = cloneUnknownArrayTail(raw.agentPrevSteps, 80);
15928
+ const rootUserInput = typeof raw.rootUserInput === "string" ? raw.rootUserInput.trim() : "";
15876
15929
  return {
15877
15930
  trajectoryId: typeof raw.trajectoryId === "string" ? raw.trajectoryId : void 0,
15931
+ taskBoundaryId: typeof raw.taskBoundaryId === "string" && raw.taskBoundaryId.trim() ? raw.taskBoundaryId.trim() : void 0,
15932
+ rootUserInput: rootUserInput || void 0,
15878
15933
  history: history2,
15879
15934
  plannerHistory,
15880
15935
  agentPrevSteps,
15881
15936
  pendingAskUser: pendingQuestions.length ? {
15882
15937
  questions: pendingQuestions,
15883
15938
  source: raw.pendingAskUser?.source === "planner" ? "planner" : "act",
15884
- askedAt: Number(raw.pendingAskUser?.askedAt) || now()
15939
+ askedAt: Number(raw.pendingAskUser?.askedAt) || now(),
15940
+ boundaryId: typeof raw.pendingAskUser?.boundaryId === "string" && raw.pendingAskUser.boundaryId.trim() ? raw.pendingAskUser.boundaryId.trim() : void 0,
15941
+ ...pendingStepRef ? { stepRef: pendingStepRef } : {}
15885
15942
  } : void 0,
15886
15943
  updatedAt: Number(raw.updatedAt) || now()
15887
15944
  };
@@ -15926,7 +15983,13 @@ function sanitizeSharedState(raw, siteId, sessionId) {
15926
15983
  openedAt: Number(entry?.openedAt) || now(),
15927
15984
  updatedAt: Number(entry?.updatedAt) || now(),
15928
15985
  external: !!entry?.external,
15929
- openerRuntimeId: typeof entry?.openerRuntimeId === "string" ? entry.openerRuntimeId : void 0
15986
+ openerRuntimeId: typeof entry?.openerRuntimeId === "string" ? entry.openerRuntimeId : void 0,
15987
+ detachedAt: Number(entry?.detachedAt) || void 0,
15988
+ detachedReason: entry?.detachedReason === "navigation_handoff" || entry?.detachedReason === "tab_close" || entry?.detachedReason === "opened_pending_attach" || entry?.detachedReason === "unknown" ? entry.detachedReason : void 0,
15989
+ handoffId: typeof entry?.handoffId === "string" ? entry.handoffId : void 0,
15990
+ handoffRunId: typeof entry?.handoffRunId === "string" ? entry.handoffRunId : void 0,
15991
+ handoffTargetUrl: typeof entry?.handoffTargetUrl === "string" ? entry.handoffTargetUrl : void 0,
15992
+ handoffCreatedAt: Number(entry?.handoffCreatedAt) || void 0
15930
15993
  })).filter((entry) => !!entry.logicalTabId) : [],
15931
15994
  nextLogicalTabId: Math.max(1, Number(raw.nextLogicalTabId) || 1),
15932
15995
  activeLogicalTabId: Number(raw.activeLogicalTabId) || void 0,
@@ -16012,35 +16075,176 @@ var SessionCoordinator = class _SessionCoordinator {
16012
16075
  lastNotifiedRole;
16013
16076
  roleChangeTimer = null;
16014
16077
  static ROLE_CHANGE_DEBOUNCE_MS = 200;
16078
+ tabFreshnessTs(tab) {
16079
+ return Math.max(Number(tab.updatedAt) || 0, Number(tab.openedAt) || 0, Number(tab.detachedAt) || 0);
16080
+ }
16081
+ isBetterTabCandidate(next, current) {
16082
+ if (!current)
16083
+ return true;
16084
+ const nextFreshness = this.tabFreshnessTs(next);
16085
+ const currentFreshness = this.tabFreshnessTs(current);
16086
+ if (nextFreshness !== currentFreshness)
16087
+ return nextFreshness > currentFreshness;
16088
+ if (!!next.runtimeId !== !!current.runtimeId)
16089
+ return !!next.runtimeId;
16090
+ return next.logicalTabId < current.logicalTabId;
16091
+ }
16092
+ isRuntimeTabFresh(tab, nowMs2) {
16093
+ if (!tab.runtimeId)
16094
+ return false;
16095
+ if (tab.runtimeId === this.runtimeId)
16096
+ return true;
16097
+ return nowMs2 - this.tabFreshnessTs(tab) <= STALE_RUNTIME_TAB_MS;
16098
+ }
16099
+ isTabVisibleInScope(tab, nowMs2, scope) {
16100
+ if (tab.runtimeId) {
16101
+ return this.isRuntimeTabFresh(tab, nowMs2);
16102
+ }
16103
+ if (tab.external) {
16104
+ return nowMs2 - this.tabFreshnessTs(tab) <= STALE_DETACHED_EXTERNAL_TAB_MS;
16105
+ }
16106
+ if (tab.detachedReason === "opened_pending_attach") {
16107
+ return nowMs2 - this.tabFreshnessTs(tab) <= STALE_PENDING_ATTACH_TAB_MS;
16108
+ }
16109
+ if (scope === "context") {
16110
+ return false;
16111
+ }
16112
+ if (tab.detachedReason === "navigation_handoff") {
16113
+ return nowMs2 - this.tabFreshnessTs(tab) <= STALE_NAVIGATION_HANDOFF_TAB_MS;
16114
+ }
16115
+ return nowMs2 - this.tabFreshnessTs(tab) <= STALE_DETACHED_TAB_MS;
16116
+ }
16117
+ normalizeDraftTabs(draft) {
16118
+ const byLogicalTabId = /* @__PURE__ */ new Map();
16119
+ for (const tab of draft.tabs) {
16120
+ if (!Number.isFinite(Number(tab.logicalTabId)) || Number(tab.logicalTabId) <= 0)
16121
+ continue;
16122
+ const existing = byLogicalTabId.get(tab.logicalTabId);
16123
+ if (this.isBetterTabCandidate(tab, existing)) {
16124
+ byLogicalTabId.set(tab.logicalTabId, tab);
16125
+ }
16126
+ }
16127
+ const dedupedByLogical = [];
16128
+ const emittedLogical = /* @__PURE__ */ new Set();
16129
+ for (const tab of draft.tabs) {
16130
+ if (emittedLogical.has(tab.logicalTabId))
16131
+ continue;
16132
+ const chosen = byLogicalTabId.get(tab.logicalTabId);
16133
+ if (!chosen)
16134
+ continue;
16135
+ if (chosen === tab) {
16136
+ dedupedByLogical.push(chosen);
16137
+ emittedLogical.add(chosen.logicalTabId);
16138
+ }
16139
+ }
16140
+ for (const [logicalTabId, tab] of byLogicalTabId.entries()) {
16141
+ if (emittedLogical.has(logicalTabId))
16142
+ continue;
16143
+ dedupedByLogical.push(tab);
16144
+ emittedLogical.add(logicalTabId);
16145
+ }
16146
+ const byRuntimeId = /* @__PURE__ */ new Map();
16147
+ for (const tab of dedupedByLogical) {
16148
+ if (!tab.runtimeId)
16149
+ continue;
16150
+ const existing = byRuntimeId.get(tab.runtimeId);
16151
+ if (this.isBetterTabCandidate(tab, existing)) {
16152
+ byRuntimeId.set(tab.runtimeId, tab);
16153
+ }
16154
+ }
16155
+ draft.tabs = dedupedByLogical.filter((tab) => !tab.runtimeId || byRuntimeId.get(tab.runtimeId) === tab).sort((a, b) => a.logicalTabId - b.logicalTabId);
16156
+ if (!draft.tabs.length) {
16157
+ draft.activeLogicalTabId = void 0;
16158
+ draft.nextLogicalTabId = 1;
16159
+ return;
16160
+ }
16161
+ const nowMs2 = now();
16162
+ const currentActive = draft.activeLogicalTabId ? draft.tabs.find((tab) => tab.logicalTabId === draft.activeLogicalTabId) : void 0;
16163
+ const localAttached = draft.tabs.find((tab) => tab.runtimeId === this.runtimeId);
16164
+ const liveTabs = draft.tabs.filter((tab) => this.isRuntimeTabFresh(tab, nowMs2));
16165
+ const freshestLiveTab = [...liveTabs].sort((a, b) => this.tabFreshnessTs(b) - this.tabFreshnessTs(a))[0];
16166
+ let nextActiveLogicalTabId = currentActive?.logicalTabId;
16167
+ if (!currentActive) {
16168
+ nextActiveLogicalTabId = localAttached?.logicalTabId || freshestLiveTab?.logicalTabId || draft.tabs[0]?.logicalTabId;
16169
+ } else if (!currentActive.runtimeId || !this.isRuntimeTabFresh(currentActive, nowMs2)) {
16170
+ nextActiveLogicalTabId = localAttached?.logicalTabId || freshestLiveTab?.logicalTabId || currentActive.logicalTabId;
16171
+ }
16172
+ draft.activeLogicalTabId = nextActiveLogicalTabId;
16173
+ const maxLogicalTabId = draft.tabs.reduce((max, tab) => Math.max(max, tab.logicalTabId), 0);
16174
+ draft.nextLogicalTabId = Math.max(Number(draft.nextLogicalTabId) || 1, maxLogicalTabId + 1);
16175
+ }
16015
16176
  pruneDetachedTabs(draft, options) {
16016
16177
  const dropRuntimeDetached = !!options?.dropRuntimeDetached;
16017
16178
  const dropAllDetachedExternal = !!options?.dropAllDetachedExternal;
16179
+ const keepOnlyActiveLiveTab = !!options?.keepOnlyActiveLiveTab;
16180
+ const keepRecentExternalPlaceholders = !!options?.keepRecentExternalPlaceholders;
16018
16181
  const nowMs2 = now();
16019
- const before = draft.tabs.length;
16020
16182
  draft.tabs = draft.tabs.filter((tab) => {
16021
16183
  if (tab.runtimeId) {
16022
- if (tab.runtimeId === this.runtimeId)
16023
- return true;
16024
- return nowMs2 - Math.max(tab.updatedAt || 0, tab.openedAt || 0) <= STALE_RUNTIME_TAB_MS;
16184
+ return this.isRuntimeTabFresh(tab, nowMs2);
16025
16185
  }
16026
- if (dropRuntimeDetached && tab.openerRuntimeId === this.runtimeId) {
16186
+ if (dropRuntimeDetached && !tab.external && tab.openerRuntimeId === this.runtimeId) {
16027
16187
  return false;
16028
16188
  }
16029
16189
  if (tab.external) {
16030
16190
  if (dropAllDetachedExternal)
16031
16191
  return false;
16032
- return nowMs2 - Math.max(tab.updatedAt || 0, tab.openedAt || 0) <= STALE_DETACHED_EXTERNAL_TAB_MS;
16192
+ return nowMs2 - this.tabFreshnessTs(tab) <= STALE_DETACHED_EXTERNAL_TAB_MS;
16033
16193
  }
16034
- return nowMs2 - Math.max(tab.updatedAt || 0, tab.openedAt || 0) <= STALE_DETACHED_TAB_MS;
16035
- });
16036
- if (before !== draft.tabs.length) {
16037
- if (draft.activeLogicalTabId && !draft.tabs.some((tab) => tab.logicalTabId === draft.activeLogicalTabId)) {
16038
- draft.activeLogicalTabId = this.localLogicalTabId || draft.tabs[0]?.logicalTabId;
16194
+ const ageMs = nowMs2 - this.tabFreshnessTs(tab);
16195
+ if (tab.detachedReason === "navigation_handoff") {
16196
+ return ageMs <= STALE_NAVIGATION_HANDOFF_TAB_MS;
16039
16197
  }
16040
- if (draft.nextLogicalTabId <= (draft.tabs.at(-1)?.logicalTabId ?? 0)) {
16041
- draft.nextLogicalTabId = draft.tabs.reduce((max, tab) => Math.max(max, tab.logicalTabId), 0) + 1;
16198
+ if (tab.detachedReason === "opened_pending_attach") {
16199
+ return ageMs <= STALE_PENDING_ATTACH_TAB_MS;
16042
16200
  }
16201
+ return ageMs <= STALE_DETACHED_TAB_MS;
16202
+ });
16203
+ if (keepOnlyActiveLiveTab) {
16204
+ this.normalizeDraftTabs(draft);
16205
+ const liveTabs = draft.tabs.filter((tab) => this.isRuntimeTabFresh(tab, nowMs2));
16206
+ const activeLiveTab = liveTabs.find((tab) => tab.logicalTabId === draft.activeLogicalTabId);
16207
+ const localLiveTab = liveTabs.find((tab) => tab.runtimeId === this.runtimeId);
16208
+ const fallbackLiveTab = [...liveTabs].sort((a, b) => this.tabFreshnessTs(b) - this.tabFreshnessTs(a))[0];
16209
+ const keepLiveTabId = activeLiveTab?.logicalTabId || localLiveTab?.logicalTabId || fallbackLiveTab?.logicalTabId;
16210
+ draft.tabs = draft.tabs.filter((tab) => {
16211
+ if (tab.runtimeId) {
16212
+ return !!keepLiveTabId && tab.logicalTabId === keepLiveTabId;
16213
+ }
16214
+ if (tab.external) {
16215
+ return keepRecentExternalPlaceholders && nowMs2 - this.tabFreshnessTs(tab) <= STALE_DETACHED_EXTERNAL_TAB_MS;
16216
+ }
16217
+ return false;
16218
+ });
16219
+ draft.activeLogicalTabId = keepLiveTabId || draft.tabs.find((tab) => tab.runtimeId)?.logicalTabId || draft.tabs[0]?.logicalTabId;
16043
16220
  }
16221
+ this.normalizeDraftTabs(draft);
16222
+ }
16223
+ resetDraftToSingleCurrentTab(draft, url, title) {
16224
+ const nowTs = now();
16225
+ const normalizedUrl = normalizeUrl2(url);
16226
+ const existingLocal = draft.tabs.find((tab) => tab.runtimeId === this.runtimeId);
16227
+ const openedAt = Number(existingLocal?.openedAt) || nowTs;
16228
+ draft.tabs = [
16229
+ {
16230
+ logicalTabId: 1,
16231
+ runtimeId: this.runtimeId,
16232
+ url: normalizedUrl || existingLocal?.url || "",
16233
+ title: title || existingLocal?.title,
16234
+ openedAt,
16235
+ updatedAt: nowTs,
16236
+ external: false,
16237
+ detachedAt: void 0,
16238
+ detachedReason: void 0
16239
+ }
16240
+ ];
16241
+ draft.activeLogicalTabId = 1;
16242
+ draft.nextLogicalTabId = 2;
16243
+ draft.lease = {
16244
+ holderRuntimeId: this.runtimeId,
16245
+ expiresAt: nowTs + this.leaseMs,
16246
+ updatedAt: nowTs
16247
+ };
16044
16248
  }
16045
16249
  constructor(options) {
16046
16250
  this.siteId = options.siteId;
@@ -16056,15 +16260,16 @@ var SessionCoordinator = class _SessionCoordinator {
16056
16260
  this.key = `${SHARED_KEY_PREFIX}${this.siteId}:${this.sessionId}`;
16057
16261
  this.channelName = `${SHARED_CHANNEL_PREFIX}${this.siteId}:${this.sessionId}`;
16058
16262
  this.state = this.loadState();
16263
+ this.normalizeDraftTabs(this.state);
16059
16264
  }
16060
- start() {
16265
+ start(initialHandoff) {
16061
16266
  if (this.started)
16062
16267
  return;
16063
16268
  this.started = true;
16064
16269
  this.mutate("local", (draft) => {
16065
16270
  this.pruneDetachedTabs(draft, { dropRuntimeDetached: true, dropAllDetachedExternal: true });
16066
16271
  });
16067
- this.registerCurrentTab(window.location.href, document.title || void 0);
16272
+ this.registerCurrentTab(window.location.href, document.title || void 0, initialHandoff);
16068
16273
  this.claimLease(false);
16069
16274
  if (!this.state.task) {
16070
16275
  this.mutate("local", (draft) => {
@@ -16227,25 +16432,23 @@ var SessionCoordinator = class _SessionCoordinator {
16227
16432
  }
16228
16433
  return true;
16229
16434
  }
16230
- listTabs() {
16435
+ listTabs(options) {
16436
+ const scope = options?.scope === "all" ? "all" : "context";
16231
16437
  const nowMs2 = now();
16232
- return this.state.tabs.filter((tab) => {
16233
- if (tab.runtimeId) {
16234
- if (tab.runtimeId === this.runtimeId)
16235
- return true;
16236
- return nowMs2 - Math.max(tab.updatedAt || 0, tab.openedAt || 0) <= STALE_RUNTIME_TAB_MS;
16237
- }
16238
- if (tab.external) {
16239
- return nowMs2 - Math.max(tab.updatedAt || 0, tab.openedAt || 0) <= STALE_DETACHED_EXTERNAL_TAB_MS;
16240
- }
16241
- return nowMs2 - Math.max(tab.updatedAt || 0, tab.openedAt || 0) <= STALE_DETACHED_TAB_MS;
16242
- });
16438
+ return this.state.tabs.filter((tab) => this.isTabVisibleInScope(tab, nowMs2, scope));
16243
16439
  }
16244
16440
  pruneTabs(options) {
16245
16441
  this.mutate("local", (draft) => {
16246
16442
  this.pruneDetachedTabs(draft, options);
16247
16443
  });
16248
16444
  }
16445
+ resetTabsToCurrent(url, title) {
16446
+ this.mutate("local", (draft) => {
16447
+ this.resetDraftToSingleCurrentTab(draft, url, title);
16448
+ });
16449
+ this.notifyRoleChange();
16450
+ return this.localLogicalTabId || 1;
16451
+ }
16249
16452
  startNewTask(task) {
16250
16453
  const startedAt = Number(task.startedAt) || now();
16251
16454
  const nextTask = {
@@ -16265,7 +16468,7 @@ var SessionCoordinator = class _SessionCoordinator {
16265
16468
  draft.uiStatus = void 0;
16266
16469
  draft.activeRun = void 0;
16267
16470
  draft.workerContext = void 0;
16268
- this.pruneDetachedTabs(draft, { dropAllDetachedExternal: true });
16471
+ this.resetDraftToSingleCurrentTab(draft, window.location.href, document.title || void 0);
16269
16472
  });
16270
16473
  return nextTask;
16271
16474
  }
@@ -16405,28 +16608,56 @@ var SessionCoordinator = class _SessionCoordinator {
16405
16608
  requestControl() {
16406
16609
  return this.claimLease(true);
16407
16610
  }
16408
- registerCurrentTab(url, title) {
16611
+ registerCurrentTab(url, title, handoff) {
16409
16612
  const normalizedUrl = normalizeUrl2(url);
16613
+ const handoffId = typeof handoff?.handoffId === "string" ? handoff.handoffId.trim() : "";
16614
+ const handoffTabId = Number(handoff?.sourceLogicalTabId);
16410
16615
  let nextLocalTabId;
16411
16616
  this.mutate("local", (draft) => {
16617
+ const nowTs = now();
16618
+ const leaseValid = !!(draft.lease && draft.lease.expiresAt > nowTs);
16619
+ const controllerRuntimeId = leaseValid ? draft.lease?.holderRuntimeId : void 0;
16620
+ const shouldPreferLocalAsActive = !controllerRuntimeId || controllerRuntimeId === this.runtimeId;
16412
16621
  const existing = draft.tabs.find((tab) => tab.runtimeId === this.runtimeId);
16413
16622
  if (existing) {
16414
16623
  existing.url = normalizedUrl || existing.url;
16415
16624
  existing.title = title || existing.title;
16416
- existing.updatedAt = now();
16625
+ existing.updatedAt = nowTs;
16626
+ existing.detachedAt = void 0;
16627
+ existing.detachedReason = void 0;
16628
+ existing.handoffId = void 0;
16629
+ existing.handoffRunId = void 0;
16630
+ existing.handoffTargetUrl = void 0;
16631
+ existing.handoffCreatedAt = void 0;
16417
16632
  nextLocalTabId = existing.logicalTabId;
16418
16633
  } else {
16419
- let adopted = draft.tabs.find((tab) => !tab.runtimeId && tab.url === normalizedUrl && now() - tab.openedAt < 18e4);
16634
+ let adopted;
16635
+ if (handoffId) {
16636
+ adopted = draft.tabs.find((tab) => {
16637
+ if (tab.runtimeId)
16638
+ return false;
16639
+ if (String(tab.handoffId || "").trim() !== handoffId)
16640
+ return false;
16641
+ if (Number.isFinite(handoffTabId) && handoffTabId > 0 && tab.logicalTabId !== handoffTabId)
16642
+ return false;
16643
+ const ageMs = nowTs - Number(tab.handoffCreatedAt || tab.updatedAt || tab.openedAt || 0);
16644
+ return ageMs >= 0 && ageMs <= 3e4;
16645
+ });
16646
+ }
16420
16647
  if (!adopted) {
16421
- const currentHost = extractHostname2(normalizedUrl);
16422
- const candidates = draft.tabs.filter((tab) => !tab.runtimeId && currentHost && extractHostname2(tab.url) === currentHost && now() - tab.updatedAt < 1e4).sort((a, b) => b.updatedAt - a.updatedAt);
16423
- adopted = candidates[0];
16648
+ adopted = draft.tabs.find((tab) => !tab.runtimeId && !tab.external && tab.url === normalizedUrl && nowTs - tab.openedAt < 18e4);
16424
16649
  }
16425
16650
  if (adopted) {
16426
16651
  adopted.runtimeId = this.runtimeId;
16427
16652
  adopted.url = normalizedUrl || adopted.url;
16428
16653
  adopted.title = title || adopted.title;
16429
- adopted.updatedAt = now();
16654
+ adopted.updatedAt = nowTs;
16655
+ adopted.detachedAt = void 0;
16656
+ adopted.detachedReason = void 0;
16657
+ adopted.handoffId = void 0;
16658
+ adopted.handoffRunId = void 0;
16659
+ adopted.handoffTargetUrl = void 0;
16660
+ adopted.handoffCreatedAt = void 0;
16430
16661
  nextLocalTabId = adopted.logicalTabId;
16431
16662
  } else {
16432
16663
  const logicalTabId = draft.nextLogicalTabId++;
@@ -16435,14 +16666,17 @@ var SessionCoordinator = class _SessionCoordinator {
16435
16666
  runtimeId: this.runtimeId,
16436
16667
  url: normalizedUrl,
16437
16668
  title,
16438
- openedAt: now(),
16439
- updatedAt: now(),
16440
- external: false
16669
+ openedAt: nowTs,
16670
+ updatedAt: nowTs,
16671
+ external: false,
16672
+ detachedAt: void 0,
16673
+ detachedReason: void 0
16441
16674
  });
16442
16675
  nextLocalTabId = logicalTabId;
16443
16676
  }
16444
16677
  }
16445
- if (!draft.activeLogicalTabId) {
16678
+ const activeEntry = draft.activeLogicalTabId ? draft.tabs.find((tab) => tab.logicalTabId === draft.activeLogicalTabId) : void 0;
16679
+ if (!activeEntry || !activeEntry.runtimeId || activeEntry.logicalTabId === nextLocalTabId || shouldPreferLocalAsActive) {
16446
16680
  draft.activeLogicalTabId = nextLocalTabId;
16447
16681
  }
16448
16682
  });
@@ -16453,11 +16687,14 @@ var SessionCoordinator = class _SessionCoordinator {
16453
16687
  const normalizedUrl = normalizeUrl2(payload.url);
16454
16688
  let logicalTabId = 0;
16455
16689
  this.mutate("local", (draft) => {
16690
+ const nowTs = now();
16456
16691
  const existing = draft.tabs.find((tab) => !tab.runtimeId && !!tab.external === !!payload.external && tab.url === normalizedUrl);
16457
16692
  if (existing) {
16458
16693
  existing.title = payload.title || existing.title;
16459
- existing.updatedAt = now();
16694
+ existing.updatedAt = nowTs;
16460
16695
  existing.openerRuntimeId = payload.openerRuntimeId || existing.openerRuntimeId;
16696
+ existing.detachedAt = existing.detachedAt || nowTs;
16697
+ existing.detachedReason = "opened_pending_attach";
16461
16698
  logicalTabId = existing.logicalTabId;
16462
16699
  return;
16463
16700
  }
@@ -16467,10 +16704,12 @@ var SessionCoordinator = class _SessionCoordinator {
16467
16704
  runtimeId: void 0,
16468
16705
  url: normalizedUrl,
16469
16706
  title: payload.title,
16470
- openedAt: now(),
16471
- updatedAt: now(),
16707
+ openedAt: nowTs,
16708
+ updatedAt: nowTs,
16472
16709
  external: !!payload.external,
16473
- openerRuntimeId: payload.openerRuntimeId
16710
+ openerRuntimeId: payload.openerRuntimeId,
16711
+ detachedAt: nowTs,
16712
+ detachedReason: "opened_pending_attach"
16474
16713
  });
16475
16714
  });
16476
16715
  return { logicalTabId };
@@ -16554,21 +16793,32 @@ var SessionCoordinator = class _SessionCoordinator {
16554
16793
  title
16555
16794
  });
16556
16795
  }
16557
- broadcastClosing() {
16796
+ broadcastClosing(handoff) {
16558
16797
  this.closing = true;
16559
- if (!this.channel)
16560
- return;
16561
16798
  this.mutate("local", (draft) => {
16799
+ const nowTs = now();
16562
16800
  const localTab = draft.tabs.find((t) => t.runtimeId === this.runtimeId);
16563
16801
  if (localTab) {
16564
16802
  localTab.runtimeId = void 0;
16565
- localTab.updatedAt = now();
16803
+ localTab.updatedAt = nowTs;
16804
+ localTab.detachedAt = nowTs;
16805
+ localTab.detachedReason = handoff ? "navigation_handoff" : "tab_close";
16806
+ localTab.handoffId = typeof handoff?.handoffId === "string" ? handoff.handoffId : void 0;
16807
+ localTab.handoffRunId = typeof handoff?.runId === "string" ? handoff.runId : void 0;
16808
+ localTab.handoffTargetUrl = typeof handoff?.targetUrl === "string" ? handoff.targetUrl : void 0;
16809
+ localTab.handoffCreatedAt = Number(handoff?.ts) || nowTs;
16810
+ }
16811
+ if (draft.lease?.holderRuntimeId === this.runtimeId) {
16812
+ draft.lease = void 0;
16566
16813
  }
16567
16814
  });
16815
+ if (!this.channel)
16816
+ return;
16568
16817
  this.channel.postMessage({
16569
16818
  type: "tab_closing",
16570
16819
  runtimeId: this.runtimeId,
16571
- logicalTabId: this.localLogicalTabId
16820
+ logicalTabId: this.localLogicalTabId,
16821
+ handoff
16572
16822
  });
16573
16823
  }
16574
16824
  isTabAlive(logicalTabId) {
@@ -16694,8 +16944,24 @@ var SessionCoordinator = class _SessionCoordinator {
16694
16944
  }
16695
16945
  handleRemoteTabClosing(payload) {
16696
16946
  const { runtimeId: closingRuntimeId } = payload;
16947
+ const handoff = payload?.handoff;
16948
+ const handoffId = typeof handoff?.handoffId === "string" && handoff.handoffId.trim() ? handoff.handoffId.trim() : void 0;
16949
+ const handoffRunId = typeof handoff?.runId === "string" && handoff.runId.trim() ? handoff.runId.trim() : void 0;
16950
+ const handoffTargetUrl = typeof handoff?.targetUrl === "string" && handoff.targetUrl.trim() ? handoff.targetUrl.trim() : void 0;
16951
+ const handoffCreatedAt = Number(handoff?.ts) || now();
16697
16952
  this.mutate("local", (draft) => {
16698
- draft.tabs = draft.tabs.filter((t) => t.runtimeId !== closingRuntimeId);
16953
+ const nowTs = now();
16954
+ const tab = draft.tabs.find((t) => t.runtimeId === closingRuntimeId);
16955
+ if (tab) {
16956
+ tab.runtimeId = void 0;
16957
+ tab.updatedAt = nowTs;
16958
+ tab.detachedAt = nowTs;
16959
+ tab.detachedReason = handoffId ? "navigation_handoff" : "tab_close";
16960
+ tab.handoffId = handoffId;
16961
+ tab.handoffRunId = handoffRunId;
16962
+ tab.handoffTargetUrl = handoffTargetUrl;
16963
+ tab.handoffCreatedAt = handoffCreatedAt;
16964
+ }
16699
16965
  if (draft.lease?.holderRuntimeId === closingRuntimeId) {
16700
16966
  draft.lease = void 0;
16701
16967
  }
@@ -16706,7 +16972,7 @@ var SessionCoordinator = class _SessionCoordinator {
16706
16972
  draft.activeRun = void 0;
16707
16973
  }
16708
16974
  if (draft.activeLogicalTabId && !draft.tabs.some((t) => t.logicalTabId === draft.activeLogicalTabId)) {
16709
- draft.activeLogicalTabId = void 0;
16975
+ draft.activeLogicalTabId = this.localLogicalTabId || draft.tabs[0]?.logicalTabId;
16710
16976
  }
16711
16977
  });
16712
16978
  this.notifyRoleChange();
@@ -16722,21 +16988,32 @@ var SessionCoordinator = class _SessionCoordinator {
16722
16988
  currentTab.url = currentUrl || currentTab.url;
16723
16989
  currentTab.title = currentTitle || currentTab.title;
16724
16990
  currentTab.updatedAt = now();
16991
+ currentTab.detachedAt = void 0;
16992
+ currentTab.detachedReason = void 0;
16725
16993
  } else {
16994
+ const nowTs = now();
16726
16995
  const logicalTabId = draft.nextLogicalTabId++;
16727
16996
  draft.tabs.push({
16728
16997
  logicalTabId,
16729
16998
  runtimeId: this.runtimeId,
16730
16999
  url: currentUrl,
16731
17000
  title: currentTitle,
16732
- openedAt: now(),
16733
- updatedAt: now(),
16734
- external: false
17001
+ openedAt: nowTs,
17002
+ updatedAt: nowTs,
17003
+ external: false,
17004
+ detachedAt: void 0,
17005
+ detachedReason: void 0
16735
17006
  });
16736
17007
  }
16737
17008
  const lease = draft.lease;
16738
17009
  const leaseExpired = !lease || lease.expiresAt <= now();
16739
- if (leaseExpired || lease?.holderRuntimeId === this.runtimeId) {
17010
+ const remoteHolderRuntimeId = lease && lease.holderRuntimeId !== this.runtimeId && lease.expiresAt > now() ? lease.holderRuntimeId : void 0;
17011
+ const remoteHolderTab = remoteHolderRuntimeId ? draft.tabs.find((tab) => tab.runtimeId === remoteHolderRuntimeId) : void 0;
17012
+ const remoteHolderAlive = !!(remoteHolderTab && remoteHolderTab.updatedAt > now() - 2 * this.heartbeatMs);
17013
+ const hasRemoteActiveRun2 = !!(draft.activeRun?.runtimeId && draft.activeRun.runtimeId !== this.runtimeId);
17014
+ const hasRemoteWorkflowLock = !!(draft.workflowLock && draft.workflowLock.runtimeId !== this.runtimeId && draft.workflowLock.expiresAt > now());
17015
+ const shouldReclaimFromObserver = !!(remoteHolderRuntimeId && !remoteHolderAlive && !hasRemoteActiveRun2 && !hasRemoteWorkflowLock);
17016
+ if (leaseExpired || lease?.holderRuntimeId === this.runtimeId || shouldReclaimFromObserver) {
16740
17017
  draft.lease = {
16741
17018
  holderRuntimeId: this.runtimeId,
16742
17019
  expiresAt: now() + this.leaseMs,
@@ -16755,6 +17032,7 @@ var SessionCoordinator = class _SessionCoordinator {
16755
17032
  }
16756
17033
  reloadFromStorage() {
16757
17034
  const fresh = this.loadState();
17035
+ this.normalizeDraftTabs(fresh);
16758
17036
  if (fresh.seq > this.state.seq || fresh.seq === this.state.seq && fresh.updatedAt > this.state.updatedAt) {
16759
17037
  this.state = fresh;
16760
17038
  this.syncLocalLogicalTabId();
@@ -16822,6 +17100,7 @@ var SessionCoordinator = class _SessionCoordinator {
16822
17100
  }
16823
17101
  const draft = sanitizeSharedState(this.state, this.siteId, this.sessionId);
16824
17102
  updater(draft);
17103
+ this.normalizeDraftTabs(draft);
16825
17104
  draft.seq = Math.max(1, Number(draft.seq) || 1) + (source === "local" ? 1 : 0);
16826
17105
  draft.updatedAt = now();
16827
17106
  this.state = draft;
@@ -16842,6 +17121,7 @@ var SessionCoordinator = class _SessionCoordinator {
16842
17121
  if (incoming.seq === this.state.seq && incoming.updatedAt <= this.state.updatedAt)
16843
17122
  return;
16844
17123
  this.pruneDetachedTabs(incoming);
17124
+ this.normalizeDraftTabs(incoming);
16845
17125
  this.state = incoming;
16846
17126
  this.syncLocalLogicalTabId();
16847
17127
  this.onStateChange?.(this.state, "remote");
@@ -17340,6 +17620,47 @@ function clearCrossDomainResumeCookie(siteId) {
17340
17620
  }
17341
17621
  }
17342
17622
 
17623
+ // dist/taskBoundaryGuards.js
17624
+ function normalizeTaskBoundaryId(input) {
17625
+ const normalized = String(input || "").trim();
17626
+ return normalized || void 0;
17627
+ }
17628
+ function shouldAdoptIncomingBoundary(params) {
17629
+ const incoming = normalizeTaskBoundaryId(params.incomingBoundaryId);
17630
+ const current = normalizeTaskBoundaryId(params.currentBoundaryId);
17631
+ if (!incoming)
17632
+ return false;
17633
+ if (incoming === current)
17634
+ return false;
17635
+ if (!current)
17636
+ return params.allowBootstrapAdoption !== false;
17637
+ if (params.taskEpochAdvanced === true && params.hasPendingRun !== true) {
17638
+ return true;
17639
+ }
17640
+ return false;
17641
+ }
17642
+ function shouldAcceptWorkerSnapshot(params) {
17643
+ const incoming = normalizeTaskBoundaryId(params.incomingBoundaryId);
17644
+ const current = normalizeTaskBoundaryId(params.currentBoundaryId);
17645
+ if (incoming && current && incoming === current) {
17646
+ return { accept: true, reason: "match" };
17647
+ }
17648
+ if (!incoming) {
17649
+ if (!current && params.allowBootstrapAdoption !== false) {
17650
+ return { accept: true, reason: "bootstrap_adopt" };
17651
+ }
17652
+ return { accept: false, reason: "missing_incoming" };
17653
+ }
17654
+ if (shouldAdoptIncomingBoundary(params)) {
17655
+ return {
17656
+ accept: true,
17657
+ adoptedBoundaryId: incoming,
17658
+ reason: current ? "epoch_adopt" : "bootstrap_adopt"
17659
+ };
17660
+ }
17661
+ return { accept: false, reason: "mismatch" };
17662
+ }
17663
+
17343
17664
  // dist/taskLifecycleGuards.js
17344
17665
  function shouldStartFreshTask(taskStatus) {
17345
17666
  return taskStatus === "completed" || taskStatus === "ended";
@@ -17368,7 +17689,13 @@ function shouldClearPendingFromSharedState(params) {
17368
17689
  return false;
17369
17690
  }
17370
17691
  function shouldIgnoreRunScopedMessage(params) {
17371
- const { type, messageRunId, pendingRunId, sharedActiveRunId, taskStatus, ignoredRunIds: ignoredRunIds2 } = params;
17692
+ const { type, messageRunId, messageTaskBoundaryId, currentTaskBoundaryId: currentTaskBoundaryId2, pendingRunId, sharedActiveRunId, taskStatus, ignoredRunIds: ignoredRunIds2 } = params;
17693
+ const currentBoundary = normalizeTaskBoundaryId(currentTaskBoundaryId2);
17694
+ const messageBoundary = normalizeTaskBoundaryId(messageTaskBoundaryId);
17695
+ if ((type === "run_started" || type === "run_completed") && currentBoundary) {
17696
+ if (!messageBoundary || messageBoundary !== currentBoundary)
17697
+ return true;
17698
+ }
17372
17699
  if (!messageRunId && type !== "run_started")
17373
17700
  return false;
17374
17701
  if (messageRunId && ignoredRunIds2?.has(messageRunId))
@@ -17451,6 +17778,8 @@ var suppressCheckpointSync = false;
17451
17778
  var currentMode = "controller";
17452
17779
  var workerReady = false;
17453
17780
  var autoResumeAttempted = false;
17781
+ var agentNavigationPending = false;
17782
+ var currentTaskBoundaryId = "";
17454
17783
  var runSafetyTimer = null;
17455
17784
  var unloadHandlerInstalled = false;
17456
17785
  var pendingTaskSuggestion = null;
@@ -17503,6 +17832,106 @@ function createId2(prefix) {
17503
17832
  return `${prefix}-${Date.now()}-${Math.floor(Math.random() * 1e5)}`;
17504
17833
  }
17505
17834
  }
17835
+ function createTaskBoundaryId() {
17836
+ return createId2("task-boundary");
17837
+ }
17838
+ function sanitizeNavigationHandoff(input) {
17839
+ if (!input || typeof input !== "object")
17840
+ return void 0;
17841
+ const handoffId = typeof input.handoffId === "string" ? input.handoffId.trim() : "";
17842
+ const targetUrl = typeof input.targetUrl === "string" ? input.targetUrl.trim() : "";
17843
+ if (!handoffId || !targetUrl)
17844
+ return void 0;
17845
+ const sourceLogicalTabId = Number(input.sourceLogicalTabId);
17846
+ return {
17847
+ handoffId,
17848
+ targetUrl,
17849
+ sourceLogicalTabId: Number.isFinite(sourceLogicalTabId) && sourceLogicalTabId > 0 ? sourceLogicalTabId : void 0,
17850
+ runId: typeof input.runId === "string" && input.runId.trim() ? input.runId.trim() : void 0,
17851
+ createdAt: Number(input.createdAt) || Date.now(),
17852
+ consumed: input.consumed === true
17853
+ };
17854
+ }
17855
+ function toSharedNavigationHandoff(input) {
17856
+ if (!input)
17857
+ return void 0;
17858
+ return {
17859
+ handoffId: input.handoffId,
17860
+ targetUrl: input.targetUrl,
17861
+ sourceLogicalTabId: input.sourceLogicalTabId,
17862
+ runId: input.runId,
17863
+ ts: input.createdAt
17864
+ };
17865
+ }
17866
+ function toPersistedNavigationHandoff(intent) {
17867
+ return {
17868
+ handoffId: intent.handoffId,
17869
+ targetUrl: intent.targetUrl,
17870
+ sourceLogicalTabId: intent.sourceLogicalTabId,
17871
+ runId: intent.runId,
17872
+ createdAt: Number(intent.ts) || Date.now(),
17873
+ consumed: false
17874
+ };
17875
+ }
17876
+ function resolveExistingTaskBoundaryIdFromState(state) {
17877
+ const pendingCandidate = typeof state?.pendingRun?.taskBoundaryId === "string" ? state.pendingRun.taskBoundaryId : void 0;
17878
+ if (pendingCandidate)
17879
+ return normalizeTaskBoundaryId(pendingCandidate);
17880
+ const workerCandidate = typeof state?.workerState?.taskBoundaryId === "string" ? state.workerState.taskBoundaryId : void 0;
17881
+ if (workerCandidate)
17882
+ return normalizeTaskBoundaryId(workerCandidate);
17883
+ return void 0;
17884
+ }
17885
+ function resolveTaskBoundaryIdFromState(state) {
17886
+ const existing = resolveExistingTaskBoundaryIdFromState(state);
17887
+ if (existing)
17888
+ return existing;
17889
+ return createTaskBoundaryId();
17890
+ }
17891
+ function resolveCurrentTaskBoundaryCandidate() {
17892
+ return normalizeTaskBoundaryId(currentTaskBoundaryId) || resolveExistingTaskBoundaryIdFromState(runtimeState);
17893
+ }
17894
+ function shouldAcceptIncomingWorkerBoundary(params) {
17895
+ const decision = shouldAcceptWorkerSnapshot({
17896
+ source: params.source,
17897
+ incomingBoundaryId: params.incomingBoundaryId,
17898
+ currentBoundaryId: resolveCurrentTaskBoundaryCandidate(),
17899
+ taskEpochAdvanced: params.taskEpochAdvanced,
17900
+ hasPendingRun: !!runtimeState?.pendingRun,
17901
+ taskStatus: runtimeState?.activeTask?.status,
17902
+ allowBootstrapAdoption: params.allowBootstrapAdoption
17903
+ });
17904
+ if (!decision.accept)
17905
+ return false;
17906
+ if (decision.adoptedBoundaryId) {
17907
+ currentTaskBoundaryId = decision.adoptedBoundaryId;
17908
+ }
17909
+ return true;
17910
+ }
17911
+ function getUnconsumedNavigationHandoff(maxAgeMs = 12e4) {
17912
+ const handoff = sanitizeNavigationHandoff(runtimeState?.lastNavigationHandoff);
17913
+ if (!handoff || handoff.consumed === true)
17914
+ return void 0;
17915
+ const ageMs = Date.now() - Number(handoff.createdAt || 0);
17916
+ if (!Number.isFinite(ageMs) || ageMs < -5e3 || ageMs > maxAgeMs)
17917
+ return void 0;
17918
+ return handoff;
17919
+ }
17920
+ function syncCurrentTaskBoundaryId(options) {
17921
+ const pendingBoundary = sanitizePendingRun(runtimeState?.pendingRun)?.taskBoundaryId;
17922
+ if (pendingBoundary) {
17923
+ currentTaskBoundaryId = pendingBoundary;
17924
+ return;
17925
+ }
17926
+ const workerBoundary = sanitizeWorkerState(runtimeState?.workerState)?.taskBoundaryId;
17927
+ if (workerBoundary) {
17928
+ currentTaskBoundaryId = workerBoundary;
17929
+ return;
17930
+ }
17931
+ if (!currentTaskBoundaryId || options?.rotateIfMissing) {
17932
+ currentTaskBoundaryId = createTaskBoundaryId();
17933
+ }
17934
+ }
17506
17935
  function getOrCreateRuntimeId(siteId) {
17507
17936
  const key = `${RUNTIME_ID_PREFIX}${siteId}`;
17508
17937
  try {
@@ -17673,6 +18102,7 @@ function createDefaultRuntimeState(sessionId, rid) {
17673
18102
  executionMode: "controller",
17674
18103
  workerState: void 0,
17675
18104
  pendingRun: void 0,
18105
+ lastNavigationHandoff: void 0,
17676
18106
  taskEpoch: 1,
17677
18107
  activeTask: createDefaultTaskState(),
17678
18108
  lastRoutingDecision: void 0,
@@ -18054,9 +18484,12 @@ function shouldIgnoreRunScopedWorkerMessage(msg) {
18054
18484
  if (!RUN_SCOPED_WORKER_MESSAGE_TYPES.has(type))
18055
18485
  return false;
18056
18486
  const messageRunId = typeof msg?.runId === "string" && msg.runId ? msg.runId : void 0;
18487
+ const messageTaskBoundaryId = typeof msg?.taskBoundaryId === "string" && msg.taskBoundaryId ? msg.taskBoundaryId : void 0;
18057
18488
  return shouldIgnoreRunScopedMessage({
18058
18489
  type,
18059
18490
  messageRunId,
18491
+ messageTaskBoundaryId,
18492
+ currentTaskBoundaryId: resolveCurrentTaskBoundaryCandidate(),
18060
18493
  pendingRunId: getPendingRunId(),
18061
18494
  sharedActiveRunId: sessionCoordinator?.getState()?.activeRun?.runId,
18062
18495
  taskStatus: runtimeState?.activeTask?.status,
@@ -18097,14 +18530,66 @@ function normalizeAskUserQuestions(input) {
18097
18530
  }
18098
18531
  return out.slice(0, 6);
18099
18532
  }
18533
+ function buildAskUserDispatchText(text, askUserAnswers) {
18534
+ const trimmed = String(text || "").trim();
18535
+ if (trimmed)
18536
+ return trimmed;
18537
+ if (!askUserAnswers || typeof askUserAnswers !== "object")
18538
+ return "";
18539
+ const answersByKey = askUserAnswers.answersByKey && typeof askUserAnswers.answersByKey === "object" ? askUserAnswers.answersByKey : {};
18540
+ const pendingQuestions = normalizeAskUserQuestions(runtimeState?.workerState?.pendingAskUser?.questions);
18541
+ const rawKeys = Array.isArray(askUserAnswers.keys) ? askUserAnswers.keys : [];
18542
+ const resolvedKeys = rawKeys.map((key) => String(key || "").trim()).filter(Boolean);
18543
+ if (resolvedKeys.length === 0) {
18544
+ for (const question of pendingQuestions) {
18545
+ const key = String(question.key || "").trim();
18546
+ if (key)
18547
+ resolvedKeys.push(key);
18548
+ }
18549
+ }
18550
+ if (resolvedKeys.length === 0) {
18551
+ for (const key of Object.keys(answersByKey)) {
18552
+ const normalizedKey = String(key || "").trim();
18553
+ if (normalizedKey)
18554
+ resolvedKeys.push(normalizedKey);
18555
+ }
18556
+ }
18557
+ const lines = [];
18558
+ const seen = /* @__PURE__ */ new Set();
18559
+ for (const key of resolvedKeys) {
18560
+ const normalizedKey = String(key || "").trim();
18561
+ if (!normalizedKey || seen.has(normalizedKey))
18562
+ continue;
18563
+ seen.add(normalizedKey);
18564
+ const value = String(answersByKey[normalizedKey] || "").trim();
18565
+ lines.push(`${normalizedKey}: ${value || "(no answer provided)"}`);
18566
+ }
18567
+ if (lines.length > 0) {
18568
+ return lines.join("\n");
18569
+ }
18570
+ return String(askUserAnswers.rawText || "").trim();
18571
+ }
18100
18572
  function normalizeRunCompletionState(msg) {
18101
18573
  if (!msg || typeof msg !== "object") {
18102
- return { taskComplete: false, needsUserInput: false };
18574
+ return {
18575
+ taskComplete: false,
18576
+ needsUserInput: false,
18577
+ terminalState: "in_progress",
18578
+ contextResetRecommended: false
18579
+ };
18103
18580
  }
18581
+ const incomingTerminal = String(msg.terminalState || "").trim().toLowerCase();
18104
18582
  const needsUserInput = msg.needsUserInput === true;
18105
- const taskComplete = msg.taskComplete === true && !needsUserInput;
18583
+ const inferredTerminalState = incomingTerminal === "waiting_input" || incomingTerminal === "in_progress" || incomingTerminal === "completed" || incomingTerminal === "failed" ? incomingTerminal : needsUserInput ? "waiting_input" : msg.taskComplete === true ? "completed" : msg.ok === false ? "failed" : "in_progress";
18584
+ const taskComplete = inferredTerminalState === "completed" || msg.taskComplete === true && inferredTerminalState !== "waiting_input";
18106
18585
  const questions = normalizeAskUserQuestions(msg.questions);
18107
- return { taskComplete, needsUserInput, ...questions.length ? { questions } : {} };
18586
+ return {
18587
+ taskComplete,
18588
+ needsUserInput: inferredTerminalState === "waiting_input" || needsUserInput,
18589
+ terminalState: inferredTerminalState,
18590
+ contextResetRecommended: msg.contextResetRecommended === true || inferredTerminalState === "completed",
18591
+ ...questions.length ? { questions } : {}
18592
+ };
18108
18593
  }
18109
18594
  function getLatestAssistantText(runId) {
18110
18595
  if (runId && latestAssistantByRunId.has(runId)) {
@@ -18318,6 +18803,7 @@ function sanitizeTimelineEvents(input) {
18318
18803
  function sanitizeWorkerState(input) {
18319
18804
  if (!input || typeof input !== "object")
18320
18805
  return void 0;
18806
+ const taskBoundaryIdCandidate = String(input.taskBoundaryId || "").trim();
18321
18807
  const historyRaw = Array.isArray(input.history) ? input.history : [];
18322
18808
  const plannerRaw = Array.isArray(input.plannerHistory) ? input.plannerHistory : [];
18323
18809
  const agentPrevStepsRaw = Array.isArray(input.agentPrevSteps) ? input.agentPrevSteps : Array.isArray(input.lastToolPreviousSteps) ? input.lastToolPreviousSteps : [];
@@ -18325,13 +18811,24 @@ function sanitizeWorkerState(input) {
18325
18811
  const plannerHistory = cloneUnknownArrayTail2(plannerRaw, MAX_WORKER_STEPS);
18326
18812
  const agentPrevSteps = cloneUnknownArrayTail2(agentPrevStepsRaw, MAX_WORKER_STEPS * 2);
18327
18813
  const pendingQuestions = normalizeAskUserQuestions(input?.pendingAskUser?.questions);
18814
+ const pendingStepRefRaw = input?.pendingAskUser?.stepRef;
18815
+ const pendingStepRef = pendingStepRefRaw && Number.isFinite(Number(pendingStepRefRaw.stepIndex)) && Number(pendingStepRefRaw.stepIndex) >= 0 && Number.isFinite(Number(pendingStepRefRaw.functionIndex)) && Number(pendingStepRefRaw.functionIndex) >= 0 ? {
18816
+ stepIndex: Number(pendingStepRefRaw.stepIndex),
18817
+ functionIndex: Number(pendingStepRefRaw.functionIndex),
18818
+ ...typeof pendingStepRefRaw.accTreeId === "string" && pendingStepRefRaw.accTreeId.trim() ? { accTreeId: pendingStepRefRaw.accTreeId.trim() } : {}
18819
+ } : void 0;
18328
18820
  const pendingAskUser = pendingQuestions.length ? {
18329
18821
  questions: pendingQuestions,
18330
18822
  source: input?.pendingAskUser?.source === "planner" ? "planner" : "act",
18331
- askedAt: Number(input?.pendingAskUser?.askedAt) || Date.now()
18823
+ askedAt: Number(input?.pendingAskUser?.askedAt) || Date.now(),
18824
+ boundaryId: typeof input?.pendingAskUser?.boundaryId === "string" && input.pendingAskUser.boundaryId.trim() ? input.pendingAskUser.boundaryId.trim() : void 0,
18825
+ ...pendingStepRef ? { stepRef: pendingStepRef } : {}
18332
18826
  } : void 0;
18827
+ const rootUserInput = typeof input.rootUserInput === "string" ? input.rootUserInput.trim() : "";
18333
18828
  return {
18334
18829
  trajectoryId: typeof input.trajectoryId === "string" ? input.trajectoryId : void 0,
18830
+ taskBoundaryId: taskBoundaryIdCandidate || void 0,
18831
+ rootUserInput: rootUserInput || void 0,
18335
18832
  history: history2,
18336
18833
  plannerHistory,
18337
18834
  agentPrevSteps,
@@ -18360,6 +18857,7 @@ function normalizePersistedState(raw, sessionId, rid) {
18360
18857
  executionMode: raw.executionMode === "observer" || raw.executionMode === "controller" ? raw.executionMode : "controller",
18361
18858
  workerState: sanitizeWorkerState(raw.workerState),
18362
18859
  pendingRun,
18860
+ lastNavigationHandoff: sanitizeNavigationHandoff(raw.lastNavigationHandoff),
18363
18861
  taskEpoch: Math.max(1, Number(raw.taskEpoch) || 1),
18364
18862
  activeTask: parsedTask,
18365
18863
  lastRoutingDecision: raw.lastRoutingDecision && (raw.lastRoutingDecision.mode === "act" || raw.lastRoutingDecision.mode === "planner") ? {
@@ -18395,13 +18893,23 @@ function toSharedWorkerContext(state) {
18395
18893
  const pendingQuestions = normalizeAskUserQuestions(state.pendingAskUser?.questions);
18396
18894
  return {
18397
18895
  trajectoryId: state.trajectoryId,
18896
+ taskBoundaryId: typeof state.taskBoundaryId === "string" ? state.taskBoundaryId : void 0,
18897
+ rootUserInput: typeof state.rootUserInput === "string" ? state.rootUserInput : void 0,
18398
18898
  history: Array.isArray(state.history) ? state.history.slice(-MAX_WORKER_HISTORY).map((message) => toWorkerHistoryEntry(message)).filter((message) => !!message) : [],
18399
18899
  plannerHistory: cloneUnknownArrayTail2(state.plannerHistory, MAX_WORKER_STEPS),
18400
18900
  agentPrevSteps: cloneUnknownArrayTail2(state.agentPrevSteps, MAX_WORKER_STEPS * 2),
18401
18901
  pendingAskUser: pendingQuestions.length ? {
18402
18902
  questions: pendingQuestions,
18403
18903
  source: state.pendingAskUser?.source === "planner" ? "planner" : "act",
18404
- askedAt: Number(state.pendingAskUser?.askedAt) || Date.now()
18904
+ askedAt: Number(state.pendingAskUser?.askedAt) || Date.now(),
18905
+ boundaryId: typeof state.pendingAskUser?.boundaryId === "string" && state.pendingAskUser.boundaryId.trim() ? state.pendingAskUser.boundaryId.trim() : void 0,
18906
+ ...state.pendingAskUser?.stepRef && Number.isFinite(Number(state.pendingAskUser.stepRef.stepIndex)) && Number(state.pendingAskUser.stepRef.stepIndex) >= 0 && Number.isFinite(Number(state.pendingAskUser.stepRef.functionIndex)) && Number(state.pendingAskUser.stepRef.functionIndex) >= 0 ? {
18907
+ stepRef: {
18908
+ stepIndex: Number(state.pendingAskUser.stepRef.stepIndex),
18909
+ functionIndex: Number(state.pendingAskUser.stepRef.functionIndex),
18910
+ ...typeof state.pendingAskUser.stepRef.accTreeId === "string" && state.pendingAskUser.stepRef.accTreeId.trim() ? { accTreeId: state.pendingAskUser.stepRef.accTreeId.trim() } : {}
18911
+ }
18912
+ } : {}
18405
18913
  } : void 0,
18406
18914
  updatedAt: Number(state.updatedAt) || Date.now()
18407
18915
  };
@@ -18411,6 +18919,7 @@ function sanitizePendingRun(input) {
18411
18919
  return void 0;
18412
18920
  const id = typeof input.id === "string" && input.id.trim() ? input.id.trim() : void 0;
18413
18921
  const text = typeof input.text === "string" ? input.text.trim() : "";
18922
+ const taskBoundaryId = typeof input.taskBoundaryId === "string" && input.taskBoundaryId.trim() ? input.taskBoundaryId.trim() : void 0;
18414
18923
  if (!id || !text)
18415
18924
  return void 0;
18416
18925
  return {
@@ -18419,6 +18928,7 @@ function sanitizePendingRun(input) {
18419
18928
  startedAt: Number(input.startedAt) || Date.now(),
18420
18929
  attempts: Math.max(0, Number(input.attempts) || 0),
18421
18930
  autoResume: input.autoResume !== false,
18931
+ taskBoundaryId,
18422
18932
  resumeRequired: input.resumeRequired === true,
18423
18933
  resumeReason: input.resumeReason === "cross_host_navigation" || input.resumeReason === "agent_navigation" || input.resumeReason === "handoff" || input.resumeReason === "page_reload" ? input.resumeReason : void 0
18424
18934
  };
@@ -18441,17 +18951,19 @@ function ensureUnloadHandler() {
18441
18951
  unloadHandlerInstalled = true;
18442
18952
  const onPageHide = () => {
18443
18953
  if (runtimeState?.pendingRun?.autoResume) {
18954
+ const effectiveReason = runtimeState.pendingRun.resumeReason || (agentNavigationPending ? "agent_navigation" : "page_reload");
18444
18955
  const markedPending = sanitizePendingRun({
18445
18956
  ...runtimeState.pendingRun,
18446
18957
  resumeRequired: true,
18447
- resumeReason: runtimeState.pendingRun.resumeReason || "page_reload"
18958
+ resumeReason: effectiveReason
18448
18959
  });
18449
18960
  runtimeState.pendingRun = markedPending;
18450
18961
  if (markedPending) {
18451
18962
  sessionCoordinator?.clearActiveRunRuntimeId(markedPending.id);
18452
18963
  }
18453
18964
  }
18454
- sessionCoordinator?.broadcastClosing();
18965
+ const pendingHandoff = getUnconsumedNavigationHandoff();
18966
+ sessionCoordinator?.broadcastClosing(toSharedNavigationHandoff(pendingHandoff));
18455
18967
  if (runtimeState?.pendingRun) {
18456
18968
  sessionCoordinator?.releaseWorkflowLock(runtimeState.pendingRun.id);
18457
18969
  }
@@ -18463,8 +18975,16 @@ function ensureUnloadHandler() {
18463
18975
  id: runtimeState.pendingRun.id,
18464
18976
  text: runtimeState.pendingRun.text,
18465
18977
  startedAt: runtimeState.pendingRun.startedAt,
18466
- attempts: runtimeState.pendingRun.attempts
18978
+ attempts: runtimeState.pendingRun.attempts,
18979
+ taskBoundaryId: runtimeState.pendingRun.taskBoundaryId || currentTaskBoundaryId
18467
18980
  },
18981
+ handoff: pendingHandoff ? {
18982
+ handoffId: pendingHandoff.handoffId,
18983
+ sourceLogicalTabId: pendingHandoff.sourceLogicalTabId,
18984
+ runId: pendingHandoff.runId,
18985
+ targetUrl: pendingHandoff.targetUrl,
18986
+ createdAt: pendingHandoff.createdAt
18987
+ } : void 0,
18468
18988
  activeTask: runtimeState.activeTask ? { taskId: runtimeState.activeTask.taskId, status: runtimeState.activeTask.status } : void 0,
18469
18989
  taskEpoch: runtimeState.taskEpoch,
18470
18990
  timestamp: Date.now()
@@ -18732,7 +19252,20 @@ function setExecutionMode(mode, info) {
18732
19252
  function setPendingRun(next) {
18733
19253
  if (!runtimeState)
18734
19254
  return;
18735
- runtimeState.pendingRun = next;
19255
+ if (next) {
19256
+ runtimeState.pendingRun = sanitizePendingRun({
19257
+ ...next,
19258
+ taskBoundaryId: next.taskBoundaryId || currentTaskBoundaryId
19259
+ });
19260
+ } else {
19261
+ runtimeState.pendingRun = void 0;
19262
+ }
19263
+ persistRuntimeState();
19264
+ }
19265
+ function setLatestNavigationHandoff(next) {
19266
+ if (!runtimeState)
19267
+ return;
19268
+ runtimeState.lastNavigationHandoff = sanitizeNavigationHandoff(next);
18736
19269
  persistRuntimeState();
18737
19270
  }
18738
19271
  function postRun(text, options) {
@@ -18762,12 +19295,15 @@ function postRun(text, options) {
18762
19295
  appendUiMessage("user", trimmed, true);
18763
19296
  }
18764
19297
  const previousAttempts = runtimeState?.pendingRun?.id === runId ? runtimeState.pendingRun.attempts : 0;
19298
+ const boundaryForRun = runtimeState?.pendingRun?.id === runId ? runtimeState.pendingRun.taskBoundaryId || currentTaskBoundaryId : currentTaskBoundaryId;
19299
+ agentNavigationPending = false;
18765
19300
  setPendingRun({
18766
19301
  id: runId,
18767
19302
  text: trimmed,
18768
19303
  startedAt: Date.now(),
18769
19304
  attempts: resume ? previousAttempts + 1 : 0,
18770
19305
  autoResume: options?.autoResume !== false,
19306
+ taskBoundaryId: boundaryForRun,
18771
19307
  resumeRequired: false,
18772
19308
  resumeReason: void 0
18773
19309
  });
@@ -18800,15 +19336,18 @@ function postRun(text, options) {
18800
19336
  }, 5 * 6e4);
18801
19337
  }
18802
19338
  function dispatchUserPrompt(text, options) {
18803
- const trimmed = String(text || "").trim();
19339
+ const trimmed = buildAskUserDispatchText(text, options?.askUserAnswers);
18804
19340
  if (!trimmed)
18805
19341
  return;
18806
19342
  maybeClearStalePendingRun();
18807
19343
  const activeTaskStatus = runtimeState?.activeTask?.status;
18808
19344
  const shouldStartFreshTask2 = !!options?.startNewTask || shouldStartFreshTask(activeTaskStatus);
18809
- sessionCoordinator?.pruneTabs({
19345
+ sessionCoordinator?.pruneTabs(shouldStartFreshTask2 ? {
18810
19346
  dropRuntimeDetached: true,
18811
- dropAllDetachedExternal: shouldStartFreshTask2
19347
+ keepOnlyActiveLiveTab: true,
19348
+ keepRecentExternalPlaceholders: true
19349
+ } : {
19350
+ dropRuntimeDetached: true
18812
19351
  });
18813
19352
  if (shouldStartFreshTask2) {
18814
19353
  const autoReason = options?.reason || (activeTaskStatus === "completed" ? "auto_after_task_complete" : "auto_after_task_end");
@@ -18914,12 +19453,24 @@ async function applyAsyncRuntimeStateHydration(key) {
18914
19453
  const incomingUpdatedAt = Number(normalized.updatedAt) || 0;
18915
19454
  if (incomingUpdatedAt <= localUpdatedAt + 200)
18916
19455
  return;
19456
+ const localTaskEpoch = Math.max(1, Number(runtimeState.taskEpoch) || 1);
19457
+ const incomingTaskEpoch = Math.max(1, Number(normalized.taskEpoch) || 1);
19458
+ const incomingBoundaryId = resolveExistingTaskBoundaryIdFromState(normalized);
19459
+ if (!shouldAcceptIncomingWorkerBoundary({
19460
+ source: "indexeddb_checkpoint",
19461
+ incomingBoundaryId,
19462
+ taskEpochAdvanced: incomingTaskEpoch > localTaskEpoch,
19463
+ allowBootstrapAdoption: true
19464
+ })) {
19465
+ return;
19466
+ }
18917
19467
  runtimeState = normalizePersistedState({
18918
19468
  ...runtimeState,
18919
19469
  ...normalized,
18920
19470
  sessionId: runtimeState.sessionId,
18921
19471
  runtimeId
18922
19472
  }, runtimeState.sessionId, runtimeId);
19473
+ syncCurrentTaskBoundaryId({ rotateIfMissing: !currentTaskBoundaryId });
18923
19474
  persistRuntimeState();
18924
19475
  if (ui) {
18925
19476
  ui.clearMessages();
@@ -18939,6 +19490,9 @@ async function applyAsyncRuntimeStateHydration(key) {
18939
19490
  ui.close();
18940
19491
  }
18941
19492
  }
19493
+ if (workerReady && worker) {
19494
+ worker.postMessage({ type: "update_config", config: { taskBoundaryId: currentTaskBoundaryId } });
19495
+ }
18942
19496
  if (workerReady && worker && runtimeState.workerState && currentMode === "controller") {
18943
19497
  worker.postMessage({ type: "hydrate_state", state: runtimeState.workerState });
18944
19498
  emit("context_restored", { source: "indexeddb_checkpoint", ts: Date.now() });
@@ -18959,6 +19513,9 @@ function applyCoordinatorState(state, source) {
18959
19513
  runtimeState.timeline = [];
18960
19514
  clearTaskUiState();
18961
19515
  setPendingRun(void 0);
19516
+ runtimeState.workerState = void 0;
19517
+ currentTaskBoundaryId = createTaskBoundaryId();
19518
+ worker?.postMessage({ type: "update_config", config: { taskBoundaryId: currentTaskBoundaryId } });
18962
19519
  hideTaskSuggestion();
18963
19520
  }
18964
19521
  const incomingMessages = sanitizeUiMessages(state.uiMessages);
@@ -19024,7 +19581,8 @@ function applyCoordinatorState(state, source) {
19024
19581
  text: state.activeRun.text,
19025
19582
  startedAt: state.activeRun.startedAt,
19026
19583
  attempts: runtimeState.pendingRun?.attempts || 0,
19027
- autoResume: true
19584
+ autoResume: true,
19585
+ resumeReason: runtimeState.pendingRun?.resumeReason
19028
19586
  }));
19029
19587
  } else {
19030
19588
  const shouldClearPending = shouldClearPendingFromSharedState({
@@ -19051,9 +19609,16 @@ function applyCoordinatorState(state, source) {
19051
19609
  const incomingWorker = sanitizeWorkerState(state.workerContext);
19052
19610
  const localUpdatedAt = Number(runtimeState.workerState?.updatedAt) || 0;
19053
19611
  const incomingUpdatedAt = Number(incomingWorker?.updatedAt) || 0;
19054
- if (incomingWorker && incomingUpdatedAt > localUpdatedAt + 100) {
19612
+ if (incomingWorker && incomingUpdatedAt > localUpdatedAt + 100 && shouldAcceptIncomingWorkerBoundary({
19613
+ source: "shared_worker_context",
19614
+ incomingBoundaryId: incomingWorker.taskBoundaryId,
19615
+ taskEpochAdvanced,
19616
+ allowBootstrapAdoption: true
19617
+ })) {
19055
19618
  runtimeState.workerState = incomingWorker;
19619
+ syncCurrentTaskBoundaryId();
19056
19620
  if (workerReady && worker && currentMode === "controller") {
19621
+ worker.postMessage({ type: "update_config", config: { taskBoundaryId: currentTaskBoundaryId } });
19057
19622
  worker.postMessage({ type: "hydrate_state", state: incomingWorker });
19058
19623
  emit("context_restored", { source: "shared_session", ts: Date.now() });
19059
19624
  }
@@ -19063,6 +19628,7 @@ function applyCoordinatorState(state, source) {
19063
19628
  runtimeState.uiMessages = sanitizeUiMessages(runtimeState.uiMessages);
19064
19629
  runtimeState.timeline = sanitizeTimelineEvents(runtimeState.timeline);
19065
19630
  runtimeState.workerState = sanitizeWorkerState(runtimeState.workerState);
19631
+ syncCurrentTaskBoundaryId();
19066
19632
  syncQuestionPromptFromWorkerState();
19067
19633
  runtimeState.activeTask = sanitizeTask(runtimeState.activeTask, createDefaultTaskState("implicit"));
19068
19634
  persistRuntimeState();
@@ -19081,6 +19647,7 @@ function cloneRuntimeStateForCheckpoint(state) {
19081
19647
  executionMode: state.executionMode === "observer" || state.executionMode === "controller" ? state.executionMode : "controller",
19082
19648
  workerState: sanitizeWorkerState(state.workerState),
19083
19649
  pendingRun: sanitizePendingRun(state.pendingRun),
19650
+ lastNavigationHandoff: sanitizeNavigationHandoff(state.lastNavigationHandoff),
19084
19651
  taskEpoch: Math.max(1, Number(state.taskEpoch) || 1),
19085
19652
  activeTask: sanitizeTask(state.activeTask, fallbackTask),
19086
19653
  lastRoutingDecision: state.lastRoutingDecision && (state.lastRoutingDecision.mode === "act" || state.lastRoutingDecision.mode === "planner") ? {
@@ -19135,7 +19702,10 @@ function applyCloudCheckpointPayload(payload) {
19135
19702
  runtimeState.sessionId = remoteSessionId;
19136
19703
  currentConfig = { ...currentConfig, sessionId: remoteSessionId };
19137
19704
  setupSessionCoordinator(currentConfig);
19138
- worker?.postMessage({ type: "update_config", config: { sessionId: remoteSessionId } });
19705
+ worker?.postMessage({
19706
+ type: "update_config",
19707
+ config: { sessionId: remoteSessionId, taskBoundaryId: currentTaskBoundaryId }
19708
+ });
19139
19709
  }
19140
19710
  if (payload.sharedState && sessionCoordinator) {
19141
19711
  const hydrated = sessionCoordinator.hydrateExternalState(payload.sharedState);
@@ -19157,12 +19727,24 @@ function applyCloudCheckpointPayload(payload) {
19157
19727
  const localUpdatedAt = Number(runtimeState.updatedAt) || 0;
19158
19728
  const incomingUpdatedAt = Number(incomingState.updatedAt) || 0;
19159
19729
  if (incomingUpdatedAt > localUpdatedAt + 200) {
19730
+ const localTaskEpoch = Math.max(1, Number(runtimeState.taskEpoch) || 1);
19731
+ const incomingTaskEpoch = Math.max(1, Number(incomingState.taskEpoch) || 1);
19732
+ const incomingBoundaryId = resolveExistingTaskBoundaryIdFromState(incomingState);
19733
+ if (!shouldAcceptIncomingWorkerBoundary({
19734
+ source: "cloud_checkpoint",
19735
+ incomingBoundaryId,
19736
+ taskEpochAdvanced: incomingTaskEpoch > localTaskEpoch,
19737
+ allowBootstrapAdoption: true
19738
+ })) {
19739
+ return;
19740
+ }
19160
19741
  runtimeState = normalizePersistedState({
19161
19742
  ...runtimeState,
19162
19743
  ...incomingState,
19163
19744
  sessionId: remoteSessionId,
19164
19745
  runtimeId
19165
19746
  }, remoteSessionId, runtimeId);
19747
+ syncCurrentTaskBoundaryId();
19166
19748
  persistRuntimeState();
19167
19749
  if (runtimeState.uiStatus) {
19168
19750
  ui?.setStatus(runtimeState.uiStatus);
@@ -19176,6 +19758,9 @@ function applyCloudCheckpointPayload(payload) {
19176
19758
  else
19177
19759
  ui?.close();
19178
19760
  }
19761
+ if (workerReady) {
19762
+ worker?.postMessage({ type: "update_config", config: { taskBoundaryId: currentTaskBoundaryId } });
19763
+ }
19179
19764
  if (workerReady && runtimeState.workerState) {
19180
19765
  worker?.postMessage({ type: "hydrate_state", state: runtimeState.workerState });
19181
19766
  emit("context_restored", { source: "cloud_checkpoint", ts: Date.now() });
@@ -19285,12 +19870,18 @@ function setupSessionCoordinator(cfg) {
19285
19870
  const incomingWorker = sanitizeWorkerState(sharedWorkerContext);
19286
19871
  const localUpdatedAt = Number(runtimeState?.workerState?.updatedAt) || 0;
19287
19872
  const incomingUpdatedAt = Number(incomingWorker?.updatedAt) || 0;
19288
- if (incomingWorker && incomingUpdatedAt > localUpdatedAt + 100) {
19873
+ if (incomingWorker && incomingUpdatedAt > localUpdatedAt + 100 && shouldAcceptIncomingWorkerBoundary({
19874
+ source: "controller_handoff",
19875
+ incomingBoundaryId: incomingWorker.taskBoundaryId,
19876
+ allowBootstrapAdoption: true
19877
+ })) {
19289
19878
  if (runtimeState) {
19290
19879
  runtimeState.workerState = incomingWorker;
19880
+ syncCurrentTaskBoundaryId();
19291
19881
  persistRuntimeState();
19292
19882
  }
19293
19883
  if (workerReady && worker) {
19884
+ worker.postMessage({ type: "update_config", config: { taskBoundaryId: currentTaskBoundaryId } });
19294
19885
  worker.postMessage({ type: "hydrate_state", state: incomingWorker });
19295
19886
  emit("context_restored", { source: "controller_handoff", ts: Date.now() });
19296
19887
  }
@@ -19313,7 +19904,15 @@ function setupSessionCoordinator(cfg) {
19313
19904
  }
19314
19905
  }
19315
19906
  });
19316
- sessionCoordinator.start();
19907
+ const startupHandoff = getUnconsumedNavigationHandoff();
19908
+ sessionCoordinator.start(toSharedNavigationHandoff(startupHandoff));
19909
+ if (runtimeState?.lastNavigationHandoff && startupHandoff) {
19910
+ runtimeState.lastNavigationHandoff = sanitizeNavigationHandoff({
19911
+ ...runtimeState.lastNavigationHandoff,
19912
+ consumed: true
19913
+ });
19914
+ persistRuntimeState();
19915
+ }
19317
19916
  sessionCoordinator.setRpcRequestHandler(async (request) => {
19318
19917
  if (!bridge)
19319
19918
  throw new Error("Bridge not available");
@@ -19460,10 +20059,18 @@ function handleWorkerMessage(msg) {
19460
20059
  }
19461
20060
  if (msg.type === "state_snapshot") {
19462
20061
  if (runtimeState) {
19463
- runtimeState.workerState = sanitizeWorkerState({
20062
+ const incomingWorkerState = sanitizeWorkerState({
19464
20063
  ...msg.state || {},
19465
20064
  updatedAt: Date.now()
19466
20065
  });
20066
+ if (!incomingWorkerState || !shouldAcceptIncomingWorkerBoundary({
20067
+ source: "worker_snapshot",
20068
+ incomingBoundaryId: incomingWorkerState.taskBoundaryId,
20069
+ allowBootstrapAdoption: true
20070
+ })) {
20071
+ return;
20072
+ }
20073
+ runtimeState.workerState = incomingWorkerState;
19467
20074
  const activeRunId = typeof msg?.activeRun?.runId === "string" && msg.activeRun.runId ? msg.activeRun.runId : void 0;
19468
20075
  const activeRunText = typeof msg?.activeRun?.text === "string" ? msg.activeRun.text : void 0;
19469
20076
  const canAdoptActiveRun = shouldAdoptSnapshotActiveRun({
@@ -19516,6 +20123,7 @@ function handleWorkerMessage(msg) {
19516
20123
  return;
19517
20124
  }
19518
20125
  const existing = runtimeState?.pendingRun;
20126
+ const messageTaskBoundaryId = typeof msg?.taskBoundaryId === "string" ? normalizeTaskBoundaryId(msg.taskBoundaryId) : void 0;
19519
20127
  if (typeof msg.runId === "string" && msg.runId) {
19520
20128
  removeIgnoredRunId(msg.runId);
19521
20129
  }
@@ -19524,7 +20132,10 @@ function handleWorkerMessage(msg) {
19524
20132
  text: msg.text,
19525
20133
  startedAt: existing?.startedAt || Date.now(),
19526
20134
  attempts: existing?.attempts || 0,
19527
- autoResume: existing?.autoResume !== false
20135
+ autoResume: existing?.autoResume !== false,
20136
+ taskBoundaryId: messageTaskBoundaryId || existing?.taskBoundaryId || currentTaskBoundaryId,
20137
+ resumeReason: existing?.resumeReason,
20138
+ resumeRequired: existing?.resumeRequired
19528
20139
  }));
19529
20140
  sessionCoordinator?.setActiveRun({ runId: msg.runId, text: String(msg.text || "") });
19530
20141
  ui?.setRunning(true);
@@ -19576,9 +20187,16 @@ function handleWorkerMessage(msg) {
19576
20187
  if (!isTaskRunning()) {
19577
20188
  return;
19578
20189
  }
20190
+ const completionState = normalizeRunCompletionState(msg);
19579
20191
  ui?.setQuestionPrompt(void 0);
19580
- markTaskRunning("worker_run_failed");
19581
- setUiStatus(`Task failed: ${String(msg.error)}`);
20192
+ if (completionState.contextResetRecommended) {
20193
+ markTaskCompleted("worker_run_failed_terminal");
20194
+ sessionCoordinator?.resetTabsToCurrent(window.location.href, document.title || void 0);
20195
+ setUiStatus(`Task failed: ${String(msg.error)}. Start a new task to continue.`);
20196
+ } else {
20197
+ markTaskRunning("worker_run_failed");
20198
+ setUiStatus(`Task failed: ${String(msg.error)}`);
20199
+ }
19582
20200
  if (completedRunId) {
19583
20201
  latestAssistantByRunId.delete(completedRunId);
19584
20202
  }
@@ -19595,20 +20213,39 @@ function handleWorkerMessage(msg) {
19595
20213
  const completionState = normalizeRunCompletionState(msg);
19596
20214
  const taskComplete = completionState.taskComplete;
19597
20215
  const needsUserInput = completionState.needsUserInput;
19598
- const questions = completionState.questions || normalizeAskUserQuestions(msg.questions);
19599
- if (taskComplete) {
20216
+ const terminalState = completionState.terminalState;
20217
+ const completionQuestions = completionState.questions || normalizeAskUserQuestions(msg.questions);
20218
+ const pendingStateQuestions = normalizeAskUserQuestions(runtimeState?.workerState?.pendingAskUser?.questions);
20219
+ const questions = completionQuestions.length ? completionQuestions : needsUserInput ? pendingStateQuestions : [];
20220
+ if (terminalState === "failed") {
19600
20221
  ui?.setQuestionPrompt(void 0);
19601
- markTaskCompleted("worker_task_complete");
19602
- sessionCoordinator?.pruneTabs({
19603
- dropRuntimeDetached: true,
19604
- dropAllDetachedExternal: true
20222
+ if (completionState.contextResetRecommended) {
20223
+ markTaskCompleted("worker_run_failed_terminal");
20224
+ sessionCoordinator?.resetTabsToCurrent(window.location.href, document.title || void 0);
20225
+ setUiStatus("Task failed. Start a new task to continue.");
20226
+ } else {
20227
+ markTaskRunning("worker_run_failed");
20228
+ setUiStatus("Task failed.");
20229
+ }
20230
+ appendTimelineEvent({
20231
+ kind: "error",
20232
+ title: "Run failed",
20233
+ detail: typeof msg.error === "string" ? msg.error : "Run reported failure state.",
20234
+ status: "error"
19605
20235
  });
20236
+ } else if (taskComplete || terminalState === "completed") {
20237
+ ui?.setQuestionPrompt(void 0);
20238
+ markTaskCompleted("worker_task_complete");
20239
+ sessionCoordinator?.resetTabsToCurrent(window.location.href, document.title || void 0);
19606
20240
  setUiStatus("Task completed");
19607
20241
  finalizeSuccessfulRunTimeline(typeof msg.runId === "string" ? msg.runId : void 0);
19608
20242
  } else {
19609
- markTaskRunning(needsUserInput ? "worker_waiting_for_input" : "worker_continuation");
20243
+ const nextReason = needsUserInput ? "worker_waiting_for_input" : terminalState === "in_progress" ? "worker_continuation" : "worker_task_active";
20244
+ markTaskRunning(nextReason);
19610
20245
  if (needsUserInput && questions.length > 0) {
19611
20246
  ui?.setQuestionPrompt({ questions });
20247
+ } else if (needsUserInput) {
20248
+ syncQuestionPromptFromWorkerState();
19612
20249
  } else {
19613
20250
  ui?.setQuestionPrompt(void 0);
19614
20251
  }
@@ -19631,13 +20268,29 @@ function handleWorkerMessage(msg) {
19631
20268
  loadAndMergeShortcuts(currentConfig);
19632
20269
  }
19633
20270
  const sharedWorkerContext = sessionCoordinator?.getWorkerContext();
19634
- const sharedWorkerState = sanitizeWorkerState(sharedWorkerContext);
19635
- const localUpdatedAt = Number(runtimeState?.workerState?.updatedAt) || 0;
20271
+ const sharedWorkerStateCandidate = sanitizeWorkerState(sharedWorkerContext);
20272
+ const sharedWorkerState = sharedWorkerStateCandidate && shouldAcceptIncomingWorkerBoundary({
20273
+ source: "ready_hydrate",
20274
+ incomingBoundaryId: sharedWorkerStateCandidate.taskBoundaryId,
20275
+ allowBootstrapAdoption: true
20276
+ }) ? sharedWorkerStateCandidate : void 0;
20277
+ const localWorkerStateCandidate = sanitizeWorkerState(runtimeState?.workerState);
20278
+ const localWorkerState = localWorkerStateCandidate && shouldAcceptIncomingWorkerBoundary({
20279
+ source: "ready_hydrate",
20280
+ incomingBoundaryId: localWorkerStateCandidate.taskBoundaryId,
20281
+ allowBootstrapAdoption: true
20282
+ }) ? localWorkerStateCandidate : void 0;
20283
+ const localUpdatedAt = Number(localWorkerState?.updatedAt) || 0;
19636
20284
  const sharedUpdatedAt = Number(sharedWorkerState?.updatedAt) || 0;
19637
- const stateToHydrate = sharedWorkerState && sharedUpdatedAt > localUpdatedAt + 100 ? sharedWorkerState : runtimeState?.workerState;
19638
- if (runtimeState && stateToHydrate && stateToHydrate !== runtimeState.workerState) {
19639
- runtimeState.workerState = stateToHydrate;
19640
- persistRuntimeState();
20285
+ const stateToHydrate = sharedWorkerState && sharedUpdatedAt > localUpdatedAt + 100 ? sharedWorkerState : localWorkerState;
20286
+ if (runtimeState) {
20287
+ if (stateToHydrate && stateToHydrate !== runtimeState.workerState) {
20288
+ runtimeState.workerState = stateToHydrate;
20289
+ persistRuntimeState();
20290
+ } else if (!stateToHydrate && runtimeState.workerState) {
20291
+ runtimeState.workerState = void 0;
20292
+ persistRuntimeState();
20293
+ }
19641
20294
  }
19642
20295
  if (stateToHydrate) {
19643
20296
  worker?.postMessage({ type: "hydrate_state", state: stateToHydrate });
@@ -20008,26 +20661,33 @@ function createRuntime(cfg) {
20008
20661
  status: "info"
20009
20662
  });
20010
20663
  },
20011
- onBeforeAgentNavigation: (_targetUrl) => {
20012
- if (!runtimeState?.pendingRun)
20664
+ onBeforeAgentNavigation: (intent) => {
20665
+ agentNavigationPending = true;
20666
+ if (!runtimeState)
20667
+ return;
20668
+ setLatestNavigationHandoff(toPersistedNavigationHandoff(intent));
20669
+ if (!runtimeState.pendingRun)
20013
20670
  return;
20014
- const currentHost = new URL(window.location.href).hostname;
20015
- const targetHost = new URL(_targetUrl, window.location.href).hostname;
20016
- if (currentHost === targetHost) {
20671
+ if (!intent.isCrossHost) {
20017
20672
  runtimeState.pendingRun = sanitizePendingRun({
20018
20673
  ...runtimeState.pendingRun,
20674
+ taskBoundaryId: runtimeState.pendingRun.taskBoundaryId || currentTaskBoundaryId,
20019
20675
  resumeRequired: true,
20020
20676
  resumeReason: "agent_navigation"
20021
20677
  });
20022
20678
  persistRuntimeState();
20023
20679
  }
20024
20680
  },
20025
- onBeforeCrossHostNavigation: (_targetUrl) => {
20681
+ onBeforeCrossHostNavigation: (intent) => {
20682
+ agentNavigationPending = true;
20026
20683
  if (!runtimeState || !currentConfig)
20027
20684
  return;
20685
+ setLatestNavigationHandoff(toPersistedNavigationHandoff(intent));
20686
+ const handoffForCookie = getUnconsumedNavigationHandoff();
20028
20687
  if (runtimeState.pendingRun) {
20029
20688
  runtimeState.pendingRun = sanitizePendingRun({
20030
20689
  ...runtimeState.pendingRun,
20690
+ taskBoundaryId: runtimeState.pendingRun.taskBoundaryId || currentTaskBoundaryId,
20031
20691
  resumeRequired: true,
20032
20692
  resumeReason: "cross_host_navigation"
20033
20693
  });
@@ -20038,7 +20698,15 @@ function createRuntime(cfg) {
20038
20698
  id: runtimeState.pendingRun.id,
20039
20699
  text: runtimeState.pendingRun.text,
20040
20700
  startedAt: runtimeState.pendingRun.startedAt,
20041
- attempts: runtimeState.pendingRun.attempts
20701
+ attempts: runtimeState.pendingRun.attempts,
20702
+ taskBoundaryId: runtimeState.pendingRun.taskBoundaryId || currentTaskBoundaryId
20703
+ } : void 0,
20704
+ handoff: handoffForCookie ? {
20705
+ handoffId: handoffForCookie.handoffId,
20706
+ sourceLogicalTabId: handoffForCookie.sourceLogicalTabId,
20707
+ runId: handoffForCookie.runId,
20708
+ targetUrl: handoffForCookie.targetUrl,
20709
+ createdAt: handoffForCookie.createdAt
20042
20710
  } : void 0,
20043
20711
  activeTask: runtimeState.activeTask ? {
20044
20712
  taskId: runtimeState.activeTask.taskId,
@@ -20137,7 +20805,7 @@ function createRuntime(cfg) {
20137
20805
  title: document.title
20138
20806
  };
20139
20807
  },
20140
- listSessionTabs: () => sessionCoordinator?.listTabs() || []
20808
+ listSessionTabs: () => sessionCoordinator?.listTabs({ scope: "context" }) || []
20141
20809
  });
20142
20810
  channel.port1.start?.();
20143
20811
  const workerUrl = cfg.workerUrl ? cfg.workerUrl : new URL("./worker/worker.js", import.meta.url).toString();
@@ -20152,7 +20820,77 @@ function createRuntime(cfg) {
20152
20820
  } catch (_e) {
20153
20821
  }
20154
20822
  worker = new Worker(effectiveWorkerUrl, { type: "module" });
20155
- worker.postMessage({ type: "init", config: cfg, port: channel.port2 }, [channel.port2]);
20823
+ worker.postMessage({
20824
+ type: "init",
20825
+ config: {
20826
+ ...cfg,
20827
+ taskBoundaryId: currentTaskBoundaryId
20828
+ },
20829
+ port: channel.port2
20830
+ }, [channel.port2]);
20831
+ const cancelCurrentFlow = (reason = "manual_cancel_task") => {
20832
+ if (!runtimeState)
20833
+ return;
20834
+ const pendingRun = runtimeState.pendingRun;
20835
+ if (pendingRun) {
20836
+ addIgnoredRunId(pendingRun.id);
20837
+ worker?.postMessage({ type: "cancel_run", runId: pendingRun.id });
20838
+ sessionCoordinator?.releaseWorkflowLock(pendingRun.id);
20839
+ sessionCoordinator?.setActiveRun(void 0);
20840
+ setPendingRun(void 0);
20841
+ runtimeState.workerState = void 0;
20842
+ sessionCoordinator?.setWorkerContext(void 0);
20843
+ currentTaskBoundaryId = createTaskBoundaryId();
20844
+ worker?.postMessage({ type: "update_config", config: { taskBoundaryId: currentTaskBoundaryId } });
20845
+ if (runSafetyTimer) {
20846
+ clearTimeout(runSafetyTimer);
20847
+ runSafetyTimer = null;
20848
+ }
20849
+ ui?.setRunning(false);
20850
+ ui?.setQuestionPrompt(void 0);
20851
+ setUiStatus("Task cancelled.");
20852
+ appendUiMessage("system", "Task cancelled.", true);
20853
+ appendTimelineEvent({
20854
+ kind: "info",
20855
+ title: "Run cancelled",
20856
+ status: "info"
20857
+ });
20858
+ persistRuntimeState();
20859
+ return;
20860
+ }
20861
+ const hasPendingQuestions = normalizeAskUserQuestions(runtimeState.workerState?.pendingAskUser?.questions).length > 0;
20862
+ const activeStatus = runtimeState.activeTask?.status;
20863
+ if (!hasPendingQuestions && activeStatus !== "running") {
20864
+ return;
20865
+ }
20866
+ currentTaskBoundaryId = createTaskBoundaryId();
20867
+ runtimeState.workerState = void 0;
20868
+ worker?.postMessage({ type: "update_config", config: { taskBoundaryId: currentTaskBoundaryId } });
20869
+ if (runSafetyTimer) {
20870
+ clearTimeout(runSafetyTimer);
20871
+ runSafetyTimer = null;
20872
+ }
20873
+ ui?.setRunning(false);
20874
+ ui?.setQuestionPrompt(void 0);
20875
+ hideTaskSuggestion();
20876
+ setPendingRun(void 0);
20877
+ const task = ensureActiveTask(reason);
20878
+ if (task) {
20879
+ task.status = "ended";
20880
+ task.endedAt = Date.now();
20881
+ task.boundaryReason = reason;
20882
+ }
20883
+ sessionCoordinator?.endTask(reason);
20884
+ sessionCoordinator?.setActiveRun(void 0);
20885
+ sessionCoordinator?.setWorkerContext(void 0);
20886
+ setUiStatus(reason === "question_prompt_cancel" ? "Input request cancelled." : "Task cancelled.");
20887
+ appendTimelineEvent({
20888
+ kind: "info",
20889
+ title: reason === "question_prompt_cancel" ? "Input request cancelled" : "Task cancelled",
20890
+ status: "info"
20891
+ });
20892
+ persistRuntimeState();
20893
+ };
20156
20894
  ui = mountWidget({
20157
20895
  shortcuts: getRenderableShortcuts(sanitizeShortcutList(cfg.ui?.shortcuts || [])),
20158
20896
  greeting: resolveEffectiveGreetingConfig(cfg),
@@ -20182,26 +20920,10 @@ function createRuntime(cfg) {
20182
20920
  }
20183
20921
  },
20184
20922
  onCancelRun: () => {
20185
- if (runtimeState?.pendingRun) {
20186
- addIgnoredRunId(runtimeState.pendingRun.id);
20187
- worker?.postMessage({ type: "cancel_run", runId: runtimeState.pendingRun.id });
20188
- sessionCoordinator?.releaseWorkflowLock(runtimeState.pendingRun.id);
20189
- sessionCoordinator?.setActiveRun(void 0);
20190
- setPendingRun(void 0);
20191
- if (runSafetyTimer) {
20192
- clearTimeout(runSafetyTimer);
20193
- runSafetyTimer = null;
20194
- }
20195
- ui?.setRunning(false);
20196
- ui?.setQuestionPrompt(void 0);
20197
- setUiStatus("Task cancelled.");
20198
- appendUiMessage("system", "Task cancelled.", true);
20199
- appendTimelineEvent({
20200
- kind: "info",
20201
- title: "Run cancelled",
20202
- status: "info"
20203
- });
20204
- }
20923
+ cancelCurrentFlow("manual_cancel_task");
20924
+ },
20925
+ onCancelQuestionFlow: () => {
20926
+ cancelCurrentFlow("question_prompt_cancel");
20205
20927
  },
20206
20928
  onNewTask: () => {
20207
20929
  newTask({ reason: "manual_new_task", clearUi: true });
@@ -20377,10 +21099,17 @@ function boot(cfg) {
20377
21099
  seeded.pendingRun = sanitizePendingRun({
20378
21100
  ...crossDomainResume.pendingRun,
20379
21101
  autoResume: true,
21102
+ taskBoundaryId: crossDomainResume.pendingRun.taskBoundaryId,
20380
21103
  resumeRequired: true,
20381
21104
  resumeReason: "cross_host_navigation"
20382
21105
  });
20383
21106
  }
21107
+ if (crossDomainResume.handoff) {
21108
+ seeded.lastNavigationHandoff = sanitizeNavigationHandoff({
21109
+ ...crossDomainResume.handoff,
21110
+ createdAt: crossDomainResume.handoff.createdAt
21111
+ });
21112
+ }
20384
21113
  if (crossDomainResume.activeTask) {
20385
21114
  seeded.activeTask = {
20386
21115
  ...createDefaultTaskState("cross_domain_resume"),
@@ -20396,6 +21125,19 @@ function boot(cfg) {
20396
21125
  if (desiredSessionId && runtimeState.sessionId !== desiredSessionId) {
20397
21126
  runtimeState = createDefaultRuntimeState(desiredSessionId, runtimeId);
20398
21127
  }
21128
+ currentTaskBoundaryId = resolveTaskBoundaryIdFromState(runtimeState);
21129
+ if (runtimeState.pendingRun && !runtimeState.pendingRun.taskBoundaryId) {
21130
+ runtimeState.pendingRun = sanitizePendingRun({
21131
+ ...runtimeState.pendingRun,
21132
+ taskBoundaryId: currentTaskBoundaryId
21133
+ });
21134
+ }
21135
+ if (runtimeState.workerState && !runtimeState.workerState.taskBoundaryId) {
21136
+ runtimeState.workerState = sanitizeWorkerState({
21137
+ ...runtimeState.workerState,
21138
+ taskBoundaryId: currentTaskBoundaryId
21139
+ });
21140
+ }
20399
21141
  currentConfig = {
20400
21142
  ...cfg,
20401
21143
  visitorId: resolvedVisitorId || cfg.visitorId,
@@ -20547,7 +21289,13 @@ function update(cfg) {
20547
21289
  });
20548
21290
  }
20549
21291
  }
20550
- worker.postMessage({ type: "update_config", config: cfg });
21292
+ worker.postMessage({
21293
+ type: "update_config",
21294
+ config: {
21295
+ ...cfg,
21296
+ taskBoundaryId: currentTaskBoundaryId
21297
+ }
21298
+ });
20551
21299
  applyEffectiveSiteConfig(currentConfig);
20552
21300
  if (shouldReloadRemoteSiteConfig) {
20553
21301
  loadAndMergeShortcuts(currentConfig);
@@ -20583,6 +21331,8 @@ function shutdown() {
20583
21331
  currentConfig = null;
20584
21332
  workerReady = false;
20585
21333
  autoResumeAttempted = false;
21334
+ agentNavigationPending = false;
21335
+ currentTaskBoundaryId = "";
20586
21336
  runtimeId = "";
20587
21337
  resolvedVisitorId = void 0;
20588
21338
  resolvedVisitor = void 0;
@@ -20657,6 +21407,7 @@ function newTask(options) {
20657
21407
  ui?.setRunning(false);
20658
21408
  ui?.setQuestionPrompt(void 0);
20659
21409
  autoResumeAttempted = false;
21410
+ currentTaskBoundaryId = createTaskBoundaryId();
20660
21411
  runtimeState.taskEpoch = taskEpoch;
20661
21412
  runtimeState.activeTask = nextTask;
20662
21413
  setPendingRun(void 0);
@@ -20679,7 +21430,7 @@ function newTask(options) {
20679
21430
  sessionCoordinator?.setWorkerContext(void 0);
20680
21431
  setUiStatus("New task started.");
20681
21432
  persistRuntimeState();
20682
- worker?.postMessage({ type: "start_new_task", taskId: nextTask.taskId });
21433
+ worker?.postMessage({ type: "start_new_task", taskId: nextTask.taskId, taskBoundaryId: currentTaskBoundaryId });
20683
21434
  emit("task_started", {
20684
21435
  taskId: nextTask.taskId,
20685
21436
  reason,
@@ -20702,7 +21453,9 @@ function endTask(options) {
20702
21453
  sessionCoordinator?.releaseWorkflowLock(runtimeState.pendingRun.id);
20703
21454
  }
20704
21455
  setPendingRun(void 0);
21456
+ currentTaskBoundaryId = createTaskBoundaryId();
20705
21457
  runtimeState.workerState = void 0;
21458
+ worker?.postMessage({ type: "update_config", config: { taskBoundaryId: currentTaskBoundaryId } });
20706
21459
  if (runSafetyTimer) {
20707
21460
  clearTimeout(runSafetyTimer);
20708
21461
  runSafetyTimer = null;