@spotpatch/runtime 1.1.2 → 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.js CHANGED
@@ -86,6 +86,7 @@ import {
86
86
  AGENT_CHECK_STATUSES,
87
87
  AGENT_FILE_CHANGE_KINDS,
88
88
  AGENT_JOB_STATUSES,
89
+ AGENT_WORKSPACE_STATES,
89
90
  ERROR_CODES
90
91
  } from "@spotpatch/shared";
91
92
  var ERROR_CODE_VALUES = new Set(Object.values(ERROR_CODES));
@@ -93,6 +94,7 @@ var CAPABILITY_STATE_VALUES = new Set(AGENT_CAPABILITY_STATES);
93
94
  var CHECK_STATUS_VALUES = new Set(AGENT_CHECK_STATUSES);
94
95
  var FILE_CHANGE_KIND_VALUES = new Set(AGENT_FILE_CHANGE_KINDS);
95
96
  var JOB_STATUS_VALUES = new Set(AGENT_JOB_STATUSES);
97
+ var WORKSPACE_STATE_VALUES = new Set(AGENT_WORKSPACE_STATES);
96
98
  var PROFILE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
97
99
  var JOB_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
98
100
  var ISO_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/;
@@ -146,6 +148,35 @@ function isAgentCapabilitySnapshot(value) {
146
148
  }
147
149
  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
150
  }
151
+ function isAgentWorkspaceHealthSnapshot(value) {
152
+ if (!isRecord(value) || !hasOnlyKeys(value, [
153
+ "state",
154
+ "checkedAt",
155
+ "changes",
156
+ "canIncludeLocalChanges",
157
+ "errorCode"
158
+ ]) || 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, [
159
+ "staged",
160
+ "unstaged",
161
+ "untracked",
162
+ "conflicted",
163
+ "total"
164
+ ])) {
165
+ return false;
166
+ }
167
+ const changes = value.changes;
168
+ const { staged, unstaged, untracked, conflicted, total } = changes;
169
+ if (!isNonnegativeInteger(staged) || !isNonnegativeInteger(unstaged) || !isNonnegativeInteger(untracked) || !isNonnegativeInteger(conflicted) || !isNonnegativeInteger(total) || total < staged || total < unstaged || total < untracked || total < conflicted || total > staged + unstaged + untracked) {
170
+ return false;
171
+ }
172
+ if (value.state === "ready") {
173
+ return total === 0 && !value.canIncludeLocalChanges && value.errorCode === void 0;
174
+ }
175
+ if (value.state === "consent-required") {
176
+ return total > 0 && conflicted === 0 && value.canIncludeLocalChanges && value.errorCode === void 0;
177
+ }
178
+ return !value.canIncludeLocalChanges && value.errorCode !== void 0;
179
+ }
149
180
  function isAgentJobSnapshot(value) {
150
181
  if (!isRecord(value) || !hasOnlyKeys(value, [
151
182
  "jobId",
@@ -486,6 +517,17 @@ function createRuntimeApi(options) {
486
517
  request
487
518
  );
488
519
  },
520
+ async agentWorkspaceHealth() {
521
+ const data = await requestJson(
522
+ SPOTPATCH_ENDPOINTS.agentWorkspaceHealth,
523
+ "POST",
524
+ emptyAction
525
+ );
526
+ if (!isAgentWorkspaceHealthSnapshot(data)) {
527
+ throw new RuntimeApiError();
528
+ }
529
+ return deepFreeze(data);
530
+ },
489
531
  async createAgentJob(request) {
490
532
  const serverRequest = omitBrowserCodeContext(request);
491
533
  return parseJobSnapshot(
@@ -1563,15 +1605,65 @@ var AGENT_PANEL_STYLES = `
1563
1605
  min-height: 46px;
1564
1606
  margin-top: 7px;
1565
1607
  border: 1px solid var(--spotpatch-border);
1566
- border-radius: 10px;
1567
- padding: 8px 30px 8px 10px;
1608
+ border-radius: 12px;
1609
+ padding: 10px 38px 10px 13px;
1568
1610
  color: #e8ebf2;
1569
1611
  color-scheme: dark;
1570
- background: var(--spotpatch-bg-raised);
1612
+ background: linear-gradient(180deg, rgb(255 255 255 / 4%), rgb(255 255 255 / 2%));
1571
1613
  font-size: 15px;
1614
+ font-weight: 560;
1615
+ line-height: 1.35;
1572
1616
  outline: none;
1617
+ transition: border-color 160ms ease, background-color 160ms ease, box-shadow 160ms ease;
1618
+ }
1619
+ .spotpatch-agent select:hover { border-color: rgb(148 163 184 / 38%); background-color: rgb(255 255 255 / 5%); }
1620
+ .spotpatch-agent select:focus-visible { border-color: rgb(139 124 247 / 76%); box-shadow: 0 0 0 3px rgb(109 93 246 / 18%); }
1621
+ .spotpatch-agent select option { min-height: 42px; color: #e8ebf2; background: #10151e; }
1622
+ @supports (appearance: base-select) {
1623
+ .spotpatch-agent select,
1624
+ ::picker(select) { appearance: base-select; }
1625
+ .spotpatch-agent select { padding: 10px 13px; }
1626
+ .spotpatch-agent select::picker-icon {
1627
+ color: #9da6b5;
1628
+ transition: rotate 180ms cubic-bezier(.2, .8, .2, 1), color 160ms ease;
1629
+ }
1630
+ .spotpatch-agent select:open::picker-icon { rotate: 180deg; color: #c4b5fd; }
1631
+ .spotpatch-agent select::picker(select) {
1632
+ top: calc(anchor(bottom) + 7px);
1633
+ left: anchor(left);
1634
+ width: anchor-size(width);
1635
+ max-height: min(280px, 40vh);
1636
+ margin: 0;
1637
+ overflow: hidden;
1638
+ border: 1px solid rgb(139 124 247 / 28%);
1639
+ border-radius: 12px;
1640
+ padding: 6px;
1641
+ color: #e8ebf2;
1642
+ background: #10151e;
1643
+ box-shadow: 0 18px 50px rgb(0 0 0 / 38%), 0 0 0 1px rgb(255 255 255 / 3%);
1644
+ opacity: 0;
1645
+ transform: translateY(-5px) scale(.985);
1646
+ transform-origin: top center;
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
+ transition: color 120ms ease, background-color 120ms ease;
1661
+ }
1662
+ .spotpatch-agent select option:hover,
1663
+ .spotpatch-agent select option:focus { color: #fff; background: rgb(109 93 246 / 16%); }
1664
+ .spotpatch-agent select option:checked { color: #fff; background: rgb(109 93 246 / 24%); }
1665
+ .spotpatch-agent select option::checkmark { color: #8b7cf7; }
1573
1666
  }
1574
- .spotpatch-agent select option { color: #e8ebf2; background: #10151e; }
1575
1667
  .spotpatch-agent select:focus-visible,
1576
1668
  .spotpatch-consent input:focus-visible {
1577
1669
  outline: 2px solid #8b7cf7;
@@ -1588,11 +1680,22 @@ var AGENT_PANEL_STYLES = `
1588
1680
  line-height: 1.6;
1589
1681
  }
1590
1682
  .spotpatch-consent input { width: 16px; height: 16px; flex: none; margin: 3px 0 0; accent-color: var(--spotpatch-accent); }
1683
+ .spotpatch-workspace-consent {
1684
+ margin-top: 12px;
1685
+ border: 1px solid rgb(251 191 36 / 18%);
1686
+ border-radius: 10px;
1687
+ padding: 11px 12px;
1688
+ background: rgb(120 53 15 / 9%);
1689
+ }
1690
+ .spotpatch-workspace-consent strong { display: block; color: #fde68a; font-size: 13px; font-weight: 620; }
1691
+ .spotpatch-workspace-consent small { display: block; margin-top: 2px; color: #aeb6c4; font-size: 12px; line-height: 1.5; }
1692
+ .spotpatch-agent-health-list { display: grid; gap: 8px; margin-top: 14px; }
1693
+ .spotpatch-agent-workspace,
1591
1694
  .spotpatch-agent-capability {
1592
1695
  display: flex;
1593
1696
  align-items: center;
1594
1697
  gap: 8px;
1595
- margin: 14px 0 0;
1698
+ margin: 0;
1596
1699
  color: #929bab;
1597
1700
  font-size: 13px;
1598
1701
  }
@@ -1609,6 +1712,21 @@ var AGENT_PANEL_STYLES = `
1609
1712
  .spotpatch-agent-capability[data-state="ready"]::before { background: var(--spotpatch-success); }
1610
1713
  .spotpatch-agent-capability[data-state="error"] { color: #fecaca; }
1611
1714
  .spotpatch-agent-capability[data-state="error"]::before { background: var(--spotpatch-danger); }
1715
+ .spotpatch-agent-workspace::before {
1716
+ width: 6px;
1717
+ height: 6px;
1718
+ flex: none;
1719
+ border-radius: 999px;
1720
+ background: #64748b;
1721
+ content: "";
1722
+ }
1723
+ .spotpatch-agent-workspace[data-state="checking"]::before { background: var(--spotpatch-warning); }
1724
+ .spotpatch-agent-workspace[data-state="ready"] { color: #a7f3d0; }
1725
+ .spotpatch-agent-workspace[data-state="ready"]::before { background: var(--spotpatch-success); }
1726
+ .spotpatch-agent-workspace[data-state="consent-required"] { color: #fde68a; }
1727
+ .spotpatch-agent-workspace[data-state="consent-required"]::before { background: #fbbf24; }
1728
+ .spotpatch-agent-workspace[data-state="blocked"] { color: #fecaca; }
1729
+ .spotpatch-agent-workspace[data-state="blocked"]::before { background: var(--spotpatch-danger); }
1612
1730
  .spotpatch-agent-job { padding: 16px; }
1613
1731
  .spotpatch-agent-job-meta { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
1614
1732
  .spotpatch-agent-model { min-width: 0; overflow: hidden; color: #d6dafe; font-size: 13px; text-overflow: ellipsis; white-space: nowrap; }
@@ -1697,10 +1815,26 @@ function createAgentPanel(document, ai, localizer) {
1697
1815
  consentCheckbox.type = "checkbox";
1698
1816
  const consentText = createMarkedElement(document, "span");
1699
1817
  consent.append(consentCheckbox, consentText);
1818
+ const workspaceConsent = createMarkedElement(document, "label");
1819
+ workspaceConsent.className = "spotpatch-consent spotpatch-workspace-consent";
1820
+ workspaceConsent.hidden = true;
1821
+ const workspaceConsentCheckbox = createMarkedElement(document, "input");
1822
+ workspaceConsentCheckbox.type = "checkbox";
1823
+ const workspaceConsentContent = createMarkedElement(document, "span");
1824
+ const workspaceConsentTitle = createMarkedElement(document, "strong");
1825
+ const workspaceConsentHelp = createMarkedElement(document, "small");
1826
+ workspaceConsentContent.append(workspaceConsentTitle, workspaceConsentHelp);
1827
+ workspaceConsent.append(workspaceConsentCheckbox, workspaceConsentContent);
1828
+ const healthList = createMarkedElement(document, "div");
1829
+ healthList.className = "spotpatch-agent-health-list";
1830
+ const workspace = createMarkedElement(document, "p");
1831
+ workspace.className = "spotpatch-agent-workspace";
1832
+ workspace.dataset.state = "idle";
1700
1833
  const capability = createMarkedElement(document, "p");
1701
1834
  capability.className = "spotpatch-agent-capability";
1702
1835
  capability.dataset.state = "idle";
1703
- setup.append(selectors, consent, capability);
1836
+ healthList.append(workspace, capability);
1837
+ setup.append(selectors, consent, workspaceConsent, healthList);
1704
1838
  const job = createMarkedElement(document, "div");
1705
1839
  job.className = "spotpatch-agent-job";
1706
1840
  job.hidden = true;
@@ -1759,6 +1893,9 @@ function createAgentPanel(document, ai, localizer) {
1759
1893
  let latestCapabilityState = "idle";
1760
1894
  let latestCapabilityMessage = messages.agent.connectionNotTested;
1761
1895
  let latestCapabilityErrorCode;
1896
+ let latestWorkspaceState = "idle";
1897
+ let latestWorkspaceSnapshot;
1898
+ let latestWorkspaceErrorCode;
1762
1899
  for (const provider of providers) {
1763
1900
  addOption(document, providerSelect, provider.id, provider.label);
1764
1901
  }
@@ -1786,7 +1923,7 @@ function createAgentPanel(document, ai, localizer) {
1786
1923
  testButton.hidden = !setupVisible;
1787
1924
  runButton.hidden = !setupVisible;
1788
1925
  testButton.disabled = !editingEnabled || capabilityProbing || selectedProvider() === void 0;
1789
- runButton.disabled = !editingEnabled || capabilityProbing || !contextReady || !consentCheckbox.checked || selectedProvider() === void 0 || modelSelect.value.length === 0;
1926
+ 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
1927
  if (!jobPresented || !selectionVisible) {
1791
1928
  cancelButton.hidden = true;
1792
1929
  applyButton.hidden = true;
@@ -1794,6 +1931,24 @@ function createAgentPanel(document, ai, localizer) {
1794
1931
  resetButton.hidden = true;
1795
1932
  }
1796
1933
  };
1934
+ const renderWorkspaceMessage = () => {
1935
+ if (latestWorkspaceState === "idle") {
1936
+ workspace.textContent = messages.agent.workspaceNotChecked;
1937
+ } else if (latestWorkspaceState === "checking") {
1938
+ workspace.textContent = messages.agent.checkingWorkspace;
1939
+ } else if (latestWorkspaceState === "ready") {
1940
+ workspace.textContent = messages.agent.workspaceReady;
1941
+ } else if (latestWorkspaceState === "consent-required") {
1942
+ const changes = latestWorkspaceSnapshot?.changes;
1943
+ workspace.textContent = messages.agent.workspaceDirty(
1944
+ changes?.staged ?? 0,
1945
+ changes?.unstaged ?? 0,
1946
+ changes?.untracked ?? 0
1947
+ );
1948
+ } else {
1949
+ workspace.textContent = latestWorkspaceErrorCode === void 0 ? messages.errors.INTERNAL_ERROR : messages.errors[latestWorkspaceErrorCode];
1950
+ }
1951
+ };
1797
1952
  function handleProviderChange() {
1798
1953
  populateModels();
1799
1954
  consentCheckbox.checked = false;
@@ -1818,6 +1973,7 @@ function createAgentPanel(document, ai, localizer) {
1818
1973
  providerSelect.addEventListener("change", handleProviderChange);
1819
1974
  modelSelect.addEventListener("change", handleModelChange);
1820
1975
  consentCheckbox.addEventListener("change", refreshActions);
1976
+ workspaceConsentCheckbox.addEventListener("change", refreshActions);
1821
1977
  function renderCurrentJob() {
1822
1978
  if (latestSnapshot === void 0) {
1823
1979
  return;
@@ -1872,6 +2028,8 @@ function createAgentPanel(document, ai, localizer) {
1872
2028
  modelText.textContent = messages.agent.model;
1873
2029
  providerSelect.setAttribute("aria-label", messages.agent.providerAriaLabel);
1874
2030
  modelSelect.setAttribute("aria-label", messages.agent.modelAriaLabel);
2031
+ workspaceConsentTitle.textContent = messages.agent.includeLocalChanges;
2032
+ workspaceConsentHelp.textContent = messages.agent.includeLocalChangesHelp;
1875
2033
  diff.setAttribute("aria-label", messages.agent.diffAriaLabel);
1876
2034
  testButton.textContent = messages.agent.testConnection;
1877
2035
  applyButton.textContent = messages.agent.apply;
@@ -1892,6 +2050,7 @@ function createAgentPanel(document, ai, localizer) {
1892
2050
  } else {
1893
2051
  capability.textContent = latestCapabilityMessage;
1894
2052
  }
2053
+ renderWorkspaceMessage();
1895
2054
  refreshActions();
1896
2055
  renderCurrentJob();
1897
2056
  }
@@ -1902,6 +2061,7 @@ function createAgentPanel(document, ai, localizer) {
1902
2061
  providerSelect,
1903
2062
  modelSelect,
1904
2063
  consentCheckbox,
2064
+ workspaceConsentCheckbox,
1905
2065
  testButton,
1906
2066
  runButton,
1907
2067
  cancelButton,
@@ -1911,6 +2071,9 @@ function createAgentPanel(document, ai, localizer) {
1911
2071
  consentGranted() {
1912
2072
  return consentCheckbox.checked;
1913
2073
  },
2074
+ workspaceConsentGranted() {
2075
+ return workspaceConsentCheckbox.checked;
2076
+ },
1914
2077
  readSelection() {
1915
2078
  const provider = selectedProvider();
1916
2079
  const model = provider?.models.find(
@@ -1931,6 +2094,20 @@ function createAgentPanel(document, ai, localizer) {
1931
2094
  capability.textContent = capabilitySnapshot?.state === "agent-ready" ? `${message} \xB7 ${messages.agent.toolsReady}` : message;
1932
2095
  refreshActions();
1933
2096
  },
2097
+ renderWorkspaceHealth(state, snapshot, errorCode) {
2098
+ latestWorkspaceState = state;
2099
+ latestWorkspaceSnapshot = snapshot;
2100
+ latestWorkspaceErrorCode = errorCode ?? snapshot?.errorCode;
2101
+ workspace.dataset.state = state;
2102
+ if (state !== "checking") {
2103
+ workspaceConsent.hidden = state !== "consent-required";
2104
+ }
2105
+ if (state === "idle" || state === "ready" || state === "blocked") {
2106
+ workspaceConsentCheckbox.checked = false;
2107
+ }
2108
+ renderWorkspaceMessage();
2109
+ refreshActions();
2110
+ },
1934
2111
  renderJob(snapshot, result, activities, errorCode) {
1935
2112
  jobPresented = true;
1936
2113
  setup.hidden = true;
@@ -1938,6 +2115,7 @@ function createAgentPanel(document, ai, localizer) {
1938
2115
  providerSelect.disabled = true;
1939
2116
  modelSelect.disabled = true;
1940
2117
  consentCheckbox.disabled = true;
2118
+ workspaceConsentCheckbox.disabled = true;
1941
2119
  latestSnapshot = snapshot;
1942
2120
  latestResult = result;
1943
2121
  latestActivities = activities;
@@ -1955,6 +2133,7 @@ function createAgentPanel(document, ai, localizer) {
1955
2133
  providerSelect.disabled = false;
1956
2134
  modelSelect.disabled = false;
1957
2135
  consentCheckbox.disabled = false;
2136
+ workspaceConsentCheckbox.disabled = false;
1958
2137
  activity.replaceChildren();
1959
2138
  files.replaceChildren();
1960
2139
  checks.replaceChildren();
@@ -1972,6 +2151,7 @@ function createAgentPanel(document, ai, localizer) {
1972
2151
  providerSelect.disabled = !enabled || jobPresented;
1973
2152
  modelSelect.disabled = !enabled || jobPresented;
1974
2153
  consentCheckbox.disabled = !enabled || jobPresented;
2154
+ workspaceConsentCheckbox.disabled = !enabled || jobPresented;
1975
2155
  refreshActions();
1976
2156
  },
1977
2157
  setProviderConsent(granted) {
@@ -1993,6 +2173,7 @@ function createAgentPanel(document, ai, localizer) {
1993
2173
  providerSelect.removeEventListener("change", handleProviderChange);
1994
2174
  modelSelect.removeEventListener("change", handleModelChange);
1995
2175
  consentCheckbox.removeEventListener("change", refreshActions);
2176
+ workspaceConsentCheckbox.removeEventListener("change", refreshActions);
1996
2177
  }
1997
2178
  });
1998
2179
  }
@@ -2162,8 +2343,16 @@ var ERROR_MESSAGES_EN = Object.freeze({
2162
2343
  [ERROR_CODES2.AGENT_BUSY]: "Another write Agent job is still active.",
2163
2344
  [ERROR_CODES2.AGENT_LIMIT_EXCEEDED]: "The Agent stopped at a configured time, turn, output, or size limit.",
2164
2345
  [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.",
2346
+ [ERROR_CODES2.WORKTREE_DIRTY]: "Local changes require explicit inclusion consent before running AI.",
2347
+ [ERROR_CODES2.WORKTREE_NOT_REPOSITORY]: "The Vite root is not the top level of an initialized Git repository.",
2348
+ [ERROR_CODES2.WORKTREE_OPERATION_IN_PROGRESS]: "Finish the active merge, rebase, cherry-pick, or revert before running AI.",
2349
+ [ERROR_CODES2.WORKTREE_CONFLICTED]: "Resolve all merge conflicts before running AI; SpotPatch will not guess a resolution.",
2350
+ [ERROR_CODES2.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: "Local untracked files exceed the safe isolation limit. Reduce their count or total size.",
2351
+ [ERROR_CODES2.WORKTREE_UNTRACKED_UNSUPPORTED]: "An untracked item is missing, linked, or not a regular file and cannot be isolated safely.",
2352
+ [ERROR_CODES2.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: "Local changes include conflicts, unsupported files, or exceed the safe baseline limit.",
2166
2353
  [ERROR_CODES2.TOOL_DENIED]: "A model tool request violated the local safety policy.",
2354
+ [ERROR_CODES2.TOOL_INPUT_INVALID]: "The model sent invalid tool arguments or reused a tool call ID inconsistently.",
2355
+ [ERROR_CODES2.TOOL_PATH_DENIED]: "The model tried to access a protected, non-text, linked, or out-of-project path.",
2167
2356
  [ERROR_CODES2.PATCH_REJECTED]: "The proposed patch did not pass local policy.",
2168
2357
  [ERROR_CODES2.VALIDATION_FAILED]: "Required project checks failed. The change cannot be applied.",
2169
2358
  [ERROR_CODES2.APPLY_CONFLICT]: "Project files changed after the Agent baseline; no overwrite was performed.",
@@ -2187,8 +2376,16 @@ var ERROR_MESSAGES_ZH = Object.freeze({
2187
2376
  [ERROR_CODES2.AGENT_BUSY]: "\u5F53\u524D\u9879\u76EE\u5DF2\u6709\u4E00\u4E2A\u5199\u5165\u4EFB\u52A1\u6B63\u5728\u8FD0\u884C\u3002",
2188
2377
  [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
2378
  [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",
2379
+ [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",
2380
+ [ERROR_CODES2.WORKTREE_NOT_REPOSITORY]: "Vite \u6839\u76EE\u5F55\u4E0D\u662F\u5DF2\u521D\u59CB\u5316 Git \u4ED3\u5E93\u7684\u9876\u5C42\u76EE\u5F55\u3002",
2381
+ [ERROR_CODES2.WORKTREE_OPERATION_IN_PROGRESS]: "\u8BF7\u5148\u5B8C\u6210\u5F53\u524D merge\u3001rebase\u3001cherry-pick \u6216 revert\uFF0C\u518D\u8FD0\u884C AI\u3002",
2382
+ [ERROR_CODES2.WORKTREE_CONFLICTED]: "\u8BF7\u5148\u89E3\u51B3\u5168\u90E8\u5408\u5E76\u51B2\u7A81\uFF1BSpotPatch \u4E0D\u4F1A\u731C\u6D4B\u51B2\u7A81\u89E3\u51B3\u65B9\u5F0F\u3002",
2383
+ [ERROR_CODES2.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",
2384
+ [ERROR_CODES2.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",
2385
+ [ERROR_CODES2.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",
2191
2386
  [ERROR_CODES2.TOOL_DENIED]: "\u6A21\u578B\u5DE5\u5177\u8BF7\u6C42\u8FDD\u53CD\u672C\u5730\u5B89\u5168\u7B56\u7565\u3002",
2387
+ [ERROR_CODES2.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",
2388
+ [ERROR_CODES2.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",
2192
2389
  [ERROR_CODES2.PATCH_REJECTED]: "\u5EFA\u8BAE\u8865\u4E01\u672A\u901A\u8FC7\u672C\u5730\u7B56\u7565\u68C0\u67E5\u3002",
2193
2390
  [ERROR_CODES2.VALIDATION_FAILED]: "\u9879\u76EE\u5FC5\u9700\u68C0\u67E5\u5931\u8D25\uFF0C\u4E0D\u80FD\u5E94\u7528\u672C\u6B21\u53D8\u66F4\u3002",
2194
2391
  [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 +2550,13 @@ var UI_MESSAGES = Object.freeze({
2353
2550
  providerUnavailable: "Provider configuration is unavailable.",
2354
2551
  consent: (provider) => `I understand selected context and allowed source may be sent to ${provider}; its data policy is my responsibility.`,
2355
2552
  connectionNotTested: "Connection not tested",
2553
+ workspaceNotChecked: "Local workspace not checked",
2554
+ checkingWorkspace: "Checking Git workspace and isolated execution\u2026",
2555
+ workspaceReady: "Local workspace is ready for isolated execution",
2556
+ workspaceDirty: (staged, unstaged, untracked) => `Local changes found \xB7 ${String(staged)} staged \xB7 ${String(unstaged)} unstaged \xB7 ${String(untracked)} untracked`,
2557
+ includeLocalChanges: "Allow the Agent to continue from my current local changes",
2558
+ includeLocalChangesHelp: "The Agent may edit these files. SpotPatch preserves the baseline and applies or reverts only the Agent delta.",
2559
+ localChangesConsentRequired: "Confirm inclusion of current local changes before running AI.",
2356
2560
  capabilityVerified: "Agent capability verified",
2357
2561
  capabilityVerifiedAnnouncement: "AI provider capability verified.",
2358
2562
  testingCapability: "Testing authentication, tools, continuation, and streaming\u2026",
@@ -2361,7 +2565,7 @@ var UI_MESSAGES = Object.freeze({
2361
2565
  reverting: "Reverting the applied Agent change.",
2362
2566
  consentRequired: "Confirm remote provider data transmission before running AI.",
2363
2567
  toolsReady: "tools and streaming ready",
2364
- testConnection: "Test connection",
2568
+ testConnection: "Check environment",
2365
2569
  verifyAndRun: "Verify & run",
2366
2570
  verifying: "Verifying\u2026",
2367
2571
  run: "Run AI",
@@ -2482,6 +2686,13 @@ var UI_MESSAGES = Object.freeze({
2482
2686
  providerUnavailable: "\u6A21\u578B\u670D\u52A1\u914D\u7F6E\u4E0D\u53EF\u7528\u3002",
2483
2687
  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
2688
  connectionNotTested: "\u5C1A\u672A\u6D4B\u8BD5\u8FDE\u63A5",
2689
+ workspaceNotChecked: "\u5C1A\u672A\u68C0\u67E5\u672C\u5730\u5DE5\u4F5C\u533A",
2690
+ checkingWorkspace: "\u6B63\u5728\u68C0\u67E5 Git \u5DE5\u4F5C\u533A\u4E0E\u9694\u79BB\u6267\u884C\u73AF\u5883\u2026\u2026",
2691
+ workspaceReady: "\u672C\u5730\u5DE5\u4F5C\u533A\u5DF2\u6EE1\u8DB3\u9694\u79BB\u6267\u884C\u6761\u4EF6",
2692
+ 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)}`,
2693
+ includeLocalChanges: "\u5141\u8BB8 Agent \u57FA\u4E8E\u6211\u5F53\u524D\u7684\u672C\u5730\u4FEE\u6539\u7EE7\u7EED",
2694
+ 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",
2695
+ localChangesConsentRequired: "\u8FD0\u884C AI \u524D\uFF0C\u8BF7\u786E\u8BA4\u5141\u8BB8\u7EB3\u5165\u5F53\u524D\u672C\u5730\u4FEE\u6539\u3002",
2485
2696
  capabilityVerified: "Agent \u80FD\u529B\u9A8C\u8BC1\u901A\u8FC7",
2486
2697
  capabilityVerifiedAnnouncement: "AI \u6A21\u578B\u670D\u52A1\u80FD\u529B\u9A8C\u8BC1\u901A\u8FC7\u3002",
2487
2698
  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 +2701,7 @@ var UI_MESSAGES = Object.freeze({
2490
2701
  reverting: "\u6B63\u5728\u64A4\u9500\u5DF2\u5E94\u7528\u7684 Agent \u53D8\u66F4\u3002",
2491
2702
  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
2703
  toolsReady: "\u5DE5\u5177\u8C03\u7528\u4E0E\u6D41\u5F0F\u54CD\u5E94\u5DF2\u5C31\u7EEA",
2493
- testConnection: "\u6D4B\u8BD5\u8FDE\u63A5",
2704
+ testConnection: "\u68C0\u67E5\u8FD0\u884C\u73AF\u5883",
2494
2705
  verifyAndRun: "\u9A8C\u8BC1\u5E76\u8FD0\u884C",
2495
2706
  verifying: "\u9A8C\u8BC1\u4E2D\u2026\u2026",
2496
2707
  run: "\u8FD0\u884C AI",
@@ -3740,6 +3951,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3740
3951
  agentProviderSelect: agentPanel.providerSelect,
3741
3952
  agentModelSelect: agentPanel.modelSelect,
3742
3953
  agentConsentCheckbox: agentPanel.consentCheckbox,
3954
+ agentWorkspaceConsentCheckbox: agentPanel.workspaceConsentCheckbox,
3743
3955
  agentTestButton: agentPanel.testButton,
3744
3956
  agentRunButton: agentPanel.runButton,
3745
3957
  agentCancelButton: agentPanel.cancelButton,
@@ -3851,6 +4063,10 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3851
4063
  previewButton.classList.toggle("spotpatch-primary", !agentReady);
3852
4064
  placeDialog();
3853
4065
  },
4066
+ renderAgentWorkspaceHealth(state, snapshot, errorCode) {
4067
+ agentPanel.renderWorkspaceHealth(state, snapshot, errorCode);
4068
+ placeDialog();
4069
+ },
3854
4070
  renderAgentJob(snapshot, result, activities, errorCode) {
3855
4071
  agentPanel.renderJob(snapshot, result, activities, errorCode);
3856
4072
  placeDialog();
@@ -3871,6 +4087,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3871
4087
  },
3872
4088
  locale: localizer.locale,
3873
4089
  messages: localizer.messages,
4090
+ agentWorkspaceConsentGranted: agentPanel.workspaceConsentGranted,
3874
4091
  subscribeLocale: localizer.subscribe,
3875
4092
  dispose() {
3876
4093
  diagnostics.removeEventListener("toggle", placeDialog);
@@ -3974,6 +4191,22 @@ function createAgentWorkflow(options) {
3974
4191
  let appliedDuringCurrentJob = false;
3975
4192
  let actionPending = false;
3976
4193
  const selectedProfiles = () => options.view.readAgentSelection();
4194
+ const refreshWorkspaceHealth = async (workflowRevision) => {
4195
+ options.view.renderAgentWorkspaceHealth("checking");
4196
+ try {
4197
+ const health = await options.api.agentWorkspaceHealth();
4198
+ if (workflowRevision !== revision) {
4199
+ return health;
4200
+ }
4201
+ options.view.renderAgentWorkspaceHealth(health.state, health, health.errorCode);
4202
+ return health;
4203
+ } catch (error) {
4204
+ if (workflowRevision === revision) {
4205
+ options.view.renderAgentWorkspaceHealth("blocked", void 0, errorCode(error));
4206
+ }
4207
+ throw error;
4208
+ }
4209
+ };
3977
4210
  const renderJob = (explicitErrorCode) => {
3978
4211
  if (snapshot === void 0) {
3979
4212
  return;
@@ -4149,6 +4382,10 @@ function createAgentWorkflow(options) {
4149
4382
  },
4150
4383
  beginSelection() {
4151
4384
  resetState();
4385
+ const workflowRevision = revision;
4386
+ if (options.ai.enabled) {
4387
+ void refreshWorkspaceHealth(workflowRevision).catch(() => void 0);
4388
+ }
4152
4389
  },
4153
4390
  cancel() {
4154
4391
  if (snapshot?.canCancel === true) {
@@ -4181,12 +4418,17 @@ function createAgentWorkflow(options) {
4181
4418
  providerOrModelChanged() {
4182
4419
  revision += 1;
4183
4420
  restoreProviderState();
4421
+ const workflowRevision = revision;
4422
+ void refreshWorkspaceHealth(workflowRevision).catch(() => void 0);
4184
4423
  },
4185
4424
  reset() {
4186
4425
  const mustReselect = appliedDuringCurrentJob;
4187
4426
  resetState();
4188
4427
  if (mustReselect) {
4189
4428
  options.onReselectRequired();
4429
+ } else if (options.ai.enabled) {
4430
+ const workflowRevision = revision;
4431
+ void refreshWorkspaceHealth(workflowRevision).catch(() => void 0);
4190
4432
  }
4191
4433
  },
4192
4434
  revert() {
@@ -4213,15 +4455,27 @@ function createAgentWorkflow(options) {
4213
4455
  providerConsents.add(selection.providerProfileId);
4214
4456
  const workflowRevision = ++revision;
4215
4457
  options.view.setAgentEditingEnabled(false);
4216
- void probe(workflowRevision).then(async () => {
4458
+ void Promise.all([
4459
+ probe(workflowRevision),
4460
+ refreshWorkspaceHealth(workflowRevision)
4461
+ ]).then(async ([, health]) => {
4217
4462
  if (workflowRevision !== revision) {
4218
4463
  return;
4219
4464
  }
4465
+ if (health.state === "blocked") {
4466
+ throw new RuntimeApiError(
4467
+ health.errorCode ?? ERROR_CODES3.WORKTREE_LOCAL_CHANGES_UNSUPPORTED
4468
+ );
4469
+ }
4470
+ if (health.state === "consent-required" && !options.view.agentWorkspaceConsentGranted()) {
4471
+ throw new RuntimeApiError(ERROR_CODES3.WORKTREE_DIRTY);
4472
+ }
4220
4473
  const created = await options.api.createAgentJob({
4221
4474
  annotation,
4222
4475
  providerProfileId: selection.providerProfileId,
4223
4476
  modelProfileId: selection.modelProfileId,
4224
- providerDataConsent: true
4477
+ providerDataConsent: true,
4478
+ workingTreeMode: health.state === "consent-required" ? "include-local-changes" : "require-clean"
4225
4479
  });
4226
4480
  if (workflowRevision !== revision) {
4227
4481
  return;
@@ -4250,7 +4504,10 @@ function createAgentWorkflow(options) {
4250
4504
  return;
4251
4505
  }
4252
4506
  const workflowRevision = ++revision;
4253
- void probe(workflowRevision).catch((error) => {
4507
+ void Promise.all([
4508
+ probe(workflowRevision),
4509
+ refreshWorkspaceHealth(workflowRevision)
4510
+ ]).catch((error) => {
4254
4511
  if (workflowRevision === revision) {
4255
4512
  options.view.renderAgentCapability(
4256
4513
  "error",