foundry-design-web-adapter 0.2.0-beta.10 → 0.2.0-beta.12

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/README.md CHANGED
@@ -6,7 +6,7 @@ Foundry is a local-first precision design workbench for Codex, Cursor, and Claud
6
6
 
7
7
  Foundry is distributed through npm, so testers do not need GitHub access.
8
8
 
9
- > **Current public beta:** `0.2.0-beta.10`. Both npm `latest` and `beta` point to the same tested release. Use the unqualified command below for normal installation.
9
+ > **Current public beta:** `0.2.0-beta.12`. Both npm `latest` and `beta` point to the same tested release. Use the unqualified command below for normal installation.
10
10
 
11
11
  Full documentation is available at [withfoundry.ai](https://withfoundry.ai).
12
12
 
@@ -48,7 +48,15 @@ Then restart the coding agent, reopen the same project folder, and ask:
48
48
  Start Foundry for this project and keep listening for Apply with agent requests.
49
49
  ```
50
50
 
51
- You do not need to run a second terminal command when the agent starts Foundry for you. Apply requests remain queued safely when the agent is offline. Run `npx foundry-design doctor --repair` if the connection or generated integration needs repair.
51
+ You do not need to run a second terminal command when the agent starts Foundry for you. Apply requests remain queued safely when the agent is offline. After an active agent claims a batch, the MCP bridge keeps that handoff alive through source edits, rebuilding, and verification. A claim abandoned before source work begins returns to the queue. If the agent disappears during source work, Foundry preserves the same run in **Needs attention** and waits for you to choose **Resume with agent**. It never guesses whether a half-finished edit is safe to repeat.
52
+
53
+ Check the complete connection at any time with:
54
+
55
+ ```bash
56
+ npx foundry-design status
57
+ ```
58
+
59
+ Foundry reports project integration, the exact MCP package version, runtime health, and the live agent listener separately. Run `npx foundry-design doctor --repair` if configuration or generated integration needs repair.
52
60
 
53
61
  Update an existing installation with:
54
62
 
@@ -68,11 +76,15 @@ Supported automatic web integration currently includes Next.js App Router, Vite,
68
76
 
69
77
  The npm setup above is the public beta installation path and includes the same portable skill and MCP connection used by the agent plugin bundle.
70
78
 
79
+ ### Claude Desktop extension
80
+
81
+ The repository also produces a validated `.mcpb` extension for Claude Desktop. It bundles the local Foundry MCP bridge and can be installed through **Settings → Extensions → Advanced → Install Extension**. This provides a host-native alternative when a Claude Desktop surface does not merge the normal user-level Claude Code MCP configuration. Project instrumentation is still installed with `npx foundry-design`.
82
+
71
83
  If an npm mirror or existing `npx` cache reports an old tag, bypass it with a temporary cache and the exact current release:
72
84
 
73
85
  ```bash
74
86
  FOUNDRY_NPX_CACHE="$(mktemp -d)"
75
- npx --yes --prefer-online --registry=https://registry.npmjs.org --cache "$FOUNDRY_NPX_CACHE" --package=foundry-design@0.2.0-beta.10 foundry-design
87
+ npx --yes --prefer-online --registry=https://registry.npmjs.org --cache "$FOUNDRY_NPX_CACHE" --package=foundry-design@0.2.0-beta.12 foundry-design
76
88
  ```
77
89
 
78
90
  The beta supports Node.js 20 or newer. Read the [local-first safety model](https://withfoundry.ai/#safety) before using it with sensitive work.
@@ -104,7 +116,8 @@ Foundry remains local-first. Installing the plugin does not create an account, e
104
116
  - Persistent, coalescing change ledger with JSON and consolidated prompt export
105
117
  - Center-workspace review with editable approved batches, grouped targets, and unresolved-target blocking
106
118
  - Persistent Apply with agent runs across Codex, Cursor, and Claude Code through MCP
107
- - Leased agent handoffs that safely return abandoned or interrupted claims to the queue
119
+ - Automatically renewed agent handoffs that remain claimed during source inspection and safely return abandoned work to the queue
120
+ - Explicit same-run recovery when an agent disappears during source editing, rebuilding, or verification
108
121
  - Live source, rebuild, validation, retry, and rendered-verification progress
109
122
  - Rendered verification that resumes after refresh and does not depend on Review remaining open
110
123
  - Local MCP bridge for agent access
@@ -123,6 +136,8 @@ pnpm check
123
136
  pnpm release:check
124
137
  pnpm release:pack
125
138
  pnpm release:test-install
139
+ pnpm test:e2e
140
+ pnpm mcpb:build
126
141
  pnpm build
127
142
  pnpm foundry setup --project /path/to/project --agent codex --local-mcp
128
143
  pnpm foundry index --project /path/to/project --output /tmp/foundry-design-graph.json
package/dist/adapter.js CHANGED
@@ -948,6 +948,43 @@ function createSafeDiagnostics(input) {
948
948
  };
949
949
  }
950
950
 
951
+ // src/apply-run.ts
952
+ var ACTIVE_APPLY_STATES = [
953
+ "queued",
954
+ "claimed",
955
+ "applying",
956
+ "rebuilding",
957
+ "verifying"
958
+ ];
959
+ function isActiveApplyRun(run) {
960
+ return ACTIVE_APPLY_STATES.includes(run.state);
961
+ }
962
+ function applyRunAction(run, agentConnected, labels) {
963
+ if (run.state === "needs_attention" && run.interruptedState) {
964
+ return { action: "resume", label: "Resume with agent", disabled: false };
965
+ }
966
+ if (run.state === "needs_attention" || run.state === "failed") {
967
+ return { action: "retry", label: "Retry with agent", disabled: false };
968
+ }
969
+ if ((run.state === "queued" || run.state === "claimed") && !agentConnected) {
970
+ return { action: "reconnect", label: "Reconnect agent", disabled: false };
971
+ }
972
+ return {
973
+ action: "status",
974
+ label: run.state === "passed" ? "Verified" : isActiveApplyRun(run) ? labels[run.state] ?? run.state : "Run complete",
975
+ disabled: true
976
+ };
977
+ }
978
+ function applyRunMessage(run, agentConnected) {
979
+ if (run.state === "queued") {
980
+ return (run.requeueCount ?? 0) > 0 ? "The previous agent did not begin source work, so Foundry safely returned this batch to the queue." : "The reviewed changes are queued and ready for an active coding agent.";
981
+ }
982
+ if (run.state === "claimed") {
983
+ return agentConnected ? "The agent received this batch. Foundry is keeping the handoff active while source work begins." : "The agent disconnected before source work began. Foundry will return this batch to the queue shortly.";
984
+ }
985
+ return run.messages?.at(-1)?.message ?? run.error ?? "Apply run created.";
986
+ }
987
+
951
988
  // src/workspace.ts
952
989
  function resolveInterfaceTheme(preference, systemPrefersDark) {
953
990
  return preference === "system" ? systemPrefersDark ? "dark" : "light" : preference;
@@ -3327,6 +3364,8 @@ function installFoundryInspector(options = {}) {
3327
3364
  const reviewCount = shadow.querySelector(".review-count");
3328
3365
  const applyButton = shadow.querySelector(".review-actions .apply");
3329
3366
  const reviewCancel = shadow.querySelector(".review-cancel");
3367
+ let cancelConfirmationRunId;
3368
+ let cancelConfirmationUntil = 0;
3330
3369
  function applyInterfaceTheme(preference, persist = true) {
3331
3370
  interfaceThemePreference = preference;
3332
3371
  const resolved = resolvedInterfaceTheme();
@@ -5386,8 +5425,8 @@ function installFoundryInspector(options = {}) {
5386
5425
  function renderApplyRun(run) {
5387
5426
  const attention = ["needs_attention", "failed"].includes(run.state);
5388
5427
  const passed = run.state === "passed";
5389
- const active = ["queued", "claimed", "applying", "rebuilding", "verifying"].includes(run.state);
5390
- const latestMessage = run.state === "queued" ? run.requeueCount > 0 ? "The previous agent did not begin source work, so Foundry safely returned this batch to the queue." : "The reviewed changes are queued and ready for an active coding agent." : run.state === "claimed" ? activeAgentPresence.connected ? "The agent received this batch. Waiting for source work to begin." : "The agent disconnected before source work began. Foundry will return this batch to the queue shortly." : run.messages.at(-1)?.message ?? run.error ?? "Apply run created.";
5428
+ const active = isActiveApplyRun(run);
5429
+ const latestMessage = applyRunMessage(run, activeAgentPresence.connected);
5391
5430
  latestApplyState = run.state;
5392
5431
  if (run.state === "passed") completeOnboardingStep("apply");
5393
5432
  captureVerifiedRun(run);
@@ -5397,21 +5436,13 @@ function installFoundryInspector(options = {}) {
5397
5436
  ).join(
5398
5437
  ""
5399
5438
  )}</div>${run.changedFiles.length ? `<div class="run-files"><strong>Changed files</strong>${run.changedFiles.map((file) => `<code>${escapeHtml(file)}</code>`).join("")}</div>` : ""}${run.validationResults.length ? `<div class="result-list">${run.validationResults.map((result) => `<div class="result-row ${result.passed ? "pass" : "fail"}"><span>${result.passed ? "Passed" : "Failed"} \xB7 ${escapeHtml(result.name)}</span><span>${escapeHtml(result.summary ?? "")}</span></div>`).join("")}</div>` : ""}${run.verificationResults.length ? `<div class="result-list">${run.verificationResults.map((result) => `<div class="result-row ${result.passed ? "pass" : "fail"}"><span>${result.passed ? "Matched" : "Mismatch"} \xB7 ${escapeHtml(result.property)}</span><span>${escapeHtml(verificationResultValue(result))}${result.reason ? `<br/>${escapeHtml(result.reason)}` : ""}</span></div>`).join("")}</div>` : ""}`;
5400
- if (attention) {
5401
- applyButton.dataset.action = "retry";
5402
- applyButton.textContent = "Retry with agent";
5403
- applyButton.disabled = false;
5404
- } else if (["queued", "claimed"].includes(run.state) && !activeAgentPresence.connected) {
5405
- applyButton.dataset.action = "reconnect";
5406
- applyButton.textContent = "Reconnect agent";
5407
- applyButton.disabled = false;
5408
- } else {
5409
- applyButton.dataset.action = "status";
5410
- applyButton.textContent = passed ? "Verified" : active ? runStateLabels[run.state] ?? run.state : "Run complete";
5411
- applyButton.disabled = true;
5412
- }
5439
+ const primaryAction = applyRunAction(run, activeAgentPresence.connected, runStateLabels);
5440
+ applyButton.dataset.action = primaryAction.action;
5441
+ applyButton.textContent = primaryAction.label;
5442
+ applyButton.disabled = primaryAction.disabled;
5443
+ const confirmingCancel = active && cancelConfirmationRunId === run.id && Date.now() < cancelConfirmationUntil;
5413
5444
  reviewCancel.dataset.action = active ? "cancel" : "back";
5414
- reviewCancel.textContent = active ? "Cancel" : "Back";
5445
+ reviewCancel.textContent = active ? confirmingCancel ? "Confirm stop" : "Stop apply" : "Back";
5415
5446
  maybeVerifyRun(run);
5416
5447
  }
5417
5448
  function renderReviewPayload(payload) {
@@ -5567,9 +5598,40 @@ function installFoundryInspector(options = {}) {
5567
5598
  showToast(error instanceof Error ? error.message : "Could not retry apply run");
5568
5599
  }
5569
5600
  }
5601
+ async function resumeRun() {
5602
+ const run = activeReviewPayload?.applyRuns?.at(-1);
5603
+ if (!run) return;
5604
+ try {
5605
+ renderReviewPayload(
5606
+ await sessionRequest(`/apply-runs/${encodeURIComponent(run.id)}/resume`, {
5607
+ method: "POST",
5608
+ body: "{}"
5609
+ })
5610
+ );
5611
+ showToast("Resume authorized. Waiting for an agent to reinspect this run.");
5612
+ } catch (error) {
5613
+ showToast(error instanceof Error ? error.message : "Could not resume apply run");
5614
+ }
5615
+ }
5570
5616
  async function cancelRun() {
5571
5617
  const run = activeReviewPayload?.applyRuns?.at(-1);
5572
5618
  if (!run) return;
5619
+ const now = Date.now();
5620
+ if (cancelConfirmationRunId !== run.id || now >= cancelConfirmationUntil) {
5621
+ cancelConfirmationRunId = run.id;
5622
+ cancelConfirmationUntil = now + 5e3;
5623
+ reviewCancel.textContent = "Confirm stop";
5624
+ showToast("Press Confirm stop within 5 seconds to cancel this source run.");
5625
+ window.setTimeout(() => {
5626
+ if (cancelConfirmationRunId !== run.id || Date.now() < cancelConfirmationUntil) return;
5627
+ cancelConfirmationRunId = void 0;
5628
+ cancelConfirmationUntil = 0;
5629
+ if (activeReviewPayload) renderReviewPayload(activeReviewPayload);
5630
+ }, 5100);
5631
+ return;
5632
+ }
5633
+ cancelConfirmationRunId = void 0;
5634
+ cancelConfirmationUntil = 0;
5573
5635
  try {
5574
5636
  renderReviewPayload(
5575
5637
  await sessionRequest(`/apply-runs/${encodeURIComponent(run.id)}/cancel`, {
@@ -9187,6 +9249,7 @@ function installFoundryInspector(options = {}) {
9187
9249
  applyButton.addEventListener("click", () => {
9188
9250
  if (applyButton.dataset.action === "apply") void submitReviewedRun();
9189
9251
  if (applyButton.dataset.action === "retry") void retryRun();
9252
+ if (applyButton.dataset.action === "resume") void resumeRun();
9190
9253
  if (applyButton.dataset.action === "reconnect") copyAgentListenerInstruction();
9191
9254
  });
9192
9255
  function handleGlobalShortcuts(event) {
@@ -0,0 +1,19 @@
1
+ export interface ApplyRunView {
2
+ state: string;
3
+ interruptedState?: 'applying' | 'rebuilding' | 'verifying';
4
+ requeueCount?: number;
5
+ error?: string;
6
+ messages?: Array<{
7
+ message: string;
8
+ }>;
9
+ }
10
+ export interface ApplyRunAction {
11
+ action: 'resume' | 'retry' | 'reconnect' | 'status';
12
+ label: string;
13
+ disabled: boolean;
14
+ }
15
+ export declare const ACTIVE_APPLY_STATES: readonly ["queued", "claimed", "applying", "rebuilding", "verifying"];
16
+ export declare function isActiveApplyRun(run: ApplyRunView): boolean;
17
+ export declare function applyRunAction(run: ApplyRunView, agentConnected: boolean, labels: Record<string, string>): ApplyRunAction;
18
+ export declare function applyRunMessage(run: ApplyRunView, agentConnected: boolean): string;
19
+ //# sourceMappingURL=apply-run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply-run.d.ts","sourceRoot":"","sources":["../src/apply-run.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,UAAU,GAAG,YAAY,GAAG,WAAW,CAAC;IAC3D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,QAAQ,GAAG,OAAO,GAAG,WAAW,GAAG,QAAQ,CAAC;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,eAAO,MAAM,mBAAmB,uEAMtB,CAAC;AAEX,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAE3D;AAED,wBAAgB,cAAc,CAC5B,GAAG,EAAE,YAAY,EACjB,cAAc,EAAE,OAAO,EACvB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,cAAc,CAoBhB;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,YAAY,EAAE,cAAc,EAAE,OAAO,GAAG,MAAM,CAYlF"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAgHA,MAAM,WAAW,uBAAuB;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,IAAI,IAAI,CAAC;IAChB,cAAc,IAAI,IAAI,CAAC;IACvB,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IACnC,OAAO,IAAI,IAAI,CAAC;CACjB;AA4lCD,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,uBAA4B,GACpC,0BAA0B,CAuiP5B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAiHA,MAAM,WAAW,uBAAuB;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,IAAI,IAAI,CAAC;IAChB,cAAc,IAAI,IAAI,CAAC;IACvB,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IACnC,OAAO,IAAI,IAAI,CAAC;CACjB;AA4lCD,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,uBAA4B,GACpC,0BAA0B,CAsjP5B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foundry-design-web-adapter",
3
- "version": "0.2.0-beta.10",
3
+ "version": "0.2.0-beta.12",
4
4
  "description": "Development-only browser inspector for Foundry",
5
5
  "type": "module",
6
6
  "main": "dist/adapter.js",