diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 53d064c..0822e5d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,12 +4,17 @@ on: push: tags: ['v*'] workflow_dispatch: + inputs: + replace_existing: + description: Replace the existing release for the current package version from main + type: boolean + default: false permissions: contents: read concurrency: - group: release-${{ github.ref }} + group: release cancel-in-progress: false env: @@ -19,9 +24,11 @@ env: jobs: verify: - if: github.event.repository.private == false && startsWith(github.ref, 'refs/tags/v') + if: github.event.repository.private == false && (startsWith(github.ref, 'refs/tags/v') || (github.ref == 'refs/heads/main' && inputs.replace_existing)) runs-on: ubuntu-22.04 timeout-minutes: 5 + outputs: + tag: ${{ steps.version.outputs.tag }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -31,8 +38,10 @@ jobs: with: python-version: '3.13' - name: Require a release version from main + id: version env: TAG: ${{ github.ref_name }} + REPLACE_RELEASE: ${{ github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && inputs.replace_existing }} GH_TOKEN: ${{ github.token }} run: | python - <<'PY' @@ -40,10 +49,17 @@ jobs: from pathlib import Path version = tomllib.loads(Path('Cargo.toml').read_text())['package']['version'] assert re.fullmatch(r'[0-9]+\.[0-9]+\.[0-9]+', version), 'Use an approved release version' - assert os.environ['TAG'] == f'v{version}', 'Tag must match Cargo.toml' + tag = f'v{version}' + if os.environ['REPLACE_RELEASE'] != 'true': + assert os.environ['TAG'] == tag, 'Tag must match Cargo.toml' + with open(os.environ['GITHUB_OUTPUT'], 'a') as output: + output.write(f'tag={tag}\n') PY + TAG=$(sed -n 's/^tag=//p' "$GITHUB_OUTPUT") git merge-base --is-ancestor "$GITHUB_SHA" origin/main - if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + if [ "$REPLACE_RELEASE" = true ]; then + test "$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json isImmutable --jq .isImmutable)" = false + elif gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then echo 'This release already exists; do not replace its artifacts.' >&2 exit 1 fi @@ -53,7 +69,7 @@ jobs: uses: ./.github/workflows/ci.yml build: - needs: test + needs: verify name: Build (${{ matrix.target }}) runs-on: ${{ matrix.os }} timeout-minutes: 25 @@ -94,25 +110,52 @@ jobs: retention-days: 7 publish: - needs: build + needs: [verify, test, build] runs-on: ubuntu-22.04 - timeout-minutes: 5 + timeout-minutes: 10 permissions: contents: write + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + TAG: ${{ needs.verify.outputs.tag }} + REPLACE_RELEASE: ${{ github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && inputs.replace_existing }} steps: - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: pattern: binary-* merge-multiple: true path: dist + - name: Preserve the previous release before replacement + if: env.REPLACE_RELEASE == 'true' + run: | + mkdir previous + gh release view "$TAG" --json name,body,tagName,targetCommitish,isDraft,isPrerelease,isImmutable > previous/release.json + gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$TAG" > previous/tag.json + gh release download "$TAG" --dir previous + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + if: env.REPLACE_RELEASE == 'true' + with: + name: previous-release + path: previous/* + if-no-files-found: error + retention-days: 7 - name: Publish the verified binaries and checksums env: - GH_TOKEN: ${{ github.token }} - GH_REPO: ${{ github.repository }} - TAG: ${{ github.ref_name }} + RELEASE_NOTES: 'Native MCP server for Windows, macOS, and Linux. Give your agent https://github.com/mcpads/dotmend and ask it to follow INSTALL.md. Agents briefly explain the available human controls and the one-stroke Undo limit when sharing the screen. Requires stateless MCP 2026-07-28. The browser interface and editing guide are embedded in each executable. SHA256SUMS covers all binary archives.' run: | cd dist sha256sum -- *.tar.gz *.zip > SHA256SUMS - gh release create "$TAG" --verify-tag --title "Dotmend ${TAG#v}" \ - --notes 'Native MCP server for Windows, macOS, and Linux. Give your agent https://github.com/mcpads/dotmend and ask it to follow INSTALL.md. Requires stateless MCP 2026-07-28. The browser interface and editing guide are embedded in each executable. SHA256SUMS covers all binary archives.' \ - --latest -- ./*.tar.gz ./*.zip SHA256SUMS + if [ "$REPLACE_RELEASE" = true ]; then + gh release edit "$TAG" --draft=true + tag_object=$(gh api --method POST "repos/$GITHUB_REPOSITORY/git/tags" \ + -f tag="$TAG" -f message="Dotmend ${TAG#v}" -f object="$GITHUB_SHA" -f type=commit --jq .sha) + gh api --method PATCH "repos/$GITHUB_REPOSITORY/git/refs/tags/$TAG" \ + -f sha="$tag_object" -F force=true >/dev/null + gh release upload "$TAG" --clobber -- ./*.tar.gz ./*.zip SHA256SUMS + gh release edit "$TAG" --draft=false --latest --verify-tag \ + --target "$GITHUB_SHA" --title "Dotmend ${TAG#v}" --notes "$RELEASE_NOTES" + else + gh release create "$TAG" --verify-tag --title "Dotmend ${TAG#v}" \ + --notes "$RELEASE_NOTES" --latest -- ./*.tar.gz ./*.zip SHA256SUMS + fi diff --git a/resources/agent-usage.md b/resources/agent-usage.md index 3d855b8..0ed6b89 100644 --- a/resources/agent-usage.md +++ b/resources/agent-usage.md @@ -8,6 +8,8 @@ Art coordinates start at the top-left `(0,0)`, with `+x` right and `+y` down. Th Humans paint with palette colors, mark suspected issues, undo the last stroke once and save. Handle collections, filters, past candidates, regions and reviews through conversation and tools. +When first sharing the screen, briefly explain its available controls in the user's language: choose a palette color, then click or drag to paint; **Mark issues** uses left clicks/drags to add marks and right clicks/drags to remove them; **Undo** (Ctrl/Cmd+Z) reverses the last stroke once; **Save** (Ctrl/Cmd+S) records the current result. A click or drag is one stroke, and painting and marking share the same undo. Tell the user to ask you for older results or other operations. Adapt the explanation to read-only and playback views; repeat it only if asked or needed. Do not send the user to this guide or require IDs and technical settings. + 1. Choose a fresh random `control_id` per independent task (16..128 ASCII letters, digits, underscores or hyphens); retain it across calls and connections. Call `open_workbench({control_id})` for the instance ID and URL. Do not bypass another owner or the shared limit. Read `inspect_presentation({})`; no existing view returns `presentation:null`. 2. Call `present_art({control_id,workbench_id,view})`. Set `view.title`, `note` and `items` to describe the user's task. An art item is `{kind:"art",art_id,label,region,scale,editable,request_id?}`. Attach `request_id` to an editable item carrying out a protected request. 3. Copy the freshly read IDs into `view.expected_presentation_id` and `view.expected_state_id`. Both keys must be explicit `null` for the first view. On conflict, read the human's latest edits first. @@ -15,7 +17,7 @@ Humans paint with palette colors, mark suspected issues, undo the last stroke on Do not close the screen while the human is working. Finish with `close_workbench({control_id,workbench_id})` and check its result. `inspect_workbench({control_id})` distinguishes `owned`, `closing`, `busy` and `closed` without renewing activity. Reopening with the same control_id reuses its ID and URL and renews activity; it does not change its timeout. Successful presentation and human actions renew activity; polling does not. The default idle timeout is 1800 seconds, configurable from 1 to 1800. HTTP ends when the MCP process hosting it exits or the idle deadline expires; art and drafts remain. -One explicit controller owns each workspace's screen, with a shared maximum of four. The same control_id works across MCP connections; different tasks remain separate even on one connection. Share only the workbench URL with the human. Independent candidate creation does not require screen control. Never bypass control using shell servers, alternate runtime directories or lock-file changes. The local HTTP interface is not an MCP transport or a public agent mutation API. Closing a forwarding MCP connection does not close the host's screen. If close returns closing, inspect until closed. +One explicit controller owns each workspace's screen, with a shared maximum of four. The same control_id works across MCP connections; different tasks remain separate even on one connection. Share the workbench URL with the human; keep control IDs private. Independent candidate creation does not require screen control. Never bypass control using shell servers, alternate runtime directories or lock-file changes. The local HTTP interface is not an MCP transport or a public agent mutation API. Closing a forwarding MCP connection does not close the host's screen. If close returns closing, inspect until closed. For collections and filters, use `list_art` and present the selected candidates in order. For history, inspect parent IDs or archived presentations and states. `inspect_presentation({presentation_id,state_id})` is read-only; use `present_art` to change the visible screen. A response with `is_current:false` is not the current screen. diff --git a/src/server_instructions.rs b/src/server_instructions.rs index 7548a8b..497dc65 100644 --- a/src/server_instructions.rs +++ b/src/server_instructions.rs @@ -1,11 +1,11 @@ pub const INSTRUCTIONS: &str = r#"Create palette-constrained retro game art; you are the user's interface. tools/list defines exact inputs/outputs; dotmend://guides/editing gives procedures. MCP uses request metadata, never connection history. WORKBENCH -Choose a fresh random control_id per independent task; retain it across calls and connections. Pass it to open_workbench, inspect_workbench, close_workbench and present_art. Reopening with the same control_id reuses the instance. Also pass its workbench_id to present_art {control_id,workbench_id,view} and close_workbench; share only the URL with the human. +Choose a fresh random control_id per independent task; retain it across calls and connections. Pass it to open_workbench, inspect_workbench, close_workbench and present_art. Reopening with the same control_id reuses the instance. Also pass its workbench_id to present_art {control_id,workbench_id,view} and close_workbench. Share the URL with the human; keep control IDs private. One controller per workspace; four workbenches per shared runtime directory. Never bypass control or limits with shell servers, PIDs or alternate paths. HTTP ends when its hosting MCP process exits or its returned idle timeout expires; polling does not renew activity. Close when finished, never while the human is working. Art and drafts persist. HUMAN SCREEN -Humans paint, mark issues, undo once and save. Prepare collections, filters, past art, reference crops and known-time playback yourself. +At first handoff, briefly explain available controls in the user's language: palette + click/drag paints; Mark issues: left adds, right removes; Undo once for the last stroke (Ctrl/Cmd+Z); Save (Ctrl/Cmd+S). Offer older results through conversation. Repeat only when needed. Prepare collections, filters, past art, reference crops and known-time playback yourself. Read inspect_presentation; copy current IDs into view.expected_presentation_id/expected_state_id (both explicit null only for the first view). saved.art_ids holds the explicit save; state.art_ids may contain a newer draft. state.concerns and saved.concerns identify marked candidates and pixels; marks request observation, not edit permission or rejection. EDIT AND RECOVER