@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.js CHANGED
@@ -146,6 +146,35 @@ function isAgentCapabilitySnapshot(value) {
146
146
  }
147
147
  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));
148
148
  }
149
+ function isAgentWorkspaceHealthSnapshot(value) {
150
+ if (!isRecord(value) || !hasOnlyKeys(value, [
151
+ "state",
152
+ "checkedAt",
153
+ "changes",
154
+ "canIncludeLocalChanges",
155
+ "errorCode"
156
+ ]) || 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, [
157
+ "staged",
158
+ "unstaged",
159
+ "untracked",
160
+ "conflicted",
161
+ "total"
162
+ ])) {
163
+ return false;
164
+ }
165
+ const changes = value.changes;
166
+ const { staged, unstaged, untracked, conflicted, total } = changes;
167
+ if (!isNonnegativeInteger(staged) || !isNonnegativeInteger(unstaged) || !isNonnegativeInteger(untracked) || !isNonnegativeInteger(conflicted) || !isNonnegativeInteger(total) || total < staged || total < unstaged || total < untracked || total < conflicted || total > staged + unstaged + untracked) {
168
+ return false;
169
+ }
170
+ if (value.state === "ready") {
171
+ return total === 0 && !value.canIncludeLocalChanges && value.errorCode === void 0;
172
+ }
173
+ if (value.state === "consent-required") {
174
+ return total > 0 && conflicted === 0 && value.canIncludeLocalChanges && value.errorCode === void 0;
175
+ }
176
+ return !value.canIncludeLocalChanges && value.errorCode !== void 0;
177
+ }
149
178
  function isAgentJobSnapshot(value) {
150
179
  if (!isRecord(value) || !hasOnlyKeys(value, [
151
180
  "jobId",
@@ -486,6 +515,17 @@ function createRuntimeApi(options) {
486
515
  request
487
516
  );
488
517
  },
518
+ async agentWorkspaceHealth() {
519
+ const data = await requestJson(
520
+ SPOTPATCH_ENDPOINTS.agentWorkspaceHealth,
521
+ "POST",
522
+ emptyAction
523
+ );
524
+ if (!isAgentWorkspaceHealthSnapshot(data)) {
525
+ throw new RuntimeApiError();
526
+ }
527
+ return deepFreeze(data);
528
+ },
489
529
  async createAgentJob(request) {
490
530
  const serverRequest = omitBrowserCodeContext(request);
491
531
  return parseJobSnapshot(
@@ -1563,15 +1603,62 @@ var AGENT_PANEL_STYLES = `
1563
1603
  min-height: 46px;
1564
1604
  margin-top: 7px;
1565
1605
  border: 1px solid var(--spotpatch-border);
1566
- border-radius: 10px;
1567
- padding: 8px 30px 8px 10px;
1606
+ border-radius: 12px;
1607
+ padding: 10px 38px 10px 13px;
1568
1608
  color: #e8ebf2;
1569
1609
  color-scheme: dark;
1570
- background: var(--spotpatch-bg-raised);
1610
+ background: linear-gradient(180deg, rgb(255 255 255 / 4%), rgb(255 255 255 / 2%));
1571
1611
  font-size: 15px;
1612
+ font-weight: 560;
1613
+ line-height: 1.35;
1572
1614
  outline: none;
1615
+ transition: 160ms ease;
1616
+ }
1617
+ .spotpatch-agent select:hover { border-color: rgb(148 163 184 / 38%); background-color: rgb(255 255 255 / 5%); }
1618
+ .spotpatch-agent select:focus-visible { border-color: rgb(139 124 247 / 76%); box-shadow: 0 0 0 3px rgb(109 93 246 / 18%); }
1619
+ .spotpatch-agent select option { min-height: 42px; color: #e8ebf2; background: #10151e; }
1620
+ @supports (appearance: base-select) {
1621
+ .spotpatch-agent select,
1622
+ ::picker(select) { appearance: base-select; }
1623
+ .spotpatch-agent select { padding: 10px 13px; }
1624
+ .spotpatch-agent select::picker-icon {
1625
+ color: #9da6b5;
1626
+ transition: rotate 180ms cubic-bezier(.2, .8, .2, 1);
1627
+ }
1628
+ .spotpatch-agent select:open::picker-icon { rotate: 180deg; color: #c4b5fd; }
1629
+ .spotpatch-agent select::picker(select) {
1630
+ top: calc(anchor(bottom) + 7px);
1631
+ left: anchor(left);
1632
+ width: anchor-size(width);
1633
+ max-height: min(280px, 40vh);
1634
+ margin: 0;
1635
+ overflow: hidden;
1636
+ border: 1px solid rgb(139 124 247 / 28%);
1637
+ border-radius: 12px;
1638
+ padding: 6px;
1639
+ background: #10151e;
1640
+ box-shadow: 0 18px 50px rgb(0 0 0 / 38%);
1641
+ opacity: 0;
1642
+ transform: translateY(-5px) scale(.985);
1643
+ transition: opacity 150ms ease, transform 180ms cubic-bezier(.2, .8, .2, 1), overlay 180ms allow-discrete, display 180ms allow-discrete;
1644
+ }
1645
+ ::picker(select):popover-open { opacity: 1; transform: translateY(0) scale(1); }
1646
+ @starting-style {
1647
+ ::picker(select):popover-open { opacity: 0; transform: translateY(-5px) scale(.985); }
1648
+ }
1649
+ .spotpatch-agent select option {
1650
+ min-height: 42px;
1651
+ border-radius: 8px;
1652
+ padding: 10px 12px;
1653
+ color: #dfe4ee;
1654
+ background: transparent;
1655
+ cursor: pointer;
1656
+ }
1657
+ .spotpatch-agent select option:hover,
1658
+ .spotpatch-agent select option:focus { color: #fff; background: rgb(109 93 246 / 16%); }
1659
+ .spotpatch-agent select option:checked { color: #fff; background: rgb(109 93 246 / 24%); }
1660
+ .spotpatch-agent select option::checkmark { color: #8b7cf7; }
1573
1661
  }
1574
- .spotpatch-agent select option { color: #e8ebf2; background: #10151e; }
1575
1662
  .spotpatch-agent select:focus-visible,
1576
1663
  .spotpatch-consent input:focus-visible {
1577
1664
  outline: 2px solid #8b7cf7;
@@ -1588,11 +1675,22 @@ var AGENT_PANEL_STYLES = `
1588
1675
  line-height: 1.6;
1589
1676
  }
1590
1677
  .spotpatch-consent input { width: 16px; height: 16px; flex: none; margin: 3px 0 0; accent-color: var(--spotpatch-accent); }
1678
+ .spotpatch-workspace-consent {
1679
+ margin-top: 12px;
1680
+ border: 1px solid rgb(251 191 36 / 18%);
1681
+ border-radius: 10px;
1682
+ padding: 11px 12px;
1683
+ background: rgb(120 53 15 / 9%);
1684
+ }
1685
+ .spotpatch-workspace-consent strong { display: block; color: #fde68a; font-size: 13px; font-weight: 620; }
1686
+ .spotpatch-workspace-consent small { display: block; margin-top: 2px; color: #aeb6c4; font-size: 12px; line-height: 1.5; }
1687
+ .spotpatch-agent-health-list { display: grid; gap: 8px; margin-top: 14px; }
1688
+ .spotpatch-agent-workspace,
1591
1689
  .spotpatch-agent-capability {
1592
1690
  display: flex;
1593
1691
  align-items: center;
1594
1692
  gap: 8px;
1595
- margin: 14px 0 0;
1693
+ margin: 0;
1596
1694
  color: #929bab;
1597
1695
  font-size: 13px;
1598
1696
  }
@@ -1609,6 +1707,21 @@ var AGENT_PANEL_STYLES = `
1609
1707
  .spotpatch-agent-capability[data-state="ready"]::before { background: var(--spotpatch-success); }
1610
1708
  .spotpatch-agent-capability[data-state="error"] { color: #fecaca; }
1611
1709
  .spotpatch-agent-capability[data-state="error"]::before { background: var(--spotpatch-danger); }
1710
+ .spotpatch-agent-workspace::before {
1711
+ width: 6px;
1712
+ height: 6px;
1713
+ flex: none;
1714
+ border-radius: 999px;
1715
+ background: #64748b;
1716
+ content: "";
1717
+ }
1718
+ .spotpatch-agent-workspace[data-state="checking"]::before { background: var(--spotpatch-warning); }
1719
+ .spotpatch-agent-workspace[data-state="ready"] { color: #a7f3d0; }
1720
+ .spotpatch-agent-workspace[data-state="ready"]::before { background: var(--spotpatch-success); }
1721
+ .spotpatch-agent-workspace[data-state="consent-required"] { color: #fde68a; }
1722
+ .spotpatch-agent-workspace[data-state="consent-required"]::before { background: #fbbf24; }
1723
+ .spotpatch-agent-workspace[data-state="blocked"] { color: #fecaca; }
1724
+ .spotpatch-agent-workspace[data-state="blocked"]::before { background: var(--spotpatch-danger); }
1612
1725
  .spotpatch-agent-job { padding: 16px; }
1613
1726
  .spotpatch-agent-job-meta { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
1614
1727
  .spotpatch-agent-model { min-width: 0; overflow: hidden; color: #d6dafe; font-size: 13px; text-overflow: ellipsis; white-space: nowrap; }
@@ -1697,10 +1810,26 @@ function createAgentPanel(document, ai, localizer) {
1697
1810
  consentCheckbox.type = "checkbox";
1698
1811
  const consentText = createMarkedElement(document, "span");
1699
1812
  consent.append(consentCheckbox, consentText);
1813
+ const workspaceConsent = createMarkedElement(document, "label");
1814
+ workspaceConsent.className = "spotpatch-consent spotpatch-workspace-consent";
1815
+ workspaceConsent.hidden = true;
1816
+ const workspaceConsentCheckbox = createMarkedElement(document, "input");
1817
+ workspaceConsentCheckbox.type = "checkbox";
1818
+ const workspaceConsentContent = createMarkedElement(document, "span");
1819
+ const workspaceConsentTitle = createMarkedElement(document, "strong");
1820
+ const workspaceConsentHelp = createMarkedElement(document, "small");
1821
+ workspaceConsentContent.append(workspaceConsentTitle, workspaceConsentHelp);
1822
+ workspaceConsent.append(workspaceConsentCheckbox, workspaceConsentContent);
1823
+ const healthList = createMarkedElement(document, "div");
1824
+ healthList.className = "spotpatch-agent-health-list";
1825
+ const workspace = createMarkedElement(document, "p");
1826
+ workspace.className = "spotpatch-agent-workspace";
1827
+ workspace.dataset.state = "idle";
1700
1828
  const capability = createMarkedElement(document, "p");
1701
1829
  capability.className = "spotpatch-agent-capability";
1702
1830
  capability.dataset.state = "idle";
1703
- setup.append(selectors, consent, capability);
1831
+ healthList.append(workspace, capability);
1832
+ setup.append(selectors, consent, workspaceConsent, healthList);
1704
1833
  const job = createMarkedElement(document, "div");
1705
1834
  job.className = "spotpatch-agent-job";
1706
1835
  job.hidden = true;
@@ -1759,6 +1888,9 @@ function createAgentPanel(document, ai, localizer) {
1759
1888
  let latestCapabilityState = "idle";
1760
1889
  let latestCapabilityMessage = messages.agent.connectionNotTested;
1761
1890
  let latestCapabilityErrorCode;
1891
+ let latestWorkspaceState = "idle";
1892
+ let latestWorkspaceSnapshot;
1893
+ let latestWorkspaceErrorCode;
1762
1894
  for (const provider of providers) {
1763
1895
  addOption(document, providerSelect, provider.id, provider.label);
1764
1896
  }
@@ -1786,7 +1918,7 @@ function createAgentPanel(document, ai, localizer) {
1786
1918
  testButton.hidden = !setupVisible;
1787
1919
  runButton.hidden = !setupVisible;
1788
1920
  testButton.disabled = !editingEnabled || capabilityProbing || selectedProvider() === void 0;
1789
- runButton.disabled = !editingEnabled || capabilityProbing || !contextReady || !consentCheckbox.checked || selectedProvider() === void 0 || modelSelect.value.length === 0;
1921
+ 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;
1790
1922
  if (!jobPresented || !selectionVisible) {
1791
1923
  cancelButton.hidden = true;
1792
1924
  applyButton.hidden = true;
@@ -1794,6 +1926,24 @@ function createAgentPanel(document, ai, localizer) {
1794
1926
  resetButton.hidden = true;
1795
1927
  }
1796
1928
  };
1929
+ const renderWorkspaceMessage = () => {
1930
+ if (latestWorkspaceState === "idle") {
1931
+ workspace.textContent = messages.agent.workspaceNotChecked;
1932
+ } else if (latestWorkspaceState === "checking") {
1933
+ workspace.textContent = messages.agent.checkingWorkspace;
1934
+ } else if (latestWorkspaceState === "ready") {
1935
+ workspace.textContent = messages.agent.workspaceReady;
1936
+ } else if (latestWorkspaceState === "consent-required") {
1937
+ const changes = latestWorkspaceSnapshot?.changes;
1938
+ workspace.textContent = messages.agent.workspaceDirty(
1939
+ changes?.staged ?? 0,
1940
+ changes?.unstaged ?? 0,
1941
+ changes?.untracked ?? 0
1942
+ );
1943
+ } else {
1944
+ workspace.textContent = latestWorkspaceErrorCode === void 0 ? messages.errors.INTERNAL_ERROR : messages.errors[latestWorkspaceErrorCode];
1945
+ }
1946
+ };
1797
1947
  function handleProviderChange() {
1798
1948
  populateModels();
1799
1949
  consentCheckbox.checked = false;
@@ -1818,6 +1968,7 @@ function createAgentPanel(document, ai, localizer) {
1818
1968
  providerSelect.addEventListener("change", handleProviderChange);
1819
1969
  modelSelect.addEventListener("change", handleModelChange);
1820
1970
  consentCheckbox.addEventListener("change", refreshActions);
1971
+ workspaceConsentCheckbox.addEventListener("change", refreshActions);
1821
1972
  function renderCurrentJob() {
1822
1973
  if (latestSnapshot === void 0) {
1823
1974
  return;
@@ -1872,6 +2023,8 @@ function createAgentPanel(document, ai, localizer) {
1872
2023
  modelText.textContent = messages.agent.model;
1873
2024
  providerSelect.setAttribute("aria-label", messages.agent.providerAriaLabel);
1874
2025
  modelSelect.setAttribute("aria-label", messages.agent.modelAriaLabel);
2026
+ workspaceConsentTitle.textContent = messages.agent.includeLocalChanges;
2027
+ workspaceConsentHelp.textContent = messages.agent.includeLocalChangesHelp;
1875
2028
  diff.setAttribute("aria-label", messages.agent.diffAriaLabel);
1876
2029
  testButton.textContent = messages.agent.testConnection;
1877
2030
  applyButton.textContent = messages.agent.apply;
@@ -1892,6 +2045,7 @@ function createAgentPanel(document, ai, localizer) {
1892
2045
  } else {
1893
2046
  capability.textContent = latestCapabilityMessage;
1894
2047
  }
2048
+ renderWorkspaceMessage();
1895
2049
  refreshActions();
1896
2050
  renderCurrentJob();
1897
2051
  }
@@ -1902,6 +2056,7 @@ function createAgentPanel(document, ai, localizer) {
1902
2056
  providerSelect,
1903
2057
  modelSelect,
1904
2058
  consentCheckbox,
2059
+ workspaceConsentCheckbox,
1905
2060
  testButton,
1906
2061
  runButton,
1907
2062
  cancelButton,
@@ -1911,6 +2066,9 @@ function createAgentPanel(document, ai, localizer) {
1911
2066
  consentGranted() {
1912
2067
  return consentCheckbox.checked;
1913
2068
  },
2069
+ workspaceConsentGranted() {
2070
+ return workspaceConsentCheckbox.checked;
2071
+ },
1914
2072
  readSelection() {
1915
2073
  const provider = selectedProvider();
1916
2074
  const model = provider?.models.find(
@@ -1931,6 +2089,20 @@ function createAgentPanel(document, ai, localizer) {
1931
2089
  capability.textContent = capabilitySnapshot?.state === "agent-ready" ? `${message} \xB7 ${messages.agent.toolsReady}` : message;
1932
2090
  refreshActions();
1933
2091
  },
2092
+ renderWorkspaceHealth(state, snapshot, errorCode) {
2093
+ latestWorkspaceState = state;
2094
+ latestWorkspaceSnapshot = snapshot;
2095
+ latestWorkspaceErrorCode = errorCode ?? snapshot?.errorCode;
2096
+ workspace.dataset.state = state;
2097
+ if (state !== "checking") {
2098
+ workspaceConsent.hidden = state !== "consent-required";
2099
+ }
2100
+ if (state === "idle" || state === "ready" || state === "blocked") {
2101
+ workspaceConsentCheckbox.checked = false;
2102
+ }
2103
+ renderWorkspaceMessage();
2104
+ refreshActions();
2105
+ },
1934
2106
  renderJob(snapshot, result, activities, errorCode) {
1935
2107
  jobPresented = true;
1936
2108
  setup.hidden = true;
@@ -1938,6 +2110,7 @@ function createAgentPanel(document, ai, localizer) {
1938
2110
  providerSelect.disabled = true;
1939
2111
  modelSelect.disabled = true;
1940
2112
  consentCheckbox.disabled = true;
2113
+ workspaceConsentCheckbox.disabled = true;
1941
2114
  latestSnapshot = snapshot;
1942
2115
  latestResult = result;
1943
2116
  latestActivities = activities;
@@ -1955,6 +2128,7 @@ function createAgentPanel(document, ai, localizer) {
1955
2128
  providerSelect.disabled = false;
1956
2129
  modelSelect.disabled = false;
1957
2130
  consentCheckbox.disabled = false;
2131
+ workspaceConsentCheckbox.disabled = false;
1958
2132
  activity.replaceChildren();
1959
2133
  files.replaceChildren();
1960
2134
  checks.replaceChildren();
@@ -1972,6 +2146,7 @@ function createAgentPanel(document, ai, localizer) {
1972
2146
  providerSelect.disabled = !enabled || jobPresented;
1973
2147
  modelSelect.disabled = !enabled || jobPresented;
1974
2148
  consentCheckbox.disabled = !enabled || jobPresented;
2149
+ workspaceConsentCheckbox.disabled = !enabled || jobPresented;
1975
2150
  refreshActions();
1976
2151
  },
1977
2152
  setProviderConsent(granted) {
@@ -1993,6 +2168,7 @@ function createAgentPanel(document, ai, localizer) {
1993
2168
  providerSelect.removeEventListener("change", handleProviderChange);
1994
2169
  modelSelect.removeEventListener("change", handleModelChange);
1995
2170
  consentCheckbox.removeEventListener("change", refreshActions);
2171
+ workspaceConsentCheckbox.removeEventListener("change", refreshActions);
1996
2172
  }
1997
2173
  });
1998
2174
  }
@@ -2162,8 +2338,16 @@ var ERROR_MESSAGES_EN = Object.freeze({
2162
2338
  [ERROR_CODES2.AGENT_BUSY]: "Another write Agent job is still active.",
2163
2339
  [ERROR_CODES2.AGENT_LIMIT_EXCEEDED]: "The Agent stopped at a configured time, turn, output, or size limit.",
2164
2340
  [ERROR_CODES2.AGENT_CANCELLED]: "The Agent job was cancelled.",
2165
- [ERROR_CODES2.WORKTREE_DIRTY]: "Commit or otherwise clean staged, unstaged, and untracked files before running AI.",
2341
+ [ERROR_CODES2.WORKTREE_DIRTY]: "Confirm inclusion of local changes before running AI.",
2342
+ [ERROR_CODES2.WORKTREE_NOT_REPOSITORY]: "Vite root must be an initialized Git repository root.",
2343
+ [ERROR_CODES2.WORKTREE_OPERATION_IN_PROGRESS]: "Finish the active merge, rebase, cherry-pick, or revert.",
2344
+ [ERROR_CODES2.WORKTREE_CONFLICTED]: "Resolve all Git conflicts before running AI.",
2345
+ [ERROR_CODES2.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: "Reduce untracked files below the safe count and size limits.",
2346
+ [ERROR_CODES2.WORKTREE_UNTRACKED_UNSUPPORTED]: "An untracked path is missing, linked, or not a regular file.",
2347
+ [ERROR_CODES2.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: "Local changes cannot be isolated safely.",
2166
2348
  [ERROR_CODES2.TOOL_DENIED]: "A model tool request violated the local safety policy.",
2349
+ [ERROR_CODES2.TOOL_INPUT_INVALID]: "The model sent invalid tool arguments or reused a tool ID.",
2350
+ [ERROR_CODES2.TOOL_PATH_DENIED]: "The model requested a protected, linked, non-text, or external path.",
2167
2351
  [ERROR_CODES2.PATCH_REJECTED]: "The proposed patch did not pass local policy.",
2168
2352
  [ERROR_CODES2.VALIDATION_FAILED]: "Required project checks failed. The change cannot be applied.",
2169
2353
  [ERROR_CODES2.APPLY_CONFLICT]: "Project files changed after the Agent baseline; no overwrite was performed.",
@@ -2187,8 +2371,16 @@ var ERROR_MESSAGES_ZH = Object.freeze({
2187
2371
  [ERROR_CODES2.AGENT_BUSY]: "\u5F53\u524D\u9879\u76EE\u5DF2\u6709\u4E00\u4E2A\u5199\u5165\u4EFB\u52A1\u6B63\u5728\u8FD0\u884C\u3002",
2188
2372
  [ERROR_CODES2.AGENT_LIMIT_EXCEEDED]: "Agent \u8FBE\u5230\u65F6\u95F4\u3001\u8F6E\u6B21\u3001\u8F93\u51FA\u6216\u53D8\u66F4\u89C4\u6A21\u9650\u5236\u3002",
2189
2373
  [ERROR_CODES2.AGENT_CANCELLED]: "Agent \u4EFB\u52A1\u5DF2\u53D6\u6D88\u3002",
2190
- [ERROR_CODES2.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",
2374
+ [ERROR_CODES2.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",
2375
+ [ERROR_CODES2.WORKTREE_NOT_REPOSITORY]: "Vite \u6839\u76EE\u5F55\u4E0D\u662F\u5DF2\u521D\u59CB\u5316 Git \u4ED3\u5E93\u7684\u9876\u5C42\u76EE\u5F55\u3002",
2376
+ [ERROR_CODES2.WORKTREE_OPERATION_IN_PROGRESS]: "\u8BF7\u5148\u5B8C\u6210\u5F53\u524D merge\u3001rebase\u3001cherry-pick \u6216 revert\uFF0C\u518D\u8FD0\u884C AI\u3002",
2377
+ [ERROR_CODES2.WORKTREE_CONFLICTED]: "\u8BF7\u5148\u89E3\u51B3\u5168\u90E8 Git \u51B2\u7A81\uFF0C\u518D\u8FD0\u884C AI\u3002",
2378
+ [ERROR_CODES2.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",
2379
+ [ERROR_CODES2.WORKTREE_UNTRACKED_UNSUPPORTED]: "\u672A\u8DDF\u8E2A\u9879\u5DF2\u4E22\u5931\u3001\u662F\u7B26\u53F7\u94FE\u63A5\u6216\u4E0D\u662F\u666E\u901A\u6587\u4EF6\u3002",
2380
+ [ERROR_CODES2.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: "\u5F53\u524D\u672C\u5730\u4FEE\u6539\u65E0\u6CD5\u5B89\u5168\u9694\u79BB\u3002",
2191
2381
  [ERROR_CODES2.TOOL_DENIED]: "\u6A21\u578B\u5DE5\u5177\u8BF7\u6C42\u8FDD\u53CD\u672C\u5730\u5B89\u5168\u7B56\u7565\u3002",
2382
+ [ERROR_CODES2.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",
2383
+ [ERROR_CODES2.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",
2192
2384
  [ERROR_CODES2.PATCH_REJECTED]: "\u5EFA\u8BAE\u8865\u4E01\u672A\u901A\u8FC7\u672C\u5730\u7B56\u7565\u68C0\u67E5\u3002",
2193
2385
  [ERROR_CODES2.VALIDATION_FAILED]: "\u9879\u76EE\u5FC5\u9700\u68C0\u67E5\u5931\u8D25\uFF0C\u4E0D\u80FD\u5E94\u7528\u672C\u6B21\u53D8\u66F4\u3002",
2194
2386
  [ERROR_CODES2.APPLY_CONFLICT]: "Agent \u5EFA\u7ACB\u57FA\u7EBF\u540E\u9879\u76EE\u6587\u4EF6\u5DF2\u53D8\u5316\uFF0C\u672A\u6267\u884C\u8986\u76D6\u3002",
@@ -2353,6 +2545,13 @@ var UI_MESSAGES = Object.freeze({
2353
2545
  providerUnavailable: "Provider configuration is unavailable.",
2354
2546
  consent: (provider) => `I understand selected context and allowed source may be sent to ${provider}; its data policy is my responsibility.`,
2355
2547
  connectionNotTested: "Connection not tested",
2548
+ workspaceNotChecked: "Local workspace not checked",
2549
+ checkingWorkspace: "Checking Git workspace and isolated execution\u2026",
2550
+ workspaceReady: "Local workspace is ready for isolated execution",
2551
+ workspaceDirty: (staged, unstaged, untracked) => `Local changes found \xB7 ${String(staged)} staged \xB7 ${String(unstaged)} unstaged \xB7 ${String(untracked)} untracked`,
2552
+ includeLocalChanges: "Allow the Agent to continue from my current local changes",
2553
+ includeLocalChangesHelp: "The Agent may edit these files. SpotPatch preserves the baseline and applies or reverts only the Agent delta.",
2554
+ localChangesConsentRequired: "Confirm inclusion of current local changes before running AI.",
2356
2555
  capabilityVerified: "Agent capability verified",
2357
2556
  capabilityVerifiedAnnouncement: "AI provider capability verified.",
2358
2557
  testingCapability: "Testing authentication, tools, continuation, and streaming\u2026",
@@ -2361,7 +2560,7 @@ var UI_MESSAGES = Object.freeze({
2361
2560
  reverting: "Reverting the applied Agent change.",
2362
2561
  consentRequired: "Confirm remote provider data transmission before running AI.",
2363
2562
  toolsReady: "tools and streaming ready",
2364
- testConnection: "Test connection",
2563
+ testConnection: "Check environment",
2365
2564
  verifyAndRun: "Verify & run",
2366
2565
  verifying: "Verifying\u2026",
2367
2566
  run: "Run AI",
@@ -2482,6 +2681,13 @@ var UI_MESSAGES = Object.freeze({
2482
2681
  providerUnavailable: "\u6A21\u578B\u670D\u52A1\u914D\u7F6E\u4E0D\u53EF\u7528\u3002",
2483
2682
  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`,
2484
2683
  connectionNotTested: "\u5C1A\u672A\u6D4B\u8BD5\u8FDE\u63A5",
2684
+ workspaceNotChecked: "\u5C1A\u672A\u68C0\u67E5\u672C\u5730\u5DE5\u4F5C\u533A",
2685
+ checkingWorkspace: "\u6B63\u5728\u68C0\u67E5 Git \u5DE5\u4F5C\u533A\u4E0E\u9694\u79BB\u6267\u884C\u73AF\u5883\u2026\u2026",
2686
+ workspaceReady: "\u672C\u5730\u5DE5\u4F5C\u533A\u5DF2\u6EE1\u8DB3\u9694\u79BB\u6267\u884C\u6761\u4EF6",
2687
+ 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)}`,
2688
+ includeLocalChanges: "\u5141\u8BB8 Agent \u57FA\u4E8E\u6211\u5F53\u524D\u7684\u672C\u5730\u4FEE\u6539\u7EE7\u7EED",
2689
+ 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",
2690
+ localChangesConsentRequired: "\u8FD0\u884C AI \u524D\uFF0C\u8BF7\u786E\u8BA4\u5141\u8BB8\u7EB3\u5165\u5F53\u524D\u672C\u5730\u4FEE\u6539\u3002",
2485
2691
  capabilityVerified: "Agent \u80FD\u529B\u9A8C\u8BC1\u901A\u8FC7",
2486
2692
  capabilityVerifiedAnnouncement: "AI \u6A21\u578B\u670D\u52A1\u80FD\u529B\u9A8C\u8BC1\u901A\u8FC7\u3002",
2487
2693
  testingCapability: "\u6B63\u5728\u9A8C\u8BC1\u9274\u6743\u3001\u5DE5\u5177\u8C03\u7528\u3001\u8FDE\u7EED\u8C03\u7528\u4E0E\u6D41\u5F0F\u54CD\u5E94\u2026\u2026",
@@ -2490,7 +2696,7 @@ var UI_MESSAGES = Object.freeze({
2490
2696
  reverting: "\u6B63\u5728\u64A4\u9500\u5DF2\u5E94\u7528\u7684 Agent \u53D8\u66F4\u3002",
2491
2697
  consentRequired: "\u8FD0\u884C AI \u524D\uFF0C\u8BF7\u5148\u786E\u8BA4\u5141\u8BB8\u5411\u8FDC\u7A0B\u6A21\u578B\u670D\u52A1\u4F20\u8F93\u6570\u636E\u3002",
2492
2698
  toolsReady: "\u5DE5\u5177\u8C03\u7528\u4E0E\u6D41\u5F0F\u54CD\u5E94\u5DF2\u5C31\u7EEA",
2493
- testConnection: "\u6D4B\u8BD5\u8FDE\u63A5",
2699
+ testConnection: "\u68C0\u67E5\u8FD0\u884C\u73AF\u5883",
2494
2700
  verifyAndRun: "\u9A8C\u8BC1\u5E76\u8FD0\u884C",
2495
2701
  verifying: "\u9A8C\u8BC1\u4E2D\u2026\u2026",
2496
2702
  run: "\u8FD0\u884C AI",
@@ -3740,6 +3946,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3740
3946
  agentProviderSelect: agentPanel.providerSelect,
3741
3947
  agentModelSelect: agentPanel.modelSelect,
3742
3948
  agentConsentCheckbox: agentPanel.consentCheckbox,
3949
+ agentWorkspaceConsentCheckbox: agentPanel.workspaceConsentCheckbox,
3743
3950
  agentTestButton: agentPanel.testButton,
3744
3951
  agentRunButton: agentPanel.runButton,
3745
3952
  agentCancelButton: agentPanel.cancelButton,
@@ -3851,6 +4058,10 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3851
4058
  previewButton.classList.toggle("spotpatch-primary", !agentReady);
3852
4059
  placeDialog();
3853
4060
  },
4061
+ renderAgentWorkspaceHealth(state, snapshot, errorCode) {
4062
+ agentPanel.renderWorkspaceHealth(state, snapshot, errorCode);
4063
+ placeDialog();
4064
+ },
3854
4065
  renderAgentJob(snapshot, result, activities, errorCode) {
3855
4066
  agentPanel.renderJob(snapshot, result, activities, errorCode);
3856
4067
  placeDialog();
@@ -3871,6 +4082,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3871
4082
  },
3872
4083
  locale: localizer.locale,
3873
4084
  messages: localizer.messages,
4085
+ agentWorkspaceConsentGranted: agentPanel.workspaceConsentGranted,
3874
4086
  subscribeLocale: localizer.subscribe,
3875
4087
  dispose() {
3876
4088
  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 ?? ERROR_CODES3.WORKTREE_LOCAL_CHANGES_UNSUPPORTED
4463
+ );
4464
+ }
4465
+ if (health.state === "consent-required" && !options.view.agentWorkspaceConsentGranted()) {
4466
+ throw new RuntimeApiError(ERROR_CODES3.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",