@bastani/atomic 0.9.28-alpha.3 → 0.9.28-alpha.4

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.
Files changed (211) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/builtin/intercom/package.json +2 -2
  3. package/dist/builtin/mcp/package.json +1 -1
  4. package/dist/builtin/subagents/CHANGELOG.md +6 -0
  5. package/dist/builtin/subagents/package.json +3 -3
  6. package/dist/builtin/subagents/skills/agent-browser/SKILL.md +1 -1
  7. package/dist/builtin/subagents/skills/cua-driver/BROWSER.md +60 -39
  8. package/dist/builtin/subagents/skills/cua-driver/EMBEDDING.md +119 -36
  9. package/dist/builtin/subagents/skills/cua-driver/LINUX.md +225 -183
  10. package/dist/builtin/subagents/skills/cua-driver/MACOS.md +120 -89
  11. package/dist/builtin/subagents/skills/cua-driver/README.md +16 -7
  12. package/dist/builtin/subagents/skills/cua-driver/RECORDING.md +85 -115
  13. package/dist/builtin/subagents/skills/cua-driver/RUNTIME.md +113 -0
  14. package/dist/builtin/subagents/skills/cua-driver/SKILL.md +55 -1071
  15. package/dist/builtin/subagents/skills/cua-driver/VISUAL.md +125 -0
  16. package/dist/builtin/subagents/skills/cua-driver/WINDOWS.md +145 -185
  17. package/dist/builtin/subagents/skills/cua-driver/WORKFLOW.md +173 -0
  18. package/dist/builtin/web-access/package.json +2 -2
  19. package/dist/builtin/workflows/CHANGELOG.md +10 -0
  20. package/dist/builtin/workflows/README.md +1 -1
  21. package/dist/builtin/workflows/package.json +2 -2
  22. package/dist/builtin/workflows/skills/impeccable/SKILL.md +8 -2
  23. package/dist/builtin/workflows/skills/impeccable/reference/adapt.md +6 -0
  24. package/dist/builtin/workflows/skills/impeccable/reference/audit.md +2 -1
  25. package/dist/builtin/workflows/skills/impeccable/reference/component-review.md +63 -0
  26. package/dist/builtin/workflows/skills/impeccable/reference/degraded/asset-producer.md +5 -0
  27. package/dist/builtin/workflows/skills/impeccable/reference/degraded/finish-reviewer.md +1 -1
  28. package/dist/builtin/workflows/skills/impeccable/reference/generate.md +101 -0
  29. package/dist/builtin/workflows/skills/impeccable/reference/harden.md +9 -0
  30. package/dist/builtin/workflows/skills/impeccable/reference/hooks.md +3 -1
  31. package/dist/builtin/workflows/skills/impeccable/reference/mode-operate.md +21 -0
  32. package/dist/builtin/workflows/skills/impeccable/reference/mode-persuade.md +19 -0
  33. package/dist/builtin/workflows/skills/impeccable/reference/mode-read.md +21 -0
  34. package/dist/builtin/workflows/skills/impeccable/reference/new-work.md +19 -12
  35. package/dist/builtin/workflows/skills/impeccable/reference/region-map.md +26 -0
  36. package/dist/builtin/workflows/skills/impeccable/reference/routing.md +1 -1
  37. package/dist/builtin/workflows/skills/impeccable/reference/visualize.md +5 -5
  38. package/dist/builtin/workflows/skills/impeccable/scripts/VERSION +1 -1
  39. package/dist/builtin/workflows/skills/impeccable/scripts/command-metadata.json +4 -0
  40. package/dist/builtin/workflows/skills/impeccable/scripts/impeccable.cmd +2 -2
  41. package/dist/builtin/workflows/skills/impeccable/scripts/live-browser-session.js +5 -1
  42. package/dist/builtin/workflows/skills/impeccable/scripts/live-browser.js +524 -4
  43. package/dist/builtin/workflows/skills/prompt-engineer/SKILL.md +1 -0
  44. package/dist/builtin/workflows/skills/prompt-engineer/references/claude_haiku_5_5.md +81 -0
  45. package/dist/codemode-worker.js +2 -2
  46. package/dist/core/agent-session-compaction.d.ts +3 -3
  47. package/dist/core/agent-session-compaction.d.ts.map +1 -1
  48. package/dist/core/agent-session-compaction.js +5 -1
  49. package/dist/core/agent-session-compaction.js.map +1 -1
  50. package/dist/core/agent-session-events.d.ts.map +1 -1
  51. package/dist/core/agent-session-events.js +1 -0
  52. package/dist/core/agent-session-events.js.map +1 -1
  53. package/dist/core/agent-session-extension-bindings.d.ts.map +1 -1
  54. package/dist/core/agent-session-extension-bindings.js +4 -0
  55. package/dist/core/agent-session-extension-bindings.js.map +1 -1
  56. package/dist/core/agent-session-methods.d.ts +6 -1
  57. package/dist/core/agent-session-methods.d.ts.map +1 -1
  58. package/dist/core/agent-session-methods.js.map +1 -1
  59. package/dist/core/agent-session-prompt.d.ts.map +1 -1
  60. package/dist/core/agent-session-prompt.js +3 -2
  61. package/dist/core/agent-session-prompt.js.map +1 -1
  62. package/dist/core/agent-session-types.d.ts +1 -0
  63. package/dist/core/agent-session-types.d.ts.map +1 -1
  64. package/dist/core/agent-session-types.js.map +1 -1
  65. package/dist/core/agent-session.d.ts +1 -0
  66. package/dist/core/agent-session.d.ts.map +1 -1
  67. package/dist/core/agent-session.js.map +1 -1
  68. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  69. package/dist/core/export-html/tool-renderer.js +2 -0
  70. package/dist/core/export-html/tool-renderer.js.map +1 -1
  71. package/dist/core/extensions/agent-events.d.ts +2 -0
  72. package/dist/core/extensions/agent-events.d.ts.map +1 -1
  73. package/dist/core/extensions/agent-events.js.map +1 -1
  74. package/dist/core/extensions/context-types.d.ts +2 -0
  75. package/dist/core/extensions/context-types.d.ts.map +1 -1
  76. package/dist/core/extensions/context-types.js.map +1 -1
  77. package/dist/core/extensions/mcp-server-registry.d.ts +4 -1
  78. package/dist/core/extensions/mcp-server-registry.d.ts.map +1 -1
  79. package/dist/core/extensions/mcp-server-registry.js +7 -4
  80. package/dist/core/extensions/mcp-server-registry.js.map +1 -1
  81. package/dist/core/extensions/runner.d.ts +3 -0
  82. package/dist/core/extensions/runner.d.ts.map +1 -1
  83. package/dist/core/extensions/runner.js +19 -0
  84. package/dist/core/extensions/runner.js.map +1 -1
  85. package/dist/core/extensions/tool-types.d.ts +2 -0
  86. package/dist/core/extensions/tool-types.d.ts.map +1 -1
  87. package/dist/core/extensions/tool-types.js.map +1 -1
  88. package/dist/core/fast-model-variants.d.ts.map +1 -1
  89. package/dist/core/fast-model-variants.js +3 -2
  90. package/dist/core/fast-model-variants.js.map +1 -1
  91. package/dist/core/model-resolver-defaults.d.ts.map +1 -1
  92. package/dist/core/model-resolver-defaults.js +2 -0
  93. package/dist/core/model-resolver-defaults.js.map +1 -1
  94. package/dist/core/nested-tool-calls.d.ts +1 -0
  95. package/dist/core/nested-tool-calls.d.ts.map +1 -1
  96. package/dist/core/nested-tool-calls.js +1 -0
  97. package/dist/core/nested-tool-calls.js.map +1 -1
  98. package/dist/core/settings-types.js.map +1 -1
  99. package/dist/core/tools/bash.d.ts.map +1 -1
  100. package/dist/core/tools/bash.js +7 -3
  101. package/dist/core/tools/bash.js.map +1 -1
  102. package/dist/core/tools/edit.js +1 -1
  103. package/dist/core/tools/edit.js.map +1 -1
  104. package/dist/extensions/codemode/execute.d.ts.map +1 -1
  105. package/dist/extensions/codemode/execute.js +55 -7
  106. package/dist/extensions/codemode/execute.js.map +1 -1
  107. package/dist/extensions/codemode/index.d.ts.map +1 -1
  108. package/dist/extensions/codemode/index.js +7 -0
  109. package/dist/extensions/codemode/index.js.map +1 -1
  110. package/dist/extensions/codemode/tool.d.ts +2 -0
  111. package/dist/extensions/codemode/tool.d.ts.map +1 -1
  112. package/dist/extensions/codemode/tool.js +2 -0
  113. package/dist/extensions/codemode/tool.js.map +1 -1
  114. package/dist/index.d.ts +1 -1
  115. package/dist/index.d.ts.map +1 -1
  116. package/dist/index.js.map +1 -1
  117. package/dist/modes/interactive/components/bash-execution.d.ts +5 -1
  118. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  119. package/dist/modes/interactive/components/bash-execution.js +17 -14
  120. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  121. package/dist/modes/interactive/components/branch-summary-message.d.ts +2 -1
  122. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  123. package/dist/modes/interactive/components/branch-summary-message.js +5 -2
  124. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  125. package/dist/modes/interactive/components/chat-message-renderer.d.ts +1 -0
  126. package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -1
  127. package/dist/modes/interactive/components/chat-message-renderer.js +19 -8
  128. package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -1
  129. package/dist/modes/interactive/components/compaction-boundary-message.d.ts +3 -2
  130. package/dist/modes/interactive/components/compaction-boundary-message.d.ts.map +1 -1
  131. package/dist/modes/interactive/components/compaction-boundary-message.js +7 -4
  132. package/dist/modes/interactive/components/compaction-boundary-message.js.map +1 -1
  133. package/dist/modes/interactive/components/custom-entry.d.ts +3 -1
  134. package/dist/modes/interactive/components/custom-entry.d.ts.map +1 -1
  135. package/dist/modes/interactive/components/custom-entry.js +8 -2
  136. package/dist/modes/interactive/components/custom-entry.js.map +1 -1
  137. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  138. package/dist/modes/interactive/components/custom-message.js +1 -0
  139. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  140. package/dist/modes/interactive/components/settings-selector-items.js +1 -1
  141. package/dist/modes/interactive/components/settings-selector-items.js.map +1 -1
  142. package/dist/modes/interactive/components/skill-invocation-message.d.ts +2 -1
  143. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  144. package/dist/modes/interactive/components/skill-invocation-message.js +5 -2
  145. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  146. package/dist/modes/interactive/components/tool-execution.d.ts +4 -0
  147. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  148. package/dist/modes/interactive/components/tool-execution.js +10 -0
  149. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  150. package/dist/modes/interactive/interactive-agent-events.js +3 -1
  151. package/dist/modes/interactive/interactive-agent-events.js.map +1 -1
  152. package/dist/modes/interactive/interactive-auth-login.js +6 -0
  153. package/dist/modes/interactive/interactive-auth-login.js.map +1 -1
  154. package/dist/modes/interactive/interactive-bash-compact.js +2 -2
  155. package/dist/modes/interactive/interactive-bash-compact.js.map +1 -1
  156. package/dist/modes/interactive/interactive-extension-dialogs.js +11 -2
  157. package/dist/modes/interactive/interactive-extension-dialogs.js.map +1 -1
  158. package/dist/modes/interactive/interactive-mode-base.d.ts +3 -0
  159. package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
  160. package/dist/modes/interactive/interactive-mode-base.js +6 -0
  161. package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
  162. package/dist/modes/interactive/interactive-mode-surface.d.ts +2 -1
  163. package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
  164. package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
  165. package/dist/modes/interactive/interactive-render-chat.js +7 -5
  166. package/dist/modes/interactive/interactive-render-chat.js.map +1 -1
  167. package/dist/modes/interactive/interactive-selectors.js +8 -1
  168. package/dist/modes/interactive/interactive-selectors.js.map +1 -1
  169. package/dist/modes/interactive/interactive-session-runtime.js +1 -0
  170. package/dist/modes/interactive/interactive-session-runtime.js.map +1 -1
  171. package/dist/modes/interactive/interactive-startup.js +1 -0
  172. package/dist/modes/interactive/interactive-startup.js.map +1 -1
  173. package/dist/modes/interactive/program-status-reporter.d.ts +24 -0
  174. package/dist/modes/interactive/program-status-reporter.d.ts.map +1 -0
  175. package/dist/modes/interactive/program-status-reporter.js +96 -0
  176. package/dist/modes/interactive/program-status-reporter.js.map +1 -0
  177. package/dist/modes/interactive-engine/engine-custom-ui.d.ts.map +1 -1
  178. package/dist/modes/interactive-engine/engine-custom-ui.js +1 -0
  179. package/dist/modes/interactive-engine/engine-custom-ui.js.map +1 -1
  180. package/dist/modes/interactive-engine/engine-render-service.d.ts.map +1 -1
  181. package/dist/modes/interactive-engine/engine-render-service.js +3 -1
  182. package/dist/modes/interactive-engine/engine-render-service.js.map +1 -1
  183. package/dist/modes/interactive-engine/protocol.d.ts +1 -0
  184. package/dist/modes/interactive-engine/protocol.d.ts.map +1 -1
  185. package/dist/modes/interactive-engine/protocol.js +1 -0
  186. package/dist/modes/interactive-engine/protocol.js.map +1 -1
  187. package/dist/modes/interactive-engine/remote-renderer.d.ts +3 -0
  188. package/dist/modes/interactive-engine/remote-renderer.d.ts.map +1 -1
  189. package/dist/modes/interactive-engine/remote-renderer.js +7 -0
  190. package/dist/modes/interactive-engine/remote-renderer.js.map +1 -1
  191. package/docs/codemode.md +10 -6
  192. package/docs/compaction.md +1 -1
  193. package/docs/computer-use.md +9 -9
  194. package/docs/custom-provider/streaming.md +4 -0
  195. package/docs/environment-variables.md +2 -0
  196. package/docs/extensions/api-reference.md +1 -1
  197. package/docs/extensions/authoring.md +2 -0
  198. package/docs/extensions/events.md +5 -1
  199. package/docs/getting-started/first-session.md +1 -1
  200. package/docs/json.md +6 -1
  201. package/docs/models/evals.md +34 -3
  202. package/docs/models.md +1 -1
  203. package/docs/providers.md +8 -0
  204. package/docs/sdk.md +7 -1
  205. package/docs/settings.md +1 -1
  206. package/docs/skills.md +1 -1
  207. package/docs/terminal-setup.md +14 -0
  208. package/docs/workflows/authoring.md +3 -3
  209. package/examples/extensions/sandbox/package-lock.json +3 -3
  210. package/npm-shrinkwrap.json +67 -67
  211. package/package.json +8 -8
@@ -165,6 +165,14 @@
165
165
  }
166
166
  let parameterGenerationState = 'idle';
167
167
  let parameterReadyAnnouncedSession = null;
168
+ // 'agent' when the generate verb fired this session's Go (the generate
169
+ // lane declares no knobs, so its bar never shows a pending Tune chip);
170
+ // null for every Go a user presses.
171
+ let sessionOrigin = null;
172
+ // The generate lane picks for the agent and never edits copy in the
173
+ // browser, so its selection carries no edit-copy badge (set on the
174
+ // agent-target pick, cleared with the session; a user's pick never sets it).
175
+ let editBadgeSuppressed = false;
168
176
  let svelteComponentSession = null;
169
177
  let svelteRuntimePromise = null;
170
178
  let pendingSvelteComponentRetryObserver = null;
@@ -983,9 +991,20 @@
983
991
  }
984
992
  } catch { /* cross-origin */ }
985
993
  }
994
+ // The selector a mechanical bake would anchor lasting rules on, and how
995
+ // many elements it matches right now: the bake refuses anything but one,
996
+ // since its rules would restyle every match, not just this element.
997
+ const cssIdent = (s) => /^[A-Za-z_-][\w-]*$/.test(s);
998
+ const anchorClasses = [...el.classList].filter(cssIdent);
999
+ const anchor = el.id && cssIdent(el.id)
1000
+ ? '#' + el.id
1001
+ : (anchorClasses.length ? el.tagName.toLowerCase() + '.' + anchorClasses.join('.') : null);
1002
+ let anchorMatches = null;
1003
+ if (anchor) { try { anchorMatches = document.querySelectorAll(anchor).length; } catch { anchorMatches = null; } }
986
1004
  return {
987
1005
  tagName: el.tagName.toLowerCase(), id: el.id || null,
988
1006
  classes: [...el.classList],
1007
+ anchor, anchorMatches,
989
1008
  textContent: (el.textContent || '').slice(0, 500),
990
1009
  outerHTML: sanitizedContextOuterHTML(el, 10000),
991
1010
  computedStyles: {
@@ -2037,6 +2056,7 @@
2037
2056
  function setLiveState(next) {
2038
2057
  state = next;
2039
2058
  window.__IMPECCABLE_LIVE_STATE__ = next;
2059
+ retryDeclinedAgentTargets();
2040
2060
  syncPageInteractionCursor();
2041
2061
  // Whether a queued steer is still behind a generation is a function of this
2042
2062
  // state, so the hint has to move with it, not only with the 5s poll.
@@ -4014,6 +4034,7 @@
4014
4034
 
4015
4035
  function hidePendingApplyDock() {
4016
4036
  pendingApplyInFlight = false;
4037
+ retryDeclinedAgentTargets();
4017
4038
  clearStoredManualApplyState();
4018
4039
  if (pendingIntroAnimation) { pendingIntroAnimation.cancel(); pendingIntroAnimation = null; }
4019
4040
  if (pendingDockEl) pendingDockEl.style.display = 'none';
@@ -4047,6 +4068,7 @@
4047
4068
  function setPendingApplyLoading(loading, count) {
4048
4069
  if (!pendingPillEl || !pendingPillLabelEl || !pendingPillCountEl || !pendingTrashBtn) return;
4049
4070
  pendingApplyInFlight = loading === true;
4071
+ if (!pendingApplyInFlight) retryDeclinedAgentTargets();
4050
4072
  const currentCount = count || parseInt(pendingPillEl.dataset.count || '0', 10) || 0;
4051
4073
  if (pendingApplyInFlight) storeManualApplyState(currentCount);
4052
4074
  else clearStoredManualApplyState();
@@ -4688,6 +4710,7 @@
4688
4710
  }
4689
4711
 
4690
4712
  function renderEditBadge(mode) {
4713
+ if (editBadgeSuppressed || sessionOrigin === 'agent') mode = 'hidden';
4691
4714
  if (mode === 'hidden' || !editBadgeEl) {
4692
4715
  hideConfigureBarTooltip();
4693
4716
  if (editBadgeEl) editBadgeEl.style.display = 'none';
@@ -6181,6 +6204,8 @@
6181
6204
  resetSessionFileMeta();
6182
6205
  currentSessionId = null;
6183
6206
  parameterGenerationState = 'idle';
6207
+ sessionOrigin = null;
6208
+ editBadgeSuppressed = false;
6184
6209
  parameterReadyAnnouncedSession = null;
6185
6210
  expectedVariants = 0;
6186
6211
  arrivedVariants = 0;
@@ -7112,6 +7137,398 @@
7112
7137
  }
7113
7138
 
7114
7139
  //
7140
+ // ------------------------------------------------------------------
7141
+ // Agent-initiated targeting (the `generate` command). The agent names an
7142
+ // element by CSS selector over POST /agent-target; the server pushes an
7143
+ // `agent_target` SSE message here. The overlay resolves the selector,
7144
+ // scrolls the element into view, enters the same picked state a user
7145
+ // click produces, and fires the normal Go pipeline, so everything
7146
+ // downstream (generate event, variants, cycling, accept) is unchanged.
7147
+ // The verdict goes back through POST /agent-target-result, which resolves
7148
+ // the agent's held-open CLI call.
7149
+
7150
+ function postAgentTargetResult(targetId, result) {
7151
+ fetch('http://localhost:' + PORT + '/agent-target-result?token=' + TOKEN, {
7152
+ method: 'POST',
7153
+ headers: { 'Content-Type': 'application/json' },
7154
+ body: JSON.stringify({ token: TOKEN, targetId, clientId: AGENT_TARGET_CLIENT_ID, ...result }),
7155
+ }).catch(() => { /* server gone; nothing to report to */ });
7156
+ }
7157
+
7158
+ function describeAgentTargetCandidate(el) {
7159
+ return {
7160
+ tag: el.tagName.toLowerCase(),
7161
+ id: el.id || null,
7162
+ classes: [...el.classList].filter((c) => !c.startsWith('impeccable-')),
7163
+ text: (el.textContent || '').trim().slice(0, 80),
7164
+ };
7165
+ }
7166
+
7167
+ function resolveAgentTargetElement(msg) {
7168
+ let matched;
7169
+ try {
7170
+ matched = [...document.querySelectorAll(msg.selector)];
7171
+ } catch {
7172
+ return { error: { ok: false, error: 'invalid_selector', selector: msg.selector } };
7173
+ }
7174
+ let candidates = matched.filter((el) => pickable(el));
7175
+ if (msg.text) {
7176
+ const needle = String(msg.text).toLowerCase();
7177
+ candidates = candidates.filter((el) => (el.textContent || '').toLowerCase().includes(needle));
7178
+ }
7179
+ if (candidates.length === 0) {
7180
+ return {
7181
+ error: {
7182
+ ok: false,
7183
+ error: 'no_match',
7184
+ selector: msg.selector,
7185
+ matchCount: 0,
7186
+ // How many nodes the raw selector hit before the pickable/text
7187
+ // filters: distinguishes a wrong selector from an unpickable match.
7188
+ rawMatchCount: matched.length,
7189
+ },
7190
+ };
7191
+ }
7192
+ if (Number.isInteger(msg.index)) {
7193
+ const el = candidates[msg.index - 1];
7194
+ if (!el) {
7195
+ return { error: { ok: false, error: 'index_out_of_range', selector: msg.selector, matchCount: candidates.length } };
7196
+ }
7197
+ return { el, matchCount: candidates.length };
7198
+ }
7199
+ if (candidates.length > 1) {
7200
+ return {
7201
+ error: {
7202
+ ok: false,
7203
+ error: 'ambiguous',
7204
+ selector: msg.selector,
7205
+ matchCount: candidates.length,
7206
+ candidates: candidates.slice(0, 8).map(describeAgentTargetCandidate),
7207
+ },
7208
+ };
7209
+ }
7210
+ return { el: candidates[0], matchCount: 1 };
7211
+ }
7212
+
7213
+ function scrollAgentTargetIntoView(el, done) {
7214
+ const rect = el.getBoundingClientRect();
7215
+ if (rect.top >= 0 && rect.bottom <= window.innerHeight) { done(); return; }
7216
+ let settled = false;
7217
+ let fallback = null;
7218
+ const finish = () => {
7219
+ if (settled) return;
7220
+ settled = true;
7221
+ removeEventListener('scrollend', finish, true);
7222
+ if (fallback) clearTimeout(fallback);
7223
+ done();
7224
+ };
7225
+ // scrollend where supported; a timer covers engines without it and the
7226
+ // no-movement case (element already at its final resting position).
7227
+ addEventListener('scrollend', finish, true);
7228
+ fallback = setTimeout(finish, 1200);
7229
+ el.scrollIntoView({ block: 'center', behavior: 'smooth' });
7230
+ }
7231
+
7232
+ // One id per page load: the server keys claims and roll-call reports on
7233
+ // it, and only the tab that holds the lease can renew it.
7234
+ const AGENT_TARGET_CLIENT_ID = id8();
7235
+
7236
+ // The agent target an agent-initiated Go is serving: set by
7237
+ // actOnAgentTarget around its handleGo call, read once by handleGo.
7238
+ let agentTargetForGo = null;
7239
+
7240
+ // The helper's word on its global bar. The generate lane asks the helper
7241
+ // to keep it out of the way (`impeccable live --no-live-bar`, or an agent
7242
+ // target carrying hideLiveBar), and the helper tells every connected tab
7243
+ // at once (`live_bar`) and every later connection on `connected`, so the
7244
+ // bar stays hidden in every tab, through reloads, the accept, and the
7245
+ // bake, until the helper stops and takes the overlay with it. The variant
7246
+ // controls still show.
7247
+ let liveBarHiddenByHelper = false;
7248
+
7249
+ function applyLiveBarPreference(hidden) {
7250
+ liveBarHiddenByHelper = hidden === true;
7251
+ setLiveBarHidden(liveBarHiddenByHelper);
7252
+ }
7253
+
7254
+ // A plain live session must never notice this code: hiding remembers the
7255
+ // bar's own display value and restoring puts exactly that back, and a
7256
+ // restore on a bar that is not hidden is a no-op, so the `connected`
7257
+ // frame every session receives changes nothing unless the lane asked.
7258
+ function setLiveBarHidden(hidden) {
7259
+ if (!globalBarEl) return;
7260
+ if (hidden) {
7261
+ if (globalBarEl.style.display !== 'none') {
7262
+ globalBarEl.dataset.liveBarDisplay = globalBarEl.style.display || 'flex';
7263
+ globalBarEl.style.display = 'none';
7264
+ }
7265
+ return;
7266
+ }
7267
+ if (globalBarEl.style.display === 'none') {
7268
+ globalBarEl.style.display = globalBarEl.dataset.liveBarDisplay || 'flex';
7269
+ }
7270
+ }
7271
+
7272
+ function claimAgentTarget(targetId, report) {
7273
+ return fetch('http://localhost:' + PORT + '/agent-target-claim?token=' + TOKEN, {
7274
+ method: 'POST',
7275
+ headers: { 'Content-Type': 'application/json' },
7276
+ body: JSON.stringify({ token: TOKEN, targetId, clientId: AGENT_TARGET_CLIENT_ID, ...report }),
7277
+ }).then((res) => res.json())
7278
+ .then((j) => ({ granted: !!j && j.granted === true, pending: !!j && j.pending === true }))
7279
+ .catch(() => ({ granted: false, pending: false }));
7280
+ }
7281
+
7282
+ // `exceptTargetId` is the target this call is about: a tab acting on it
7283
+ // is not busy for itself, but it is busy for every other target, or two
7284
+ // held requests could both be claimed here and the second Go would
7285
+ // overwrite the session the first one minted.
7286
+ function agentTargetBusyReason(exceptTargetId) {
7287
+ if (pendingApplyInFlight) return 'manual_apply_in_flight';
7288
+ if (state !== 'IDLE' && state !== 'PICKING' && state !== 'CONFIGURING') return 'session_active';
7289
+ for (const [targetId, status] of agentTargetsSeen) {
7290
+ if (status === 'acting' && targetId !== exceptTargetId) return 'agent_target_in_flight';
7291
+ }
7292
+ return null;
7293
+ }
7294
+
7295
+ // Targets this tab declined as busy. A busy report is only this tab's word
7296
+ // at that moment: the moment it is free again (setLiveState), it claims
7297
+ // each of these as eligible, and the server drops the stale report, so a
7298
+ // busy verdict is never built on a tab that has since gone idle. The
7299
+ // server denies claims for resolved targets, so retries are harmless.
7300
+ const busyDeclinedTargets = new Map();
7301
+
7302
+ function declineAgentTargetBusy(msg, busy) {
7303
+ busyDeclinedTargets.set(msg.targetId, msg);
7304
+ noteAgentTarget(msg.targetId, 'declined');
7305
+ claimAgentTarget(msg.targetId, { eligible: false, state, reason: busy });
7306
+ }
7307
+
7308
+ // A torn-down overlay, or one whose helper connection is gone, cannot
7309
+ // serve a target and must not even claim one: it would hold the lease for
7310
+ // a request it will never act on.
7311
+ function agentTargetOverlayGone() {
7312
+ return !evtSource;
7313
+ }
7314
+
7315
+ // A denied claimant retries at this cadence, a little over the lease, so
7316
+ // the first retry after a dead holder's lease lapses is granted.
7317
+ const AGENT_TARGET_RESCUE_RETRY_MS = 3500;
7318
+
7319
+ // Claim the lease and act as the holder. A denied claim means another tab
7320
+ // holds the lease. That holder can die before posting its result (reload,
7321
+ // crash, even after renewing), and its lease lapses after ~3s, so this tab
7322
+ // keeps retrying for as long as the server still holds the request: the
7323
+ // answer's `pending` is the server's word that the request is alive, and
7324
+ // it turns false the moment the request resolved or timed out, so no tab
7325
+ // retries a request nobody awaits. A tab that turned busy meanwhile joins
7326
+ // the roll call instead of taking a lease it cannot use. The first claim
7327
+ // and the busy-to-idle re-claim share this.
7328
+ function claimAndActOnAgentTarget(msg) {
7329
+ if (agentTargetOverlayGone()) return;
7330
+ const busy = agentTargetBusyReason(msg.targetId);
7331
+ if (busy) { declineAgentTargetBusy(msg, busy); return; }
7332
+ if (declineAgentTargetUnresolvable(msg)) return;
7333
+ claimAgentTarget(msg.targetId, { eligible: true }).then((claim) => {
7334
+ if (claim.granted) { noteAgentTarget(msg.targetId, 'acting'); actOnAgentTarget(msg); return; }
7335
+ noteAgentTarget(msg.targetId, 'denied');
7336
+ if (!claim.pending) return;
7337
+ setTimeout(() => claimAndActOnAgentTarget(msg), AGENT_TARGET_RESCUE_RETRY_MS);
7338
+ });
7339
+ }
7340
+
7341
+ function retryDeclinedAgentTargets() {
7342
+ if (busyDeclinedTargets.size === 0 || agentTargetBusyReason()) return;
7343
+ for (const [targetId, msg] of busyDeclinedTargets) {
7344
+ busyDeclinedTargets.delete(targetId);
7345
+ claimAndActOnAgentTarget(msg);
7346
+ }
7347
+ }
7348
+
7349
+ // This page's participation in each target it heard: 'acting' once a
7350
+ // claim was granted, 'done' once it replied (or stood down from a lapsed
7351
+ // lease), else the word it last gave. The server replays pending targets
7352
+ // to every connection that opens. After a reconnect that overlapped the
7353
+ // old connection the server still holds this page's word; after one that
7354
+ // did not, it dropped the word on the close, so a replayed target is
7355
+ // handled again: a busy or unresolvable page re-declines (idempotent), an
7356
+ // idle page claims.
7357
+ const agentTargetsSeen = new Map();
7358
+ function noteAgentTarget(targetId, status) {
7359
+ agentTargetsSeen.set(targetId, status);
7360
+ if (agentTargetsSeen.size > 100) agentTargetsSeen.delete(agentTargetsSeen.keys().next().value);
7361
+ }
7362
+
7363
+ // A target this page took a lease on is off-limits for a replay: while
7364
+ // acting (a second claim or Go), and once done, because its result may
7365
+ // still be on the wire and this tab is GENERATING by then, so handling
7366
+ // the replay would decline busy, hand the lease back mid-resolution, and
7367
+ // let another tab fire a second Go.
7368
+ function agentTargetTaken(targetId) {
7369
+ const status = agentTargetsSeen.get(targetId);
7370
+ return status === 'acting' || status === 'done';
7371
+ }
7372
+
7373
+ // Only a page that can resolve the target claims it. A tab whose page
7374
+ // lacks the element declines with its resolution verdict instead, so a
7375
+ // first-wins claim never lets the wrong page answer for a target that
7376
+ // another page has. The server prefers a busy report (a tab that could
7377
+ // serve later) over these, and returns the resolution verdict only when
7378
+ // no connected page can serve.
7379
+ //
7380
+ // An element can be momentarily absent (a route still rendering, an HMR
7381
+ // commit mid-swap), so a failed resolution is not this page's final word:
7382
+ // it is re-checked a few times over about two seconds, claiming the
7383
+ // moment the element mounts, and only the last miss is reported. The
7384
+ // server's timeout still bounds the whole exchange.
7385
+ // The page reports the miss at once (so the other overlays' words can
7386
+ // complete the roll call) and keeps re-checking at this cadence for as
7387
+ // long as the server says the request is pending: the server holds an
7388
+ // all-no_match roll call open for a short grace precisely so a late mount
7389
+ // can still be claimed, drops the stale report on an eligible claim, and
7390
+ // ends the watch by answering pending:false once the request resolved or
7391
+ // timed out.
7392
+ const AGENT_TARGET_RESOLVE_WATCH_MS = 500;
7393
+
7394
+ function declineAgentTargetUnresolvable(msg) {
7395
+ const probe = resolveAgentTargetElement(msg);
7396
+ if (!probe.error) return false;
7397
+ reportAgentTargetUnresolvable(msg, probe.error);
7398
+ return true;
7399
+ }
7400
+
7401
+ function reportAgentTargetUnresolvable(msg, error) {
7402
+ noteAgentTarget(msg.targetId, 'declined');
7403
+ claimAgentTarget(msg.targetId, { eligible: false, state, reason: 'no_match', result: error }).then((answer) => {
7404
+ if (!answer.pending) return;
7405
+ setTimeout(() => watchAgentTargetResolution(msg, error), AGENT_TARGET_RESOLVE_WATCH_MS);
7406
+ });
7407
+ }
7408
+
7409
+ function watchAgentTargetResolution(msg, lastError) {
7410
+ if (agentTargetOverlayGone() || agentTargetTaken(msg.targetId)) return;
7411
+ const busy = agentTargetBusyReason(msg.targetId);
7412
+ if (busy) { declineAgentTargetBusy(msg, busy); return; }
7413
+ const probe = resolveAgentTargetElement(msg);
7414
+ if (!probe.error) { claimAndActOnAgentTarget(msg); return; }
7415
+ // Still unresolvable: re-report (idempotent); the answer says whether
7416
+ // the server is still holding the request open.
7417
+ reportAgentTargetUnresolvable(msg, probe.error || lastError);
7418
+ }
7419
+
7420
+ function handleAgentTarget(msg) {
7421
+ if (!msg || typeof msg.targetId !== 'string') return;
7422
+ if (agentTargetTaken(msg.targetId)) return;
7423
+ noteAgentTarget(msg.targetId, 'heard');
7424
+ const busy = agentTargetBusyReason(msg.targetId);
7425
+ if (busy) {
7426
+ // Roll call: a busy tab reports itself and never acts. The server
7427
+ // answers `busy` the moment every connected overlay has reported, so
7428
+ // an idle tab elsewhere is never raced by a timer.
7429
+ declineAgentTargetBusy(msg, busy);
7430
+ return;
7431
+ }
7432
+ if (declineAgentTargetUnresolvable(msg)) return;
7433
+ // Eligible tabs race for the server's lease and only the holder acts. A
7434
+ // hidden tab yields a short head start so a visible one wins when both
7435
+ // exist, and still serves the request on its own: the user finds the
7436
+ // selection waiting when they return to it.
7437
+ setTimeout(() => claimAndActOnAgentTarget(msg), document.hidden ? 150 : 0);
7438
+ }
7439
+
7440
+ function actOnAgentTarget(msg) {
7441
+ if (agentTargetOverlayGone()) return;
7442
+ // Every exit ends this tab's acting state, so a later target is not
7443
+ // refused for a Go that already happened or never will.
7444
+ const reply = (result) => { noteAgentTarget(msg.targetId, 'done'); postAgentTargetResult(msg.targetId, result); };
7445
+ const busy = agentTargetBusyReason(msg.targetId);
7446
+ if (busy) {
7447
+ // Turned busy between claim and act: report it, which also hands the
7448
+ // lease back so the roll call can complete or a rescuer can claim.
7449
+ declineAgentTargetBusy(msg, busy);
7450
+ return;
7451
+ }
7452
+ const resolved = resolveAgentTargetElement(msg);
7453
+ if (resolved.error) {
7454
+ // The element went away between claim and act. A result would end the
7455
+ // request for every tab; a decline hands the lease back so another
7456
+ // page or a remount can still serve it.
7457
+ reportAgentTargetUnresolvable(msg, resolved.error);
7458
+ return;
7459
+ }
7460
+ const el = resolved.el;
7461
+ if (msg.dryRun) {
7462
+ reply({
7463
+ ok: true,
7464
+ dryRun: true,
7465
+ matchCount: resolved.matchCount,
7466
+ element: describeAgentTargetCandidate(el),
7467
+ });
7468
+ return;
7469
+ }
7470
+ scrollAgentTargetIntoView(el, () => {
7471
+ // Torn down during the scroll settle: do not renew. The lease lapses
7472
+ // for a rescuer instead of Go minting a session on a dismantled
7473
+ // overlay.
7474
+ if (agentTargetOverlayGone()) return;
7475
+ // Renew the lease right before the irreversible part: a tab whose
7476
+ // lease lapsed while it scrolled (a rescuer took over) stops here, so
7477
+ // one request never gets two Go presses.
7478
+ claimAgentTarget(msg.targetId, { eligible: true }).then((renewal) => {
7479
+ if (!renewal.granted) { noteAgentTarget(msg.targetId, 'done'); return; }
7480
+ // An insert placement left mid-configure gives way, exactly as a
7481
+ // click outside it does in handleClick.
7482
+ if (state === 'CONFIGURING' && configureKind === 'insert') cancelInsertConfigure();
7483
+ // Mirror of the user-click pick entry in handleClick, minus the
7484
+ // pick-mode gate (the agent's intent replaces the toggle); the entry
7485
+ // goes through beginNewLiveConfiguration like every other pick so
7486
+ // deferred recovery sees a fresh interaction revision.
7487
+ selectedElement = el;
7488
+ beginNewLiveConfiguration();
7489
+ showHighlight(selectedElement);
7490
+ clearAnnotations();
7491
+ showAnnotOverlay(selectedElement);
7492
+ showBar('configure');
7493
+ editBadgeSuppressed = true;
7494
+ renderEditBadge('hidden');
7495
+ startScrollTracking();
7496
+ maybePrefetchPage();
7497
+ maybeWarnConditionalAncestor(selectedElement);
7498
+ // Preset what the agent asked for, then fire the same Go a user press
7499
+ // fires. handleGo reads exactly these inputs.
7500
+ selectedAction = msg.action;
7501
+ selectedCount = msg.count;
7502
+ // updateBarContent rebuilds the configure row and replaces the input
7503
+ // element, so the prompt must be written into the input it creates,
7504
+ // never before (the action-chip click handler does the same dance).
7505
+ updateBarContent('configure');
7506
+ const input = uiGetById(PREFIX + '-input');
7507
+ if (input) input.value = msg.prompt || '';
7508
+ // The target rides on the generate event too: the helper resolves
7509
+ // the request from whichever lands first, so a page that dies
7510
+ // between Go and its result cannot leave the request pending for a
7511
+ // second Go elsewhere.
7512
+ const candidate = describeAgentTargetCandidate(el);
7513
+ agentTargetForGo = { targetId: msg.targetId, matchCount: resolved.matchCount, action: msg.action, count: msg.count, element: candidate };
7514
+ handleGo();
7515
+ agentTargetForGo = null;
7516
+ if (state === 'GENERATING' && currentSessionId) {
7517
+ reply({
7518
+ ok: true,
7519
+ matchCount: resolved.matchCount,
7520
+ sessionId: currentSessionId,
7521
+ action: msg.action,
7522
+ count: msg.count,
7523
+ element: candidate,
7524
+ });
7525
+ } else {
7526
+ reply({ ok: false, error: 'go_failed', state });
7527
+ }
7528
+ });
7529
+ });
7530
+ }
7531
+
7115
7532
  // SSE (server→browser) + fetch POST (browser→server)
7116
7533
  // Zero-dependency replacement for WebSocket.
7117
7534
  //
@@ -7121,7 +7538,7 @@
7121
7538
  const SSE_MAX_RETRIES = 20; // generous: heartbeats keep the connection alive, so retries mean real trouble
7122
7539
 
7123
7540
  function connectSSE() {
7124
- evtSource = new EventSource('http://localhost:' + PORT + '/events?token=' + TOKEN);
7541
+ evtSource = new EventSource('http://localhost:' + PORT + '/events?token=' + TOKEN + '&clientId=' + AGENT_TARGET_CLIENT_ID);
7125
7542
 
7126
7543
  evtSource.onopen = () => {
7127
7544
  sseRetries = 0; // reset on successful (re)connect
@@ -7132,8 +7549,11 @@
7132
7549
  let msg; try { msg = JSON.parse(e.data); } catch { return; }
7133
7550
  switch (msg.type) {
7134
7551
  case 'connected':
7552
+ applyLiveBarPreference(msg.hideLiveBar === true);
7135
7553
  hasProjectContext = !!msg.hasProjectContext;
7136
- if (!hasProjectContext) showToast(`No PRODUCT.md found. Variants will be brand-agnostic. Run ${IMPECCABLE_COMMAND} init to generate one.`, 7000);
7554
+ // The generate lane runs without PRODUCT.md by design and never
7555
+ // sends the user to init, so its quiet chrome skips this notice.
7556
+ if (!hasProjectContext && !liveBarHiddenByHelper) showToast(`No PRODUCT.md found. Variants will be brand-agnostic. Run ${IMPECCABLE_COMMAND} init to generate one.`, 7000);
7137
7557
  console.log('[impeccable] Live mode connected.');
7138
7558
  syncAgentPollingUi(!!msg.agentPolling);
7139
7559
  startAgentStatusPoll();
@@ -7143,9 +7563,15 @@
7143
7563
  syncPageInteractionCursor();
7144
7564
  syncPageChatFocus('sse-connected');
7145
7565
  break;
7566
+ case 'live_bar':
7567
+ applyLiveBarPreference(msg.hidden === true);
7568
+ break;
7146
7569
  case 'agent_polling':
7147
7570
  syncAgentPollingUi(!!msg.connected);
7148
7571
  break;
7572
+ case 'agent_target':
7573
+ handleAgentTarget(msg);
7574
+ break;
7149
7575
  case 'agent_phase':
7150
7576
  if (msg.id === currentSessionId && (state === 'GENERATING' || state === 'CYCLING')) {
7151
7577
  // Advance the visible phase monotonically. A behind/resumed
@@ -7208,6 +7634,11 @@
7208
7634
  disableInlineEdit();
7209
7635
  refreshParamsPanel();
7210
7636
  }
7637
+ // The done reply is the agent's last word on this generation:
7638
+ // with every variant mounted and no knobs declared, the Tune
7639
+ // chip must stop spinning. A reload between the mount and this
7640
+ // reply restored the pending state from the cache.
7641
+ completeParameterGenerationIfReady();
7211
7642
  break;
7212
7643
  }
7213
7644
  // Source fallback when HMR did not land variants in this tab.
@@ -7371,6 +7802,15 @@
7371
7802
  }).then(async res => {
7372
7803
  if (res.ok) return res;
7373
7804
  const body = await res.json().catch(() => ({}));
7805
+ // The helper refused to open a session for an agent target it has
7806
+ // already answered (another page served it after this page's lease
7807
+ // lapsed mid-capture, or the request timed out): drop the local
7808
+ // session and hand the surface back.
7809
+ if (body.error === 'agent_target_already_served' && msg.type === 'generate'
7810
+ && msg.id && msg.id === currentSessionId) {
7811
+ abandonSupersededGo(msg.id);
7812
+ return null;
7813
+ }
7374
7814
  // The server refused to journal progress for a session it has never
7375
7815
  // seen: this browser is carrying state from another project or a
7376
7816
  // wiped store (two apps sharing a localhost port). Continuing to
@@ -7392,6 +7832,14 @@
7392
7832
  return sessionCreationGate.then(doSend);
7393
7833
  }
7394
7834
 
7835
+ function abandonSupersededGo(sessionId) {
7836
+ if (sessionId !== currentSessionId) return;
7837
+ console.warn('[impeccable] The helper already answered this agent target; clearing session ' + sessionId + '.');
7838
+ markSessionHandled();
7839
+ cleanup({ instantChrome: true });
7840
+ showToast('The helper already answered this request, so this session was cleared. Pick an element to start fresh.', 6000);
7841
+ }
7842
+
7395
7843
  let abandonedForeignSessionId = null;
7396
7844
  function abandonForeignSession(sessionId) {
7397
7845
  if (abandonedForeignSessionId === sessionId || sessionId !== currentSessionId) return;
@@ -7796,6 +8244,7 @@
7796
8244
  visibleVariant = 0;
7797
8245
  generationPhase = 'queued';
7798
8246
  parameterGenerationState = 'pending';
8247
+ sessionOrigin = agentTargetForGo ? 'agent' : null;
7799
8248
  parameterReadyAnnouncedSession = null;
7800
8249
  resetSessionFileMeta();
7801
8250
 
@@ -7821,6 +8270,24 @@
7821
8270
  };
7822
8271
  if (snapshot.comments.length > 0) basePayload.comments = snapshot.comments;
7823
8272
  if (snapshot.strokes.length > 0) basePayload.strokes = snapshot.strokes;
8273
+ if (agentTargetForGo) {
8274
+ // An agent-initiated Go names the target it serves (see
8275
+ // actOnAgentTarget): the helper resolves that request from this event
8276
+ // as well as from the overlay's own result post.
8277
+ basePayload.agentTarget = {
8278
+ targetId: agentTargetForGo.targetId,
8279
+ clientId: AGENT_TARGET_CLIENT_ID,
8280
+ result: {
8281
+ ok: true,
8282
+ matchCount: agentTargetForGo.matchCount,
8283
+ sessionId: currentSessionId,
8284
+ action: agentTargetForGo.action,
8285
+ count: agentTargetForGo.count,
8286
+ element: agentTargetForGo.element,
8287
+ },
8288
+ };
8289
+ agentTargetForGo = null;
8290
+ }
7824
8291
 
7825
8292
  // Hide the interactive overlay so it doesn't linger during generation.
7826
8293
  hideAnnotOverlay();
@@ -7881,6 +8348,7 @@
7881
8348
  visibleVariant = 0;
7882
8349
  generationPhase = 'queued';
7883
8350
  parameterGenerationState = 'pending';
8351
+ sessionOrigin = agentTargetForGo ? 'agent' : null;
7884
8352
  parameterReadyAnnouncedSession = null;
7885
8353
  resetSessionFileMeta();
7886
8354
  selectedElement = placeholderElement;
@@ -8927,6 +9395,8 @@ void main() {
8927
9395
  pagePickSkipClick = false;
8928
9396
  currentSessionId = null;
8929
9397
  parameterGenerationState = 'idle';
9398
+ sessionOrigin = null;
9399
+ editBadgeSuppressed = false;
8930
9400
  parameterReadyAnnouncedSession = null;
8931
9401
  selectedAction = 'impeccable';
8932
9402
  pendingAcceptedSession = null;
@@ -9018,6 +9488,7 @@ void main() {
9018
9488
  paramsCurrentValues = { ...saved.paramValues };
9019
9489
  }
9020
9490
  if (saved.parameterState) parameterGenerationState = saved.parameterState;
9491
+ sessionOrigin = saved.origin === 'agent' ? 'agent' : null;
9021
9492
  if (saved.generationPhase) generationPhase = saved.generationPhase;
9022
9493
  }
9023
9494
 
@@ -9105,7 +9576,12 @@ void main() {
9105
9576
  }
9106
9577
 
9107
9578
  function restoreSessionWithoutWrapper(reason, activeSessions) {
9108
- const cached = loadSession();
9579
+ // The session cache is per origin, so a tab on another page of the same
9580
+ // app sees this page's session too. Only the page that saved it may
9581
+ // resume it: the server-adoption branch below already applies the same
9582
+ // check, and a tab on another page has nothing to render for it.
9583
+ const cachedRaw = loadSession();
9584
+ const cached = cachedRaw?.id && !pageMatchesCurrent(cachedRaw.pageUrl) ? null : cachedRaw;
9109
9585
  // localStorage is a cache, not a gate. A cleared tab, a second browser
9110
9586
  // profile, or a teardown that dropped local state all leave the durable
9111
9587
  // server session as the only record of work in progress; adopt it instead
@@ -9218,6 +9694,7 @@ void main() {
9218
9694
  pageUrl: location.pathname,
9219
9695
  paramValues: { ...paramsCurrentValues },
9220
9696
  parameterState: parameterGenerationState,
9697
+ origin: sessionOrigin || undefined,
9221
9698
  insertPlaceholder: insertPlaceholderSnapshot || undefined,
9222
9699
  pickedAnchor: pickedAnchorSnapshot || undefined,
9223
9700
  pickedAnchorViewportTop: Number.isFinite(pickedAnchorViewportTop) ? pickedAnchorViewportTop : undefined,
@@ -9343,6 +9820,8 @@ void main() {
9343
9820
  pagePickSkipClick = false;
9344
9821
  currentSessionId = null;
9345
9822
  parameterGenerationState = 'idle';
9823
+ sessionOrigin = null;
9824
+ editBadgeSuppressed = false;
9346
9825
  parameterReadyAnnouncedSession = null;
9347
9826
  selectedAction = 'impeccable';
9348
9827
  renderEditBadge('hidden');
@@ -9601,6 +10080,14 @@ void main() {
9601
10080
 
9602
10081
  const resumedState = arrivedVariants > 0 ? 'CYCLING' : 'GENERATING';
9603
10082
 
10083
+ // A reload between the variants mounting and the agent's done reply
10084
+ // restores a pending Tune state from the cache; the helper knows whether
10085
+ // that generation already finished.
10086
+ if (arrivedVariants >= expectedVariants && expectedVariants > 0
10087
+ && (parameterGenerationState === 'pending' || parameterGenerationState === 'loading')) {
10088
+ settleParameterStateFromHelper(sessionId);
10089
+ }
10090
+
9604
10091
  // Find the visible variant's content element for highlight positioning.
9605
10092
  const isInsert = wrapper.dataset.impeccableMode === 'insert';
9606
10093
  const visEl = visibleVariant > 0 ? pickVariantContent(wrapper, visibleVariant) : null;
@@ -11064,6 +11551,21 @@ void main() {
11064
11551
  }
11065
11552
  }
11066
11553
 
11554
+ // After a resume the cache may say the Tune knobs are still coming while
11555
+ // the agent already replied done before the reload. The helper's session
11556
+ // record settles it; otherwise the done reply on SSE does.
11557
+ function settleParameterStateFromHelper(sessionId) {
11558
+ fetch('http://localhost:' + PORT + '/status?token=' + TOKEN, { cache: 'no-store' })
11559
+ .then((res) => (res.ok ? res.json() : null))
11560
+ .then((data) => {
11561
+ if (!data || sessionId !== currentSessionId) return;
11562
+ const session = (data.activeSessions || []).find((s) => s && s.id === sessionId);
11563
+ if (!session) return;
11564
+ if (session.generationCompletedAt || session.generationPhase === 'completed') completeParameterGenerationIfReady();
11565
+ })
11566
+ .catch(() => { /* the done reply on SSE settles it otherwise */ });
11567
+ }
11568
+
11067
11569
  function fetchAgentPollingStatus() {
11068
11570
  fetch('http://localhost:' + PORT + '/status?token=' + TOKEN, { cache: 'no-store' })
11069
11571
  .then((res) => (res.ok ? res.json() : null))
@@ -11103,11 +11605,15 @@ void main() {
11103
11605
  uiAppendStyle(s);
11104
11606
  }
11105
11607
 
11608
+ // The generate lane's helper says so in the served script itself, so a
11609
+ // lane session never draws the bar at all; every other session mounts
11610
+ // it exactly as before.
11611
+ const barHiddenFromStart = window.__IMPECCABLE_LIVE_BAR_HIDDEN__ === true;
11106
11612
  globalBarEl = el('div', {
11107
11613
  position: 'fixed', bottom: '14px', left: '50%',
11108
11614
  transform: 'translateX(-50%) translateY(20px)',
11109
11615
  zIndex: Z.bar + 5,
11110
- display: 'flex', alignItems: 'stretch',
11616
+ display: barHiddenFromStart ? 'none' : 'flex', alignItems: 'stretch',
11111
11617
  gap: '0',
11112
11618
  width: 'max-content',
11113
11619
  background: P.surface,
@@ -11123,6 +11629,10 @@ void main() {
11123
11629
  });
11124
11630
  globalBarEl.id = PREFIX + '-global-bar';
11125
11631
  globalBarEl.dataset.theme = theme;
11632
+ if (barHiddenFromStart) {
11633
+ liveBarHiddenByHelper = true;
11634
+ globalBarEl.dataset.liveBarDisplay = 'flex';
11635
+ }
11126
11636
 
11127
11637
  // Brand mark - kinpaku Impeccable icon (site header / favicon paths).
11128
11638
  const brand = el('span', {
@@ -11518,6 +12028,9 @@ void main() {
11518
12028
  // Listen for detection results AND ready signal
11519
12029
  window.addEventListener('message', onDetectMessage);
11520
12030
  updateGlobalBarState();
12031
+ // The helper may already have said the bar stays hidden (a connect
12032
+ // that raced the bar build, or a reload mid-lane): re-apply it here.
12033
+ if (liveBarHiddenByHelper) setLiveBarHidden(true);
11521
12034
  }
11522
12035
 
11523
12036
  function updateGlobalBarState() {
@@ -11714,6 +12227,13 @@ void main() {
11714
12227
 
11715
12228
  /** Full teardown: remove all UI, disconnect SSE, clean up. */
11716
12229
  function teardown() {
12230
+ // Declined targets die with the overlay: the IDLE transition below must
12231
+ // not re-claim a lease this page can no longer act on. So does the
12232
+ // target ledger: an 'acting' entry from a Go that never happened must
12233
+ // not refuse every target the next connection hears.
12234
+ busyDeclinedTargets.clear();
12235
+ agentTargetsSeen.clear();
12236
+ liveBarHiddenByHelper = false;
11717
12237
  stopAgentStatusPoll();
11718
12238
  hideAgentPollTooltip();
11719
12239
  if (agentPollTooltipEl) {