@spotpatch/runtime 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -104,6 +104,7 @@ var CAPABILITY_STATE_VALUES = new Set(import_shared2.AGENT_CAPABILITY_STATES);
104
104
  var CHECK_STATUS_VALUES = new Set(import_shared2.AGENT_CHECK_STATUSES);
105
105
  var FILE_CHANGE_KIND_VALUES = new Set(import_shared2.AGENT_FILE_CHANGE_KINDS);
106
106
  var JOB_STATUS_VALUES = new Set(import_shared2.AGENT_JOB_STATUSES);
107
+ var WORKSPACE_STATE_VALUES = new Set(import_shared2.AGENT_WORKSPACE_STATES);
107
108
  var PROFILE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
108
109
  var JOB_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
109
110
  var ISO_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/;
@@ -157,6 +158,35 @@ function isAgentCapabilitySnapshot(value) {
157
158
  }
158
159
  return isProfileId(value.providerProfileId) && isBoundedString(value.providerLabel, 100) && isProfileId(value.modelProfileId) && isBoundedString(value.modelLabel, 100) && (value.protocol === "responses" || value.protocol === "chat-completions") && typeof value.state === "string" && CAPABILITY_STATE_VALUES.has(value.state) && typeof value.authenticated === "boolean" && typeof value.modelAvailable === "boolean" && typeof value.toolCalling === "boolean" && typeof value.toolResultContinuation === "boolean" && typeof value.streaming === "boolean" && (value.checkedAt === void 0 || isIsoTimestamp(value.checkedAt)) && (value.errorCode === void 0 || isAgentErrorCode(value.errorCode));
159
160
  }
161
+ function isAgentWorkspaceHealthSnapshot(value) {
162
+ if (!isRecord(value) || !hasOnlyKeys(value, [
163
+ "state",
164
+ "checkedAt",
165
+ "changes",
166
+ "canIncludeLocalChanges",
167
+ "errorCode"
168
+ ]) || typeof value.state !== "string" || !WORKSPACE_STATE_VALUES.has(value.state) || !isIsoTimestamp(value.checkedAt) || typeof value.canIncludeLocalChanges !== "boolean" || value.errorCode !== void 0 && !isAgentErrorCode(value.errorCode) || !isRecord(value.changes) || !hasOnlyKeys(value.changes, [
169
+ "staged",
170
+ "unstaged",
171
+ "untracked",
172
+ "conflicted",
173
+ "total"
174
+ ])) {
175
+ return false;
176
+ }
177
+ const changes = value.changes;
178
+ const { staged, unstaged, untracked, conflicted, total } = changes;
179
+ if (!isNonnegativeInteger(staged) || !isNonnegativeInteger(unstaged) || !isNonnegativeInteger(untracked) || !isNonnegativeInteger(conflicted) || !isNonnegativeInteger(total) || total < staged || total < unstaged || total < untracked || total < conflicted || total > staged + unstaged + untracked) {
180
+ return false;
181
+ }
182
+ if (value.state === "ready") {
183
+ return total === 0 && !value.canIncludeLocalChanges && value.errorCode === void 0;
184
+ }
185
+ if (value.state === "consent-required") {
186
+ return total > 0 && conflicted === 0 && value.canIncludeLocalChanges && value.errorCode === void 0;
187
+ }
188
+ return !value.canIncludeLocalChanges && value.errorCode !== void 0;
189
+ }
160
190
  function isAgentJobSnapshot(value) {
161
191
  if (!isRecord(value) || !hasOnlyKeys(value, [
162
192
  "jobId",
@@ -497,6 +527,17 @@ function createRuntimeApi(options) {
497
527
  request
498
528
  );
499
529
  },
530
+ async agentWorkspaceHealth() {
531
+ const data = await requestJson(
532
+ import_shared3.SPOTPATCH_ENDPOINTS.agentWorkspaceHealth,
533
+ "POST",
534
+ emptyAction
535
+ );
536
+ if (!isAgentWorkspaceHealthSnapshot(data)) {
537
+ throw new RuntimeApiError();
538
+ }
539
+ return deepFreeze(data);
540
+ },
500
541
  async createAgentJob(request) {
501
542
  const serverRequest = omitBrowserCodeContext(request);
502
543
  return parseJobSnapshot(
@@ -1567,12 +1608,64 @@ var AGENT_PANEL_STYLES = `
1567
1608
  min-height: 46px;
1568
1609
  margin-top: 7px;
1569
1610
  border: 1px solid var(--spotpatch-border);
1570
- border-radius: 10px;
1571
- padding: 8px 30px 8px 10px;
1611
+ border-radius: 12px;
1612
+ padding: 10px 38px 10px 13px;
1572
1613
  color: #e8ebf2;
1573
- background: var(--spotpatch-bg-raised);
1614
+ color-scheme: dark;
1615
+ background: linear-gradient(180deg, rgb(255 255 255 / 4%), rgb(255 255 255 / 2%));
1574
1616
  font-size: 15px;
1617
+ font-weight: 560;
1618
+ line-height: 1.35;
1575
1619
  outline: none;
1620
+ transition: border-color 160ms ease, background-color 160ms ease, box-shadow 160ms ease;
1621
+ }
1622
+ .spotpatch-agent select:hover { border-color: rgb(148 163 184 / 38%); background-color: rgb(255 255 255 / 5%); }
1623
+ .spotpatch-agent select:focus-visible { border-color: rgb(139 124 247 / 76%); box-shadow: 0 0 0 3px rgb(109 93 246 / 18%); }
1624
+ .spotpatch-agent select option { min-height: 42px; color: #e8ebf2; background: #10151e; }
1625
+ @supports (appearance: base-select) {
1626
+ .spotpatch-agent select,
1627
+ ::picker(select) { appearance: base-select; }
1628
+ .spotpatch-agent select { padding: 10px 13px; }
1629
+ .spotpatch-agent select::picker-icon {
1630
+ color: #9da6b5;
1631
+ transition: rotate 180ms cubic-bezier(.2, .8, .2, 1), color 160ms ease;
1632
+ }
1633
+ .spotpatch-agent select:open::picker-icon { rotate: 180deg; color: #c4b5fd; }
1634
+ .spotpatch-agent select::picker(select) {
1635
+ top: calc(anchor(bottom) + 7px);
1636
+ left: anchor(left);
1637
+ width: anchor-size(width);
1638
+ max-height: min(280px, 40vh);
1639
+ margin: 0;
1640
+ overflow: hidden;
1641
+ border: 1px solid rgb(139 124 247 / 28%);
1642
+ border-radius: 12px;
1643
+ padding: 6px;
1644
+ color: #e8ebf2;
1645
+ background: #10151e;
1646
+ box-shadow: 0 18px 50px rgb(0 0 0 / 38%), 0 0 0 1px rgb(255 255 255 / 3%);
1647
+ opacity: 0;
1648
+ transform: translateY(-5px) scale(.985);
1649
+ transform-origin: top center;
1650
+ transition: opacity 150ms ease, transform 180ms cubic-bezier(.2, .8, .2, 1), overlay 180ms allow-discrete, display 180ms allow-discrete;
1651
+ }
1652
+ ::picker(select):popover-open { opacity: 1; transform: translateY(0) scale(1); }
1653
+ @starting-style {
1654
+ ::picker(select):popover-open { opacity: 0; transform: translateY(-5px) scale(.985); }
1655
+ }
1656
+ .spotpatch-agent select option {
1657
+ min-height: 42px;
1658
+ border-radius: 8px;
1659
+ padding: 10px 12px;
1660
+ color: #dfe4ee;
1661
+ background: transparent;
1662
+ cursor: pointer;
1663
+ transition: color 120ms ease, background-color 120ms ease;
1664
+ }
1665
+ .spotpatch-agent select option:hover,
1666
+ .spotpatch-agent select option:focus { color: #fff; background: rgb(109 93 246 / 16%); }
1667
+ .spotpatch-agent select option:checked { color: #fff; background: rgb(109 93 246 / 24%); }
1668
+ .spotpatch-agent select option::checkmark { color: #8b7cf7; }
1576
1669
  }
1577
1670
  .spotpatch-agent select:focus-visible,
1578
1671
  .spotpatch-consent input:focus-visible {
@@ -1590,11 +1683,22 @@ var AGENT_PANEL_STYLES = `
1590
1683
  line-height: 1.6;
1591
1684
  }
1592
1685
  .spotpatch-consent input { width: 16px; height: 16px; flex: none; margin: 3px 0 0; accent-color: var(--spotpatch-accent); }
1686
+ .spotpatch-workspace-consent {
1687
+ margin-top: 12px;
1688
+ border: 1px solid rgb(251 191 36 / 18%);
1689
+ border-radius: 10px;
1690
+ padding: 11px 12px;
1691
+ background: rgb(120 53 15 / 9%);
1692
+ }
1693
+ .spotpatch-workspace-consent strong { display: block; color: #fde68a; font-size: 13px; font-weight: 620; }
1694
+ .spotpatch-workspace-consent small { display: block; margin-top: 2px; color: #aeb6c4; font-size: 12px; line-height: 1.5; }
1695
+ .spotpatch-agent-health-list { display: grid; gap: 8px; margin-top: 14px; }
1696
+ .spotpatch-agent-workspace,
1593
1697
  .spotpatch-agent-capability {
1594
1698
  display: flex;
1595
1699
  align-items: center;
1596
1700
  gap: 8px;
1597
- margin: 14px 0 0;
1701
+ margin: 0;
1598
1702
  color: #929bab;
1599
1703
  font-size: 13px;
1600
1704
  }
@@ -1611,6 +1715,21 @@ var AGENT_PANEL_STYLES = `
1611
1715
  .spotpatch-agent-capability[data-state="ready"]::before { background: var(--spotpatch-success); }
1612
1716
  .spotpatch-agent-capability[data-state="error"] { color: #fecaca; }
1613
1717
  .spotpatch-agent-capability[data-state="error"]::before { background: var(--spotpatch-danger); }
1718
+ .spotpatch-agent-workspace::before {
1719
+ width: 6px;
1720
+ height: 6px;
1721
+ flex: none;
1722
+ border-radius: 999px;
1723
+ background: #64748b;
1724
+ content: "";
1725
+ }
1726
+ .spotpatch-agent-workspace[data-state="checking"]::before { background: var(--spotpatch-warning); }
1727
+ .spotpatch-agent-workspace[data-state="ready"] { color: #a7f3d0; }
1728
+ .spotpatch-agent-workspace[data-state="ready"]::before { background: var(--spotpatch-success); }
1729
+ .spotpatch-agent-workspace[data-state="consent-required"] { color: #fde68a; }
1730
+ .spotpatch-agent-workspace[data-state="consent-required"]::before { background: #fbbf24; }
1731
+ .spotpatch-agent-workspace[data-state="blocked"] { color: #fecaca; }
1732
+ .spotpatch-agent-workspace[data-state="blocked"]::before { background: var(--spotpatch-danger); }
1614
1733
  .spotpatch-agent-job { padding: 16px; }
1615
1734
  .spotpatch-agent-job-meta { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
1616
1735
  .spotpatch-agent-model { min-width: 0; overflow: hidden; color: #d6dafe; font-size: 13px; text-overflow: ellipsis; white-space: nowrap; }
@@ -1699,10 +1818,26 @@ function createAgentPanel(document, ai, localizer) {
1699
1818
  consentCheckbox.type = "checkbox";
1700
1819
  const consentText = createMarkedElement(document, "span");
1701
1820
  consent.append(consentCheckbox, consentText);
1821
+ const workspaceConsent = createMarkedElement(document, "label");
1822
+ workspaceConsent.className = "spotpatch-consent spotpatch-workspace-consent";
1823
+ workspaceConsent.hidden = true;
1824
+ const workspaceConsentCheckbox = createMarkedElement(document, "input");
1825
+ workspaceConsentCheckbox.type = "checkbox";
1826
+ const workspaceConsentContent = createMarkedElement(document, "span");
1827
+ const workspaceConsentTitle = createMarkedElement(document, "strong");
1828
+ const workspaceConsentHelp = createMarkedElement(document, "small");
1829
+ workspaceConsentContent.append(workspaceConsentTitle, workspaceConsentHelp);
1830
+ workspaceConsent.append(workspaceConsentCheckbox, workspaceConsentContent);
1831
+ const healthList = createMarkedElement(document, "div");
1832
+ healthList.className = "spotpatch-agent-health-list";
1833
+ const workspace = createMarkedElement(document, "p");
1834
+ workspace.className = "spotpatch-agent-workspace";
1835
+ workspace.dataset.state = "idle";
1702
1836
  const capability = createMarkedElement(document, "p");
1703
1837
  capability.className = "spotpatch-agent-capability";
1704
1838
  capability.dataset.state = "idle";
1705
- setup.append(selectors, consent, capability);
1839
+ healthList.append(workspace, capability);
1840
+ setup.append(selectors, consent, workspaceConsent, healthList);
1706
1841
  const job = createMarkedElement(document, "div");
1707
1842
  job.className = "spotpatch-agent-job";
1708
1843
  job.hidden = true;
@@ -1761,6 +1896,9 @@ function createAgentPanel(document, ai, localizer) {
1761
1896
  let latestCapabilityState = "idle";
1762
1897
  let latestCapabilityMessage = messages.agent.connectionNotTested;
1763
1898
  let latestCapabilityErrorCode;
1899
+ let latestWorkspaceState = "idle";
1900
+ let latestWorkspaceSnapshot;
1901
+ let latestWorkspaceErrorCode;
1764
1902
  for (const provider of providers) {
1765
1903
  addOption(document, providerSelect, provider.id, provider.label);
1766
1904
  }
@@ -1788,7 +1926,7 @@ function createAgentPanel(document, ai, localizer) {
1788
1926
  testButton.hidden = !setupVisible;
1789
1927
  runButton.hidden = !setupVisible;
1790
1928
  testButton.disabled = !editingEnabled || capabilityProbing || selectedProvider() === void 0;
1791
- runButton.disabled = !editingEnabled || capabilityProbing || !contextReady || !consentCheckbox.checked || selectedProvider() === void 0 || modelSelect.value.length === 0;
1929
+ runButton.disabled = !editingEnabled || capabilityProbing || !contextReady || !consentCheckbox.checked || latestWorkspaceState === "idle" || latestWorkspaceState === "checking" || latestWorkspaceState === "blocked" || latestWorkspaceState === "consent-required" && !workspaceConsentCheckbox.checked || selectedProvider() === void 0 || modelSelect.value.length === 0;
1792
1930
  if (!jobPresented || !selectionVisible) {
1793
1931
  cancelButton.hidden = true;
1794
1932
  applyButton.hidden = true;
@@ -1796,8 +1934,25 @@ function createAgentPanel(document, ai, localizer) {
1796
1934
  resetButton.hidden = true;
1797
1935
  }
1798
1936
  };
1799
- populateModels();
1800
- providerSelect.addEventListener("change", () => {
1937
+ const renderWorkspaceMessage = () => {
1938
+ if (latestWorkspaceState === "idle") {
1939
+ workspace.textContent = messages.agent.workspaceNotChecked;
1940
+ } else if (latestWorkspaceState === "checking") {
1941
+ workspace.textContent = messages.agent.checkingWorkspace;
1942
+ } else if (latestWorkspaceState === "ready") {
1943
+ workspace.textContent = messages.agent.workspaceReady;
1944
+ } else if (latestWorkspaceState === "consent-required") {
1945
+ const changes = latestWorkspaceSnapshot?.changes;
1946
+ workspace.textContent = messages.agent.workspaceDirty(
1947
+ changes?.staged ?? 0,
1948
+ changes?.unstaged ?? 0,
1949
+ changes?.untracked ?? 0
1950
+ );
1951
+ } else {
1952
+ workspace.textContent = latestWorkspaceErrorCode === void 0 ? messages.errors.INTERNAL_ERROR : messages.errors[latestWorkspaceErrorCode];
1953
+ }
1954
+ };
1955
+ function handleProviderChange() {
1801
1956
  populateModels();
1802
1957
  consentCheckbox.checked = false;
1803
1958
  capability.dataset.state = "idle";
@@ -1807,8 +1962,8 @@ function createAgentPanel(document, ai, localizer) {
1807
1962
  latestCapabilityErrorCode = void 0;
1808
1963
  capabilityReady = false;
1809
1964
  refreshActions();
1810
- });
1811
- modelSelect.addEventListener("change", () => {
1965
+ }
1966
+ function handleModelChange() {
1812
1967
  capability.dataset.state = "idle";
1813
1968
  capability.textContent = messages.agent.connectionNotTested;
1814
1969
  latestCapabilityState = "idle";
@@ -1816,8 +1971,12 @@ function createAgentPanel(document, ai, localizer) {
1816
1971
  latestCapabilityErrorCode = void 0;
1817
1972
  capabilityReady = false;
1818
1973
  refreshActions();
1819
- });
1974
+ }
1975
+ populateModels();
1976
+ providerSelect.addEventListener("change", handleProviderChange);
1977
+ modelSelect.addEventListener("change", handleModelChange);
1820
1978
  consentCheckbox.addEventListener("change", refreshActions);
1979
+ workspaceConsentCheckbox.addEventListener("change", refreshActions);
1821
1980
  function renderCurrentJob() {
1822
1981
  if (latestSnapshot === void 0) {
1823
1982
  return;
@@ -1872,6 +2031,8 @@ function createAgentPanel(document, ai, localizer) {
1872
2031
  modelText.textContent = messages.agent.model;
1873
2032
  providerSelect.setAttribute("aria-label", messages.agent.providerAriaLabel);
1874
2033
  modelSelect.setAttribute("aria-label", messages.agent.modelAriaLabel);
2034
+ workspaceConsentTitle.textContent = messages.agent.includeLocalChanges;
2035
+ workspaceConsentHelp.textContent = messages.agent.includeLocalChangesHelp;
1875
2036
  diff.setAttribute("aria-label", messages.agent.diffAriaLabel);
1876
2037
  testButton.textContent = messages.agent.testConnection;
1877
2038
  applyButton.textContent = messages.agent.apply;
@@ -1892,6 +2053,7 @@ function createAgentPanel(document, ai, localizer) {
1892
2053
  } else {
1893
2054
  capability.textContent = latestCapabilityMessage;
1894
2055
  }
2056
+ renderWorkspaceMessage();
1895
2057
  refreshActions();
1896
2058
  renderCurrentJob();
1897
2059
  }
@@ -1902,6 +2064,7 @@ function createAgentPanel(document, ai, localizer) {
1902
2064
  providerSelect,
1903
2065
  modelSelect,
1904
2066
  consentCheckbox,
2067
+ workspaceConsentCheckbox,
1905
2068
  testButton,
1906
2069
  runButton,
1907
2070
  cancelButton,
@@ -1911,6 +2074,9 @@ function createAgentPanel(document, ai, localizer) {
1911
2074
  consentGranted() {
1912
2075
  return consentCheckbox.checked;
1913
2076
  },
2077
+ workspaceConsentGranted() {
2078
+ return workspaceConsentCheckbox.checked;
2079
+ },
1914
2080
  readSelection() {
1915
2081
  const provider = selectedProvider();
1916
2082
  const model = provider?.models.find(
@@ -1931,6 +2097,20 @@ function createAgentPanel(document, ai, localizer) {
1931
2097
  capability.textContent = capabilitySnapshot?.state === "agent-ready" ? `${message} \xB7 ${messages.agent.toolsReady}` : message;
1932
2098
  refreshActions();
1933
2099
  },
2100
+ renderWorkspaceHealth(state, snapshot, errorCode) {
2101
+ latestWorkspaceState = state;
2102
+ latestWorkspaceSnapshot = snapshot;
2103
+ latestWorkspaceErrorCode = errorCode ?? snapshot?.errorCode;
2104
+ workspace.dataset.state = state;
2105
+ if (state !== "checking") {
2106
+ workspaceConsent.hidden = state !== "consent-required";
2107
+ }
2108
+ if (state === "idle" || state === "ready" || state === "blocked") {
2109
+ workspaceConsentCheckbox.checked = false;
2110
+ }
2111
+ renderWorkspaceMessage();
2112
+ refreshActions();
2113
+ },
1934
2114
  renderJob(snapshot, result, activities, errorCode) {
1935
2115
  jobPresented = true;
1936
2116
  setup.hidden = true;
@@ -1938,6 +2118,7 @@ function createAgentPanel(document, ai, localizer) {
1938
2118
  providerSelect.disabled = true;
1939
2119
  modelSelect.disabled = true;
1940
2120
  consentCheckbox.disabled = true;
2121
+ workspaceConsentCheckbox.disabled = true;
1941
2122
  latestSnapshot = snapshot;
1942
2123
  latestResult = result;
1943
2124
  latestActivities = activities;
@@ -1955,6 +2136,7 @@ function createAgentPanel(document, ai, localizer) {
1955
2136
  providerSelect.disabled = false;
1956
2137
  modelSelect.disabled = false;
1957
2138
  consentCheckbox.disabled = false;
2139
+ workspaceConsentCheckbox.disabled = false;
1958
2140
  activity.replaceChildren();
1959
2141
  files.replaceChildren();
1960
2142
  checks.replaceChildren();
@@ -1972,6 +2154,7 @@ function createAgentPanel(document, ai, localizer) {
1972
2154
  providerSelect.disabled = !enabled || jobPresented;
1973
2155
  modelSelect.disabled = !enabled || jobPresented;
1974
2156
  consentCheckbox.disabled = !enabled || jobPresented;
2157
+ workspaceConsentCheckbox.disabled = !enabled || jobPresented;
1975
2158
  refreshActions();
1976
2159
  },
1977
2160
  setProviderConsent(granted) {
@@ -1990,6 +2173,10 @@ function createAgentPanel(document, ai, localizer) {
1990
2173
  },
1991
2174
  dispose() {
1992
2175
  unsubscribeLocale();
2176
+ providerSelect.removeEventListener("change", handleProviderChange);
2177
+ modelSelect.removeEventListener("change", handleModelChange);
2178
+ consentCheckbox.removeEventListener("change", refreshActions);
2179
+ workspaceConsentCheckbox.removeEventListener("change", refreshActions);
1993
2180
  }
1994
2181
  });
1995
2182
  }
@@ -2157,8 +2344,16 @@ var ERROR_MESSAGES_EN = Object.freeze({
2157
2344
  [import_shared6.ERROR_CODES.AGENT_BUSY]: "Another write Agent job is still active.",
2158
2345
  [import_shared6.ERROR_CODES.AGENT_LIMIT_EXCEEDED]: "The Agent stopped at a configured time, turn, output, or size limit.",
2159
2346
  [import_shared6.ERROR_CODES.AGENT_CANCELLED]: "The Agent job was cancelled.",
2160
- [import_shared6.ERROR_CODES.WORKTREE_DIRTY]: "Commit or otherwise clean staged, unstaged, and untracked files before running AI.",
2347
+ [import_shared6.ERROR_CODES.WORKTREE_DIRTY]: "Local changes require explicit inclusion consent before running AI.",
2348
+ [import_shared6.ERROR_CODES.WORKTREE_NOT_REPOSITORY]: "The Vite root is not the top level of an initialized Git repository.",
2349
+ [import_shared6.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS]: "Finish the active merge, rebase, cherry-pick, or revert before running AI.",
2350
+ [import_shared6.ERROR_CODES.WORKTREE_CONFLICTED]: "Resolve all merge conflicts before running AI; SpotPatch will not guess a resolution.",
2351
+ [import_shared6.ERROR_CODES.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: "Local untracked files exceed the safe isolation limit. Reduce their count or total size.",
2352
+ [import_shared6.ERROR_CODES.WORKTREE_UNTRACKED_UNSUPPORTED]: "An untracked item is missing, linked, or not a regular file and cannot be isolated safely.",
2353
+ [import_shared6.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: "Local changes include conflicts, unsupported files, or exceed the safe baseline limit.",
2161
2354
  [import_shared6.ERROR_CODES.TOOL_DENIED]: "A model tool request violated the local safety policy.",
2355
+ [import_shared6.ERROR_CODES.TOOL_INPUT_INVALID]: "The model sent invalid tool arguments or reused a tool call ID inconsistently.",
2356
+ [import_shared6.ERROR_CODES.TOOL_PATH_DENIED]: "The model tried to access a protected, non-text, linked, or out-of-project path.",
2162
2357
  [import_shared6.ERROR_CODES.PATCH_REJECTED]: "The proposed patch did not pass local policy.",
2163
2358
  [import_shared6.ERROR_CODES.VALIDATION_FAILED]: "Required project checks failed. The change cannot be applied.",
2164
2359
  [import_shared6.ERROR_CODES.APPLY_CONFLICT]: "Project files changed after the Agent baseline; no overwrite was performed.",
@@ -2182,8 +2377,16 @@ var ERROR_MESSAGES_ZH = Object.freeze({
2182
2377
  [import_shared6.ERROR_CODES.AGENT_BUSY]: "\u5F53\u524D\u9879\u76EE\u5DF2\u6709\u4E00\u4E2A\u5199\u5165\u4EFB\u52A1\u6B63\u5728\u8FD0\u884C\u3002",
2183
2378
  [import_shared6.ERROR_CODES.AGENT_LIMIT_EXCEEDED]: "Agent \u8FBE\u5230\u65F6\u95F4\u3001\u8F6E\u6B21\u3001\u8F93\u51FA\u6216\u53D8\u66F4\u89C4\u6A21\u9650\u5236\u3002",
2184
2379
  [import_shared6.ERROR_CODES.AGENT_CANCELLED]: "Agent \u4EFB\u52A1\u5DF2\u53D6\u6D88\u3002",
2185
- [import_shared6.ERROR_CODES.WORKTREE_DIRTY]: "\u8FD0\u884C AI \u524D\uFF0C\u8BF7\u5148\u63D0\u4EA4\u6216\u6E05\u7406\u6682\u5B58\u3001\u672A\u6682\u5B58\u4E0E\u672A\u8DDF\u8E2A\u6587\u4EF6\u3002",
2380
+ [import_shared6.ERROR_CODES.WORKTREE_DIRTY]: "\u8FD0\u884C AI \u524D\uFF0C\u8BF7\u660E\u786E\u540C\u610F\u5C06\u5F53\u524D\u672C\u5730\u4FEE\u6539\u7EB3\u5165\u9694\u79BB\u57FA\u7EBF\u3002",
2381
+ [import_shared6.ERROR_CODES.WORKTREE_NOT_REPOSITORY]: "Vite \u6839\u76EE\u5F55\u4E0D\u662F\u5DF2\u521D\u59CB\u5316 Git \u4ED3\u5E93\u7684\u9876\u5C42\u76EE\u5F55\u3002",
2382
+ [import_shared6.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS]: "\u8BF7\u5148\u5B8C\u6210\u5F53\u524D merge\u3001rebase\u3001cherry-pick \u6216 revert\uFF0C\u518D\u8FD0\u884C AI\u3002",
2383
+ [import_shared6.ERROR_CODES.WORKTREE_CONFLICTED]: "\u8BF7\u5148\u89E3\u51B3\u5168\u90E8\u5408\u5E76\u51B2\u7A81\uFF1BSpotPatch \u4E0D\u4F1A\u731C\u6D4B\u51B2\u7A81\u89E3\u51B3\u65B9\u5F0F\u3002",
2384
+ [import_shared6.ERROR_CODES.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: "\u672C\u5730\u672A\u8DDF\u8E2A\u6587\u4EF6\u8D85\u8FC7\u5B89\u5168\u9694\u79BB\u4E0A\u9650\uFF0C\u8BF7\u51CF\u5C11\u6587\u4EF6\u6570\u91CF\u6216\u603B\u4F53\u79EF\u3002",
2385
+ [import_shared6.ERROR_CODES.WORKTREE_UNTRACKED_UNSUPPORTED]: "\u67D0\u4E2A\u672A\u8DDF\u8E2A\u9879\u5DF2\u4E22\u5931\u3001\u662F\u7B26\u53F7\u94FE\u63A5\u6216\u4E0D\u662F\u666E\u901A\u6587\u4EF6\uFF0C\u65E0\u6CD5\u5B89\u5168\u9694\u79BB\u3002",
2386
+ [import_shared6.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: "\u672C\u5730\u4FEE\u6539\u5B58\u5728\u51B2\u7A81\u3001\u4E0D\u652F\u6301\u7684\u6587\u4EF6\uFF0C\u6216\u8D85\u8FC7\u5B89\u5168\u57FA\u7EBF\u9650\u5236\u3002",
2186
2387
  [import_shared6.ERROR_CODES.TOOL_DENIED]: "\u6A21\u578B\u5DE5\u5177\u8BF7\u6C42\u8FDD\u53CD\u672C\u5730\u5B89\u5168\u7B56\u7565\u3002",
2388
+ [import_shared6.ERROR_CODES.TOOL_INPUT_INVALID]: "\u6A21\u578B\u53D1\u9001\u4E86\u65E0\u6548\u5DE5\u5177\u53C2\u6570\uFF0C\u6216\u4E0D\u4E00\u81F4\u5730\u590D\u7528\u4E86\u5DE5\u5177\u8C03\u7528 ID\u3002",
2389
+ [import_shared6.ERROR_CODES.TOOL_PATH_DENIED]: "\u6A21\u578B\u5C1D\u8BD5\u8BBF\u95EE\u53D7\u4FDD\u62A4\u3001\u975E\u6587\u672C\u3001\u7B26\u53F7\u94FE\u63A5\u6216\u9879\u76EE\u5916\u8DEF\u5F84\u3002",
2187
2390
  [import_shared6.ERROR_CODES.PATCH_REJECTED]: "\u5EFA\u8BAE\u8865\u4E01\u672A\u901A\u8FC7\u672C\u5730\u7B56\u7565\u68C0\u67E5\u3002",
2188
2391
  [import_shared6.ERROR_CODES.VALIDATION_FAILED]: "\u9879\u76EE\u5FC5\u9700\u68C0\u67E5\u5931\u8D25\uFF0C\u4E0D\u80FD\u5E94\u7528\u672C\u6B21\u53D8\u66F4\u3002",
2189
2392
  [import_shared6.ERROR_CODES.APPLY_CONFLICT]: "Agent \u5EFA\u7ACB\u57FA\u7EBF\u540E\u9879\u76EE\u6587\u4EF6\u5DF2\u53D8\u5316\uFF0C\u672A\u6267\u884C\u8986\u76D6\u3002",
@@ -2348,6 +2551,13 @@ var UI_MESSAGES = Object.freeze({
2348
2551
  providerUnavailable: "Provider configuration is unavailable.",
2349
2552
  consent: (provider) => `I understand selected context and allowed source may be sent to ${provider}; its data policy is my responsibility.`,
2350
2553
  connectionNotTested: "Connection not tested",
2554
+ workspaceNotChecked: "Local workspace not checked",
2555
+ checkingWorkspace: "Checking Git workspace and isolated execution\u2026",
2556
+ workspaceReady: "Local workspace is ready for isolated execution",
2557
+ workspaceDirty: (staged, unstaged, untracked) => `Local changes found \xB7 ${String(staged)} staged \xB7 ${String(unstaged)} unstaged \xB7 ${String(untracked)} untracked`,
2558
+ includeLocalChanges: "Allow the Agent to continue from my current local changes",
2559
+ includeLocalChangesHelp: "The Agent may edit these files. SpotPatch preserves the baseline and applies or reverts only the Agent delta.",
2560
+ localChangesConsentRequired: "Confirm inclusion of current local changes before running AI.",
2351
2561
  capabilityVerified: "Agent capability verified",
2352
2562
  capabilityVerifiedAnnouncement: "AI provider capability verified.",
2353
2563
  testingCapability: "Testing authentication, tools, continuation, and streaming\u2026",
@@ -2356,7 +2566,7 @@ var UI_MESSAGES = Object.freeze({
2356
2566
  reverting: "Reverting the applied Agent change.",
2357
2567
  consentRequired: "Confirm remote provider data transmission before running AI.",
2358
2568
  toolsReady: "tools and streaming ready",
2359
- testConnection: "Test connection",
2569
+ testConnection: "Check environment",
2360
2570
  verifyAndRun: "Verify & run",
2361
2571
  verifying: "Verifying\u2026",
2362
2572
  run: "Run AI",
@@ -2477,6 +2687,13 @@ var UI_MESSAGES = Object.freeze({
2477
2687
  providerUnavailable: "\u6A21\u578B\u670D\u52A1\u914D\u7F6E\u4E0D\u53EF\u7528\u3002",
2478
2688
  consent: (provider) => `\u6211\u4E86\u89E3\u9009\u4E2D\u4E0A\u4E0B\u6587\u4E0E\u83B7\u51C6\u6E90\u7801\u53EF\u80FD\u53D1\u9001\u5230 ${provider}\uFF0C\u5E76\u81EA\u884C\u8D1F\u8D23\u5176\u6570\u636E\u7B56\u7565\u3002`,
2479
2689
  connectionNotTested: "\u5C1A\u672A\u6D4B\u8BD5\u8FDE\u63A5",
2690
+ workspaceNotChecked: "\u5C1A\u672A\u68C0\u67E5\u672C\u5730\u5DE5\u4F5C\u533A",
2691
+ checkingWorkspace: "\u6B63\u5728\u68C0\u67E5 Git \u5DE5\u4F5C\u533A\u4E0E\u9694\u79BB\u6267\u884C\u73AF\u5883\u2026\u2026",
2692
+ workspaceReady: "\u672C\u5730\u5DE5\u4F5C\u533A\u5DF2\u6EE1\u8DB3\u9694\u79BB\u6267\u884C\u6761\u4EF6",
2693
+ workspaceDirty: (staged, unstaged, untracked) => `\u53D1\u73B0\u672C\u5730\u4FEE\u6539 \xB7 \u6682\u5B58 ${String(staged)} \xB7 \u672A\u6682\u5B58 ${String(unstaged)} \xB7 \u672A\u8DDF\u8E2A ${String(untracked)}`,
2694
+ includeLocalChanges: "\u5141\u8BB8 Agent \u57FA\u4E8E\u6211\u5F53\u524D\u7684\u672C\u5730\u4FEE\u6539\u7EE7\u7EED",
2695
+ includeLocalChangesHelp: "Agent \u53EF\u80FD\u7EE7\u7EED\u4FEE\u6539\u8FD9\u4E9B\u6587\u4EF6\uFF1BSpotPatch \u4F1A\u4FDD\u7559\u539F\u57FA\u7EBF\uFF0C\u4EC5\u5E94\u7528\u6216\u64A4\u9500 Agent \u81EA\u5DF1\u7684\u589E\u91CF\u3002",
2696
+ localChangesConsentRequired: "\u8FD0\u884C AI \u524D\uFF0C\u8BF7\u786E\u8BA4\u5141\u8BB8\u7EB3\u5165\u5F53\u524D\u672C\u5730\u4FEE\u6539\u3002",
2480
2697
  capabilityVerified: "Agent \u80FD\u529B\u9A8C\u8BC1\u901A\u8FC7",
2481
2698
  capabilityVerifiedAnnouncement: "AI \u6A21\u578B\u670D\u52A1\u80FD\u529B\u9A8C\u8BC1\u901A\u8FC7\u3002",
2482
2699
  testingCapability: "\u6B63\u5728\u9A8C\u8BC1\u9274\u6743\u3001\u5DE5\u5177\u8C03\u7528\u3001\u8FDE\u7EED\u8C03\u7528\u4E0E\u6D41\u5F0F\u54CD\u5E94\u2026\u2026",
@@ -2485,7 +2702,7 @@ var UI_MESSAGES = Object.freeze({
2485
2702
  reverting: "\u6B63\u5728\u64A4\u9500\u5DF2\u5E94\u7528\u7684 Agent \u53D8\u66F4\u3002",
2486
2703
  consentRequired: "\u8FD0\u884C AI \u524D\uFF0C\u8BF7\u5148\u786E\u8BA4\u5141\u8BB8\u5411\u8FDC\u7A0B\u6A21\u578B\u670D\u52A1\u4F20\u8F93\u6570\u636E\u3002",
2487
2704
  toolsReady: "\u5DE5\u5177\u8C03\u7528\u4E0E\u6D41\u5F0F\u54CD\u5E94\u5DF2\u5C31\u7EEA",
2488
- testConnection: "\u6D4B\u8BD5\u8FDE\u63A5",
2705
+ testConnection: "\u68C0\u67E5\u8FD0\u884C\u73AF\u5883",
2489
2706
  verifyAndRun: "\u9A8C\u8BC1\u5E76\u8FD0\u884C",
2490
2707
  verifying: "\u9A8C\u8BC1\u4E2D\u2026\u2026",
2491
2708
  run: "\u8FD0\u884C AI",
@@ -3735,6 +3952,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3735
3952
  agentProviderSelect: agentPanel.providerSelect,
3736
3953
  agentModelSelect: agentPanel.modelSelect,
3737
3954
  agentConsentCheckbox: agentPanel.consentCheckbox,
3955
+ agentWorkspaceConsentCheckbox: agentPanel.workspaceConsentCheckbox,
3738
3956
  agentTestButton: agentPanel.testButton,
3739
3957
  agentRunButton: agentPanel.runButton,
3740
3958
  agentCancelButton: agentPanel.cancelButton,
@@ -3846,6 +4064,10 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3846
4064
  previewButton.classList.toggle("spotpatch-primary", !agentReady);
3847
4065
  placeDialog();
3848
4066
  },
4067
+ renderAgentWorkspaceHealth(state, snapshot, errorCode) {
4068
+ agentPanel.renderWorkspaceHealth(state, snapshot, errorCode);
4069
+ placeDialog();
4070
+ },
3849
4071
  renderAgentJob(snapshot, result, activities, errorCode) {
3850
4072
  agentPanel.renderJob(snapshot, result, activities, errorCode);
3851
4073
  placeDialog();
@@ -3866,6 +4088,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3866
4088
  },
3867
4089
  locale: localizer.locale,
3868
4090
  messages: localizer.messages,
4091
+ agentWorkspaceConsentGranted: agentPanel.workspaceConsentGranted,
3869
4092
  subscribeLocale: localizer.subscribe,
3870
4093
  dispose() {
3871
4094
  diagnostics.removeEventListener("toggle", placeDialog);
@@ -3967,6 +4190,22 @@ function createAgentWorkflow(options) {
3967
4190
  let appliedDuringCurrentJob = false;
3968
4191
  let actionPending = false;
3969
4192
  const selectedProfiles = () => options.view.readAgentSelection();
4193
+ const refreshWorkspaceHealth = async (workflowRevision) => {
4194
+ options.view.renderAgentWorkspaceHealth("checking");
4195
+ try {
4196
+ const health = await options.api.agentWorkspaceHealth();
4197
+ if (workflowRevision !== revision) {
4198
+ return health;
4199
+ }
4200
+ options.view.renderAgentWorkspaceHealth(health.state, health, health.errorCode);
4201
+ return health;
4202
+ } catch (error) {
4203
+ if (workflowRevision === revision) {
4204
+ options.view.renderAgentWorkspaceHealth("blocked", void 0, errorCode(error));
4205
+ }
4206
+ throw error;
4207
+ }
4208
+ };
3970
4209
  const renderJob = (explicitErrorCode) => {
3971
4210
  if (snapshot === void 0) {
3972
4211
  return;
@@ -4142,6 +4381,10 @@ function createAgentWorkflow(options) {
4142
4381
  },
4143
4382
  beginSelection() {
4144
4383
  resetState();
4384
+ const workflowRevision = revision;
4385
+ if (options.ai.enabled) {
4386
+ void refreshWorkspaceHealth(workflowRevision).catch(() => void 0);
4387
+ }
4145
4388
  },
4146
4389
  cancel() {
4147
4390
  if (snapshot?.canCancel === true) {
@@ -4174,12 +4417,17 @@ function createAgentWorkflow(options) {
4174
4417
  providerOrModelChanged() {
4175
4418
  revision += 1;
4176
4419
  restoreProviderState();
4420
+ const workflowRevision = revision;
4421
+ void refreshWorkspaceHealth(workflowRevision).catch(() => void 0);
4177
4422
  },
4178
4423
  reset() {
4179
4424
  const mustReselect = appliedDuringCurrentJob;
4180
4425
  resetState();
4181
4426
  if (mustReselect) {
4182
4427
  options.onReselectRequired();
4428
+ } else if (options.ai.enabled) {
4429
+ const workflowRevision = revision;
4430
+ void refreshWorkspaceHealth(workflowRevision).catch(() => void 0);
4183
4431
  }
4184
4432
  },
4185
4433
  revert() {
@@ -4206,15 +4454,27 @@ function createAgentWorkflow(options) {
4206
4454
  providerConsents.add(selection.providerProfileId);
4207
4455
  const workflowRevision = ++revision;
4208
4456
  options.view.setAgentEditingEnabled(false);
4209
- void probe(workflowRevision).then(async () => {
4457
+ void Promise.all([
4458
+ probe(workflowRevision),
4459
+ refreshWorkspaceHealth(workflowRevision)
4460
+ ]).then(async ([, health]) => {
4210
4461
  if (workflowRevision !== revision) {
4211
4462
  return;
4212
4463
  }
4464
+ if (health.state === "blocked") {
4465
+ throw new RuntimeApiError(
4466
+ health.errorCode ?? import_shared8.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED
4467
+ );
4468
+ }
4469
+ if (health.state === "consent-required" && !options.view.agentWorkspaceConsentGranted()) {
4470
+ throw new RuntimeApiError(import_shared8.ERROR_CODES.WORKTREE_DIRTY);
4471
+ }
4213
4472
  const created = await options.api.createAgentJob({
4214
4473
  annotation,
4215
4474
  providerProfileId: selection.providerProfileId,
4216
4475
  modelProfileId: selection.modelProfileId,
4217
- providerDataConsent: true
4476
+ providerDataConsent: true,
4477
+ workingTreeMode: health.state === "consent-required" ? "include-local-changes" : "require-clean"
4218
4478
  });
4219
4479
  if (workflowRevision !== revision) {
4220
4480
  return;
@@ -4243,7 +4503,10 @@ function createAgentWorkflow(options) {
4243
4503
  return;
4244
4504
  }
4245
4505
  const workflowRevision = ++revision;
4246
- void probe(workflowRevision).catch((error) => {
4506
+ void Promise.all([
4507
+ probe(workflowRevision),
4508
+ refreshWorkspaceHealth(workflowRevision)
4509
+ ]).catch((error) => {
4247
4510
  if (workflowRevision === revision) {
4248
4511
  options.view.renderAgentCapability(
4249
4512
  "error",