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