@spotpatch/runtime 1.1.2 → 1.2.1

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
@@ -157,6 +157,35 @@ function isAgentCapabilitySnapshot(value) {
157
157
  }
158
158
  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
159
  }
160
+ function isAgentWorkspaceHealthSnapshot(value) {
161
+ if (!isRecord(value) || !hasOnlyKeys(value, [
162
+ "state",
163
+ "checkedAt",
164
+ "changes",
165
+ "canIncludeLocalChanges",
166
+ "errorCode"
167
+ ]) || value.state !== "ready" && value.state !== "consent-required" && value.state !== "blocked" || !isIsoTimestamp(value.checkedAt) || typeof value.canIncludeLocalChanges !== "boolean" || value.errorCode !== void 0 && !isAgentErrorCode(value.errorCode) || !isRecord(value.changes) || !hasOnlyKeys(value.changes, [
168
+ "staged",
169
+ "unstaged",
170
+ "untracked",
171
+ "conflicted",
172
+ "total"
173
+ ])) {
174
+ return false;
175
+ }
176
+ const changes = value.changes;
177
+ const { staged, unstaged, untracked, conflicted, total } = changes;
178
+ if (!isNonnegativeInteger(staged) || !isNonnegativeInteger(unstaged) || !isNonnegativeInteger(untracked) || !isNonnegativeInteger(conflicted) || !isNonnegativeInteger(total) || total < staged || total < unstaged || total < untracked || total < conflicted || total > staged + unstaged + untracked) {
179
+ return false;
180
+ }
181
+ if (value.state === "ready") {
182
+ return total === 0 && !value.canIncludeLocalChanges && value.errorCode === void 0;
183
+ }
184
+ if (value.state === "consent-required") {
185
+ return total > 0 && conflicted === 0 && value.canIncludeLocalChanges && value.errorCode === void 0;
186
+ }
187
+ return !value.canIncludeLocalChanges && value.errorCode !== void 0;
188
+ }
160
189
  function isAgentJobSnapshot(value) {
161
190
  if (!isRecord(value) || !hasOnlyKeys(value, [
162
191
  "jobId",
@@ -497,6 +526,17 @@ function createRuntimeApi(options) {
497
526
  request
498
527
  );
499
528
  },
529
+ async agentWorkspaceHealth() {
530
+ const data = await requestJson(
531
+ import_shared3.SPOTPATCH_ENDPOINTS.agentWorkspaceHealth,
532
+ "POST",
533
+ emptyAction
534
+ );
535
+ if (!isAgentWorkspaceHealthSnapshot(data)) {
536
+ throw new RuntimeApiError();
537
+ }
538
+ return deepFreeze(data);
539
+ },
500
540
  async createAgentJob(request) {
501
541
  const serverRequest = omitBrowserCodeContext(request);
502
542
  return parseJobSnapshot(
@@ -1567,15 +1607,62 @@ var AGENT_PANEL_STYLES = `
1567
1607
  min-height: 46px;
1568
1608
  margin-top: 7px;
1569
1609
  border: 1px solid var(--spotpatch-border);
1570
- border-radius: 10px;
1571
- padding: 8px 30px 8px 10px;
1610
+ border-radius: 12px;
1611
+ padding: 10px 38px 10px 13px;
1572
1612
  color: #e8ebf2;
1573
1613
  color-scheme: dark;
1574
- background: var(--spotpatch-bg-raised);
1614
+ background: linear-gradient(180deg, rgb(255 255 255 / 4%), rgb(255 255 255 / 2%));
1575
1615
  font-size: 15px;
1616
+ font-weight: 560;
1617
+ line-height: 1.35;
1576
1618
  outline: none;
1619
+ transition: 160ms ease;
1620
+ }
1621
+ .spotpatch-agent select:hover { border-color: rgb(148 163 184 / 38%); background-color: rgb(255 255 255 / 5%); }
1622
+ .spotpatch-agent select:focus-visible { border-color: rgb(139 124 247 / 76%); box-shadow: 0 0 0 3px rgb(109 93 246 / 18%); }
1623
+ .spotpatch-agent select option { min-height: 42px; color: #e8ebf2; background: #10151e; }
1624
+ @supports (appearance: base-select) {
1625
+ .spotpatch-agent select,
1626
+ ::picker(select) { appearance: base-select; }
1627
+ .spotpatch-agent select { padding: 10px 13px; }
1628
+ .spotpatch-agent select::picker-icon {
1629
+ color: #9da6b5;
1630
+ transition: rotate 180ms cubic-bezier(.2, .8, .2, 1);
1631
+ }
1632
+ .spotpatch-agent select:open::picker-icon { rotate: 180deg; color: #c4b5fd; }
1633
+ .spotpatch-agent select::picker(select) {
1634
+ top: calc(anchor(bottom) + 7px);
1635
+ left: anchor(left);
1636
+ width: anchor-size(width);
1637
+ max-height: min(280px, 40vh);
1638
+ margin: 0;
1639
+ overflow: hidden;
1640
+ border: 1px solid rgb(139 124 247 / 28%);
1641
+ border-radius: 12px;
1642
+ padding: 6px;
1643
+ background: #10151e;
1644
+ box-shadow: 0 18px 50px rgb(0 0 0 / 38%);
1645
+ opacity: 0;
1646
+ transform: translateY(-5px) scale(.985);
1647
+ transition: opacity 150ms ease, transform 180ms cubic-bezier(.2, .8, .2, 1), overlay 180ms allow-discrete, display 180ms allow-discrete;
1648
+ }
1649
+ ::picker(select):popover-open { opacity: 1; transform: translateY(0) scale(1); }
1650
+ @starting-style {
1651
+ ::picker(select):popover-open { opacity: 0; transform: translateY(-5px) scale(.985); }
1652
+ }
1653
+ .spotpatch-agent select option {
1654
+ min-height: 42px;
1655
+ border-radius: 8px;
1656
+ padding: 10px 12px;
1657
+ color: #dfe4ee;
1658
+ background: transparent;
1659
+ cursor: pointer;
1660
+ }
1661
+ .spotpatch-agent select option:hover,
1662
+ .spotpatch-agent select option:focus { color: #fff; background: rgb(109 93 246 / 16%); }
1663
+ .spotpatch-agent select option:checked { color: #fff; background: rgb(109 93 246 / 24%); }
1664
+ .spotpatch-agent select option::checkmark { color: #8b7cf7; }
1577
1665
  }
1578
- .spotpatch-agent select option { color: #e8ebf2; background: #10151e; }
1579
1666
  .spotpatch-agent select:focus-visible,
1580
1667
  .spotpatch-consent input:focus-visible {
1581
1668
  outline: 2px solid #8b7cf7;
@@ -1592,11 +1679,22 @@ var AGENT_PANEL_STYLES = `
1592
1679
  line-height: 1.6;
1593
1680
  }
1594
1681
  .spotpatch-consent input { width: 16px; height: 16px; flex: none; margin: 3px 0 0; accent-color: var(--spotpatch-accent); }
1682
+ .spotpatch-workspace-consent {
1683
+ margin-top: 12px;
1684
+ border: 1px solid rgb(251 191 36 / 18%);
1685
+ border-radius: 10px;
1686
+ padding: 11px 12px;
1687
+ background: rgb(120 53 15 / 9%);
1688
+ }
1689
+ .spotpatch-workspace-consent strong { display: block; color: #fde68a; font-size: 13px; font-weight: 620; }
1690
+ .spotpatch-workspace-consent small { display: block; margin-top: 2px; color: #aeb6c4; font-size: 12px; line-height: 1.5; }
1691
+ .spotpatch-agent-health-list { display: grid; gap: 8px; margin-top: 14px; }
1692
+ .spotpatch-agent-workspace,
1595
1693
  .spotpatch-agent-capability {
1596
1694
  display: flex;
1597
1695
  align-items: center;
1598
1696
  gap: 8px;
1599
- margin: 14px 0 0;
1697
+ margin: 0;
1600
1698
  color: #929bab;
1601
1699
  font-size: 13px;
1602
1700
  }
@@ -1613,6 +1711,21 @@ var AGENT_PANEL_STYLES = `
1613
1711
  .spotpatch-agent-capability[data-state="ready"]::before { background: var(--spotpatch-success); }
1614
1712
  .spotpatch-agent-capability[data-state="error"] { color: #fecaca; }
1615
1713
  .spotpatch-agent-capability[data-state="error"]::before { background: var(--spotpatch-danger); }
1714
+ .spotpatch-agent-workspace::before {
1715
+ width: 6px;
1716
+ height: 6px;
1717
+ flex: none;
1718
+ border-radius: 999px;
1719
+ background: #64748b;
1720
+ content: "";
1721
+ }
1722
+ .spotpatch-agent-workspace[data-state="checking"]::before { background: var(--spotpatch-warning); }
1723
+ .spotpatch-agent-workspace[data-state="ready"] { color: #a7f3d0; }
1724
+ .spotpatch-agent-workspace[data-state="ready"]::before { background: var(--spotpatch-success); }
1725
+ .spotpatch-agent-workspace[data-state="consent-required"] { color: #fde68a; }
1726
+ .spotpatch-agent-workspace[data-state="consent-required"]::before { background: #fbbf24; }
1727
+ .spotpatch-agent-workspace[data-state="blocked"] { color: #fecaca; }
1728
+ .spotpatch-agent-workspace[data-state="blocked"]::before { background: var(--spotpatch-danger); }
1616
1729
  .spotpatch-agent-job { padding: 16px; }
1617
1730
  .spotpatch-agent-job-meta { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
1618
1731
  .spotpatch-agent-model { min-width: 0; overflow: hidden; color: #d6dafe; font-size: 13px; text-overflow: ellipsis; white-space: nowrap; }
@@ -1701,10 +1814,26 @@ function createAgentPanel(document, ai, localizer) {
1701
1814
  consentCheckbox.type = "checkbox";
1702
1815
  const consentText = createMarkedElement(document, "span");
1703
1816
  consent.append(consentCheckbox, consentText);
1817
+ const workspaceConsent = createMarkedElement(document, "label");
1818
+ workspaceConsent.className = "spotpatch-consent spotpatch-workspace-consent";
1819
+ workspaceConsent.hidden = true;
1820
+ const workspaceConsentCheckbox = createMarkedElement(document, "input");
1821
+ workspaceConsentCheckbox.type = "checkbox";
1822
+ const workspaceConsentContent = createMarkedElement(document, "span");
1823
+ const workspaceConsentTitle = createMarkedElement(document, "strong");
1824
+ const workspaceConsentHelp = createMarkedElement(document, "small");
1825
+ workspaceConsentContent.append(workspaceConsentTitle, workspaceConsentHelp);
1826
+ workspaceConsent.append(workspaceConsentCheckbox, workspaceConsentContent);
1827
+ const healthList = createMarkedElement(document, "div");
1828
+ healthList.className = "spotpatch-agent-health-list";
1829
+ const workspace = createMarkedElement(document, "p");
1830
+ workspace.className = "spotpatch-agent-workspace";
1831
+ workspace.dataset.state = "idle";
1704
1832
  const capability = createMarkedElement(document, "p");
1705
1833
  capability.className = "spotpatch-agent-capability";
1706
1834
  capability.dataset.state = "idle";
1707
- setup.append(selectors, consent, capability);
1835
+ healthList.append(workspace, capability);
1836
+ setup.append(selectors, consent, workspaceConsent, healthList);
1708
1837
  const job = createMarkedElement(document, "div");
1709
1838
  job.className = "spotpatch-agent-job";
1710
1839
  job.hidden = true;
@@ -1763,6 +1892,9 @@ function createAgentPanel(document, ai, localizer) {
1763
1892
  let latestCapabilityState = "idle";
1764
1893
  let latestCapabilityMessage = messages.agent.connectionNotTested;
1765
1894
  let latestCapabilityErrorCode;
1895
+ let latestWorkspaceState = "idle";
1896
+ let latestWorkspaceSnapshot;
1897
+ let latestWorkspaceErrorCode;
1766
1898
  for (const provider of providers) {
1767
1899
  addOption(document, providerSelect, provider.id, provider.label);
1768
1900
  }
@@ -1790,7 +1922,7 @@ function createAgentPanel(document, ai, localizer) {
1790
1922
  testButton.hidden = !setupVisible;
1791
1923
  runButton.hidden = !setupVisible;
1792
1924
  testButton.disabled = !editingEnabled || capabilityProbing || selectedProvider() === void 0;
1793
- runButton.disabled = !editingEnabled || capabilityProbing || !contextReady || !consentCheckbox.checked || selectedProvider() === void 0 || modelSelect.value.length === 0;
1925
+ 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;
1794
1926
  if (!jobPresented || !selectionVisible) {
1795
1927
  cancelButton.hidden = true;
1796
1928
  applyButton.hidden = true;
@@ -1798,6 +1930,24 @@ function createAgentPanel(document, ai, localizer) {
1798
1930
  resetButton.hidden = true;
1799
1931
  }
1800
1932
  };
1933
+ const renderWorkspaceMessage = () => {
1934
+ if (latestWorkspaceState === "idle") {
1935
+ workspace.textContent = messages.agent.workspaceNotChecked;
1936
+ } else if (latestWorkspaceState === "checking") {
1937
+ workspace.textContent = messages.agent.checkingWorkspace;
1938
+ } else if (latestWorkspaceState === "ready") {
1939
+ workspace.textContent = messages.agent.workspaceReady;
1940
+ } else if (latestWorkspaceState === "consent-required") {
1941
+ const changes = latestWorkspaceSnapshot?.changes;
1942
+ workspace.textContent = messages.agent.workspaceDirty(
1943
+ changes?.staged ?? 0,
1944
+ changes?.unstaged ?? 0,
1945
+ changes?.untracked ?? 0
1946
+ );
1947
+ } else {
1948
+ workspace.textContent = latestWorkspaceErrorCode === void 0 ? messages.errors.INTERNAL_ERROR : messages.errors[latestWorkspaceErrorCode];
1949
+ }
1950
+ };
1801
1951
  function handleProviderChange() {
1802
1952
  populateModels();
1803
1953
  consentCheckbox.checked = false;
@@ -1822,6 +1972,7 @@ function createAgentPanel(document, ai, localizer) {
1822
1972
  providerSelect.addEventListener("change", handleProviderChange);
1823
1973
  modelSelect.addEventListener("change", handleModelChange);
1824
1974
  consentCheckbox.addEventListener("change", refreshActions);
1975
+ workspaceConsentCheckbox.addEventListener("change", refreshActions);
1825
1976
  function renderCurrentJob() {
1826
1977
  if (latestSnapshot === void 0) {
1827
1978
  return;
@@ -1876,6 +2027,8 @@ function createAgentPanel(document, ai, localizer) {
1876
2027
  modelText.textContent = messages.agent.model;
1877
2028
  providerSelect.setAttribute("aria-label", messages.agent.providerAriaLabel);
1878
2029
  modelSelect.setAttribute("aria-label", messages.agent.modelAriaLabel);
2030
+ workspaceConsentTitle.textContent = messages.agent.includeLocalChanges;
2031
+ workspaceConsentHelp.textContent = messages.agent.includeLocalChangesHelp;
1879
2032
  diff.setAttribute("aria-label", messages.agent.diffAriaLabel);
1880
2033
  testButton.textContent = messages.agent.testConnection;
1881
2034
  applyButton.textContent = messages.agent.apply;
@@ -1896,6 +2049,7 @@ function createAgentPanel(document, ai, localizer) {
1896
2049
  } else {
1897
2050
  capability.textContent = latestCapabilityMessage;
1898
2051
  }
2052
+ renderWorkspaceMessage();
1899
2053
  refreshActions();
1900
2054
  renderCurrentJob();
1901
2055
  }
@@ -1906,6 +2060,7 @@ function createAgentPanel(document, ai, localizer) {
1906
2060
  providerSelect,
1907
2061
  modelSelect,
1908
2062
  consentCheckbox,
2063
+ workspaceConsentCheckbox,
1909
2064
  testButton,
1910
2065
  runButton,
1911
2066
  cancelButton,
@@ -1915,6 +2070,9 @@ function createAgentPanel(document, ai, localizer) {
1915
2070
  consentGranted() {
1916
2071
  return consentCheckbox.checked;
1917
2072
  },
2073
+ workspaceConsentGranted() {
2074
+ return workspaceConsentCheckbox.checked;
2075
+ },
1918
2076
  readSelection() {
1919
2077
  const provider = selectedProvider();
1920
2078
  const model = provider?.models.find(
@@ -1935,6 +2093,20 @@ function createAgentPanel(document, ai, localizer) {
1935
2093
  capability.textContent = capabilitySnapshot?.state === "agent-ready" ? `${message} \xB7 ${messages.agent.toolsReady}` : message;
1936
2094
  refreshActions();
1937
2095
  },
2096
+ renderWorkspaceHealth(state, snapshot, errorCode) {
2097
+ latestWorkspaceState = state;
2098
+ latestWorkspaceSnapshot = snapshot;
2099
+ latestWorkspaceErrorCode = errorCode ?? snapshot?.errorCode;
2100
+ workspace.dataset.state = state;
2101
+ if (state !== "checking") {
2102
+ workspaceConsent.hidden = state !== "consent-required";
2103
+ }
2104
+ if (state === "idle" || state === "ready" || state === "blocked") {
2105
+ workspaceConsentCheckbox.checked = false;
2106
+ }
2107
+ renderWorkspaceMessage();
2108
+ refreshActions();
2109
+ },
1938
2110
  renderJob(snapshot, result, activities, errorCode) {
1939
2111
  jobPresented = true;
1940
2112
  setup.hidden = true;
@@ -1942,6 +2114,7 @@ function createAgentPanel(document, ai, localizer) {
1942
2114
  providerSelect.disabled = true;
1943
2115
  modelSelect.disabled = true;
1944
2116
  consentCheckbox.disabled = true;
2117
+ workspaceConsentCheckbox.disabled = true;
1945
2118
  latestSnapshot = snapshot;
1946
2119
  latestResult = result;
1947
2120
  latestActivities = activities;
@@ -1959,6 +2132,7 @@ function createAgentPanel(document, ai, localizer) {
1959
2132
  providerSelect.disabled = false;
1960
2133
  modelSelect.disabled = false;
1961
2134
  consentCheckbox.disabled = false;
2135
+ workspaceConsentCheckbox.disabled = false;
1962
2136
  activity.replaceChildren();
1963
2137
  files.replaceChildren();
1964
2138
  checks.replaceChildren();
@@ -1976,6 +2150,7 @@ function createAgentPanel(document, ai, localizer) {
1976
2150
  providerSelect.disabled = !enabled || jobPresented;
1977
2151
  modelSelect.disabled = !enabled || jobPresented;
1978
2152
  consentCheckbox.disabled = !enabled || jobPresented;
2153
+ workspaceConsentCheckbox.disabled = !enabled || jobPresented;
1979
2154
  refreshActions();
1980
2155
  },
1981
2156
  setProviderConsent(granted) {
@@ -1997,6 +2172,7 @@ function createAgentPanel(document, ai, localizer) {
1997
2172
  providerSelect.removeEventListener("change", handleProviderChange);
1998
2173
  modelSelect.removeEventListener("change", handleModelChange);
1999
2174
  consentCheckbox.removeEventListener("change", refreshActions);
2175
+ workspaceConsentCheckbox.removeEventListener("change", refreshActions);
2000
2176
  }
2001
2177
  });
2002
2178
  }
@@ -2164,8 +2340,16 @@ var ERROR_MESSAGES_EN = Object.freeze({
2164
2340
  [import_shared6.ERROR_CODES.AGENT_BUSY]: "Another write Agent job is still active.",
2165
2341
  [import_shared6.ERROR_CODES.AGENT_LIMIT_EXCEEDED]: "The Agent stopped at a configured time, turn, output, or size limit.",
2166
2342
  [import_shared6.ERROR_CODES.AGENT_CANCELLED]: "The Agent job was cancelled.",
2167
- [import_shared6.ERROR_CODES.WORKTREE_DIRTY]: "Commit or otherwise clean staged, unstaged, and untracked files before running AI.",
2343
+ [import_shared6.ERROR_CODES.WORKTREE_DIRTY]: "Confirm inclusion of local changes before running AI.",
2344
+ [import_shared6.ERROR_CODES.WORKTREE_NOT_REPOSITORY]: "Vite root must be an initialized Git repository root.",
2345
+ [import_shared6.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS]: "Finish the active merge, rebase, cherry-pick, or revert.",
2346
+ [import_shared6.ERROR_CODES.WORKTREE_CONFLICTED]: "Resolve all Git conflicts before running AI.",
2347
+ [import_shared6.ERROR_CODES.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: "Reduce untracked files below the safe count and size limits.",
2348
+ [import_shared6.ERROR_CODES.WORKTREE_UNTRACKED_UNSUPPORTED]: "An untracked path is missing, linked, or not a regular file.",
2349
+ [import_shared6.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: "Local changes cannot be isolated safely.",
2168
2350
  [import_shared6.ERROR_CODES.TOOL_DENIED]: "A model tool request violated the local safety policy.",
2351
+ [import_shared6.ERROR_CODES.TOOL_INPUT_INVALID]: "The model sent invalid tool arguments or reused a tool ID.",
2352
+ [import_shared6.ERROR_CODES.TOOL_PATH_DENIED]: "The model requested a protected, linked, non-text, or external path.",
2169
2353
  [import_shared6.ERROR_CODES.PATCH_REJECTED]: "The proposed patch did not pass local policy.",
2170
2354
  [import_shared6.ERROR_CODES.VALIDATION_FAILED]: "Required project checks failed. The change cannot be applied.",
2171
2355
  [import_shared6.ERROR_CODES.APPLY_CONFLICT]: "Project files changed after the Agent baseline; no overwrite was performed.",
@@ -2189,8 +2373,16 @@ var ERROR_MESSAGES_ZH = Object.freeze({
2189
2373
  [import_shared6.ERROR_CODES.AGENT_BUSY]: "\u5F53\u524D\u9879\u76EE\u5DF2\u6709\u4E00\u4E2A\u5199\u5165\u4EFB\u52A1\u6B63\u5728\u8FD0\u884C\u3002",
2190
2374
  [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",
2191
2375
  [import_shared6.ERROR_CODES.AGENT_CANCELLED]: "Agent \u4EFB\u52A1\u5DF2\u53D6\u6D88\u3002",
2192
- [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",
2376
+ [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",
2377
+ [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",
2378
+ [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",
2379
+ [import_shared6.ERROR_CODES.WORKTREE_CONFLICTED]: "\u8BF7\u5148\u89E3\u51B3\u5168\u90E8 Git \u51B2\u7A81\uFF0C\u518D\u8FD0\u884C AI\u3002",
2380
+ [import_shared6.ERROR_CODES.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: "\u672A\u8DDF\u8E2A\u6587\u4EF6\u8D85\u8FC7\u5B89\u5168\u6570\u91CF\u6216\u4F53\u79EF\u4E0A\u9650\uFF0C\u8BF7\u5148\u7CBE\u7B80\u3002",
2381
+ [import_shared6.ERROR_CODES.WORKTREE_UNTRACKED_UNSUPPORTED]: "\u672A\u8DDF\u8E2A\u9879\u5DF2\u4E22\u5931\u3001\u662F\u7B26\u53F7\u94FE\u63A5\u6216\u4E0D\u662F\u666E\u901A\u6587\u4EF6\u3002",
2382
+ [import_shared6.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: "\u5F53\u524D\u672C\u5730\u4FEE\u6539\u65E0\u6CD5\u5B89\u5168\u9694\u79BB\u3002",
2193
2383
  [import_shared6.ERROR_CODES.TOOL_DENIED]: "\u6A21\u578B\u5DE5\u5177\u8BF7\u6C42\u8FDD\u53CD\u672C\u5730\u5B89\u5168\u7B56\u7565\u3002",
2384
+ [import_shared6.ERROR_CODES.TOOL_INPUT_INVALID]: "\u6A21\u578B\u53D1\u9001\u4E86\u65E0\u6548\u5DE5\u5177\u53C2\u6570\uFF0C\u6216\u590D\u7528\u4E86\u5DE5\u5177\u8C03\u7528 ID\u3002",
2385
+ [import_shared6.ERROR_CODES.TOOL_PATH_DENIED]: "\u6A21\u578B\u8BF7\u6C42\u4E86\u53D7\u4FDD\u62A4\u3001\u975E\u6587\u672C\u3001\u7B26\u53F7\u94FE\u63A5\u6216\u9879\u76EE\u5916\u8DEF\u5F84\u3002",
2194
2386
  [import_shared6.ERROR_CODES.PATCH_REJECTED]: "\u5EFA\u8BAE\u8865\u4E01\u672A\u901A\u8FC7\u672C\u5730\u7B56\u7565\u68C0\u67E5\u3002",
2195
2387
  [import_shared6.ERROR_CODES.VALIDATION_FAILED]: "\u9879\u76EE\u5FC5\u9700\u68C0\u67E5\u5931\u8D25\uFF0C\u4E0D\u80FD\u5E94\u7528\u672C\u6B21\u53D8\u66F4\u3002",
2196
2388
  [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",
@@ -2355,6 +2547,13 @@ var UI_MESSAGES = Object.freeze({
2355
2547
  providerUnavailable: "Provider configuration is unavailable.",
2356
2548
  consent: (provider) => `I understand selected context and allowed source may be sent to ${provider}; its data policy is my responsibility.`,
2357
2549
  connectionNotTested: "Connection not tested",
2550
+ workspaceNotChecked: "Local workspace not checked",
2551
+ checkingWorkspace: "Checking Git workspace and isolated execution\u2026",
2552
+ workspaceReady: "Local workspace is ready for isolated execution",
2553
+ workspaceDirty: (staged, unstaged, untracked) => `Local changes found \xB7 ${String(staged)} staged \xB7 ${String(unstaged)} unstaged \xB7 ${String(untracked)} untracked`,
2554
+ includeLocalChanges: "Allow the Agent to continue from my current local changes",
2555
+ includeLocalChangesHelp: "The Agent may edit these files. SpotPatch preserves the baseline and applies or reverts only the Agent delta.",
2556
+ localChangesConsentRequired: "Confirm inclusion of current local changes before running AI.",
2358
2557
  capabilityVerified: "Agent capability verified",
2359
2558
  capabilityVerifiedAnnouncement: "AI provider capability verified.",
2360
2559
  testingCapability: "Testing authentication, tools, continuation, and streaming\u2026",
@@ -2363,7 +2562,7 @@ var UI_MESSAGES = Object.freeze({
2363
2562
  reverting: "Reverting the applied Agent change.",
2364
2563
  consentRequired: "Confirm remote provider data transmission before running AI.",
2365
2564
  toolsReady: "tools and streaming ready",
2366
- testConnection: "Test connection",
2565
+ testConnection: "Check environment",
2367
2566
  verifyAndRun: "Verify & run",
2368
2567
  verifying: "Verifying\u2026",
2369
2568
  run: "Run AI",
@@ -2484,6 +2683,13 @@ var UI_MESSAGES = Object.freeze({
2484
2683
  providerUnavailable: "\u6A21\u578B\u670D\u52A1\u914D\u7F6E\u4E0D\u53EF\u7528\u3002",
2485
2684
  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`,
2486
2685
  connectionNotTested: "\u5C1A\u672A\u6D4B\u8BD5\u8FDE\u63A5",
2686
+ workspaceNotChecked: "\u5C1A\u672A\u68C0\u67E5\u672C\u5730\u5DE5\u4F5C\u533A",
2687
+ checkingWorkspace: "\u6B63\u5728\u68C0\u67E5 Git \u5DE5\u4F5C\u533A\u4E0E\u9694\u79BB\u6267\u884C\u73AF\u5883\u2026\u2026",
2688
+ workspaceReady: "\u672C\u5730\u5DE5\u4F5C\u533A\u5DF2\u6EE1\u8DB3\u9694\u79BB\u6267\u884C\u6761\u4EF6",
2689
+ 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)}`,
2690
+ includeLocalChanges: "\u5141\u8BB8 Agent \u57FA\u4E8E\u6211\u5F53\u524D\u7684\u672C\u5730\u4FEE\u6539\u7EE7\u7EED",
2691
+ 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",
2692
+ localChangesConsentRequired: "\u8FD0\u884C AI \u524D\uFF0C\u8BF7\u786E\u8BA4\u5141\u8BB8\u7EB3\u5165\u5F53\u524D\u672C\u5730\u4FEE\u6539\u3002",
2487
2693
  capabilityVerified: "Agent \u80FD\u529B\u9A8C\u8BC1\u901A\u8FC7",
2488
2694
  capabilityVerifiedAnnouncement: "AI \u6A21\u578B\u670D\u52A1\u80FD\u529B\u9A8C\u8BC1\u901A\u8FC7\u3002",
2489
2695
  testingCapability: "\u6B63\u5728\u9A8C\u8BC1\u9274\u6743\u3001\u5DE5\u5177\u8C03\u7528\u3001\u8FDE\u7EED\u8C03\u7528\u4E0E\u6D41\u5F0F\u54CD\u5E94\u2026\u2026",
@@ -2492,7 +2698,7 @@ var UI_MESSAGES = Object.freeze({
2492
2698
  reverting: "\u6B63\u5728\u64A4\u9500\u5DF2\u5E94\u7528\u7684 Agent \u53D8\u66F4\u3002",
2493
2699
  consentRequired: "\u8FD0\u884C AI \u524D\uFF0C\u8BF7\u5148\u786E\u8BA4\u5141\u8BB8\u5411\u8FDC\u7A0B\u6A21\u578B\u670D\u52A1\u4F20\u8F93\u6570\u636E\u3002",
2494
2700
  toolsReady: "\u5DE5\u5177\u8C03\u7528\u4E0E\u6D41\u5F0F\u54CD\u5E94\u5DF2\u5C31\u7EEA",
2495
- testConnection: "\u6D4B\u8BD5\u8FDE\u63A5",
2701
+ testConnection: "\u68C0\u67E5\u8FD0\u884C\u73AF\u5883",
2496
2702
  verifyAndRun: "\u9A8C\u8BC1\u5E76\u8FD0\u884C",
2497
2703
  verifying: "\u9A8C\u8BC1\u4E2D\u2026\u2026",
2498
2704
  run: "\u8FD0\u884C AI",
@@ -3742,6 +3948,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3742
3948
  agentProviderSelect: agentPanel.providerSelect,
3743
3949
  agentModelSelect: agentPanel.modelSelect,
3744
3950
  agentConsentCheckbox: agentPanel.consentCheckbox,
3951
+ agentWorkspaceConsentCheckbox: agentPanel.workspaceConsentCheckbox,
3745
3952
  agentTestButton: agentPanel.testButton,
3746
3953
  agentRunButton: agentPanel.runButton,
3747
3954
  agentCancelButton: agentPanel.cancelButton,
@@ -3853,6 +4060,10 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3853
4060
  previewButton.classList.toggle("spotpatch-primary", !agentReady);
3854
4061
  placeDialog();
3855
4062
  },
4063
+ renderAgentWorkspaceHealth(state, snapshot, errorCode) {
4064
+ agentPanel.renderWorkspaceHealth(state, snapshot, errorCode);
4065
+ placeDialog();
4066
+ },
3856
4067
  renderAgentJob(snapshot, result, activities, errorCode) {
3857
4068
  agentPanel.renderJob(snapshot, result, activities, errorCode);
3858
4069
  placeDialog();
@@ -3873,6 +4084,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3873
4084
  },
3874
4085
  locale: localizer.locale,
3875
4086
  messages: localizer.messages,
4087
+ agentWorkspaceConsentGranted: agentPanel.workspaceConsentGranted,
3876
4088
  subscribeLocale: localizer.subscribe,
3877
4089
  dispose() {
3878
4090
  diagnostics.removeEventListener("toggle", placeDialog);
@@ -3974,6 +4186,22 @@ function createAgentWorkflow(options) {
3974
4186
  let appliedDuringCurrentJob = false;
3975
4187
  let actionPending = false;
3976
4188
  const selectedProfiles = () => options.view.readAgentSelection();
4189
+ const refreshWorkspaceHealth = async (workflowRevision) => {
4190
+ options.view.renderAgentWorkspaceHealth("checking");
4191
+ try {
4192
+ const health = await options.api.agentWorkspaceHealth();
4193
+ if (workflowRevision !== revision) {
4194
+ return health;
4195
+ }
4196
+ options.view.renderAgentWorkspaceHealth(health.state, health, health.errorCode);
4197
+ return health;
4198
+ } catch (error) {
4199
+ if (workflowRevision === revision) {
4200
+ options.view.renderAgentWorkspaceHealth("blocked", void 0, errorCode(error));
4201
+ }
4202
+ throw error;
4203
+ }
4204
+ };
3977
4205
  const renderJob = (explicitErrorCode) => {
3978
4206
  if (snapshot === void 0) {
3979
4207
  return;
@@ -4149,6 +4377,10 @@ function createAgentWorkflow(options) {
4149
4377
  },
4150
4378
  beginSelection() {
4151
4379
  resetState();
4380
+ const workflowRevision = revision;
4381
+ if (options.ai.enabled) {
4382
+ void refreshWorkspaceHealth(workflowRevision).catch(() => void 0);
4383
+ }
4152
4384
  },
4153
4385
  cancel() {
4154
4386
  if (snapshot?.canCancel === true) {
@@ -4181,12 +4413,17 @@ function createAgentWorkflow(options) {
4181
4413
  providerOrModelChanged() {
4182
4414
  revision += 1;
4183
4415
  restoreProviderState();
4416
+ const workflowRevision = revision;
4417
+ void refreshWorkspaceHealth(workflowRevision).catch(() => void 0);
4184
4418
  },
4185
4419
  reset() {
4186
4420
  const mustReselect = appliedDuringCurrentJob;
4187
4421
  resetState();
4188
4422
  if (mustReselect) {
4189
4423
  options.onReselectRequired();
4424
+ } else if (options.ai.enabled) {
4425
+ const workflowRevision = revision;
4426
+ void refreshWorkspaceHealth(workflowRevision).catch(() => void 0);
4190
4427
  }
4191
4428
  },
4192
4429
  revert() {
@@ -4213,15 +4450,27 @@ function createAgentWorkflow(options) {
4213
4450
  providerConsents.add(selection.providerProfileId);
4214
4451
  const workflowRevision = ++revision;
4215
4452
  options.view.setAgentEditingEnabled(false);
4216
- void probe(workflowRevision).then(async () => {
4453
+ void Promise.all([
4454
+ probe(workflowRevision),
4455
+ refreshWorkspaceHealth(workflowRevision)
4456
+ ]).then(async ([, health]) => {
4217
4457
  if (workflowRevision !== revision) {
4218
4458
  return;
4219
4459
  }
4460
+ if (health.state === "blocked") {
4461
+ throw new RuntimeApiError(
4462
+ health.errorCode ?? import_shared8.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED
4463
+ );
4464
+ }
4465
+ if (health.state === "consent-required" && !options.view.agentWorkspaceConsentGranted()) {
4466
+ throw new RuntimeApiError(import_shared8.ERROR_CODES.WORKTREE_DIRTY);
4467
+ }
4220
4468
  const created = await options.api.createAgentJob({
4221
4469
  annotation,
4222
4470
  providerProfileId: selection.providerProfileId,
4223
4471
  modelProfileId: selection.modelProfileId,
4224
- providerDataConsent: true
4472
+ providerDataConsent: true,
4473
+ workingTreeMode: health.state === "consent-required" ? "include-local-changes" : "require-clean"
4225
4474
  });
4226
4475
  if (workflowRevision !== revision) {
4227
4476
  return;
@@ -4250,7 +4499,10 @@ function createAgentWorkflow(options) {
4250
4499
  return;
4251
4500
  }
4252
4501
  const workflowRevision = ++revision;
4253
- void probe(workflowRevision).catch((error) => {
4502
+ void Promise.all([
4503
+ probe(workflowRevision),
4504
+ refreshWorkspaceHealth(workflowRevision)
4505
+ ]).catch((error) => {
4254
4506
  if (workflowRevision === revision) {
4255
4507
  options.view.renderAgentCapability(
4256
4508
  "error",