diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c6cd85d..901e80f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: CI on: pull_request: - branches: [release] + branches: [devel, release] types: [opened, synchronize, reopened, ready_for_review, edited] permissions: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4132850..beeedd0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ concurrency: jobs: release: name: Publish from release and open promotion PR - if: github.event_name == 'push' || github.event.pull_request.merged == true + if: github.event_name == 'push' || (github.event.pull_request.merged == true && github.event.pull_request.head.ref == 'devel' && github.event.pull_request.head.repo.full_name == github.repository) runs-on: ubuntu-latest timeout-minutes: 25 permissions: @@ -37,7 +37,7 @@ jobs: cache: npm - name: Install pipeline dependencies run: npm ci --ignore-scripts - - name: Prepare version, publish package, and promote through a PR + - name: Publish, prepare main PR, and synchronize devel env: GH_TOKEN: ${{ github.token }} run: node scripts/release-pipeline.mjs diff --git a/AGENTS.md b/AGENTS.md index f6916d2..f81b32a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,10 @@ Use English for all user-facing prompts, UI labels, errors, generated bot messag Never push commits directly to `main` or modify its files through GitHub APIs. All changes reach `main` by merging a PR from `release`. Feature PRs target -`release`; version and post-publication README commits belong on `release`. +`devel`. Only a reviewed `devel` → `release` PR starts automatic publication. +Version and post-publication README commits belong on `release`; after a stable +publication, automation merges that published head back into `devel` without a +PR or force push. Never reset development work to match release. ## Project context @@ -24,11 +27,11 @@ steps, project setup, headless operation, and removal. | --- | --- | --- | | [docs/architecture.md](docs/architecture.md) | Component responsibilities, a short issue-to-PR overview, configuration ownership, scheduler ownership, and shared state. | Start here to understand how the system is divided before locating implementation code. | | [docs/bot-workflow.md](docs/bot-workflow.md) | Eight Mermaid diagrams and detailed implementation notes: startup and polling; discovery and routing; task phases; sessions and questions; media helpers; verification and publication; feedback, merging, and tab closure; status, retries, and recovery. Includes links to the source for each area. | Use for exact execution order, state transitions, checkpoint behavior, failure paths, and tracing a bot task from issue to merged PR. | -| [docs/configuration.md](docs/configuration.md) | The standard `.opencode/automation.json` format, defaults, setup flags, configuration tracking across Git branches, authors, triggers, checks, base branches, model capabilities, media helpers, custom prompts, signatures, and auto-merge settings. | Use when adding or changing user-facing configuration, defaults, or setup examples. | -| [docs/runtime.md](docs/runtime.md) | User-visible behavior while the bot runs: GitHub questions and permission replies, branch selection, media inputs, prompt loading, follow-up comments, session tabs, and routine management commands. | Use when changing issue conversations, session continuation, runtime tools, or TUI behavior. | +| [docs/configuration.md](docs/configuration.md) | The standard `.opencode/automation.json` format, defaults, setup flags, configuration tracking across Git branches, repository file approvals, authors, triggers, checks, base branches, model capabilities, media helpers, custom prompts, signatures, and auto-merge settings. | Use when adding or changing user-facing configuration, defaults, or setup examples. | +| [docs/runtime.md](docs/runtime.md) | User-visible behavior while the bot runs: GitHub questions and permission replies, branch selection, media inputs, prompt loading, follow-up comments, session tabs, runtime sidebar/status freshness, host repository inventory and discovery, local task closure, cancelling rounds while retaining tracking, and routine management commands. | Use when changing issue conversations, session continuation, runtime tools, or TUI behavior. | | [docs/advanced.md](docs/advanced.md) | Separate scheduler/dispatcher setup, multiple repositories, custom RPC jobs, full options, timeouts, management and retry commands, persistence, reconciliation, locks, and known limits. | Use for low-level configuration, operational troubleshooting, recovery, or ownership/concurrency changes. | | [docs/installation.md](docs/installation.md) | Loader registration, config-directory precedence, prerequisites, source installation, project-local installation, upgrade conflicts, testing on another machine, and migration limits. | Use when working on packaging, installers, registration, upgrades, or deployment troubleshooting. | -| [docs/releases.md](docs/releases.md) | Feature-to-release PR checks, automatic patch versions, manual npm version/tag releases, exact changelog notes, publication recovery, README commits on release, and promotion PRs into protected main. | Use for CI triggers, versioning, packaging, GitHub Release publication, branch permissions, or recovery after a failed release. | +| [docs/releases.md](docs/releases.md) | Feature-to-devel and devel-to-release PR checks, automatic patch versions, manual npm version/tag releases, exact changelog notes, publication recovery, README commits on release, automatic release-to-devel synchronization, and promotion PRs into protected main. | Use for CI triggers, versioning, packaging, GitHub Release publication, branch permissions, or recovery after a failed release. | For common investigations: @@ -40,6 +43,10 @@ For common investigations: and the configuration reference's automatic-merge rules. - **Why did a session tab open or close?** Read the runtime tab sections and workflow section 7. +- **Which folders are configured, running, paused, or missing?** Read the runtime + repository inventory section and advanced monitoring notes. `list` and `/bot` + → **Repositories** use timestamped host snapshots; do not activate owners to + inspect them. Discovery is an explicit registration operation. - **Why is polling inactive or duplicated?** Read architecture ownership, workflow section 1, and installation registration details. @@ -55,33 +62,66 @@ the installation block without making remote writes. Keep its markers intact. and GitHub plugin entrypoints. `src/easy.ts` resolves standard project settings; `src/config.ts` defines the configuration schemas and route matching. - `src/dispatcher.ts` owns discovery, the durable task lifecycle, questions, - feedback rounds, publication coordination, retries, and merge polling. + feedback rounds, publication coordination, retries, durable task closure, round cancellation/resumption, and merge polling. `src/scheduler.ts` owns interval jobs; `src/state.ts` owns persistence and locks. - `src/executor.ts` owns analysis, base selection, worktrees, session execution, verification, and pushing. `src/analysis.ts` and `src/branch.ts` validate model - decisions. `src/github.ts` implements GitHub calls; `src/approval.ts` evaluates + decisions. `src/pr-description.ts` extracts final public reports and renders and + reconciles managed PR descriptions. `src/github.ts` implements GitHub calls; `src/approval.ts` evaluates approval candidates. - `src/runtime.ts`, `src/worker.ts`, and `src/bridge.ts` implement worker hooks, runtime installation, and communication with the owner. `src/prompt.ts` loads instructions; `prompts/bot.md` contains the bundled bot instructions. + `src/repository-permissions.ts` checks canonical path boundaries for opt-in + repository file approvals; `src/runtime.ts` applies them only to bot sessions. - `src/tui.ts`, `src/ui.ts`, and `src/activity.ts` implement terminal integration - and task activity. `src/rpc.ts` defines RPC contracts; `src/manage.ts` exposes + and task activity. `src/sidebar.ts` renders the runtime panel; + `src/runtime-panel.ts` owns polling, freshness and presentation; + `src/monitor.ts` defines read-only monitoring schemas. `src/rpc.ts` defines RPC contracts; `src/manage.ts` exposes management operations. +- `src/repositories.ts` owns host registry registration, explicit discovery and + component snapshot files; `src/repository-report.ts` defines inventory schemas + and shared CLI/TUI formatting. Neither inventory reader starts bot work. - `src/setup.ts`, `src/wizard.ts`, `src/install.ts`, and `scripts/` cover setup and installation. `examples/` contains configuration examples; `test/` contains automated tests. `package.json` defines build and validation commands. -## Keeping documentation accurate +## Keeping documentation accurate — required for every change -Treat the implementation as the source of truth for current behavior. If code -and documentation disagree, inspect the relevant code and tests and make the -discrepancy explicit rather than assuming the documented behavior is implemented. -When changing behavior, update the relevant reference page and any affected -workflow diagrams. Keep the architecture page concise; put detailed execution -paths in `docs/bot-workflow.md` and user-facing runtime guidance in `docs/runtime.md`. +Documentation is part of the implementation, not a later cleanup task. **If a code +change affects anything already described, update that description and every +affected diagram in the same change and PR.** A change is not complete while its +code and documentation disagree. Do not defer documentation to a later release, +follow-up issue, or another agent. -Validate changed Mermaid diagrams with a Mermaid parser when available; checking -Markdown fences alone does not validate diagram syntax. Avoid literal semicolons -in sequence-diagram message labels because they can be parsed as statement -separators. For documentation-only changes, check links and formatting; application -tests are not needed unless executable behavior also changes. +For every code, configuration, CLI/RPC, prompt, or workflow change: + +1. Read the affected reference pages and compare their claims with the source and + relevant tests. Use the documentation map above to find all entry points. +2. Update affected behavior, defaults, commands, examples, prerequisites, limits, + failure/retry paths, and recovery instructions. Check README and cross-linked + pages as well as the primary reference; fixing only one mention is insufficient. +3. For automation changes, review all eight sections of `docs/bot-workflow.md` + for impact and update every affected Mermaid diagram and its surrounding text. + Show actual ordering, phase/status transitions, durable checkpoints, questions, + verification/publication gates, and restart paths. Do not draw desired behavior + as if it were implemented. Keep architecture concise and detailed paths in the + workflow/runtime references. +4. Validate modified Mermaid with a parser, and check local links, headings, + examples and Markdown formatting. Fences alone do not prove valid diagrams. + Avoid literal semicolons in sequence-diagram messages. Report any validation + that could not be run; do not claim it passed. +5. Before finishing, review the complete diff for code/documentation agreement. + In the PR description, identify the documentation updated, or state why the + change has no documented or user-visible behavior impact. Add accurate + `Unreleased` notes for changes that enter the next release. + +Treat implementation and verified tests as evidence of current behavior. If an +existing discrepancy is discovered, correct the affected documentation within the +authorized scope and make any remaining mismatch explicit. Distinguish model +instructions from enforced runtime behavior, and branch/unreleased features from +features already present in a published package. Do not change an unrelated +runtime behavior merely to make an old description true. + +For documentation-only changes, check links, formatting and diagram syntax; +application tests are not needed unless executable behavior also changes. diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eb1261..b41b0b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,97 @@ # Changelog Release descriptions come from the exact version section committed with the tag. -Add feature changes under `Unreleased`; after a PR merges into `release`, automation +Add feature changes under `Unreleased`; after a `devel` PR merges into `release`, automation moves them into the new patch version's section. For a manual release, prepare and commit the exact version section before creating its tag. Prerelease headings include the full version, for example `## 0.7.0-beta.1`. ## Unreleased +### Documentation + +- Align all eight bot workflow diagrams and runtime/recovery references with the + implementation, including owner lifecycle, feedback queuing, session recovery, + verification gates, merge polling and TUI commands. +- Require documentation and affected diagrams to be updated with each relevant + implementation change in repository and bundled bot instructions. + +### Fixed + +- Restore compatibility with OpenCode 2.0.6: update the pinned SDKs, discover + services through `/api/info`, refresh owner activity with `session.update`, + and interrupt sessions with `resume: false`. Replace the removed headless + activation command with location-scoped `plugin.list` and document validation. + +- Honor formal approvals for the exact verified PR commit even when publication + or description recovery finishes later. Retain review revocation, authorization, + and merge-readiness checks; keep timestamp gating for unbound merge comments. + +- Retry PR head propagation after a successful push when the remote branch still + matches the verified commit. Preserve a durable push checkpoint across restarts, + avoid repeating acknowledged pushes, and distinguish closed PRs from changed + branches while preserving manual description edits. + +- Keep repository inventory RPC responses valid JSON when an owner has no runtime + snapshots. Isolate setup-test registries so validation never adds fixture + repositories to the operator's inventory. + +- Use the successful session's final completion report as the PR description instead + of its initial acknowledgement. Persist reports across restarts, retain the + original summary plus the latest follow-up, distinguish dispatcher checks from + agent-reported tests, and preserve manual notes outside the managed section. + +- Reconcile timed-out or interrupted sessions completed manually after a blocked + task or service restart. Verify and publish through the dispatcher, then process + queued issue feedback on the same branch and PR, including legacy checkpoints. + +### Added + +- Per-repository `autoApproveRepositoryFiles` configuration, an opt-in setup + prompt, and `init --auto-approve-repository-files`. Bot sessions and native + workers can automatically access files in the repository and assigned worktree + across rounds/restarts, without global permission changes. Explicit denials, + pending questions, media-helper limits, and shell permissions remain unchanged. + +- Cancel a single bot round while retaining issue/PR tracking, with durable stop + recovery, preserved draft worktrees and fresh worktrees for later feedback. + Resume tracking a locally closed task without replaying its abandoned round + or old comments. Expose both actions in `/bot` and the CLI; show historical + errors only in details after closing or cancelling. + +- List configured repositories across the host with `opencode2-automation list` + (`--json` for scripts) and `/bot` → **Repositories**. Show owner/checkout paths, + base branches, timestamped dispatcher/scheduler status and concrete issue + failures without activating other bots. Register projects during init/startup; + import older inactive standard configurations with `list --discover `. + Preserve missing entries and explicitly mark stopped, stale or unavailable data. + +- Manage tasks directly from `/bot`: inspect details, open sessions, close idle + tabs, restart workflows, or stop sessions and durably end tracking without + deleting work. Preserve closed tasks as history and skip rediscovery, feedback, + runtime hooks and publication after closure, including missing issue/PR cases. +- Identify blocked, failed and closing issue keys and errors in the runtime + sidebar instead of showing only an anonymous attention counter. + +- Add a live BOT RUNTIME sidebar and `/botstatus` report with dispatcher operations, + scheduler scans/retries, queue counts and selected-task details. Keep stale and + unavailable readings explicit; monitor through read-only owner-scoped RPC. +- Validate native sidebar rendering, reactive updates and narrow layouts as part + of the standard check command using pinned TUI/Bun development dependencies. + +- `/restartworkflow` and the matching CLI/RPC command resume a stopped task from + its saved stage, preserving worktrees, sessions, PRs, and feedback. Checkpoint + continuation requests across restarts without bypassing checks or permissions. + +### Changed + +- Collect feature PRs on `devel` without publishing a release. Run CI on PRs to + `devel` and `release`, and publish automatic patches only after a same-repository + `devel` to `release` PR is merged. +- After stable publication and README update, automatically merge the published + release head into `devel` without a synchronization PR. Preserve new development + commits, retry concurrent updates, and fail safely on conflicts or denied pushes. + ## 0.6.5 ### Fixed @@ -44,7 +128,7 @@ include the full version, for example `## 0.7.0-beta.1`. - Run full CI when feature PRs target `release`, including new commits to open PRs. Ordinary feature pushes no longer run CI or build packages. -- Publish an automatic patch after a PR merges into `release`, and support manual +- Publish an automatic patch after a `devel` PR merges into `release`, and support manual version tags on that branch without a second version bump. - Recover interrupted publication without moving tags or republishing completed packages. Commit README on `release` before opening or updating its PR to `main`. @@ -74,4 +158,4 @@ include the full version, for example `## 0.7.0-beta.1`. - Add detailed bot workflow diagrams and a documentation map in `AGENTS.md`. - Expand bundled bot instructions for planning, delegation, verification, and handing publication back to the dispatcher. -- Add the README banner showing an OpenCode2 agent executing a task. \ No newline at end of file +- Add the README banner showing an OpenCode2 agent executing a task. diff --git a/README.md b/README.md index 657a8d5..ddb83ce 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ titles, and can merge after an authorized approval. The TUI is optional. ## Requirements -- OpenCode **2** with a working model; tested with `0.0.0-beta-19398`. +- OpenCode **2.0.6** with a working model; client/plugin SDK pinned to `2.0.6`. - Node.js 22+, npm, and Git on macOS/Linux. - GitHub authentication (`gh auth login` and `gh auth setup-git`, or a token in the service environment) and permission to comment, push, and create PRs. @@ -98,6 +98,14 @@ and respects `XDG_CONFIG_HOME` and `OPENCODE_CONFIG_DIR`. 3. Load the project with the [headless command below](#run-without-the-tui), or open it with `opencode2 /absolute/path/to/your-project`. +The wizard can enable automatic file access within this repository and its bot +worktrees. Choose **yes** at the file-access prompt, or pass +`--auto-approve-repository-files` to `init`. Existing projects can set +`"autoApproveRepositoryFiles": true` in their configuration. This does not change +global permissions or approve arbitrary shell commands. See +[repository file approvals](docs/configuration.md#repository-file-approvals) for +scope, restart steps, and pending questions. + Settings are saved to `/absolute/path/to/your-project/.opencode/automation.json`. If it already exists, edit it directly and skip `init`. Repeat setup for each repository; the plugin is installed only once. After editing settings, restart @@ -113,13 +121,19 @@ Only the authenticated GitHub user is allowed by default; add colleagues to Run once for **each configured primary checkout**, with its absolute path: ```bash -opencode2 api v2.plugin.awaitActivation --param 'location[directory]=/absolute/path/to/your-project' +opencode2 api plugin.list --param 'location[directory]=/absolute/path/to/your-project' ``` This starts the shared service if needed and loads the project's plugins. The -command exits; the bot keeps running without a TUI or extra monitoring process. +response lists the loaded plugins; confirm `automation` has `state.status` equal +to `active`. The command exits; the bot keeps running without a TUI or extra monitoring process. **Repeat it after every service restart.** +OpenCode 2.0.6 uses operation names without the `v2.` prefix and no longer exposes +`plugin.awaitActivation`. Upgrade the automation package together with OpenCode; +older beta SDKs cannot discover its service correctly. See +[upgrade compatibility](docs/installation.md#opencode-206-compatibility). + For automatic startup after a machine reboot, put one invocation per project in your operating system's startup mechanism, under the same user, after networking is available. Use absolute executable/repository paths (`command -v opencode2` @@ -249,10 +263,38 @@ installations are not removed by `npm uninstall --global`. - **Merging:** approve the bot's PR or post a configured merge phrase. The author must be allowed and have repository write access. Set `autoMerge.enabled` to `false` to disable this. `signature` controls the signature on new bot messages. +- **Repository inventory:** run `opencode2-automation list` from any directory, + or choose **Repositories** in `/bot`. See registered GitHub repositories, owner + and checkout paths, base branches, timestamped runtime status, scan timing and + issue counts/errors. Use `list --json` for scripts and + `list --discover /absolute/path/to/projects` to import older standard configs + without starting bots. See [repository inventory](docs/runtime.md#repository-inventory). +- **Runtime status:** the right sidebar's **BOT RUNTIME** panel shows dispatcher + work, GitHub discovery, scheduled scans, queue counts and the selected task. + `/botstatus` opens a full text report. Status refreshes every five seconds; + unavailable or stale readings are marked explicitly. See + [runtime panel details](docs/runtime.md#runtime-status-sidebar). +- **Task management:** `/bot` lets you open a session, inspect details, close idle + tabs, restart a stopped workflow, **Cancel current round** while keeping issue + tracking, or stop sessions and end task tracking. **Resume issue tracking** + restores a closed task for future comments without replaying its old round. Closing + tracking preserves all local work and history, works without a surviving GitHub + issue/PR, and prevents rediscovery. See [task management](docs/runtime.md#manage-tasks-from-bot). - **Progress:** use `/bot` in the TUI, or the CLI's `status`, `scan`, `pause`, and `resume` commands from the target repository. Closing a PR closes its bot tabs while retaining session history. Authorized issue comments can continue work - on an open PR without another mention. + on an open PR without another mention, after the current round publishes. +- **PR descriptions:** the successful session's final summary appears in the PR, + with dispatcher checks listed separately. Follow-ups keep the original report + and replace **Latest update**. Keep manual notes outside the managed HTML markers. + See [PR descriptions](docs/runtime.md#pr-descriptions). +- **Recovery:** completing a stopped bot session manually is detected by the + dispatcher, which verifies and publishes before processing queued comments. + Use `/restartworkflow` in the owner project's TUI or + `opencode2-automation restartworkflow 'owner/repository#123'` from its primary + checkout to recover an eligible stopped task without discarding work. Pending + questions and failing checks still block progress. See + [workflow recovery](docs/runtime.md#interrupted-sessions-and-workflow-recovery). Keep machine-specific `.opencode/automation.json` files out of Git: global `init` does not add an ignore rule. See [configuration and Git branches](docs/configuration.md#configuration-files-and-git-branches) @@ -291,21 +333,27 @@ copy it to another machine and follow the `.tgz` instructions above. ## GitHub Actions and releases -1. Work on a feature branch and add release notes under `Unreleased` in - [CHANGELOG.md](CHANGELOG.md). Ordinary branch pushes do not run CI or publish packages. -2. Open a PR into the long-lived `release` branch. CI runs lint, type checking, - tests, a build, and an installation check on Node 22 and 24. New commits to - the open PR rerun these checks. Review and merge after they pass. -3. The merge starts **Release**. It increments the patch version on `release`, - moves the unreleased notes into that version's changelog section, and pushes - the version commit and tag atomically. It builds and verifies the tagged - package, then publishes the GitHub Release with `.tgz`, SHA-256, and exact - version notes. No package is published to npm. -4. Only after publication succeeds, automation commits the versioned README link - on `release` and opens or updates a PR from `release` into `main`. -5. Review and merge that PR with a **merge commit**. All code, version metadata, - release notes, and README changes reach protected `main` through this PR. - The automation never pushes to `main` or writes its files through the API. +1. Create a feature branch from `devel` and add release notes under `Unreleased` + in [CHANGELOG.md](CHANGELOG.md). Pushes without an open PR do not run CI. +2. Open a PR into `devel`. CI runs lint, type checking, tests, a build, and an + installation check on Node 22 and 24. New commits to the open PR rerun checks. + Review and merge after they pass. Merging into `devel` does not publish a package. +3. When ready to publish the accumulated changes, open a `devel` → `release` PR. + After its checks pass, review and merge it with a **merge commit**. +4. The merge starts **Release**: an automatic patch version, exact changelog notes, + atomic version/tag push, and publication of the verified `.tgz` and SHA-256. + No package is published to npm. +5. After publication, automation commits the new README download link on `release` + and opens or updates the `release` → `main` promotion PR. It also automatically + merges that published head into `devel`, including version metadata and README, + preserving newer development work. No synchronization PR is created. +6. Review and merge the promotion PR with a **merge commit**. Protected `main` + receives all released code, metadata and README through that PR only. + +A synchronization conflict or rejected push fails the Release job without +resetting `devel` or undoing publication. Resolve the conflict or permissions and +rerun the job; it reuses the published version. Synchronization does not wait for +the main PR to merge and does not trigger another release. To choose a version manually, prepare and commit its exact changelog section on `release`, then use `npm version`, for example: @@ -321,7 +369,8 @@ git push --atomic origin release v1.0.0 The pushed tag publishes exactly `1.0.0`, without another version bump. Both `v1.0.0` and `1.0.0` tag names are accepted. The next automatic patch is `1.0.1`. Version tags must point to code on `release`; ordinary pushes to that branch -never start publication. Finish the active release before merging another feature. +never start publication. Finish the active release before merging another `devel` → `release` PR. +Feature PRs may continue to accumulate on `devel`. The README on `release` is updated after publication; the README on `main` changes when the promotion PR is merged. The tag and packaged README remain snapshots diff --git a/docs/advanced.md b/docs/advanced.md index 286176f..173e863 100644 --- a/docs/advanced.md +++ b/docs/advanced.md @@ -4,7 +4,7 @@ The [README](../README.md) covers the combined package and setup wizard. Use `examples/advanced.opencode.jsonc` for custom schedules, multiple repositories, or separately loaded scheduler and dispatcher components. -Both components use only the OpenCode **2** SDK, pinned to `0.0.0-beta-19398`: +Both components use only the OpenCode **2** SDK, pinned to `2.0.6`: - `automation.scheduler`: invokes configured RPC methods on an interval. - `automation.github`: owns issue discovery, comments, isolated work, verification, @@ -29,8 +29,9 @@ OpenCode service must be running for polling to work. | `ownerDirectory` | Absolute path of the checkout that owns automation. Worker worktrees do not activate another scheduler. | | `stateDirectory` | Shared location for queues, locks, and worktrees. Keep it consistent across components and restarts. | | `repositories` | Repositories with existing local checkouts, default base branches, allowed authors, and checks. A natural-language request can override the base before work starts. | +| `repositories[].autoApproveRepositoryFiles` | Opt-in file-access approval for that repository and the assigned task worktree, including worktrees outside the checkout. Defaults off; does not approve shell commands. | | `allowedAuthors` | GitHub users authorized to request work and approve merging. Merging also requires repository write access. | -| `checks` | Arrays of executable arguments, e.g. `[["npm", "test"]]`. `[]` skips automated tests and reports that in the PR. No implicit shell. | +| `checks` | Arrays of executable arguments, e.g. `[["npm", "test"]]`. `[]` skips dispatcher test commands; the PR distinguishes this from agent-reported tests. No implicit shell. | | `routes` | Maps full mentions to agents and models available in OpenCode. | | `routes[tag].capabilities` | Main model capabilities: `text`, `vision`, `audio`; omitted means text only. | | `routes[tag].mediaModel` | `{ model: { providerID, id }, capabilities: ["text", "vision"] }` for the media helper. | @@ -47,8 +48,9 @@ OpenCode service must be running for polling to work. Other plugins can expose idempotent RPC methods for custom scheduler jobs. A transport timeout does not prove the server never executed a request. -The executor uses the configured OpenCode permissions. Interactive permission -requests are posted to the issue and suspend the task. An authorized author must +The executor uses the configured OpenCode permissions. The optional repository +file policy handles eligible `ask` decisions first; see +[repository file approvals](configuration.md#repository-file-approvals). Remaining requests are posted to the issue and suspend the task. An authorized author must reply with the exact `/allow QUESTION_ID` or `/deny QUESTION_ID` command. Explicit OpenCode deny rules remain. Install project dependencies before running it or include suitable setup commands in your checks. @@ -71,13 +73,16 @@ node dist/manage.js run /absolute/path/to/owner-project github-issues node dist/manage.js pause /absolute/path/to/owner-project github-issues node dist/manage.js resume /absolute/path/to/owner-project github-issues node dist/manage.js retry /absolute/path/to/owner-project 'owner/repository#123' +node dist/manage.js restartworkflow /absolute/path/to/owner-project 'owner/repository#123' ``` `pause` stops scheduled scans and manual scheduler runs. It does not cancel queued work or active sessions; direct dispatcher `scan` still works. -`retry` resumes blocked or failed tasks. If a session failed or prompt delivery is -uncertain, inspect the session and worktree before explicitly starting a new one: +`retry` clears blocked or failed status at the saved phase and requires an idle +worker and maintenance loop. It does not append a continuation to an interrupted +session. Prefer `restartworkflow` to continue that same session. If prompt delivery +is uncertain, inspect the session and worktree before explicitly starting a new one: ```bash node dist/manage.js retry /absolute/path/to/owner-project 'owner/repository#123' --restart-session @@ -85,14 +90,72 @@ node dist/manage.js retry /absolute/path/to/owner-project 'owner/repository#123' This interrupts the previous session and reuses the worktree. It preserves code and already-published acknowledgement comments. An issue edited after analysis -remains blocked for review. A new authorized comment after completion starts a +still faces the phase-specific issue/route guards on its next execution. A new authorized comment after completion starts a follow-up round and updates the same open PR. +`restartworkflow` (also available as `/restartworkflow` in the owner TUI) queues +recovery at the saved phase, without interrupting active execution or clearing +the session. An interrupted session receives one checkpointed continuation +request after it becomes idle; a completed session proceeds to verification. +The request survives owner restarts. Uncertain delivery blocks inspection rather +than replaying the continuation. Repeated requests while ready/running are no-ops. +The RPC method `automation.github.restartworkflow` accepts `{ key }` and returns +`{ accepted }`. A true result acknowledges queuing, not completed publication. +Known tasks not blocked/failed return false after the pending-question and closed-PR +guards. Missing tasks, unresolved questions, closed/merged PRs, absent routes, +and unrecognized running-session errors produce errors. Recovery does not run a +scan, resume a paused scheduler, or restart the service. +Unlike `retry`, recovery can be queued while a different task is working. + +### Read-only runtime monitoring + +`automation.github.monitor` accepts `{}` and returns the owner directory, +worker operation, optional active task key, scanning flag, last scan start/finish, +optional redacted scan error, and enriched activity snapshots. It does not trigger +work or write the queue. Worker operations are `idle`, `reconciling`, `executing`, +`merging`, `maintenance`, or `stopped`; these are live diagnostics, not task phases. +Scan finish means an attempt ended, including failed attempts; inspect `scanError`. +Timing resets when the dispatcher is recreated. + +`automation.scheduler.status` supplies job `running`, `paused`, `nextAt`, failure +count, error and last start/finish. The sidebar and `/botstatus` combine these APIs. +Each poll has a four-second bound; failures retain marked stale data. The existing +`activity` RPC/events still drive tab notifications and their ten-second fallback. +Full task history remains available through `status`; see the +[runtime sidebar](runtime.md#runtime-status-sidebar) for display and selection rules. + +### Host repository inventory + +`opencode2-automation list [--json]` is independent of the current checkout and +service discovery. `automation.github.repositories` accepts `{}` and returns the +same `{ entries, warnings }` report on the connected server. The method reads +local registry/snapshot files and omits absent snapshot fields so inactive or +missing owners also produce valid JSON. It does not invoke RPC in other owner locations, +which could activate their plugins. `/bot` → **Repositories** consumes this API. + +`init` and combined-plugin activation register standard configurations. Dispatcher +startup registers all advanced `repositories` entries under its canonical owner. +Dispatcher and scheduler write separate atomic snapshots every five seconds while +they hold their existing ownership locks, with PID, timestamp and shutdown state. +Disposal settles the last snapshot before releasing ownership. Readers verify +process existence and 15-second freshness; stale details remain historical. +Snapshot/registration errors are reported but do not abort execution or alter +queue state. The registry uses private files under `XDG_STATE_HOME` (default +`~/.local/state`), separate from Git-backed state; no GitHub token or model +credentials are stored there. + +Use `list --discover /absolute/path/to/projects` to register older inactive +standard `.opencode/automation.json` configurations without loading owners. +Discovery has explicit filesystem/depth limits and does not import arbitrary +advanced plugin options. For statuses and migration details, see +[repository inventory](runtime.md#repository-inventory). + ## Persistence and reconciliation The queue stores analysis decisions and clarification dialogue, comment ID, session ID, phase, pinned base branch, worktree, base commit, pending questions, replies, permission decisions, helper -IDs, check results, PR title, publication time, PR, and merge status. Writes are +IDs, session-stop classification, recovery request and admission checkpoint, check +results, PR title, publication time, PR, and merge status. Writes are atomic; heartbeat locks prevent multiple owners of the same state directory. After a crash, allow 30 seconds for an abandoned lock to expire. Do not remove @@ -110,7 +173,7 @@ location are checked before renaming, and no model is prompted. Requests have a servers without a matching service registration skip this mechanism; use the shared service for unattended automation. -SDK adapters may ignore AbortSignal. The plugin therefore bounds its own SDK waits, +SDK adapters may ignore AbortSignal. The executor therefore bounds its local SDK waits, preserves healthy worker execution on owner disposal, and settles local state writes before releasing ownership. A replacement waits up to 15 seconds for the retiring owner's locks. RPC disposal has a five-second deadline per component; cleanup still @@ -119,6 +182,14 @@ error should be investigated via plugin details and server logs. Back up the que worktree, and session database before recovery. Reconcile an already-published PR and saved session instead of restarting implementation or deleting the worktree. +A blocked session stop is rechecked on worker passes (no more than once every +30 seconds after an unsuccessful probe, and only when a new worker pass can start). A matching saved session with a successful +final assistant response re-enters normal execution validation, checks, and +publication automatically, including legacy timeout/interruption checkpoints. +Failed checks, pending questions, and uncertain prompt delivery are not cleared. +Queued feedback is retained until publication completes. The stop itself never +automatically prompts the model; continue it manually or request workflow recovery. + Only one issue executes at a time. Checks must succeed before publication. Push uses the exact verified commit without force. Worktrees remain available for inspection; automatic cleanup is not implemented. @@ -139,3 +210,106 @@ API references: [OpenCode 2 plugins](https://opencode.ai/v2/docs/build/plugins), [GitHub issues](https://docs.github.com/en/rest/issues/issues), [comments](https://docs.github.com/en/rest/issues/comments), and [pull requests](https://docs.github.com/en/rest/pulls/pulls). + +## Ending task tracking + +Use `/bot` → select issue → **Stop and close task**. The owner-scoped RPC is +`automation.github.close` with `{ "key": "owner/repository#123" }`, returning +`{ "accepted": true }` when durable closure is queued or `false` if already closed. +There is no corresponding setup CLI subcommand. This action does not require the +GitHub issue/PR or saved session to still exist. It never deletes local work. + +The queue retains phase and history with statuses `closing` and `closed`, +`closeRequestedAt`, `closedAt`, and `closeError`. Interruption of all saved main, +earlier-round and media session IDs is bounded to 15 seconds per request; missing +sessions are ignored, other failures retry no sooner than 30 seconds. Closure +waits for the selected task's in-flight worker and question posts, then interrupts +again to cover a session creation that was already in flight. Checkpoint guards +prevent late results from publishing or reviving the task. Publication/merge +already in flight rejects admission, rather than promising to undo remote effects. + +Pending closure is resumed on startup. Keep the queue and Git worktree backups +when upgrading: older plugin builds do not understand these two new statuses. +See [runtime management](runtime.md#manage-tasks-from-bot) for the UI and limits. + +While a closure is pending, the dispatcher does not start another worker pass. +An unrelated already-running task can finish; scanning continues for other tasks. +The monitor reports task maintenance until closure completes. + + +## PR description recovery + +Task state stores `completion` (final public text or an explicit unavailable +reason, session, round, then verified commit and checks), `initialCompletion`, +`publishedBody` (the last acknowledged managed section), and `pushedCommit` (the +successfully pushed verified SHA). New rounds clear the current completion and +push checkpoint while retaining the original report and published body. +Older verifying/publishing tasks recover missing +summaries from their saved sessions; this does not rerun the model. Missing or +empty successful reports are marked unavailable, while transient reads retry. +Already completed or closed tasks are not bulk rewritten on upgrade. + +Publication reads the PR at the verified head and only replaces the managed HTML +marker region. Notes outside it are preserved. An exact known legacy body can be +replaced; unknown unmarked text is retained with the new section appended, since +it might contain manual edits. A later legacy round may not have enough saved +information to identify its old acknowledgement exactly. + +After push, GitHub's PR head can temporarily lag behind its branch ref. Before +description reconciliation and again before PATCH, the plugin reads the remote +branch ref. If it matches the verified SHA but the PR head does not, publication +enters `retry_wait` and retries with the normal backoff, up to `maxAttempts`. +Retries reuse the saved report and verified commit, skipping a push already +recorded in `pushedCommit`, including after a restart. Exhausted retries become +`failed`; inspect the reported SHAs and use `/restartworkflow` after resolving the +problem. An unacknowledged push still uses normal non-force push reconciliation. + +An edited/removed managed section, changed remote branch, closed follow-up PR, or oversized +body blocks at `publishing`. Preserve your notes outside the markers and restore +the previous managed section from `publishedBody` in the task checkpoint (or PR +edit history), then use the normal workflow retry. Do not delete the queue or +restart implementation just to retry a description update. If a PATCH succeeded +but its response was lost, matching desired content is accepted without another +write. Body, PR head and branch ref are reread before PATCH; edits after those reads cannot +be atomically excluded by this implementation. + +Each rendered report is limited to 22,000 UTF-8 bytes with an explicit truncation +notice; full saved text remains in task state and the session. Dispatcher check +text is limited to 8,000 bytes. The complete description, including retained +notes and signature, must fit within the automation limit of 60,000 bytes or +publication blocks without dropping notes. Titles are not regenerated on updates. + + +## Round cancellation and resuming tracking + +Owner RPC methods `automation.github.cancelround` and +`automation.github.resumetracking` accept `{ "key": "owner/repository#123" }` +and return `{ "accepted": true }` when cancellation is queued. A no-op returns +`false`; invalid transitions and publication/merge in flight throw an actionable +error. Matching CLI commands work from the configured owner checkout. See +[runtime semantics](runtime.md#cancelling-one-round-while-keeping-tracking). + +Durable `cancellation` stores the request time and interruption error. Status +`cancelling` gates worker checkpoints, session prompts, runtime hooks, discovery +updates and publication; scans leave its cursor unchanged. A worker pass resumes +due cancellation after restart and retries failed interruption after 30 seconds. +It drains the selected worker and pending question posts before entering `watching`. +The queue keeps `cancelledRounds` with prior errors, questions, feedback, worktree, +session and verification/report snapshots. `controlVersion` increases on operator +transitions, so stale TUI events cannot undo explicit resumption. + +Watching continues PR-state discovery and accepts new authorized issue comments. +`publishedHead` retains the successfully published SHA and merge-comment window time +across rounds; merge checks use it while watching. No saved published head means +no automatic merge until the next successful publication. Old round checkpoints +are not treated as a new successful publication. Explicit resumption validates +GitHub objects and advances the comment cursor to the observed backlog without +queuing it; failed validation leaves tracking closed. + +The next round allocates `localBranch` and a new managed worktree, fetching the +existing remote PR head if applicable. The prior worktree and branch are never +reset, cleaned, deleted or force-pushed. Checks validate the new local branch; +push still targets the task's original remote `branch`. Normal publication and +verification guards apply, including ancestry of the pinned base. The archived +round is not replayed or published automatically. Existing queued feedback is +retained by cancellation, while resuming a closed task explicitly skips backlog. diff --git a/docs/architecture.md b/docs/architecture.md index aee64b2..9f4c91e 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -8,28 +8,54 @@ loads a generic scheduler, a GitHub dispatcher, and a terminal UI component. - **Scheduler:** durable interval jobs, pause/resume, backoff, and no overlapping invocation of the same job. Calls dispatcher RPC rather than GitHub directly. - **Dispatcher:** discovers issues and authorized comments, persists the queue, - coordinates execution, publishes PRs, and polls for merge approval. + coordinates execution and recovery, publishes PRs, and polls for merge approval. - **Executor:** generates an acknowledgement, runs an OpenCode session in an isolated Git worktree, verifies changes, and pushes the verified commit. - **Terminal UI:** subscribes to activity events and polls for missed updates. Opens background tabs, closes task tabs after PR closure while retaining - session history, and exposes the `/bot` task selector. + session history, and exposes `/bot` and `/restartworkflow` task selectors. + A read-only runtime sidebar and `/botstatus` combine live dispatcher diagnostics, + scheduler state and task snapshots, marking stale or unavailable data. +- **Repository inventory:** `init` and owner activation register configured + checkouts in a per-user host registry. Dispatcher and scheduler publish separate + local status snapshots every five seconds. CLI `list` reads these without + activating owners; `/bot` → **Repositories** reads the connected server's same + registry through `automation.github.repositories`. Missing/stale data is explicit; + discovery can import inactive standard configs without starting automation. ## Workflow 1. Match a configured mention in an authorized issue or comment. -2. Generate an English problem summary and plan without editing code. -3. Publish a signed acknowledgement before starting implementation. +2. Generate a structured analysis without tools. Questions and proposals requiring + a choice wait for an authorized reply before implementation can begin. +3. Publish a signed acknowledgement, resolve the base branch, and pin that choice. 4. Create or reuse the task worktree and checkpoint the session identity before prompting the executor. The executor must not publish directly. -5. Verify changes, generate a descriptive PR title, push, and create the PR. -6. Process subsequent authorized issue comments as new rounds on the same branch. +5. Validate session success and save the final public completion report. Verify + changes, bind the report to the verified commit, then push and create or reconcile + the PR with that report and separate dispatcher checks. Generate its title only + if creating a PR without an already-saved title. + Checkpoint successful pushes. Retry a lagging PR head only while the remote + branch matches the verified commit; block actual branch changes or closure. +6. After publication, process queued authorized issue comments as new rounds on + the same worktree and branch, with a new main session and the existing open PR. + After cancellation, use a fresh local branch/worktree from the published head, + preserving the abandoned worktree and the remote PR branch. + Keep the original PR report and update its Latest update section after pushing; + preserve manual notes outside the managed description. 7. Merge only after eligible approval of the published head, repository permission checks, and GitHub merge readiness checks. Post a signed acknowledgement. + Formal reviews are bound to the exact commit and survive later description + recovery; unbound merge comments must follow the latest publication. -A failure retains the current phase and retry state. A possibly running session -is reconciled before starting another issue. Unknown prompt delivery is blocked -for inspection. RPC events are ephemeral; they are not the durable queue. +A failure retains the current phase and retry state. An eligible `running` task +with a saved session takes priority over other ready tasks. Unknown prompt +delivery is blocked for inspection. Stopped sessions completed manually are detected automatically and +rejoin verification/publication before queued feedback runs. Explicit workflow +recovery preserves checkpoints and continues the same session when needed. It does +not resume a paused scheduler or clear pending questions and failing checks. RPC +events are ephemeral; they are not the durable queue. See the eight +[workflow diagrams](bot-workflow.md) for exact sequencing and guards. ## Configuration and ownership @@ -37,6 +63,10 @@ The easy setup writes a per-project `.opencode/automation.json`. Global plugin loaders remain inactive in projects without configuration. Account defaults come from GitHub authentication, while the wizard queries OpenCode for a model default. User-configured values are preserved rather than replaced during upgrades. +An opt-in repository file-access policy is stored in that project's configuration +and passed to its worktree runtime. Permission hooks apply it to associated bot +sessions and native workers using canonical paths, while retaining explicit +denials, shell rules, and the media helper's no-tools restriction. The primary checkout owns scheduling. Worker worktrees do not start additional schedulers. A shared Git-directory state folder holds the queue and locks; separate @@ -46,10 +76,34 @@ In the shared background service, each scheduler/dispatcher component renews one empty maintenance session in the owner location at startup and every ten minutes. OpenCode counts durable session events as activity; polling plugin APIs alone does not prevent its hourly inactivity eviction. No model is prompted by keepalive. -The service PID must match the plugin process before any maintenance session is -created. Shutdown releases local SDK waits independently of adapter cancellation, +The service is discovered through `/api/info`; `server.info` must report the plugin +process PID before any maintenance session is created. `session.update` refreshes +its title without prompting a model. Shutdown releases local SDK waits independently of adapter cancellation, settles state writes, bounds RPC disposal, and releases locks. A healthy worktree session continues and the replacement owner reconciles its saved identity. See [advanced configuration](advanced.md) for retry commands, limits, and RPC settings, and the [README](../README.md) for installation and user-facing behavior. + +Runtime monitoring is observational: `automation.github.monitor` exposes the +owner's current worker operation, active task, scan timing/error, and enriched +activity snapshots. Scheduler status retains its existing RPC. The TUI polls both +independently every five seconds through the connected client; it does not infer +worker activity from queue status alone. These live diagnostics do not add durable +workflow phases or replace the existing ownership keepalive. + +Operator task closure is a durable dispatcher operation: `/bot` sends the owner +`automation.github.close`, which records `closing` before interruption and later +`closed`. Closed records remain as history and prevent rediscovery; scans, runtime +hooks, feedback execution and merge monitoring exclude them. Session/worktree +data is retained. See [task management](runtime.md#manage-tasks-from-bot) for +in-flight operation limits and the distinction from closing a TUI tab. + + +Round cancellation uses `automation.github.cancelround`: persist `cancelling`, +drain execution, archive the current round, then `watching`. Discovery continues +and future feedback starts a new session in an isolated worktree. Only a saved +published head can authorize auto-merge while watching. Explicit +`automation.github.resumetracking` restores a closed task for future comments, +skipping its abandoned round and closed-period backlog after validating GitHub +objects. Both operations preserve work; neither closes the GitHub PR or issue. diff --git a/docs/bot-workflow.md b/docs/bot-workflow.md index 053d1bc..f6342de 100644 --- a/docs/bot-workflow.md +++ b/docs/bot-workflow.md @@ -3,31 +3,85 @@ This document describes the current implementation, including waiting, retries, follow-up rounds, and recovery. Mermaid nodes use the actual persisted phase and status names where applicable. `done` means publication finished; it does -not mean the PR has merged. +not mean the PR has merged. These diagrams describe the code on this branch; +features under `Unreleased` are available in a build of this branch and enter a +published package through the release process. + +Read the diagrams together: sections 1–3 cover scheduling and admission, section 4 +covers the saved main session, sections 5–7 cover helpers and publication, and +section 8 covers recovery and local task closure. Model planning, subagents and review +happen inside `running`; they are not additional persisted phases. ## 1. Startup, ownership, and polling ```mermaid flowchart TD - Load[OpenCode loads automation plugin] --> Owner{Primary Git checkout root?} - Owner -->|No| Inactive[Plugin stays inactive] - Owner -->|Yes| Config[Read explicit plugin options or .opencode/automation.json] + Activate[Open TUI or call plugin.list for the owner location] --> Load[Load combined automation plugin] + Load --> Owner{Primary Git checkout root?} + Owner -->|No or outside Git| Inactive[Plugin stays inactive] + Owner -->|Yes| Config[Use nonempty plugin options or read .opencode/automation.json] Config -->|No project config| Inactive - Config --> Resolve[Resolve repositories, authentication, routes, and defaults] - Resolve --> GH[Start GitHub plugin; acquire github lock; load queue.json] - GH --> RPC[Register dispatcher RPC and runtime bridge] - RPC --> Worker[Immediate worker tick, then workerEverySeconds] - GH --> Scheduler[Start scheduler; acquire scheduler lock; load scheduler.json] - Scheduler --> Clock[Immediate tick, then every second] - Clock --> Due{Job due and not paused or already running?} - Due -->|Yes| ScanRPC[Call automation.github.scan through RPC] - ScanRPC --> Save[Save job result and nextAt] + Config --> Register[Register primary checkout in local user inventory without activating other owners] + Register --> Resolve[Validate repository file policy and resolve GitHub auth, routes and defaults] + Resolve --> Metadata[Register resolved repositories and base branches] + Metadata --> GH[Acquire github lock and load queue.json] + GH --> RPC[Register runtime bridge and dispatcher RPC] + RPC --> Worker[Immediate worker tick, then every workerEverySeconds] + RPC --> Scheduler[Start scheduler after GitHub setup succeeds] + Scheduler --> State[Acquire scheduler lock, load scheduler.json and register RPC] + State --> Clock[Immediate scheduler tick, then every second] + Clock --> Due{Job due, unpaused and not already running?} + Due -->|Yes| Scan[Invoke configured RPC, normally automation.github.scan] + Scan --> Save[Persist result, failures and nextAt] Save --> Clock Due -->|No| Clock - Worker --> Dispatch[Advance one eligible task or check merges] + Worker --> Closing[Resume due closing and cancelling requests independently of active worker] + Closing --> Clear{Any closure or cancellation pending?} + Clear -->|Yes| Worker + Clear -->|No| Recover[Probe eligible stopped sessions and recover unpublished questions] + Recover --> Round[Promote one done or watching task with pending feedback to a new round] + Round --> Select[Choose ready or retry_wait task, saved running session first] + Select --> Candidate{Candidate exists?} + Candidate -->|No| Merge[Check eligible merges] + Candidate -->|Yes| TaskDue{Candidate nextAt elapsed?} + TaskDue -->|No| Worker + TaskDue -->|Yes| Dispatch[Advance saved phase] Dispatch --> Worker + Merge --> Worker + RPC -.-> Keepalive[Each component touches the same empty owner session every ten minutes] + State -.-> Keepalive + Keepalive --> Discover[Discover service through /api/info] + Discover --> PID{server.info PID matches this process?} + PID -->|Yes| Touch[Create or reuse maintenance session, then session.update its title] + PID -->|No| Skip[Skip keepalive] + Stop[Owner reload or shutdown] --> Cleanup[Stop timers, save stopped inventory snapshots, settle writes, dispose RPC, release locks] + Cleanup --> Preserve[Preserve durable queue and healthy worktree execution] + Preserve --> Load + View[Runtime sidebar or botstatus] -.-> Monitor[Read dispatcher monitor and scheduler status every five seconds] + Monitor -.-> RPC + Monitor -.-> State + Monitor --> Fresh{Both readings available and fresh?} + Fresh -->|Yes| Display[Show live operations, queue and selected task] + Fresh -->|No| Stale[Mark unavailable or retained stale readings] + RPC -.-> DS[Publish dispatcher snapshot every five seconds] + State -.-> SS[Publish scheduler snapshot every five seconds] + DS --> Inventory[Per-user registry and separate atomic component snapshots] + SS --> Inventory + Register --> Inventory + Metadata --> Inventory + Init[CLI init or explicit list --discover] --> Inventory + List[CLI list or bot Repositories via current owner RPC] --> Read[Read local inventory without activating owners] + Inventory -.-> Read + Read --> Check[Check paths, config, PID and 15-second freshness] + Check --> Report[Report repository status, scan timing, task counts and issue errors] ``` +- Registration and component snapshots are observational, best-effort writes. + Registry errors do not stop the bot. `init` registers after configuration succeeds; + explicit discovery imports old standard configs without auth or service startup. + The CLI and TUI report share a per-user host registry, not the task queue. + Missing paths and stale/dead processes are shown explicitly. See + [inventory behavior and limits](runtime.md#repository-inventory). - Easy configuration puts state under the shared Git directory at `opencode2-automation/`. Worker worktrees do not start another scheduler. - Default discovery interval: **60 seconds**. Default worker interval: @@ -38,7 +92,9 @@ flowchart TD - Each component renews one empty owner maintenance session at startup and every ten minutes. Durable session events refresh OpenCode's inactivity timer; listing plugins does not. No model is prompted. A PID check prevents touching a different - service. Requests do not overlap and have a 15-second deadline. Pausing issue + service, using `server.info` after discovery through `/api/info` in SDK 2.0.6. + The title update uses `session.update`. Requests do not overlap and have a + 15-second deadline. Pausing issue scans does not pause keepalive. Standalone servers without a matching registered service skip it. - State is schema-validated and saved through a temporary file, file sync, and @@ -54,33 +110,38 @@ flowchart TD Sources: [index.ts](../src/index.ts), [easy.ts](../src/easy.ts), [GitHub plugin](../src/plugins/github.ts), -[scheduler plugin](../src/plugins/scheduler.ts), [state.ts](../src/state.ts). +[scheduler plugin](../src/plugins/scheduler.ts), [lifecycle.ts](../src/lifecycle.ts), +[dispatcher.ts — workOnce](../src/dispatcher.ts), [state.ts](../src/state.ts), +[repositories.ts](../src/repositories.ts), [repository-report.ts](../src/repository-report.ts). ## 2. Discovery and routing ```mermaid flowchart TD - Scan[Scan each configured repository] --> PRs[Refresh tracked PR states, including closed issues] - PRs --> Issues[List open issues; fetch tracked issues missing from that list] - Issues --> IsPR{Entry is a pull request?} - IsPR -->|Yes| Ignore[Ignore entry] - IsPR -->|No| Comments[Read issue comments and filter authorized comments] + Scan[Scan each configured repository] --> PRs[Refresh tracked PRs excluding merged and locally closing, closed or cancelling tasks] + PRs --> Issues[List open issues and fetch missing actively tracked issues] + Issues --> Skip{PR entry, locally closing, closed or cancelling task, or closed untracked issue?} + Skip -->|Yes| Ignore[Ignore entry] + Skip -->|No| Comments[Read comments and filter authorized human comments without bot markers] Comments --> Tracked{Task already exists?} - Tracked -->|Yes| Answer{Pending published question and eligible reply?} - Answer -->|Yes| Accept[Save first eligible reply; waiting becomes ready] - Answer -->|No| Feedback[Append fresh comments to pendingFeedback] - Accept --> Feedback - Feedback --> Cursor[Persist comment cursor and queue] - Tracked -->|No| Open{Issue open?} - Open -->|No| Ignore - Open -->|Yes| Body[Match route in body if issue author is authorized] - Body --> Found{Route found?} - Found -->|No| CommentRoute[Look for a route in authorized comments] - Found -->|Yes| Queue[Persist queued / ready task] - CommentRoute -->|Route found| Queue - CommentRoute -->|No route| Ignore - Body -->|Multiple matching tags in one body| Block[Persist queued / blocked task] - CommentRoute -->|Multiple matching tags in one comment| Block + Tracked -->|Yes| Answer{Open issue with unanswered published question and eligible reply?} + Answer -->|Yes| Accept[Save first eligible answer and any permission decision] + Accept --> Ready[Only waiting status becomes ready] + Ready --> Remaining[Remove answer from fresh and previously queued feedback] + Answer -->|No| Feedback[Append remaining fresh comments to pendingFeedback] + Remaining --> Feedback + Feedback --> Cursor[Persist cursor from all observed comments and save queue] + Cursor --> Gate{Task done or watching?} + Gate -->|Yes| Later[Next available worker pass may start a follow-up round] + Gate -->|No| Retain[Keep feedback until publication or cancellation completes] + Tracked -->|No| Body[Match body route only for an authorized issue author] + Body --> Found{Body route found?} + Found -->|Yes| Queue[Persist queued / ready with initial authorized feedback] + Found -->|No| Route[Try authorized comments, keeping the last matching route] + Route -->|Route found| Queue + Route -->|No route| Ignore + Body -->|Multiple matching tags| Block[Persist queued / blocked task] + Route -->|Multiple matching tags| Block ``` Authorized comment filtering requires an author in the configured allowlist @@ -100,6 +161,13 @@ SHA-256 of that key. Initial feedback contains the authorized comments already seen. Later comments are tracked by increasing comment ID; edits do not create new feedback. PR review comments do not drive implementation rounds. +Discovery and execution are separate: saving `pendingFeedback` does not itself +clear a blocked task or interrupt its current session. Only `done` tasks start a +new round. A session-stop block can first reconcile successful manual continuation +as described in section 8. A pending question consumes its first eligible reply +instead of also treating that reply as follow-up work. The comment cursor includes +all observed comments, while only authorized, unmarked comments become inputs. + Source: [dispatcher.ts — scanOnce](../src/dispatcher.ts), [config.ts — matchRoute](../src/config.ts). @@ -107,31 +175,53 @@ Source: [dispatcher.ts — scanOnce](../src/dispatcher.ts), ```mermaid flowchart TD - Q[queued / ready] --> Guard[Re-fetch issue; validate route, authorization, and follow-up PR] - Guard --> A[analyzing: generate structured decision without tools] + Q[queued / ready] --> Guard[Re-fetch issue and validate route, authorization and follow-up PR] + Guard --> A[analyzing: generate or reuse structured decision without tools] A --> Decision{Decision kind?} - Decision -->|question| AQ[Persist proposals and question; publish one signed comment] + Decision -->|question| AQ[Persist proposals and question, publish one signed comment] AQ --> AW[analyzing / waiting] - AW -->|Authorized issue reply| Dialogue[Save dialogue; clear previous decision] + AW -->|Authorized reply| Dialogue[Save dialogue and invalidate prior decision] Dialogue --> Guard Decision -->|proceed| Ack[Publish or reconcile signed analysis acknowledgement] - Ack --> C[commented: confirmed commentID] + Ack --> C[commented with confirmed commentID] C --> Pinned{Base already pinned?} Pinned -->|No| Base[Interpret authorized branch discussion with main model] - Base --> Choice{Unambiguous valid selection?} - Choice -->|No or selected branch absent on origin| BQ[Publish base question; commented / waiting] + Base --> Choice{Valid unambiguous branch exists on origin?} + Choice -->|No| BQ[Publish base question, commented / waiting] BQ -->|Authorized reply| Base Choice -->|Yes| Pin[Persist baseBranch] - Pinned -->|Yes| Prepare[Validate repository; create or reuse isolated worktree] + Pinned -->|Yes| Prepare[Validate repository and reuse saved worktree or create a new one] Pin --> Prepare - Prepare --> R[running: checkpoint workspace and execute OpenCode session] + Prepare --> R[running: save workspace, install runtime and execute saved session] R -->|Question| RW[running / waiting] RW -->|Authorized reply| R - R -->|Successful session with no pending question| V[verifying: checks and commit] - V --> P[publishing: reconcile PR, title if needed, push and create PR] - P --> Done[pr_opened / done: save PR and publishedAt] - Done -->|New authorized issue feedback| Round[Increment round; queue feedback; reset per-round execution state] + R -->|Timeout or unsuccessful final result| Stopped[running / blocked with sessionStopped] + Stopped -->|Manual continuation succeeds and probe passes| R + Stopped -->|Explicit restartworkflow| Recover[Persist recovery intent, rejoin the same session] + Recover --> R + R -->|Validated success, no unresolved question| Report[Persist final public assistant summary with session and round] + Report --> V[verifying: configured checks and commit] + V --> P[publishing: reconcile or create PR, push when required] + V -->|Failed check or Git consistency guard| VB[verifying / blocked] + VB -->|Operator retries saved stage| V + P --> Done[pr_opened / done with PR and publishedAt] + P -->|Branch matches, PR head not yet updated| Lag[publishing / retry_wait within attempt limit] + Lag -->|Backoff elapsed, reuse saved push| P + P -->|Publication failure| PB[Retain publishing phase and apply error policy] + PB -->|Eligible retry| P + Done -->|Pending authorized feedback| Round[Increment round, move feedback and reset per-round state] Round --> Q + Cancel[Operator confirms Cancel current round] --> Cancelling[Persist cancelling and block new execution] + Cancelling --> Archive[Interrupt and drain, archive round and preserve worktree] + Archive --> Watching[watching: no replay or publication] + Watching -->|New authorized feedback| Fresh[New local branch and worktree from published PR or pinned base] + Fresh --> Round + Closed -->|Explicit Resume issue tracking| Resume[Validate open GitHub objects, skip observed backlog] + Resume --> Cancelling + Operator[Operator confirms Stop and close task] --> Closing[Persist closing at any saved phase] + Closing --> Drain[Interrupt known sessions and drain current operation] + Drain --> Closed[Persist closed and retain work and history] + Closing -.-> Guards[Reject new checkpoints, runtime hooks and publication] ``` Before `queued`, `analyzing`, or `commented` work advances, the dispatcher checks @@ -160,9 +250,18 @@ the base stays pinned across retries and rounds; later comments do not rebase wo Preparation validates the checkout root, `origin` repository, and branch name. A new worktree is created from the fetched base commit under `stateDirectory/worktrees/BRANCH-WITH-SLASHES-REPLACED-BY-DASHES`. -An existing worktree must have the expected real path, branch, and shared Git -directory. A branch already existing without its expected worktree blocks work. -The worker runtime is installed before execution. +For a saved `worktree`, preparation uses that exact path even if the branch was +renamed during recovery. Its canonical directory must be a direct child of the +managed worktree folder and the exact Git worktree root, with the expected branch +and shared Git directory. Its pinned `baseSha` is retained. A missing saved path, +or a branch already existing without its expected worktree, blocks work rather +than creating a replacement. The worker runtime is installed before execution. + +Failures retain their current phase. A stopped `running` session can return to +that phase through automatic reconciliation or explicit workflow recovery; neither +path skips the session checks or jumps straight to `done`. Recovery of `verifying` +or `publishing` retries that saved stage. The full error and command rules are in +section 8. Sources: [dispatcher.ts — workOnce, resolveAnalysis, resolveBase](../src/dispatcher.ts), [executor.ts — analyze, selectBase, GitWorkspace.prepare](../src/executor.ts), @@ -173,32 +272,86 @@ Sources: [dispatcher.ts — workOnce, resolveAnalysis, resolveBase](../src/dispa ```mermaid sequenceDiagram participant D as Dispatcher / executor - participant S as OpenCode main session + participant S as Saved OpenCode main session participant R as Worker runtime participant G as GitHub issue - participant U as Authorized user - D->>D: Save sessionID before session creation + participant U as Authorized user / operator + opt No saved session ID + D->>D: Persist sessionID before contacting OpenCode + end D->>S: Get session, create only on explicit not-found - D->>D: Validate worktree location, save sessionReady - D->>D: Save promptAttempted before sending initial prompt - D->>S: Implement agreed scope in task worktree + D->>D: Validate worktree location and save sessionReady + opt Initial prompt not attempted + D->>D: Persist promptAttempted + D->>S: Implement agreed scope with task marker + end + opt Explicit recovery queued, continuation not attempted + D->>S: Wait until current execution is idle + D->>S: Read saved outcome + alt Outcome is not succeeded + D->>S: Confirm original task marker in context + D->>D: Persist recovery.attempted before sending + D->>S: Continue same task with recovery marker and deterministic message ID + else Already succeeded + Note over D,S: Do not send another continuation + end + end D->>S: Wait for completion - opt Clarification or permission required + opt OpenCode permission evaluation + S->>R: Permission action, resources and current effect + alt Explicit OpenCode allow or deny + R-->>S: Preserve effect + else Exact saved decision for this main session + R-->>S: Apply saved allow or deny + else Repo file policy enabled, eligible action and all paths inside repo or task worktree + Note over R: Resolve canonical paths, exclude media helpers and unanswered questions + R-->>S: Allow file access without an issue question + else Permission still needs approval + R-->>S: Deny this attempt and use the question flow below + end + end + opt Clarification or remaining permission question S->>R: ask_issue / intercepted question / permission ask - R->>D: Register question against main task session + R->>D: Register against main task session D->>D: Persist pending question D->>G: Publish signed question with stable marker R-->>S: Stop work and finish turn - D->>D: Preserve running phase, set waiting status + D->>D: Preserve running phase, set waiting U->>G: Reply in the same issue - D->>G: Next scan reads eligible reply - D->>D: Persist answer, set ready - D->>S: Resume same main session with deterministic answer message ID + D->>G: Scan reads eligible answer + D->>D: Persist answer and set ready + D->>S: Resume same session with deterministic answer message ID D->>S: Wait for completion end - D->>S: Read context and final outcome - D->>D: Confirm initial prompt marker and successful final assistant message - D->>D: Advance to verifying + alt Operator closes task + U->>D: Confirm Stop and close task in bot menu + D->>D: Persist closing and reject new checkpoints and prompts + D->>S: Interrupt known task sessions with resume false and wait for idleness + D->>D: Drain in-flight worker and persist closed + Note over D,G: Preserve work and history, no GitHub closure request + else Operator cancels the round + U->>D: Confirm Cancel current round + D->>D: Persist cancelling and reject new execution checkpoints + D->>S: Interrupt saved sessions with resume false and wait for idleness + D->>D: Drain worker and question posts, archive round, enter watching + Note over D,G: Keep PR tracking and future feedback, preserve cancelled worktree + else Owner is disposed + Note over D,S: Release local wait without interrupting healthy execution + Note over D: Replacement owner loads queue and rejoins saved session + else Session deadline expires + D->>S: Interrupt execution + D->>D: Save running / blocked with sessionStopped + else Wait completes + D->>S: Read context and final outcome + alt Valid task marker, admitted recovery marker if required, and successful final assistant + D->>D: Save final public assistant text with session and round + D->>D: Clear recovery state and advance to verifying + else Unsuccessful final outcome or assistant + D->>D: Save session-stop block for later reconciliation + else Missing marker or wrong location + D->>D: Block for inspection, no automatic prompt replay + end + end ``` - A saved `sessionID` is reused after transport failure. A network error when @@ -217,6 +370,13 @@ sequenceDiagram phases or enforced review gates. The executor's `verifying` phase remains separate. A prose blocker in the final summary does not set `blocked` status; user-input blockers must go through `ask_issue`. +- `autoApproveRepositoryFiles` is a repository opt-in, carried in the generated + worktree runtime settings. It handles `external_directory`, `read`, and `edit` + requests for canonical paths within the configured checkout or assigned + worktree. Native workers inherit task association through parent lookup; new + rounds use the same repository policy. Explicit denials, pending questions, + media-helper restrictions, and shell rules are unchanged. See + [configuration and reload behavior](configuration.md#repository-file-approvals). - One unresolved question is retained at a time. Runtime hooks remove tools and reject non-question tool execution while a question is pending. Native subagent questions are attached to the main task; the reply resumes the main session. @@ -233,35 +393,62 @@ sequenceDiagram uses a deterministic message ID for retry reconciliation. - A pending question prevents verification and PR publication. Waiting tasks release worker selection so other queued tasks can proceed. -- A timed-out wait interrupts the server session and blocks for inspection. - Uncertain initial prompt delivery, a wrong session location, or a final outcome - other than `succeeded` with a non-error assistant `finish: stop` also blocks. +- A session wait deadline attempts to interrupt the server session and records + a `SessionStopped` block. A final outcome other than `succeeded`, a missing final + assistant, an assistant error, or a finish other than `stop` also records a + session stop. A successful manual continuation can be discovered automatically. +- Explicit workflow recovery waits for existing execution before deciding whether + to send a continuation. It sends nothing if the saved outcome is already + `succeeded`; normal final-message validation still applies. Otherwise it checks + the original task marker, persists `recovery.attempted`, and sends the recovery + marker with a deterministic message ID. A later retry never blindly resends + that attempted prompt. Missing recovery evidence blocks for inspection. +- Wrong session location and uncertain original prompt delivery are ordinary + `Blocked` errors, not session-stop eligibility. A pending question still prevents + publication. Successful execution clears `sessionStopped` and `recovery` as the + dispatcher advances to `verifying`. Sources: [executor.ts — runSession](../src/executor.ts), -[runtime.ts](../src/runtime.ts), [prompt.ts](../src/prompt.ts), -[dispatcher.ts — question, publishQuestion](../src/dispatcher.ts). +[runtime.ts](../src/runtime.ts), [repository-permissions.ts](../src/repository-permissions.ts), [prompt.ts](../src/prompt.ts), +[dispatcher.ts — workOnce, question, publishQuestion, restartWorkflow](../src/dispatcher.ts). ## 5. Optional media inspection ```mermaid -flowchart LR - Call[Main session calls inspect_media] --> Cap{Main model supports requested input?} - Cap -->|Yes| Main[Use same model in separate helper session] +flowchart TD + Call[inspect_media request] --> MainTask{Owning main task has route and worktree?} + MainTask -->|No| Error[Return tool error] + MainTask -->|Yes| Cap{Main model supports requested vision or audio input?} + Cap -->|Yes| Main[Select main model for a separate helper session] Cap -->|No| Other{Configured mediaModel supports input?} - Other -->|Yes| Helper[Use configured helper model] - Other -->|No| Ask[Ask in issue for configuration update or text description; wait] - Main --> Files[Validate HTTPS URLs or files inside worktree] + Other -->|Yes| Helper[Select configured helper model] + Other -->|No| Ask[Post issue question for configuration or text description and wait] + Main --> Files[Validate 1 to 8 HTTPS URLs or real files inside worktree] Helper --> Files - Files --> Session[Persist helper ID; create or reuse read-only session] - Session --> Result[Send attachments; wait; validate completed answer] - Result --> Return[Return findings to main session; main model stays unchanged] + Files -->|Invalid input| Error + Files --> Guard{Task running with no unresolved question?} + Guard -->|No| Error + Guard -->|Yes| ID[Persist deterministic helper ID for main session and tool call] + ID --> Session[Get saved helper or create only on explicit not-found] + Session --> Prompt[Send deterministic attachment prompt, hooks disable all tools] + Prompt --> Wait[Wait with session deadline] + Wait -->|Timeout| Interrupt[Interrupt helper with resume false and return error] + Wait -->|Other failure| Error + Wait -->|Completed| Result{Succeeded outcome and non-error final assistant with finish stop?} + Result -->|No| Error + Result -->|Yes| Return[Return findings to main session, keep main model unchanged] + Closing[Local task closing, closed, cancelling or watching] --> Deny[Reject helper registration and runtime lookup] ``` -Only the active main bot session can delegate media. Helpers have no tools. +Only the owning main bot session can delegate media; the helper-registration +step also requires `running`, active tracking and no unresolved question. Helpers have no tools. URLs cannot contain credentials; local paths are resolved and must remain inside the worktree. GitHub credentials are not forwarded to media URLs. A helper uses stable session and prompt IDs for a given call. Helper failures return errors; -a helper timeout attempts interruption. +a helper timeout attempts interruption. Native implementation subagents are a +separate mechanism: they may use permitted tools, while their questions route back +to the main task through parent-session lookup. Neither kind of helper creates +another dispatcher round or publishes its own PR. Source: [runtime.ts — inspect_media](../src/runtime.ts). @@ -269,40 +456,107 @@ Source: [runtime.ts — inspect_media](../src/runtime.ts). ```mermaid flowchart TD - Start[Session completed] --> Identity[Check expected worktree, branch, and shared repository] + Start[Validated session success or retry of verifying phase] --> Report[Reuse saved completion summary or recover from saved session] + Report --> Identity[Require saved workspace and base, exact managed root, branch and shared repository] Identity --> Base[Require baseSha ancestor of HEAD and no unresolved conflicts] - Base --> Checks[Run configured repository checks sequentially] - Checks --> Diff[Recheck worktree identity; git diff --check] - Diff --> Stage[git add --all; check staged diff; record staged tree] + Base --> Checks[Run configured checks sequentially, or none if list empty] + Checks -->|Configured check fails| Block[blocked at saved phase, retain work] + Checks -->|Pass| Diff[Recheck identity and git diff --check] + Diff --> Stage[git add --all, check staged diff and record staged tree] Stage --> Commit[Commit staged changes if any] - Commit --> Validate[Require committed tree equals recorded tree, changes versus base, and clean worktree] - Validate --> Save[Save checks and exact commit SHA; phase publishing] - Save --> Find[Find existing PR for task branch, including closed PRs] + Commit --> Validate[Require committed tree matches, changes versus base and clean worktree] + Identity -->|Explicit consistency guard fails| Block + Base -->|Unresolved conflicts| Block + Validate -->|Explicit consistency guard fails| Block + Validate -->|Pass| Save[Persist summary, checks and exact commit SHA, phase publishing] + Retry[Retry saved publishing phase] --> Body + Save --> Body[Recover missing legacy summary, save original report and render body] + Body --> Find[Find branch PR including closed PRs] Find --> Follow{Follow-up round?} Follow -->|Yes| Open{Existing PR open?} - Open -->|No| Block[Block; retain changes in worktree] - Open -->|Yes| Push[Validate origin and worktree; require saved HEAD and clean tree; push exact SHA] + Open -->|No| Block + Open -->|Yes| Pushed{Saved pushedCommit matches verified SHA?} + Pushed -->|Yes| Description + Pushed -->|No| Push[Validate origin, workspace, saved HEAD and clean tree, push exact SHA] Follow -->|No| Exists{PR already exists?} - Exists -->|Yes| Done[Save PR and publication time; pr_opened / done] - Exists -->|No| Title[Generate and persist descriptive English PR title] - Title --> Issue[Require issue still open] - Issue --> PushNew[Validate origin and worktree; push exact verified SHA] - PushNew --> Create[Create or reconcile signed PR targeting pinned base] - Create --> Done - Push --> Done + Exists -->|Yes| Closed{PR closed?} + Closed -->|Yes| Done[Record PR and publication time, pr_opened / done] + Closed -->|No| Description + Exists -->|No| Title[Generate title only if no saved prTitle] + Title --> Issue{Issue still open?} + Issue -->|No| Block + Issue -->|Yes| NewPushed{Saved pushedCommit matches verified SHA?} + NewPushed -->|Yes| Create + NewPushed -->|No| PushNew[Validate origin and workspace, push exact verified SHA] + PushNew --> SaveNew[Persist pushedCommit] + SaveNew --> Create[Create or reconcile signed PR against pinned base] + Create --> Description[Read open PR and remote branch, reconcile managed description] + Push --> SavePush[Persist pushedCommit] + SavePush --> Description + Description -->|Branch matches, PR head differs| Pending[Retry saved publication with backoff and attempt limit] + Pending --> Retry + Description -->|Closed PR, changed branch, edited managed block or oversized body| Block + Description -->|Unchanged or update succeeds| Acknowledge[Persist published body checkpoint] + Acknowledge --> Done + Failure[Other command, model or transport error] --> Policy[Keep current phase and apply retry policy in section 8] + Close[Operator closes task before publishing starts] --> Drain[Finish in-flight local operation, reject next checkpoint] + Drain --> Preserve[Do not publish, preserve existing local changes] + Cancel[Cancel round before publication starts] --> DrainRound[Persist cancelling, drain local work and reject publication] + DrainRound --> Watch[Archive work and watch for new comments] + InFlight[Publication already in flight] --> Refuse[Reject close or cancel request and retry after completion] ``` +Resuming `running` validates the saved session first; retrying `verifying` runs +checks again. Retrying `publishing` uses the saved verified SHA without rerunning +checks implicitly. If a push is still required, the worktree must still match +that SHA. After a checkpointed push, description reconciliation checks GitHub's +branch and PR instead; it does not publish later local edits. An already pushed +branch does not by itself make a task complete. + The configured checks are command argument arrays. A failing configured check produces `blocked`. With no configured checks, only Git consistency checks run; -the PR explicitly states that automated tests were not run. Commit hooks changing +the PR explicitly says the dispatcher did not independently rerun agent-reported +tests. Commit hooks changing the recorded tree, a dirty worktree after commit, or no diff from the base block publication. Other command failures use the general error policy below. -The PR body contains the analysis, `Closes #N`, checks, session ID, and verified -commit SHA. Push uses `COMMIT:refs/heads/TASK_BRANCH` without force. The first -publication reconciles an existing branch PR by recording it without another -push; follow-ups require an open PR and push the new verified commit. Follow-ups -do not regenerate the existing PR title or body. +The PR body uses the final public text of the successful assistant response, +with its Markdown preserved, rather than the pre-work analysis acknowledgement. +The executor saves it with the session and round before verification; verification +adds the checks and exact commit to the same snapshot. No extra model call rewrites +the report. Reasoning, tools, and failed or unfinished responses are excluded. +Missing legacy snapshots are read from the saved session; a missing session or +empty response produces an explicit summary-unavailable notice, never analysis +as a fallback. Transport errors retain the stage for retry. + +The body keeps the original report and replaces one **Latest update** section on +follow-ups. Dispatcher checks appear separately from agent-reported tests, followed +by `Closes #N`, session, round and verified commit. Push uses +`COMMIT:refs/heads/TASK_BRANCH` without force. The first publication reconciles an +existing branch PR without another push; follow-ups require an open PR and push +the new verified commit before updating its description. A successful push saves +`pushedCommit`; retries skip that push when it matches the verified SHA, including +after an owner restart. New rounds clear this checkpoint. If push succeeded but +its response or checkpoint was lost, normal non-force push reconciliation still +applies. The title is retained. +An already closed first-round PR is recorded without editing its description. + +Before reconciling the description and again before PATCH, compare the remote +branch ref with the verified SHA. If the branch matches but the PR head is stale, +`PullHeadPending` uses the normal backoff and `maxAttempts` policy at `publishing`. +It does not rerun implementation, verification, or a checkpointed push. A closed +PR or different branch SHA blocks with a distinct error; a stale PR view is not +permission to overwrite a changed branch. The guard also checks the branch when +the PR view already reports the expected SHA. + +A stable HTML marker pair encloses the bot-managed description. Notes outside +it are preserved; an edited or removed managed section blocks publication rather +than overwriting it. The last acknowledged body is checkpointed, so a lost update +response can be reconciled without duplicate sections. Exact legacy descriptions +can be replaced; otherwise unmarked content is retained and the managed section +appended. GitHub is reread before writing to detect concurrent edits, although +there is no atomic compare-and-swap across that read and write. See +[publication recovery and limits](advanced.md#pr-description-recovery) for details. Signed comments use stable `opencode2` markers; reconciliation looks for a marker posted by the authenticated account. This covers analysis acknowledgements, @@ -310,81 +564,135 @@ questions, and merge acknowledgements after a lost response. Sources: [executor.ts — GitWorkspace.verify, push, title](../src/executor.ts), [dispatcher.ts — publishing](../src/dispatcher.ts), -[github.ts — ensureComment, ensurePull](../src/github.ts). +[github.ts — ensureComment, ensurePull, updatePullBody](../src/github.ts), +[pr-description.ts — extraction, rendering and reconciliation](../src/pr-description.ts). ## 7. Feedback, merge approval, and tab closure ```mermaid flowchart TD - Done[pr_opened / done] --> Feedback{Pending issue feedback?} - Feedback -->|Yes| Round[Next worker pass starts new round on same branch and worktree] - Round --> Guard[Require open issue and open original PR; analyze and acknowledge again] - Feedback -->|No| Idle{No eligible execution task selected?} - Idle -->|No| Later[Wait for a later worker pass] - Idle -->|Yes| Enabled{Auto-merge enabled and task eligible?} - Enabled -->|No| Later - Enabled -->|Yes| Scan[Scan again before considering merge] - Scan --> Fresh{New feedback or closed PR?} + Pending[Authorized comment enters pendingFeedback] --> Done{Current task done or watching?} + Done -->|No| Keep[Retain comment while running, waiting or blocked] + Keep --> Recovery[Session recovery and publication must finish first] + Recovery --> Done + Done -->|Yes| Round[Next worker pass starts a new round, isolating worktree after cancellation] + Round --> Snapshot[Retain original report and published body, reset current completion] + Snapshot --> Guard[Require open issue, open original PR and authorized feedback, then analyze again] + Idle[Worker has no eligible execution task] --> Eligible{Auto-merge enabled and done or watching task with published head eligible?} + Eligible -->|No| Later[Wait for a later worker pass] + Eligible -->|Yes| Since{publishedAt exists?} + Since -->|No| Window[Record current time as fresh merge-comment window] + Window --> Later + Since -->|Yes| Scan[Scan again before considering merge] + Scan --> Fresh{Pending feedback or closed PR?} Fresh -->|Yes| Later - Fresh -->|No| Head[Require open non-draft PR with published commit as current head] - Head --> Review[Evaluate latest decisive reviews and configured approval comments] - Review --> Changes{Any outstanding changes-requested review?} - Changes -->|Yes| Later - Changes -->|No| Author{Eligible approver in allowlist with write, maintain, or admin permission?} - Author -->|No| Later + Fresh -->|No| Detail[Read GitHub PR details] + Detail --> Already{Already merged?} + Already -->|Yes| Ack[Post or reconcile signed merge acknowledgement, persist merged and closed PR] + Already -->|No| Head{Open, non-draft PR with saved published head?} + Head -->|No| Poll[Clear mergeError, set mergeNextAt at least 60 seconds later] + Head -->|Yes| Review[Match latest decisive reviews to exact SHA, match merge comments after publication] + Review --> Author{No outstanding changes request and eligible approver has write, maintain or admin access?} + Author -->|No| Poll Author -->|Yes| Ready{mergeable and mergeable_state clean?} - Ready -->|No| Retry[Record mergeError; retry no sooner than 60 seconds] + Ready -->|No| Error[Record mergeError and delayed retry, preserve task status] Ready -->|Yes| Merge[Request GitHub merge with exact SHA and configured method] - Merge --> Ack[Post signed merged acknowledgement; persist merged and closed PR] - Manual[Manual PR close or merge] --> Poll[Next repository scan refreshes PR state] - Ack --> UI[TUI receives activity or recovers it by polling] - Poll --> UI - UI --> Tabs[Close known task and helper tabs when idle; preserve session history] + Merge -->|Merged| Ack + Merge -->|Rejected or request fails| Error + Poll --> Later + Error --> Later + Manual[Manual PR close or merge] --> Refresh[Repository scan refreshes tracked PR state] + Ack --> UI[Activity events and TUI polling every 10 seconds] + Refresh --> UI + Local[Task closure finishes with status closed] --> UI + Menu[bot menu: select issue or Repositories] --> Action[Open session, details, close tabs, restart, cancel round, resume tracking, stop and close] + Menu -->|Repositories| Repos[Read connected server inventory, choose repository, show timestamped details] + Repos --> Observe[No task or scheduler mutation, no activation of other owners] + Action -->|Cancel current round| CancelRound[Confirm, stop round, retain PR and issue tracking] + Action -->|Resume issue tracking| Resume[Validate closed task, skip backlog and watch future comments] + CancelRound --> UI + Resume --> UI + Action -->|Stop and close task| Confirm[Confirm stop and close, queue durable closing request] + UI --> Busy{Associated tab busy?} + Busy -->|Yes| Defer[Retry closure on a later snapshot] + Busy -->|No| Tabs[Close known task and helper tabs once, preserve sessions and worktrees] + Status[Independent monitor polling every five seconds] --> Sidebar[Append BOT RUNTIME to existing sidebar] + Selected[Selected session changes] --> Sidebar + Sidebar --> Details[Show owner operations and matching task, or active task fallback] + Status --> Missing[On failure retain last readings and mark stale] + Missing --> Sidebar ``` -Merge eligibility requires `done`, a tracked nonclosed PR, a saved commit, no +Merge eligibility requires `done`, or `watching` with a saved `publishedHead`, +a tracked nonclosed PR, a saved published commit, no merged flag, no pending feedback, and an elapsed `mergeNextAt`. Missing -`publishedAt` in an older queue starts a fresh approval window rather than using -historical approval. Merge checks run when the worker has no execution task to +`publishedAt` in an older queue starts a fresh window for merge comments, which +have no commit binding. Formal reviews still require the exact verified SHA. +Merge checks run when the worker has no execution task to advance, rather than immediately after every publication. For each reviewer, the latest `APPROVED`, `CHANGES_REQUESTED`, or `DISMISSED` review is decisive. Any outstanding changes request suppresses all approval candidates, including comment approvals. An approval review must reference the -current verified SHA and have been submitted after `publishedAt`. An approval -comment must have been created after that time and match a configured phrase +current verified SHA and have a valid submission timestamp; it may precede +`publishedAt`. Finishing or retrying a PR description update does not invalidate +approval of unchanged code. An approval comment must have been created after +`publishedAt` and match a configured phrase as a whole message after case, whitespace, and trailing `.`/`!` normalization. Bot comments and marked automation comments are excluded. Default phrases are `/merge`, `lgtm, merge`, and `approved, merge`; default merge method is `squash`. The permission check then requires an allowlisted candidate with repository write, maintain, or admin access. GitHub still enforces merge requirements. -Every successful round updates `publishedAt`, so old approvals cannot authorize -the next published round. A false merge result schedules another check after -60 seconds; errors also respect GitHub retry timing. An already-merged response -can reconcile a previously lost merge response. - -Follow-up rounds reset analysis, question, current session, checks, and commit; -they retain the branch, worktree, pinned base, and previous session reference. -Preparation reuses the saved worktree path rather than deriving a new path from +Every successful round updates `publishedAt`, so old merge comments cannot +authorize the next published round. Formal reviews remain valid for the same +commit only; a different published SHA requires a matching review. When the approval method returns false (for example, +no eligible approval or a mismatched head), the dispatcher clears `mergeError` and schedules another check +after 60 seconds. An approved PR that GitHub says is not ready, a rejected merge, +or a request failure records `mergeError`; error retries also respect GitHub timing. +An already-merged response can reconcile a previously lost merge response. + +Follow-up rounds reset analysis, question, current session, session-stop/recovery +state, current completion summary, checks, and commit; they retain the original +report, last published body, branch, worktree, pinned base, and previous session reference. +After cancellation, the next round preserves that worktree as history and creates +a new local branch/worktree from the remote PR head (or pinned base without a PR). +Other preparation reuses the saved worktree path rather than deriving a new path from the branch name. A renamed branch can therefore retain its original directory. Preparation, verification, and push all check the managed path, exact Git root, -branch, and shared repository. A missing checkpoint directory blocks the task +local branch, and shared repository. The remote publication branch stays unchanged. +A missing checkpoint directory blocks the task without creating a replacement worktree. -They create a new main session, whereas an implementation-question reply resumes -the current one. Comments received while working stay queued for a later round. +A follow-up creates a new main session, whereas an implementation-question reply +or workflow recovery retains the current one. Comments received while working, +waiting or blocked stay queued until publication of the current round completes. Feedback after closure can still be queued, but the next round's guards block it. PR-state scanning is independent of auto-merge and issue openness. The TUI subscribes to activity and polls every 10 seconds, including recovery on startup. -It opens background task tabs when enabled and exposes `/bot` for session access. +It opens background task tabs when enabled and exposes `/bot` for task management +and `/restartworkflow` for operator recovery in the owner project. Commands use +owner-scoped RPC; they are not GitHub comment commands. Activity phases `merged` +and `pr_closed` are display values, not new persisted execution phases. +Local statuses `closing`/`closed` end tracking; `cancelling`/`watching` skip a round +while preserving tracking. They are durable and separate from GitHub PR state. Closure cleanup includes known earlier-round sessions and media helpers. Busy tabs wait until idle; cleanup does not delete sessions, interrupt work, or remove worktrees. A manually reopened tab is not repeatedly closed in the same TUI instance. Sources: [dispatcher.ts — workOnce, mergeOnce, scanOnce](../src/dispatcher.ts), [approval.ts](../src/approval.ts), [github.ts — mergeApproved](../src/github.ts), -[ui.ts](../src/ui.ts), [activity.ts](../src/activity.ts). +[ui.ts](../src/ui.ts), [activity.ts](../src/activity.ts), +[sidebar.ts](../src/sidebar.ts), [runtime-panel.ts](../src/runtime-panel.ts). + +The runtime sidebar has an independent five-second observation loop with a +four-second request bound and one-second local countdown updates. It combines +`automation.github.monitor` with scheduler `status`; requests never advance a +phase. It reports actual in-process worker/scan activity rather than deriving it +from `ready`. Last successful readings remain visible with stale warnings after +errors or 15 seconds without fresh data. `/botstatus` exposes a text report even +without a sidebar. See [runtime panel details](runtime.md#runtime-status-sidebar) +for task selection, cache observations and display limits. ## 8. Status, retries, and recovery @@ -396,26 +704,69 @@ An error normally preserves the phase so retry continues from its checkpoint. | `ready` | Eligible for worker selection when due. | | `waiting` | Awaiting an issue answer; no implementation or publication while unresolved. | | `retry_wait` | Transient failure; automatic retry after `nextAt`. | -| `blocked` | Explicit `Blocked` error or GitHub HTTP 401, 404, or 422; requires inspection and manual retry. | -| `failed` | Other errors reached `maxAttempts`; manual retry required. | +| `blocked` | Explicit `Blocked` or PR-description conflict, or GitHub HTTP 401, 404, or 422; requires inspection/retry, except a stopped session completed manually is reconciled automatically. | +| `failed` | Other errors reached `maxAttempts`; operator recovery/retry required unless the checkpoint also qualifies as a stopped-session recovery candidate. | | `done` | PR publication/reconciliation completed; feedback and merge monitoring remain possible. | +| `closing` | Operator requested end of tracking; interrupt sessions and drain in-flight work, retaining errors for retry. | +| `closed` | Tracking ended locally; preserve history and work, exclude discovery, runtime hooks, execution and merge monitoring until explicit resumption. | +| `cancelling` | Stop saved sessions and drain the selected round; retry interruption failure without publishing. | +| `watching` | Round cancelled; no automatic execution replay. Track PR state and new feedback, merge only against a saved published head. | ```mermaid -flowchart LR - Work[Current phase] --> Error{Result?} - Error -->|WaitingForAnswer| Wait[waiting, or ready if answer already arrived] - Error -->|Blocked or GitHub 401 / 404 / 422| Block[blocked] - Error -->|Other failure below attempt limit| Retry[retry_wait; preserve phase] +flowchart TD + Work[Execute saved phase] --> Result{Result?} + Result -->|WaitingForAnswer| Wait[waiting, or ready if answer already arrived] + Result -->|SessionStopped| Stop[running / blocked, sessionStopped true] + Result -->|Other Blocked, description conflict or GitHub 401, 404, 422| Block[blocked at saved phase] + Result -->|PR head propagation or other failure below attempt limit| Retry[retry_wait at saved phase] Retry -->|nextAt elapsed| Work - Error -->|Other failure at attempt limit| Fail[failed] - Block --> Manual[Manual retry while worker idle] + Result -->|PR head propagation or other failure at limit| Fail[failed at saved phase] + Stop --> Probe[On available worker pass, probe due saved session without unresolved question] + Legacy[Recognized legacy timeout or outcome block] --> Probe + Probe --> Complete{Matching location and task marker, succeeded outcome and valid final assistant?} + Complete -->|Yes| Rejoin[ready at running, run full session validation again] + Rejoin --> Work + Complete -->|No or probe fails| Retain[Retain block and feedback, probe no sooner than 30 seconds later] + Retain --> Probe + Command[Operator uses restartworkflow] --> Guards{Known actively tracked task, no unresolved question and no closed or merged PR?} + Guards -->|No| Reject[Return actionable error, preserve checkpoint] + Guards -->|Yes| Eligible{Status blocked or failed?} + Eligible -->|No| Noop[accepted false, do not duplicate scheduled or completed work] + Eligible -->|Yes| Safe{Route exists, and running phase is a recognized session stop?} + Safe -->|No| Reject + Safe -->|Yes| Recover[Persist recovery ID for running phase, clear error and attempts, ready at saved phase] + Recover --> Work + Block --> Manual[Operator uses retry while worker and maintenance idle] + Stop --> Manual Fail --> Manual Manual --> Restart{restartSession requested?} - Restart -->|No| Reset[Clear error and attempts; ready at saved phase] - Restart -->|Yes| Cancel[Interrupt old session; clear session ID and prompt flag] - Cancel --> Earlier[Return to commented if acknowledgement exists, otherwise queued] + Restart -->|No| Reset[Clear error and attempts, ready at saved phase] + Restart -->|Yes| Cancel[Interrupt old session, clear sessionID and promptAttempted] + Cancel --> Earlier[Return to commented if commentID exists, otherwise queued] Earlier --> Reset Reset --> Work + Close[bot menu: Stop and close task] --> Flight{Publication or merge already in flight?} + Flight -->|Yes| RejectClose[Reject closure, wait and try again] + Flight -->|No| SaveClose[Persist closing before interruption] + SaveClose --> Interrupt[Interrupt with resume false, missing sessions count as stopped] + Interrupt --> Drain[Wait for current worker and pending question posts] + Drain --> Again[Interrupt again to cover in-flight session creation] + Again --> Closed[Persist closed, preserve history and all local work] + Interrupt -->|Failure| CloseError[Retain closing with error, retry after 30 seconds] + Again -->|Failure| CloseError + CloseError --> Interrupt + Restart[Owner restart with saved closing request] --> Interrupt + CancelRound[Cancel current round] --> Publish{Publication or merge in flight?} + Publish -->|Yes| RejectClose + Publish -->|No| SaveCancel[Persist cancelling, block prompts, hooks and checkpoints] + SaveCancel --> DrainCancel[Interrupt with resume false, drain worker and questions, interrupt again] + DrainCancel -->|Success| Watch[Archive round, clear live errors, enter watching] + DrainCancel -->|Failure| RetryCancel[Keep cancelling and error, retry after 30 seconds or owner restart] + RetryCancel --> DrainCancel + Watch -->|New feedback| NewRound[New round in fresh worktree, retain archived work] + NewRound --> Work + Closed -->|Resume issue tracking| Validate[Require open issue and any known PR, skip observed backlog] + Validate --> SaveCancel ``` - Task backoff is `min(3600, 5 * 2^attempts)` seconds, with the incremented @@ -432,10 +783,97 @@ flowchart LR - `retry` accepts only blocked or failed tasks and is rejected while the worker or maintenance is busy. `restartSession` does not delete the worktree or changes; it restarts session execution from the appropriate earlier phase. +- Automatic probes select only `running` tasks with a saved session, status + `blocked` or `failed`, a recognized session stop, elapsed `nextAt`, and no + unresolved question. Probes run when the worker can begin another pass, not + concurrently with an already-running worker invocation. An unsuccessful probe + delays the next one by at least 30 seconds. Successful saved sessions re-enter + `running` validation, then configured checks and publication. This recognizes legacy + timeout/outcome errors as well as the persisted `sessionStopped` classification. + It never infers success from a clean worktree or an already-pushed commit. +- `/restartworkflow` queues a durable recovery request for a stopped task without + resetting its phase, worktree, branch, PR, or feedback. It can be queued while + another task works. For a stopped execution, the executor waits for idleness, + verifies the original task marker, and sends a checkpointed continuation only + if still incomplete. A lost response never replays that prompt blindly. Admission + does not interrupt active sessions; normal session deadlines still apply. + Unresolved questions and unsafe errors remain blocked. A missing task, pending question, or closed/merged PR produces an error + before the status check. Other statuses return `accepted: false`; this means no + recovery was queued, not that a running session was stopped. Eligible tasks need + a route, and `running` additionally needs a recognized session-stop checkpoint. - Merge errors use `mergeError` and `mergeNextAt`; they do not turn a published task into an implementation failure. - Reloading the owner project after restart restores polling from durable state. Activity events are notifications, not the durable queue. -Sources: [dispatcher.ts — workOnce, retryOnce](../src/dispatcher.ts), +Sources: [dispatcher.ts — workOnce, restartWorkflow, retryOnce](../src/dispatcher.ts), [scheduler.ts](../src/scheduler.ts), [state.ts](../src/state.ts). + +### Recovery commands and checkpoints + +Run CLI commands from the primary owner checkout, not a task worktree. +`restartworkflow` changes dispatcher state; it does not restart the OpenCode +service, resume a paused scheduler, or perform a scan itself. + +| Action | Saved phase and session | Effect | +| --- | --- | --- | +| Continue a stopped session in the TUI | Same session, `running` phase | Once successful and recognized by the probe, normal session validation, checks and publication resume automatically. | +| `/bot` → Cancel current round, or `cancelround KEY` | Archive round and retain PR tracking | Persist cancelling, drain work, then watch new feedback. Next round uses a fresh worktree. | +| `/bot` → Resume issue tracking, or `resumetracking KEY` | Preserve closed history and work | Validate GitHub objects, skip old backlog, stop saved sessions and watch future comments. | +| `/bot`, select an issue, then Stop and close task | Keep phase, sessions, worktree, branch and PR | Persist closing, interrupt saved sessions and drain work, then close local tracking. No GitHub issue/PR close or deletion. | +| `/bot`, select an issue, then Close session tabs | No checkpoint change | Close idle local tabs only, continue tracking. | +| `/restartworkflow`, then select an issue | Same phase, session, worktree, branch and PR | Queue recovery for an eligible blocked/failed task. A stopped session may receive one continuation; verification/publication retries its saved stage. | +| `opencode2-automation restartworkflow 'owner/repository#123'` | Same as the TUI command | Calls `automation.github.restartworkflow` with `{ key }`, returning `{ accepted }`. | +| `opencode2-automation retry 'owner/repository#123'` | Same saved phase and session | Clear blocked/failed status while worker and maintenance are idle; it does not send a continuation merely because a session was stopped. | +| `opencode2-automation retry 'owner/repository#123' --restart-session` | Earlier phase, new session identity on execution | Interrupt the old session and clear its ID and initial-prompt flag; preserve the worktree. Use after inspecting uncertain delivery, not as a routine publication shortcut. | +| `opencode2-automation resume` | No task checkpoint reset | Unpause the scheduler; accepted task execution has its own loop. | +| Restart service, then activate the owner | Reload durable state | Restore polling and worker selection; preserve unresolved questions and nonrecoverable blocks. | + +The queue stores `sessionStopped` to distinguish execution stops from other +blocks. `recovery.id` identifies an explicit continuation request and +`recovery.attempted` records the decision to send it before calling OpenCode. +Both are cleared after successful execution and when the next feedback round +starts. Worktree, branch, pinned base, session history and queued comments remain +separate durable checkpoints. A failed test is never treated as session success. + +Regression evidence: [core.test.ts](../test/core.test.ts), +[executor.test.ts](../test/executor.test.ts), [runtime.test.ts](../test/runtime.test.ts), +[lifecycle.test.ts](../test/lifecycle.test.ts), [ui.test.ts](../test/ui.test.ts). + +### Local closure and missing GitHub objects + +`/bot` also exposes the saved error and task identity before any operator action. +Closing is independent of GitHub availability, issue state, PR state, route validity +and pending questions. The durable `closed` record prevents the same issue key +from being rediscovered until explicit Resume issue tracking. Scans skip closing/closed/cancelling records before PR, missing-issue +and comment reads; late checkpoints and errors cannot reactivate them. There is +no automatic deletion based on an ambiguous GitHub 404 response. + +Closing preserves pending feedback and questions as history, but does not process +them. The main runtime and media helper registration reject further task activity. +The task's in-flight worker operation may finish local work before closure completes; +no subsequent verification/publication phase starts. Already-started publication +or merge refuses closure admission. In-flight comments cannot be recalled. Errors +while stopping sessions remain visible as `closing`, retried after 30 seconds or +from the menu. `accepted` acknowledges the request, not finished interruption. + +The sidebar names up to three blocked/failed/closing/cancelling tasks with their saved errors, +excludes locally closed tasks from live queue counts, and shows a separate closing +count. `/bot` retains all task records and their actions, including opening the +saved conversation after closure. See [runtime management](runtime.md#manage-tasks-from-bot). + +While a closure is pending, the dispatcher does not start another worker pass. +An unrelated already-running task can finish; scanning continues for other tasks. +The monitor reports task maintenance until closure completes. + + +Round cancellation uses the same interrupt/drain discipline as closure but ends +in `watching`. It retains pending new feedback, clears the cancelled question and +live error, and archives the stopped round. Exact permission replies for archived +question IDs are ignored. Resume issue tracking is a separate explicit action for +closed tasks: validate the open issue/PR, skip already-observed backlog, then +cancel any saved execution and watch future comments. Historic errors remain in +Show details; closed/watching sidebar snapshots do not display them as live failures. +`controlVersion` and round ordering prevent late TUI events from reviving old work. +See [cancellation behavior](runtime.md#cancelling-one-round-while-keeping-tracking) +and [persistence details](advanced.md#round-cancellation-and-resuming-tracking). diff --git a/docs/configuration.md b/docs/configuration.md index 4b2f7db..9f9760d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -53,20 +53,25 @@ Use a model available in your own OpenCode 2 installation. Optional fields: | Field | Purpose | | --- | --- | +| `autoApproveRepositoryFiles` | Opt-in automatic `external_directory`, `read`, and `edit` approval within this repository and its assigned task worktree; omitted/`false` keeps existing permission behavior. Shell rules are unchanged. | | `baseBranch` | Base for new worktrees and PRs; defaults to the GitHub default branch. | | `capabilities` | Main model support: `text`, `vision`, `audio`; defaults to `["text"]`. | | `mediaModel` | Separate helper model and its capabilities; example below. | | `systemPromptFile` | Optional Markdown instructions appended to the bundled bot prompt; path relative to the primary checkout, or absolute. | | `trigger` | Mention that starts work; defaults to `@opencodebot`. | | `everySeconds` | Polling interval; defaults to 60 seconds. | -| `check` | Test command as an argument array, such as `["npm", "test"]`; `false` skips tests. | +| `check` | Test command as an argument array, such as `["npm", "test"]`; `false` skips tests. If omitted, detect a package test script and its package manager; fail setup if no test command is found. | | `authors` | GitHub usernames allowed to request work and authorize merging (merge also requires repository write access). | | `signature` | Signature appended to every posted comment and PR description; defaults to `your-github-login[OpenCode2]`. | | `autoMerge` | Automatic merge settings: `enabled` (default `true`), `method` (default `squash`), and exact approval `comments`. | When tests are skipped, the PR explicitly reports that automated tests were not run. Git consistency checks and the requirement for an actual change remain. -Restart the service while idle after changing configuration. +Restart the service while idle after changing configuration, then activate each +owner project again. Recovery commands do not reload configuration or reset a +pinned base. Session/command deadlines and worker retry limits are advanced +`GithubOptions`, not fields accepted by the strict easy JSON schema above; see +[advanced options](advanced.md#options). For noninteractive setup, use `--yes` to accept defaults for omitted options. Provide the model and a test command (or explicitly skip tests): @@ -76,11 +81,53 @@ cd /absolute/path/to/your-project "$HOME/.local/bin/opencode2-automation" init --model provider/model --skip-tests --yes ``` -Optional flags: `--base-branch develop`, `--capabilities text`, +Optional flags: `--auto-approve-repository-files`, `--base-branch develop`, `--capabilities text`, `--media-model provider/vision-model`, `--media-capabilities text,vision`, `--system-prompt .opencode/bot.md`. With `--yes`, supply a helper explicitly if you want media support with a text-only main model. +## Repository file approvals + +During interactive `init`, choose **yes** for "Automatically approve file access +in this repository and its task worktrees". The default is **no**. Noninteractive +setup opts in with `--auto-approve-repository-files`; `--yes` alone does not enable it. +For an existing project, add this field to its existing `.opencode/automation.json` +without rerunning `init` or replacing the other settings: + +```json +{ + "model": "provider/model", + "autoApproveRepositoryFiles": true +} +``` + +This is a plugin setting for this repository, not a global OpenCode permission. +The runtime resolves the configured checkout path automatically, and also includes +the assigned task worktree when advanced state storage places it outside the +checkout. The policy follows new rounds and native subagents through their main +task; it is not tied to a previous session's `/allow` reply. Other repositories +and ordinary non-bot sessions are unaffected. No user-wide configuration is written. + +Only `ask` decisions for `external_directory`, `read`, and `edit` qualify. Every +resource must resolve inside the repository or assigned worktree. Symlink targets +are checked, including existing parents of new files. Paths to siblings, symlink +escapes, unknown patterns, and unresolvable boundaries use the normal approval +flow. Explicit OpenCode denials and exact saved `/deny` decisions remain effective. +Media helpers remain read-only and cannot use tools. Shell, network, subagent +launch, and other action permissions are unchanged; a shell command can affect +files outside its working directory, so its location does not grant blanket consent. + +After changing the setting, restart the idle service and activate the owner again. +The executor refreshes generated worktree runtime settings before continuing a +saved session. An already-pending permission question still needs its exact +`/allow QUESTION_ID` or `/deny QUESTION_ID` reply; enabling this option does not +answer it or bypass unrelated pending questions. Existing configs remain opt-out. +Set the field to `false` and reload to disable automatic file approval; previously +saved explicit approvals still have their original session scope. + +The model must still build, edit, and test in its assigned worktree. Permission to +access another checkout does not make that checkout the correct validation target. + ## Questions, branches, media, and bot instructions - **Questions:** reply in the issue as an account in `authors`; no repeated @@ -152,8 +199,12 @@ verified head SHA; a changed branch cannot be merged using an older approval. The bot does not request a protection bypass. Configure required checks and review rules on GitHub for your repository's policy. -Approvals must be newer than the bot's latest publication. On upgrade, old tasks -start watching for new approvals; historical approvals do not cause a merge. +Formal **Approve** reviews must reference the exact published commit and remain +the reviewer's latest decisive review. They can precede completion of publication: +retrying a description update does not invalidate approval of unchanged code. +Merge comments have no commit binding, so they must be newer than the bot's latest +publication. Old tasks without a publication timestamp start a fresh comment +window on upgrade; existing reviews still require the exact verified commit. Pending issue feedback is processed before attempting a merge. Merge failures are retried at intervals of at least 60 seconds and appear as `mergeError` in `status` and in `/bot`. Successful merges receive a signed PR comment. @@ -163,3 +214,14 @@ acknowledgements. They identify the message in its text; GitHub still attributes posts to the account authenticated by your token. Existing posts are not rewritten. Set `"autoMerge": { "enabled": false }` to disable automatic merging. + +## Repository inventory registration + +`init` and owner activation register the configured checkout for +`opencode2-automation list` and `/bot` → **Repositories**. No new project setting +is required. The registry stores last-resolved repository/base-branch metadata +and timestamped component snapshots under the user's state directory; it does +not replace `.opencode/automation.json` or the shared Git queue. Changes to default +branches are reflected when the owner is activated again. See +[repository inventory](runtime.md#repository-inventory) to import older inactive +configurations and distinguish configured projects from running bots. diff --git a/docs/installation.md b/docs/installation.md index 66c6c2a..b063bdd 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -7,7 +7,9 @@ and troubleshooting. The README's package installation command contains the versioned GitHub asset URL for the stable release promoted into that branch. After publication, automation updates README on `release`; its PR carries the update into `main` when merged. -While that PR awaits review, `release` contains the newer download link. For +While that PR awaits review, `release` contains the newer download link. The +publisher also merges the released version and README into `devel` automatically, +without waiting for the main PR or creating another PR. For upgrades, use that branch's current README rather than a copy from an old archive or tag, and keep the same installation prefix. Prereleases do not replace the stable link. Maintainer setup and retries are in [Release process](releases.md). @@ -42,7 +44,7 @@ hook; remove the OpenCode loaders first as described in the README. ## Prerequisites -- OpenCode **2**, with a working model. Tested SDK version: `0.0.0-beta-19398`. +- OpenCode **2.0.6**, with a working model. Client/plugin SDK version: `2.0.6`. - Node.js 22+, npm, Git, and Bash on macOS/Linux. - GitHub authentication and permission to comment, push, create PRs, and merge. - A target repository with issues enabled and at least one pushed commit. @@ -76,7 +78,8 @@ GitHub user, not the repository owner. The model default is queried from the run OpenCode service; without it, the model is required. Command-line flags override prompts. `--skip-tests` explicitly disables tests; Enter otherwise accepts the shown test command or `skip`. -The wizard also asks for model capabilities, a vision helper if the main model +The wizard also asks whether to auto-approve repository file access (default no), +plus model capabilities, a vision helper if the main model lacks vision, and the base branch. Existing JSON files can be extended manually; see [runtime settings](runtime.md). @@ -93,12 +96,70 @@ registers the server/UI loaders, and runs the wizard. Existing configuration and queues are preserved on upgrades. Do not combine global and project-local copies. No package is published to npm; `private: true` blocks accidental publication. +## TUI rendering and source validation + +The runtime sidebar uses the host's OpenTUI/Solid APIs and semantic theme tokens. +The package declares OpenTUI and Solid peer dependencies; server entrypoint imports +stay independent of renderer initialization. A sidebar update requires both the +owner plugin's monitor RPC and an updated/reopened TUI. An older or unavailable +owner is shown as unavailable rather than silently idle. The integration follows +the [OpenCode CLI plugin API](https://opencode.ai/v2/docs/build/plugins/cli/). + +For development, `npm ci` installs pinned renderer, theme and Bun test dependencies. +`npm run check` includes `npm run test:tui`, which renders the actual sidebar using +Bun's native OpenTUI support and checks live updates, stale readings, tab selection +and a narrow layout. Bun is needed for this native renderer test, not for the +Node-based management CLI. Package validation still imports the server and TUI +entrypoints in an isolated Node installation without initializing a renderer. + ## Testing and migration +### OpenCode 2.0.6 compatibility + +This branch pins `@opencode/client`, `@opencode/plugin`, and the development theme +package to `2.0.6`. Update the automation package when upgrading OpenCode from the +older beta build. An old package can still appear active and scan GitHub while +its management CLI and owner keepalive fail to discover the newer service. + +Service discovery now probes `/api/info`. Owner keepalive verifies the service +PID with `client.server.info()` and updates the same empty maintenance session +through `session.update`. Session interruption uses `resume: false` to stop work +without automatically resuming it. The package does not claim compatibility +with the earlier beta API. + +For headless startup, use `opencode2 api plugin.list --param +'location[directory]=/absolute/path/to/project'` on one line. Confirm that +`automation` is `active` in the response and repeat for each owner after a +service restart. The old `v2.plugin.awaitActivation` operation is unavailable. +`opencode2 api session.active` lists active execution before a planned restart. +After upgrading, verify `opencode2-automation status` inside each owner checkout, +fresh dispatcher/scheduler timestamps, and `/bot` in a reopened TUI. + +### State preservation + Use a separate test repository when testing on another machine. Independent machines do not share queue ownership and can duplicate work on the same issues. This installation procedure does not migrate sessions, queues, or worktrees. -Restart the service only when work is idle. Reopen clients after UI updates. +Back up shared automation state before an upgrade. Once round cancellation has +saved `cancelling` or `watching`, an older plugin that does not recognize those +statuses cannot read that queue. Do not downgrade against live newer state or +delete it to bypass validation; keep the newer plugin or restore a coordinated +backup while owners are stopped. + +Restart the service only when work is idle. Then activate every configured owner +again as shown in the README. Reopen TUI clients after UI updates to register new +commands and action menus such as `/bot` task closure and `/restartworkflow`; merely reopening an old task tab does not +reload its client's command registrations. A service restart preserves queue +blocks and pending questions. Use [workflow recovery](runtime.md#interrupted-sessions-and-workflow-recovery) +for an execution stop instead of reinstalling or deleting state. +After upgrading, activated owners register themselves for +`opencode2-automation list`. Import older, currently inactive standard projects +with `opencode2-automation list --discover /absolute/path/to/projects`; this does +not activate them. Use the same user and `XDG_STATE_HOME` as the service. The TUI's +**Repositories** option reads the connected server registry. See +[repository inventory](runtime.md#repository-inventory) for discovery limits, +status freshness, and missing-directory behavior. + Do not change an active project's `origin` to switch repositories: clone another project and configure it separately. diff --git a/docs/releases.md b/docs/releases.md index 0406733..e6f9718 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -1,6 +1,7 @@ # Release process -`release` is the persistent integration and publication branch. `main` receives +`devel` collects feature changes. `release` is the persistent publication branch. +`main` receives completed releases through PR merges only. Never push a commit directly to `main`, modify its files with the Contents API, or bypass its protection rules. @@ -9,36 +10,87 @@ modify its files with the Contents API, or bypass its protection rules. | Event | Result | | --- | --- | | Commit/push on a feature branch without an open PR | No CI run and no package build. | -| Open, reopen, or update a PR targeting `release` | Full CI on Node 22 and 24, including build and isolated package installation. | -| Merge that PR into `release` | Automatic patch version, tag, package publication, README commit on `release`, and promotion PR. | -| Close that PR without merging | No publication. | +| Open, reopen, or update a PR targeting `devel` or `release` | Full CI on Node 22 and 24, including native TUI rendering with the pinned Bun test dependency, build and isolated package installation. | +| Merge a feature PR into `devel` | Accumulate changes without publication. | +| Merge a same-repository `devel` → `release` PR | Automatic patch version, tag, publication, README commit on `release`, main promotion PR, and automatic merge back into `devel`. | +| Close a PR without merging | No publication. | | Push a version tag pointing to code on `release` | Publish that exact version, without an automatic bump. | | Push a version/README commit without a tag | No publication; automation cannot trigger itself in a loop. | | Open/update the PR from `release` into `main` | `Release ready` validates publication and README without rebuilding the package. | | Merge the promotion PR into `main` | Update `main` only; no new release or package build. | +| Automatic synchronization push into `devel` | No release, package build, or synchronization PR. | ## Automatic patch release -1. Create the feature branch from current `release`. Add accurate bullet points +1. Create the feature branch from current `devel`. Add accurate bullet points under `## Unreleased` in `CHANGELOG.md`; do not pre-bump the package version. -2. Open a PR into `release`. Its current revision must pass `Checks (Node 22)` - and `Checks (Node 24)` before the maintainer accepts and merges it. CI also - verifies that manifests agree and `Unreleased` has notes for the next patch. -3. The merged-PR workflow increments the current package's patch version. It - updates both manifests using npm, moves `Unreleased` into the exact new version - section, and records the source PR, merge SHA, and version in - `.github/release-state.json` for retries. -4. The workflow commits these files on `release`, creates an annotated `vVERSION` - tag, and pushes the branch and tag atomically. An existing tag is never moved. -5. In the same workflow run, it checks out the tag, builds the package, verifies - its installation and checksum, and creates a draft GitHub Release. It uploads - both assets before publishing the draft with the exact changelog notes. - Publication does not depend on a second workflow being triggered by the bot's tag. -6. After GitHub confirms publication and uploaded assets, it returns to `release`, - commits the versioned README block there, and opens or updates the single - `release` → `main` PR. Publication failure never advances README or creates a PR. -7. Approve the promotion's checks and review, then use **Create a merge commit**. - Preserve the long-lived `release` branch; do not delete it after merging. +2. Open a PR into `devel`. Its current revision must pass `Checks (Node 22)` and + `Checks (Node 24)` before review and merge. Every new commit reruns these checks. + Accumulate as many feature PRs as needed; none of these merges publishes a release. +3. When ready for a release, open a same-repository `devel` → `release` PR. Its + current revision must pass the same checks, including manifest consistency and + nonempty `Unreleased` notes. Review it and use **Create a merge commit**. + CI rejects other source branches targeting `release`; the publisher also + validates the source independently. Keep both long-lived branches. +4. The merged-PR workflow increments the patch version, updates both manifests, + moves `Unreleased` into the exact new version section, and records the PR, + merge SHA and version in `.github/release-state.json` for retries. +5. It commits these files on `release`, creates an annotated `vVERSION` tag, and + pushes the branch and tag atomically. An existing tag is never moved. +6. In the same run, it checks out the tag, builds and verifies the package and + checksum, then uploads both assets to a draft GitHub Release before publishing + it with exact changelog notes. No second tag-triggered workflow is required. +7. After GitHub confirms stable publication and uploaded assets, it commits the + versioned README block on `release` and opens or updates `release` → `main`. + Publication failure never advances README or creates the promotion PR. +8. The same job automatically merges that published release head into current + `devel` and pushes normally, without a PR. This brings back version metadata, + changelog, release state and README. It does not wait for the main PR to merge. +9. Review the main promotion and use **Create a merge commit**. Keep `release`. + +```mermaid +flowchart TD + F[Feature branch] --> P[PR to devel] + P --> C[CI on opening and each new commit] + C --> D[Reviewed merge into devel - no publication] + D --> R[PR from devel to release when ready] + R --> T[CI and reviewed merge commit] + T --> V[Patch version and tag on release] + V --> B[Build and verify package] + B --> U[Publish release and assets] + U --> W[Commit versioned README on release] + W --> M[Open or update release to main PR] + M --> S[Automatically merge published head into devel] + M --> A[Review and merge PR into protected main] + S --> OK[Normal push preserves development history] + S --> X[Conflict or denied push - fail job and preserve remote work] + X --> RETRY[Resolve and rerun original Release job] +``` + +## Automatic synchronization into devel + +The publisher fetches current `devel` and merges the exact published release head +in a temporary worktree. If `devel` has no new work it fast-forwards; otherwise it +creates a normal merge commit. It never resets `devel`, cherry-picks selected files, +force-pushes, creates a synchronization PR, or pushes to `main`. A completed sync +is detected by ancestry and becomes a no-op on retry. + +If development advances during the push, the publisher fetches the new tip and +retries the merge, up to three attempts. Conflicts stop synchronization without +changing remote `devel`; the publication and main PR remain available. Resolve +conflicts on `devel` while preserving its history, then rerun the original Release +job. Permission/protection failures also fail visibly; the publisher does not +bypass branch rules. A later retry reuses the published version and assets. + +New changes to `CHANGELOG.md`, manifests or the README download block can conflict +with release metadata. Such conflicts require a maintainer's decision; automation +does not silently choose one side. Avoid parallel edits to release metadata while +publishing. Other feature work can continue on `devel` throughout publication. + +The automatic push uses `GITHUB_TOKEN` and creates no PR. There is no push-to-devel +CI trigger and no publication trigger for devel merges, so this cannot start a +release loop. After sync, GitHub may require updating an already-open +`devel` → `release` PR or approval of its workflow run before fresh checks appear. ## Manual version release @@ -67,7 +119,7 @@ must be newer than GitHub's latest stable release. The next automatic patch afte manual `1.0.0` is `1.0.1`. A manual prerelease such as `1.1.0-beta.1` is published as a prerelease; it does -not replace the stable README link or open a promotion PR. It must also originate +not replace the stable README link, open a promotion PR, or synchronize `devel`. It must also originate on `release` and have its own exact changelog section. ## README and protected main @@ -87,29 +139,40 @@ whether a tag belongs to `main`. ## Repository setup -- Create `release` once from the current `main`, then target feature PRs there. - Bootstrap this workflow through the first feature PR into `release`. +- Keep `devel`, `release` and `main` as long-lived branches. Create `devel` from + current `release` when migrating. Retarget pending feature PRs to `devel`. + Include the migration workflow changes in pending feature branches so their PR + revisions use the new CI configuration. Merging these into `devel` is safe and + does not publish a package. + The first reviewed `devel` → `release` merge activates the new publication flow. - Protect `main`: require a PR, review of the current revision, and the `Release ready` status check. Apply protection to administrators as well; disable force pushes and deletion. Automation needs no bypass permission. -- The publisher needs `contents: write` for commits/tags on `release` and release - assets, and `pull-requests: write` to create/update its promotion PR. If `release` +- The publisher needs `contents: write` for commits/tags on `release`, automatic merges + into `devel`, and release assets, and `pull-requests: write` to create/update its promotion PR. If `release` has additional protection, it must permit the publisher's version and README - commits. Feature changes still enter through reviewed, passing PRs. + commits. Feature changes enter `devel` through reviewed, passing PRs. +- `devel` rules must allow the publisher's normal synchronization push. If all + direct writes require PRs with no publisher exception, no-PR synchronization + is impossible; configure an allowed automation identity. This exception applies + only to `devel` (and release metadata on `release`), never to `main`. - In **Settings → Actions → General → Workflow permissions**, enable **Allow GitHub Actions to create and approve pull requests**. The workflow only - creates/updates PRs; it never approves or merges them. Keep default token + creates/updates the main PR; it never approves or merges that PR. Its direct + release-to-devel Git merge is a separate authorized operation. Keep default token permissions read-only; the publisher grants only its required permissions. - GitHub may require **Approve workflows to run** on a PR created/updated with `GITHUB_TOKEN`. A maintainer approves those runs before review/merge. Do not disable the required promotion check to avoid that approval. -- Keep merge commits enabled and preserve `release` after promotion. Avoid squash - or rebase merging the long-lived release branch into `main`. +- Keep merge commits enabled and preserve `devel` and `release`. Avoid squash or + rebase merges for `devel` → `release` and `release` → `main`: shared ancestry is + needed for clean future promotions and automatic back-merges. ## Concurrency and recovery -Merge one feature PR at a time and wait for publication/README/PR preparation to -finish before the next merge or manual version bump. Automatic and manual runs +Merge one `devel` → `release` PR at a time and wait for publication, README, +main PR and devel synchronization to finish before another release merge or +manual version bump. Feature merges into `devel` do not need to wait. Automatic and manual runs share a concurrency group and never cancel a running publication. GitHub retains only one pending run per group; several overlapping triggers can replace pending runs. Do not use the concurrency queue as a release backlog. @@ -127,11 +190,13 @@ Use **Re-run all jobs** on the original failed Release run: - During packaging/upload: rebuild from the same tag and repair assets only while the GitHub Release is still a draft. - After publication: verify and reuse the published assets without overwriting or - rebuilding them, then finish README and PR preparation. + rebuilding them, then finish README, main PR and devel synchronization. - After the README commit or a lost PR response: reuse that commit and discover the existing open promotion PR before creating another one. +- After a devel synchronization failure: resolve the conflict or write permission, + then retry. A completed merge is detected and not duplicated. -If a newer feature merge or manual version has already advanced `release`, an old +If a newer release merge or manual version has already advanced `release`, an old run may refuse to resume. Inspect the current branch and latest release before continuing; do not reset `release` or force-move tags to make an old run succeed. Any unresolved changes remain in Git. Draft releases are not complete publications. diff --git a/docs/runtime.md b/docs/runtime.md index f0e13b5..30b3329 100644 --- a/docs/runtime.md +++ b/docs/runtime.md @@ -35,7 +35,15 @@ not by excluding the posting account's login. GitHub Bot accounts and unauthoriz authors are also excluded. A regular comment from the shared account can answer a question; the bot's own marked question, acknowledgement, or other post cannot. -For permission requests, use the exact `/allow QUESTION_ID` or `/deny QUESTION_ID` +When `autoApproveRepositoryFiles` is enabled for the repository, the runtime +approves eligible file-access requests within the checkout and assigned worktree, +including native subagents and later rounds. It still honors explicit denials and +does not grant shell permissions or tools to media helpers. A new session does not +need to repeat `/allow` for these eligible file requests. Existing unanswered +questions still require their explicit replies. See +[repository file approvals](configuration.md#repository-file-approvals). + +For permission requests that still require a reply, use the exact `/allow QUESTION_ID` or `/deny QUESTION_ID` shown in the question as your entire reply. Plain conversation does not grant permission. The decision is scoped to the operation and resource set in the current main session and its workers; explicit OpenCode deny rules still apply. @@ -142,7 +150,10 @@ The bundled prompt scopes instructions to triage, base selection, implementation delegated workers, media helpers, and title generation. Within implementation, it asks the agent to inspect the project, use relevant available workflows or skills, plan nontrivial work, delegate useful independent subtasks, verify results, -and review the final diff. Subagents and planning tools must be available in the +and review the final diff. It also requires every affected description, example +and workflow diagram to be updated before finishing, with documentation changes +(or the reason none are needed) identified in the final report. Subagents and +planning tools must be available in the OpenCode environment; the prompt does not install or enable them. Simple tasks can stay lightweight, and unavailable delegation falls back to local work. @@ -165,7 +176,9 @@ ignore it locally for machine-specific instructions. ## Follow progress and continue work Starting a session shows a notification and opens a background tab when tabs -are enabled. Use `/bot` to list tasks and open a session. +are enabled. In the owner project's TUI, use `/bot` to list tasks and open a +session, or `/restartworkflow` to select a task for recovery. Reopen TUI clients +after installing an update that adds or changes commands. Closing or merging the PR automatically closes its known bot session tabs, including earlier rounds and media helpers. This also works for manual GitHub @@ -175,21 +188,27 @@ work finishes. Session history is preserved, and `/bot` can reopen a session. Reopening it manually keeps it open for the current TUI instance. No additional configuration is required. -A new comment from an authorized author on a tracked issue starts another round: -acknowledgement, implementation, and a push to the same open PR. The mention does -not need to be repeated. Comments received during execution wait for the next -round. A mention in an authorized comment can also start work on an untracked issue. +A new comment from an authorized author on a tracked issue is saved as feedback. +After the current task reaches `done`, the next available worker pass starts a +new round: analysis and acknowledgement, implementation, checks, and a push to +the same open PR. The mention does not need to be repeated. Comments received +during execution, a pending question, or a blocked stage remain queued. Receiving +one does not itself clear the current block. A mention in an authorized comment +can also start work on an untracked issue. -Follow-up rounds reuse the worktree path saved in the queue, even if recovery +Normal follow-up rounds reuse the worktree path saved in the queue, even if recovery renamed its branch. Preparation, verification, and push validate that path as a worktree root directly inside the managed worktree directory, attached to the expected branch and repository. If the saved directory is missing, restore it before retrying; the bot does not create a replacement or discard existing work. +After explicit round cancellation, a new round deliberately uses a fresh worktree +while preserving the old one; see [cancelling rounds](#cancelling-one-round-while-keeping-tracking). Edits to existing comments and PR review comments are not supported. Closing the issue or closing/merging the PR blocks further rounds. -Management commands run from the target repository: +Except for the host-wide `list` command described below, management commands run from the primary owner checkout of the target repository, +not from a bot worktree: For source installations, replace `"$HOME/.local/bin/opencode2-automation"` with `node "$HOME/opencode2-github-automation/dist/setup.js"`. @@ -200,8 +219,306 @@ cd /absolute/path/to/your-project "$HOME/.local/bin/opencode2-automation" scan "$HOME/.local/bin/opencode2-automation" pause "$HOME/.local/bin/opencode2-automation" resume +"$HOME/.local/bin/opencode2-automation" restartworkflow 'owner/repository#123' ``` Pausing stops scheduled scans; it does not cancel accepted tasks or active sessions. Do not run independent bots on two machines against the same issues: they do not share queue ownership across machines. + +## Repository inventory + +Run these commands from **any directory**, including outside Git: + +```bash +opencode2-automation list +opencode2-automation list --json +opencode2-automation list --discover /absolute/path/to/projects +``` + +`list` reads this user's registry on this host. It never starts the service, +activates another owner, scans GitHub, retries tasks, or prompts a model. JSON +output contains `entries` and `warnings`. In the TUI, `/bot` → **Repositories** +shows the same inventory from the **connected server**, not the TUI client's +machine. Select a repository for details. This option remains available when +there are no tasks. It requires an updated, loaded owner plugin for the inventory +RPC; otherwise use the CLI on the server. Reopen the TUI after updating it. + +Entries identify the GitHub repository, full checkout and owner paths, and the +last registered base branch. Before first activation an automatic branch may +say `auto (resolved on activation)`; task-specific base overrides are still shown +in task details. Advanced configurations with multiple repositories list each +configured repository, sharing the owner's scheduler information. + +The report includes dispatcher activity, last scan attempt completion (which can +include failure), scheduler next-run timestamps, task counts and every open +blocked/failed/closing/cancelling issue key and saved error. Active counts use the actual +active task; scheduled work is separate. Closed local tracking and closed/merged +PR history do not inflate counts. A working dispatcher can have blocked tasks; +inspect the task counts as well as the owner status. + +| Status | Meaning | +| --- | --- | +| `running` | Fresh dispatcher and scheduler snapshots; at least one polling job is unpaused. This does not promise that all tasks succeeded. | +| `paused` | Both snapshots are fresh and all scheduler jobs are paused. Accepted tasks can still execute. | +| `error` | A fresh dispatcher reports stopped/scan failure, a scheduler job has failures, or the standard configuration is invalid/unreadable. | +| `not-running` | No dispatcher snapshot yet, an explicit shutdown snapshot, or its process no longer exists. | +| `unavailable` | Missing, corrupt or stale component status, or an inaccessible directory. Do not infer idleness. | +| `missing` | A registered checkout/owner directory was removed or moved. | +| `unconfigured` | Its registered standard configuration file was removed. A previously loaded runtime may still be active until reloaded. | + +Each component publishes a local snapshot every five seconds. A reading older +than 15 seconds is unavailable, even if its process still exists. Timestamps are +shown in UTC. Stopped/stale entries retain **historical** details; counts and next +run times in those snapshots are not live promises. Open the list again to refresh +it. Inventory errors do not reset queues or prevent bot execution. + +`init` registers new projects. Loading an updated combined plugin imports its +existing standard configuration; the dispatcher also registers advanced +`repositories` options. To include older **inactive** standard configurations, +run `list --discover `. Discovery only reads Git/config files and adds +registry metadata: no credentials, GitHub calls, or service activation are needed. +It examines the root plus six directory levels, at most 10,000 directories, +without following child symlinks or descending into hidden directories, +`node_modules`, `vendor`, `build`, or `dist`. Worktrees and subdirectories of a Git +checkout are excluded. Limits, unreadable folders and invalid configs are +reported. Choose a more specific root (including a hidden folder directly) when +needed. Advanced options require loading their owner once. This is an inventory +of registered/configured projects, not an exhaustive filesystem or other-user +scan. + +Registration is per canonical owner path under +`$XDG_STATE_HOME/opencode2-automation/repositories`, defaulting to +`$HOME/.local/state/opencode2-automation/repositories`. CLI and service must use +the same user and state-home environment. Per-owner atomic files avoid lost +updates when different projects register concurrently. Aliases of one owner are +deduplicated; separate clones remain separate. Missing entries are retained so +the operator can see what disappeared. The registry never replaces the queue or +session database, and `list --discover` does not rewrite project configuration. + +## Manage tasks from /bot + +Run `/bot` in the owner project's TUI, choose an issue, then choose an action. +The same picker also offers **Repositories** for the host inventory: + +- **Open session**: inspect its saved conversation, including a locally closed task. +- **Show details**: read the saved status, phase, error, branch, worktree, session, + PR link and queued feedback. These are stored checkpoints, not a fresh GitHub lookup. +- **Close session tabs**: hide that task's idle tabs in this TUI only. Busy tabs + remain open. Tracking and execution continue. +- **Restart workflow**: request the same guarded recovery as `/restartworkflow`. +- **Cancel current round**: stop the current round without publishing it. Preserve + its session and worktree, then enter `watching` for new issue comments and PR + state. Existing queued feedback remains eligible; only the current round is + cancelled. **Retry cancelling round** retries an interruption failure. +- **Resume issue tracking**: available for a locally closed task. Validate that + the issue and any known PR are open, stop any saved sessions again, then watch + future comments. The stopped round and comments already present at this action's + GitHub read are skipped. It does not replay work or immediately publish. +- **Stop and close task**: after confirmation, persist `closing`, interrupt known + main, earlier-round and media sessions, wait for idleness and the task's in-flight + worker operation, then persist `closed`. The menu offers **Retry closing task** + while closure is pending. + +Closing tracking works for queued, waiting, failed, blocked, running and published +work, even if its GitHub issue/PR or saved OpenCode session no longer exists. +It makes no GitHub close/delete request and preserves files, branches, worktrees, +commits, session history, pending questions and feedback. It does not publish +unfinished work. `closed` here means **local tracking ended**, not PR closure. +Closed tasks remain listed as history and their conversations can be reopened. + +The closed record prevents rediscovery and later comments from restarting the +same issue. Recovery/retry cannot reopen tracking; use **Resume issue tracking** +explicitly to watch that issue again. Runtime question/helper admission is disabled once closure is requested. +Related idle tabs close once when the task becomes `closed`. + +An already-started publication or automatic merge rejects closure with an explicit +message: wait for it to finish and try again. Other in-flight operations (such as +analysis, worktree preparation or checks) may finish locally before closure +completes, but cannot advance to publication. An already-submitted GitHub comment +may complete. Closing is not a rollback of earlier Git or GitHub effects. + +Interruption errors keep the task in `closing` with a visible error, retried after +30 seconds or through **Retry closing task**. A restart resumes the saved closure +instead of restarting implementation. No success is reported while interruption +has failed or the task's worker operation is still pending. Missing sessions are +already stopped and do not block closure. + +## Cancelling one round while keeping tracking + +**Cancel current round** differs from closing the task and from hiding its tab. +It persists `cancelling` before interrupting sessions, waits for in-flight local +work and question posts, interrupts again, and enters `watching` only after that +finishes. Failures remain visible and retry after 30 seconds; an owner restart +resumes cancellation. Publication or merge already in flight rejects cancellation +because remote effects cannot be rolled back. A comment already being posted can +still appear in GitHub. Cancellation does not revert previously pushed commits. + +The completed transition archives the round's session, worktree, question, +feedback, error, attempts and verification/report checkpoints. It clears the +current question, recovery request and live error, without deleting any files or +sessions. Late explicit `/allow ID` or `/deny ID` replies to archived permission +questions cannot restart the task. Other new authorized comments can request work. + +The next round starts in a **new worktree and local branch** from the published +PR branch, or the pinned base when no PR exists. Archived worktrees remain intact, +including dirty files and local commits; their changes are not automatically +included. Publication still targets the original remote branch and PR. The bot +receives only the new round's feedback and instructions not to replay cancelled +scope. Normal later rounds reuse the new worktree. Details show the last preserved +worktree; full cancellation history is retained in the queue. + +PR state monitoring continues while `watching`. Automatic merging uses only the +last saved published head, with normal approval checks. Legacy tasks without that +checkpoint still watch comments and PR state, but cannot auto-merge until another +round publishes successfully. No model runs while waiting for new feedback. + +Equivalent owner-checkout commands: + +```sh +opencode2-automation cancelround 'owner/repository#123' +opencode2-automation resumetracking 'owner/repository#123' +``` + +The sidebar shows **Watching issue — round cancelled** or **Tracking closed**. +Old failures appear under **Historical error** in `/bot` → **Show details**, not +as a live red error or failed-attempt count. Cancelling a round does not automatically +close the PR's tab; closing the tab remains an independent display action. + +## Runtime status sidebar + +The **BOT RUNTIME** section is appended to the existing right sidebar, preserving +OpenCode's context information. Open the TUI in the configured primary owner +checkout. Switching task tabs changes the task details while the scheduler and +dispatcher rows continue to describe that owner project. + +The panel shows: + +- Dispatcher activity: idle, reconciling state, executing a task's actual phase, + checking merges, maintenance, or stopped. An active task key is shown separately. +- GitHub discovery: scanning, the time the last scan finished, and any scan error. +- Scheduler jobs: running, paused, next run time or retry delay, and errors. + Pausing polling can coexist with an already-running scan or task. +- Queue counts: ready/retry-wait excluding the active task, waiting for replies, blocked/failed and published + tasks, excluding closed/merged PRs and locally closed tracking. Scheduled does not mean a model is executing. + Up to three attention rows identify blocked, failed, closing or cancelling issue keys and + saved errors. Pending closures have a separate count; use `/bot` for the full list. +- Task details: issue, phase, round, observed main-session status, task/base branches, + model, queued feedback, allocated media helper count for the current session, + failed attempts, recovery request, PR state and any task/merge error. +- Separate freshness information for dispatcher and scheduler readings. + +Task selection prefers the displayed session (including saved earlier-round and +helper IDs), then the dispatcher's active task, then an open waiting/blocked task, +then scheduled work, then the last known task. Main-session running/idle comes +from the TUI's session cache when that session is available; otherwise the panel +says `not observed`. Media counts do not claim that those helpers are running and +do not count native implementation subagents. + +Read-only snapshots refresh on startup and every five seconds, with a four-second +request deadline and no overlapping refreshes. Countdown labels update locally +every second. No model is prompted and polling does not restart jobs or repair +queue state. A failed request retains the last successful snapshot with a stale +warning; readings older than 15 seconds are also marked stale. Dispatcher and +scheduler failures are independent, so a partial failure keeps the other component +visible. Initial/unavailable data is never presented as a healthy idle service. + +Use `/botstatus` for a text report, including every known task and scheduler job, +when the sidebar is hidden or more detail is needed. The sidebar shows up to three +scheduler jobs and truncates long labels/errors. `/bot` manages task sessions; +`/restartworkflow` remains the separate explicit recovery action. + +The TUI and owner plugin must both contain the monitor API. With an older server, +an unloaded/unconfigured owner, a direct worktree-only launch, or a failed RPC, +the panel reports unavailable status. Load the configured owner and update both +sides as needed; reopen TUI clients after installation. Monitoring uses the +connected OpenCode client, so it also works with a remote service when the correct +owner location and updated plugin are available there. Live worker/scan diagnostics +reset when the owner is recreated; task checkpoints and scheduler history remain +durable as before. + +## PR descriptions + +A formal GitHub **Approve** review applies to its exact commit even when submitted +before the bot finishes updating the PR description. Retrying publication does +not invalidate that review. A new commit needs a matching review, and dismissed +approvals or outstanding change requests are not accepted. Plain merge comments +still must follow the latest publication because they do not identify a commit. + +After pushing a verified commit, GitHub may briefly show the previous commit in +the PR. If the remote branch already matches the verified commit, the bot waits +and automatically retries publication within its configured attempt limit. It +keeps the saved report and does not repeat implementation or a checkpointed push. +A changed remote branch or closed PR produces a separate blocking error. If the +propagation retries are exhausted, inspect the error and use `/restartworkflow`; +see [description recovery](advanced.md#pr-description-recovery). + +The PR contains the agent's final completion summary from the successful session, +with Markdown preserved, followed by dispatcher verification, the issue reference, +session, round and verified commit. The initial issue acknowledgement is not a +completion report. Agent-reported tests remain in the summary; the dispatcher +lists only checks it actually ran. With no configured test command it explicitly +states that agent-reported tests were not independently rerun. + +Follow-up rounds keep the original summary and replace a single **Latest update** +section after the new commit is pushed. The PR title stays unchanged. Reports are +saved before publication so a restart or lost GitHub response can reuse them. +If a legacy session is missing or has no successful final text, the description +states that its summary is unavailable. + +Put manual PR notes outside the `opencode2:pr-body` HTML markers (visible when +editing the description). Edits inside that section or removal of the markers +block further description updates to protect your changes. Inspect the task error, +resolve the conflict and retry publication; see +[description recovery](advanced.md#pr-description-recovery). Installing an update +does not automatically rewrite already completed or closed PRs. + +## Interrupted sessions and workflow recovery + +If you manually continue a timed-out or interrupted bot session in the TUI, +the dispatcher detects its successful completion automatically. It rejoins the +saved execution phase, validates the session result, runs the configured checks, +and publishes the verified changes to the same branch and PR. Pending authorized +issue comments remain queued and start the next round after publication. This +also works after a service restart once the owner is loaded; opening a TUI is +not required. Only recognized session-stop checkpoints qualify for this automatic +recovery. Other failures retain their documented retry/inspection requirements. + +Use `/restartworkflow` in the owner project's TUI and select the issue to recover +a stopped workflow. The equivalent terminal command is shown above. For a stopped +session, recovery waits for any current execution, then continues the previously +agreed task in that same session if it still needs work. For a verification or +publication failure, it retries that saved stage. It preserves the worktree, +branch, session history, pinned base, PR, and queued feedback. Repeated requests +while recovery is scheduled or running do not start duplicate work. + +Recovery does not bypass failing checks, unresolved questions or permissions, +closed PRs, or uncertain prompt delivery. Answer pending questions in the issue. +If a check still fails, fix its cause and retry; the plugin will not publish an +unverified result. A service restart restores the saved state but does not clear +these blocks. To resume paused issue polling, use `resume` separately. + +The CLI response `accepted: true` means recovery was queued, not that execution +or publication has finished. `accepted: false` means the task was not blocked or +failed and no duplicate recovery was created. Missing tasks, unresolved questions, +closed/merged PRs, missing routes, and unsafe running-session errors instead +produce an actionable error. A recovery request can be queued while another issue +is working, but it waits for a worker pass before execution. + +Use `status` to distinguish `phase` (saved execution step) from `status` (whether +it may run). Active work is normally `phase: running`, `status: ready`; `done` +means publication completed, not that the PR merged. `pendingFeedback` contains +comments awaiting a later round. The TUI's `merged` and `pr_closed` phases are +presentation values derived from the saved PR state. For detailed selection, +checkpoint and retry rules, see [workflow section 8](bot-workflow.md#8-status-retries-and-recovery). + +`retry` alone clears a block at the saved phase; it does not request a new model +continuation. `retry --restart-session` interrupts the old session and clears its +identity, so use it only after inspecting the session and uncertain prompt +results. `/restartworkflow` retains the session. Neither recovery command replaces +service startup or scheduler `resume`. The slash command belongs in OpenCode's +TUI, not in an issue comment. + +While a closure is pending, the dispatcher does not start another worker pass. +An unrelated already-running task can finish; scanning continues for other tasks. +The monitor reports task maintenance until closure completes. diff --git a/package-lock.json b/package-lock.json index 5e5dcd7..054a610 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,8 @@ "version": "0.6.5", "hasInstallScript": true, "dependencies": { - "@opencode/client": "0.0.0-beta-19398", - "@opencode/plugin": "0.0.0-beta-19398", + "@opencode/client": "2.0.6", + "@opencode/plugin": "2.0.6", "proper-lockfile": "^4.1.2", "zod": "^4.1.0" }, @@ -19,17 +19,27 @@ }, "devDependencies": { "@eslint/js": "^10.0.1", + "@opencode/theme": "2.0.6", + "@opentui/core": "0.5.10", + "@opentui/solid": "0.5.10", "@types/node": "^22.0.0", "@types/proper-lockfile": "^4.1.4", + "bun": "1.4.2", "eslint": "^10.10.0", "globals": "^17.12.0", "semver": "^7.8.5", + "solid-js": "1.9.12", "tsx": "^4.20.0", "typescript": "^5.9.0", "typescript-eslint": "^8.70.0" }, "engines": { "node": ">=22" + }, + "peerDependencies": { + "@opentui/core": ">=0.5.10", + "@opentui/solid": ">=0.5.10", + "solid-js": ">=1.9.12" } }, "node_modules/@ai-sdk/provider": { @@ -44,6 +54,20 @@ "node": ">=18" } }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@aws-crypto/crc32": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", @@ -181,9 +205,9 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.977.9", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.977.9.tgz", - "integrity": "sha512-reqPFEQrZxDZpeGj4PFMepBeR5LGYHRqq/L0motTzgFkCRBA4rFdaVXDSLYyGHhxVz7sT2PDnPN9CluGSfgyJA==", + "version": "3.978.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.978.0.tgz", + "integrity": "sha512-2yX9LUmxPklVjSGTb8dfnWRJSiFQ3TeH2nn7G1mdKHTfnabzF0+gfrS8rYfLWmZrQ8A3mEcxMJjRc51dL5KWaA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.974.5", @@ -200,12 +224,12 @@ } }, "node_modules/@aws-sdk/credential-provider-cognito-identity": { - "version": "3.972.69", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.972.69.tgz", - "integrity": "sha512-vpsh9VWmQVC/nsdzf72F2yUMBOFT1aq+hoLseYV03zjVXVHkjqSNJskFRKPFxc3MRFrHNbSSmOwsbYE15u9ecw==", + "version": "3.972.70", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.972.70.tgz", + "integrity": "sha512-KlU89w6Hmb4oZB5zFz/MNIhPOBQGVE7KrDr3BTPCwC4W+q566YH8tGNsAML781LKATtqmNCGFry8XvsJ2XPusg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/nested-clients": "^3.997.45", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", @@ -216,12 +240,12 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.70", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.70.tgz", - "integrity": "sha512-H404B7dJl2mCrBqahDEYsanB0xhdDp6tXnXcTUnXmmpy2Q3J0Ho0bUajZ2jr/RdwzCyS59Gi8xXIFwPLGBl6Uw==", + "version": "3.972.71", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.71.tgz", + "integrity": "sha512-JN+JHruYZw3GUZB8YGAlDk4wTDPOEAEEdEzj5nS0xodWR4smzHsN7PnK2j6IeOsDIj2aqua5DSbhXl9Gtf90FQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", + "@aws-sdk/core": "^3.978.0", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", @@ -232,12 +256,12 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.72", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.72.tgz", - "integrity": "sha512-X98zYOrVOeuosCX+6ktf29FC2N2GHPLia7qv6mzPzTc+RPAuHWCDS++Z6JK7eGYqb/v6uaW7bAXaOvDBfol+0w==", + "version": "3.972.73", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.73.tgz", + "integrity": "sha512-uyYYnJOnlis8uQzaYGPd7N1JoioCoNpXgnkXYixsWJXHXgXyYi8WXJSDfofxJeWfQIGWLe2Nwyq60Uc7MZdVOg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", + "@aws-sdk/core": "^3.978.0", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/fetch-http-handler": "^5.7.2", @@ -250,19 +274,19 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.973.15", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.15.tgz", - "integrity": "sha512-Rykg6s5ceBuynMOGWgoowO4N+27JfnqXAnVaSunZl0hOO1XodSrxGNz6sCEbnmS0lAfQZDKyb3fbr46gSuv6Sg==", + "version": "3.973.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.16.tgz", + "integrity": "sha512-i++ly+0Uxa+u3ebSSyr0S/3CFhFJDxCXT3+Zj+mW2bXenEx5bKGCdTIKFu39SgXBNhWDjex/8cXUx9MUTMCrTw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/credential-provider-env": "^3.972.70", - "@aws-sdk/credential-provider-http": "^3.972.72", - "@aws-sdk/credential-provider-login": "^3.972.77", - "@aws-sdk/credential-provider-process": "^3.972.70", - "@aws-sdk/credential-provider-sso": "^3.973.14", - "@aws-sdk/credential-provider-web-identity": "^3.972.76", - "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/core": "^3.978.0", + "@aws-sdk/credential-provider-env": "^3.972.71", + "@aws-sdk/credential-provider-http": "^3.972.73", + "@aws-sdk/credential-provider-login": "^3.972.78", + "@aws-sdk/credential-provider-process": "^3.972.71", + "@aws-sdk/credential-provider-sso": "^3.973.15", + "@aws-sdk/credential-provider-web-identity": "^3.972.77", + "@aws-sdk/nested-clients": "^3.997.45", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/credential-provider-imds": "^4.4.16", @@ -274,13 +298,13 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.77", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.77.tgz", - "integrity": "sha512-Jb59xfEISoN5mmbnA+HYqdtrSX3CgCtJoof+V5D8/TgUI56W63GEEd5Y58WijU3Ou6+WEgaLD1feVzaRXV5IDQ==", + "version": "3.972.78", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.78.tgz", + "integrity": "sha512-eUtswnXu0+Ii9ieRK+0L7aPFV3Z/dnW2VntJzjBP9xs8s+8p5nBNuymIXtXwZ+5r5+XJP3e32nMkuZ/r0HozEA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/core": "^3.978.0", + "@aws-sdk/nested-clients": "^3.997.45", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", @@ -291,17 +315,17 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.82", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.82.tgz", - "integrity": "sha512-znDkEOGXB8W3kG1LJUKP3foBZY/9qLM0eil/DxWXSp37XsdsRLQHE/d/OaCGGVgKpA6znR38h/+INk8do1FjiA==", + "version": "3.972.83", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.83.tgz", + "integrity": "sha512-jdso7ejzfRnatxMUZK4S/U6KbaDPCvfIV4XL+IQAPFDBt5rj5Fq595euqlK8Le4lNCMFR9oUpt+1l0aMgaayOQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.70", - "@aws-sdk/credential-provider-http": "^3.972.72", - "@aws-sdk/credential-provider-ini": "^3.973.15", - "@aws-sdk/credential-provider-process": "^3.972.70", - "@aws-sdk/credential-provider-sso": "^3.973.14", - "@aws-sdk/credential-provider-web-identity": "^3.972.76", + "@aws-sdk/credential-provider-env": "^3.972.71", + "@aws-sdk/credential-provider-http": "^3.972.73", + "@aws-sdk/credential-provider-ini": "^3.973.16", + "@aws-sdk/credential-provider-process": "^3.972.71", + "@aws-sdk/credential-provider-sso": "^3.973.15", + "@aws-sdk/credential-provider-web-identity": "^3.972.77", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/credential-provider-imds": "^4.4.16", @@ -313,12 +337,12 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.70", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.70.tgz", - "integrity": "sha512-2ry03fGRJr4sV3jI+ocjj5JqALnFD6ymM5KiNCDZMvq8bX2GSbE0vji4aM43TVCl2nXqqLRZaUxdq/KeWRAY4Q==", + "version": "3.972.71", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.71.tgz", + "integrity": "sha512-lYmXJa4gvq4xN1lrT5NiP5vIYYKcGWAdj8y+8o6dlcateB5eF3Dn8DtmjjHKfMBrTPAMr2pebIiX/UOj8c1/UA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", + "@aws-sdk/core": "^3.978.0", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", @@ -329,14 +353,14 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.973.14", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.14.tgz", - "integrity": "sha512-jkhg/8ocAAoc0RFyLMhCw+/zZh7gystQgd4F4hznNa8P4Cc501PQmxd+jGLiMHodPJ+7Zv/3znM62gZojyasmA==", + "version": "3.973.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.15.tgz", + "integrity": "sha512-6Jhcf4v0pSFdjk1EW2kvzuEBKD+UZ2uNcHUIglKKLndD20YhvkL2kdmDOV5/j4mYuWWwe/a1FQ1aomU86/Cg5Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/nested-clients": "^3.997.44", - "@aws-sdk/token-providers": "3.1116.0", + "@aws-sdk/core": "^3.978.0", + "@aws-sdk/nested-clients": "^3.997.45", + "@aws-sdk/token-providers": "3.1129.0", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", @@ -347,13 +371,13 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.76", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.76.tgz", - "integrity": "sha512-d3AGyVu759PGr35mEB2s22xxlNEA5rpdxtSPJthfPFJvoQ8dt357iVPECqWfUxXp1toJAvKmbtcIYVGigaGsCA==", + "version": "3.972.77", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.77.tgz", + "integrity": "sha512-uylIQSUWpfLuH2LovxEEfwzJGM/SabLOfLMg6YXu/E8jJEKUdpdILCVCQCdFvHyu/7dLJOHPMfrSwduxO56NkQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/core": "^3.978.0", + "@aws-sdk/nested-clients": "^3.997.45", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", @@ -392,12 +416,12 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.997.44", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.44.tgz", - "integrity": "sha512-NhEgryjlBF9w38ZXqGymQV28IhkYa1mKhlbYnqIis57AYwWGVYfUPgg/qC2rLRqOUfblxx++irvju10kVTa8Vw==", + "version": "3.997.45", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.45.tgz", + "integrity": "sha512-mooq9Q+jLa18VoM7HouczmslZU60iiB0aKc/Ztnq/luIL1ud0z4DnYprLR/ZO1gp331S9tJctM1HZr7u6YKBXQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", + "@aws-sdk/core": "^3.978.0", "@aws-sdk/signature-v4-multi-region": "^3.996.46", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", @@ -426,13 +450,13 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.1116.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1116.0.tgz", - "integrity": "sha512-ygIivKqh8aHzNkucOCXHyIBgBpLPfrSI0mCqXF+vLBsPTUKqj0VSqAY0GFPe7lQl4HntjOcQ+KSyS7oUV2C54Q==", + "version": "3.1129.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1129.0.tgz", + "integrity": "sha512-Sbl3rpzQdsG4ZK2zh0JWUYyZPKKorJlVOddA2T0DVbKJFrsW8J6wgnslxxUH04+WaBMr4A1HzJZvZX0xUvkniA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.977.9", - "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/core": "^3.978.0", + "@aws-sdk/nested-clients": "^3.997.45", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", @@ -489,6 +513,486 @@ "node": ">=18.0.0" } }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", + "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "devOptional": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "devOptional": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.7", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "devOptional": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", + "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", + "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-syntax-typescript": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@cacheable/memory": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz", @@ -1274,6 +1778,45 @@ "integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==", "license": "ISC" }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@keyv/bigmap": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", @@ -1622,13 +2165,13 @@ } }, "node_modules/@opencode/ai": { - "version": "0.0.0-beta-19398", - "resolved": "https://registry.npmjs.org/@opencode/ai/-/ai-0.0.0-beta-19398.tgz", - "integrity": "sha512-plBGJR3vrmNLNf1sJTyr64x7Ku1WRnpSZLKj9zfp2vslXpnwbPSI/95Mw0xTyqg+HSCc+TA7IyGIWJR0tev6Ig==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@opencode/ai/-/ai-2.0.6.tgz", + "integrity": "sha512-zbIvozv+drSK9gAfg2Vx3+nkMNfXAxZGAYmXKVd465EBGQv+v3D3pCJfGoj4lCQRku4vnGcu5DQf4RVhAaFX+g==", "license": "MIT", "dependencies": { "@aws-sdk/credential-providers": "3.1057.0", - "@opencode/schema": "0.0.0-beta-19398", + "@opencode/schema": "2.0.6", "@smithy/eventstream-codec": "4.2.14", "@smithy/util-utf8": "4.2.2", "aws4fetch": "1.0.20", @@ -1637,13 +2180,13 @@ } }, "node_modules/@opencode/client": { - "version": "0.0.0-beta-19398", - "resolved": "https://registry.npmjs.org/@opencode/client/-/client-0.0.0-beta-19398.tgz", - "integrity": "sha512-t0o0OznIVTf2//6/qOeahUPQ1a25MYOQLp338Zf952/36lB3lM4G9HN60tKslSelwUHHBn8ZFCDj5VF6DbZrlA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@opencode/client/-/client-2.0.6.tgz", + "integrity": "sha512-yS8Ztw5KJzoY94GTiEmaX+2OqJYVVdc2Pyj8joNvmFywMfNzhS5aqqX+LpprlZdiDHKBLN9GhMU775TRPLol0A==", "license": "MIT", "dependencies": { - "@opencode/protocol": "0.0.0-beta-19398", - "@opencode/schema": "0.0.0-beta-19398" + "@opencode/protocol": "2.0.6", + "@opencode/schema": "2.0.6" }, "peerDependencies": { "effect": "4.0.0-rc.112", @@ -1659,23 +2202,23 @@ } }, "node_modules/@opencode/plugin": { - "version": "0.0.0-beta-19398", - "resolved": "https://registry.npmjs.org/@opencode/plugin/-/plugin-0.0.0-beta-19398.tgz", - "integrity": "sha512-0UGjsGgGP9rKq700nnhOyqmmINnsn9shlx5PZ9MpLhZoJv1EFEfJLzom5paLzoKuJF3jGLMi7kvdFdo1fc/dvg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@opencode/plugin/-/plugin-2.0.6.tgz", + "integrity": "sha512-suf6q6vC6w8QnmvEC2IJrAKFeLuZlSGNsDtcah5C3gWXyZTEsOaIe9/P0+NEOXcsexH1nnUidR6MOSWdl5JuHw==", "license": "MIT", "dependencies": { "@ai-sdk/provider": "3.0.8", - "@opencode/ai": "0.0.0-beta-19398", - "@opencode/client": "0.0.0-beta-19398", - "@opencode/protocol": "0.0.0-beta-19398", - "@opencode/schema": "0.0.0-beta-19398", - "@opencode/util": "0.0.0-beta-19398", + "@opencode/ai": "2.0.6", + "@opencode/client": "2.0.6", + "@opencode/protocol": "2.0.6", + "@opencode/schema": "2.0.6", + "@opencode/util": "2.0.6", "@standard-schema/spec": "1.1.0", "effect": "4.0.0-rc.112", "zod": "4.1.8" }, "peerDependencies": { - "@opencode/theme": "0.0.0-beta-19398", + "@opencode/theme": "2.0.6", "@opentui/core": ">=0.5.10", "@opentui/solid": ">=0.5.10", "solid-js": ">=1.9.0" @@ -1705,29 +2248,40 @@ } }, "node_modules/@opencode/protocol": { - "version": "0.0.0-beta-19398", - "resolved": "https://registry.npmjs.org/@opencode/protocol/-/protocol-0.0.0-beta-19398.tgz", - "integrity": "sha512-sRgB+vA8gXMrQvG5/xZZJ6fqMDNMlvyYSPz+98iDmDiydHrrA5fj9fzFgpGjRx+lAkM0wv8z5/1jjOhldzwgRA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@opencode/protocol/-/protocol-2.0.6.tgz", + "integrity": "sha512-31pt2SC2/urvitx8q8A0i0IwewwAu70/knkSfY9NFhZyLfggfNrPMUvlzMV0rwbBgubXbyRFRshU6B2tX/6czQ==", "license": "MIT", "dependencies": { - "@opencode/schema": "0.0.0-beta-19398", + "@opencode/schema": "2.0.6", "effect": "4.0.0-rc.112" } }, "node_modules/@opencode/schema": { - "version": "0.0.0-beta-19398", - "resolved": "https://registry.npmjs.org/@opencode/schema/-/schema-0.0.0-beta-19398.tgz", - "integrity": "sha512-An+yCwSpRwDHGyr9hhznNCZZYU+t0ocYWeq5RBecOBCEfHF9t/6OMU7BQFR2/YSQLh6yUV2LuSO9RxPd+pEBcQ==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@opencode/schema/-/schema-2.0.6.tgz", + "integrity": "sha512-oJC1Uc1ZhqQ5fBUhRza5tFc4Y8UKy0bWxpOv4f9qm6VCs16xgNGPi5Dts4bM29KbtfARr3W926tHvk7BxzUf9A==", "license": "MIT", "dependencies": { "@standard-schema/spec": "1.1.0", "effect": "4.0.0-rc.112" } }, + "node_modules/@opencode/theme": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@opencode/theme/-/theme-2.0.6.tgz", + "integrity": "sha512-JivpW9TQ2Wqiu19MTgtf7cdrlxkXVkJBJhmlpmNQMu0zOqGsm3+Z49dI6NcBlmLYlOgu9ZDCFvx58S3pwNza6w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@opentui/core": "0.5.10", + "effect": "4.0.0-rc.112" + } + }, "node_modules/@opencode/util": { - "version": "0.0.0-beta-19398", - "resolved": "https://registry.npmjs.org/@opencode/util/-/util-0.0.0-beta-19398.tgz", - "integrity": "sha512-4xsvfogeFhSwQMv7e4fZ7c9BPfLBzXd3NTowqBLWlTJ9pkku6ffPitB7NanrbvTVu7A6Hq8RLqPJloyCHdyz4g==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@opencode/util/-/util-2.0.6.tgz", + "integrity": "sha512-E7snU0RQZMynpDgT7/v9QZVGOX1dviZ1TZzVkspNrLJ/ME5SH5zgto91yLp8DgvbNQNiLAijwXJanD33kYh9eQ==", "license": "MIT", "dependencies": { "@effect/opentelemetry": "4.0.0-rc.112", @@ -1946,17 +2500,394 @@ "node": ">=14" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "node_modules/@opentui/core": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core/-/core-0.5.10.tgz", + "integrity": "sha512-C3a2UbmefeAjIxAgm4BqjuSxKT4oqutfvYFwVvUgMxmGRHkNbBc/s7sukV0JgwcxFcV3uMFrXxo+E+BQtvuOiw==", + "devOptional": true, "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" + "dependencies": { + "bun-ffi-structs": "0.3.1", + "diff": "9.0.0", + "marked": "17.0.1", + "string-width": "7.2.0", + "strip-ansi": "7.1.2" + }, + "optionalDependencies": { + "@opentui/core-darwin-arm64": "0.5.10", + "@opentui/core-darwin-x64": "0.5.10", + "@opentui/core-linux-arm64": "0.5.10", + "@opentui/core-linux-arm64-musl": "0.5.10", + "@opentui/core-linux-x64": "0.5.10", + "@opentui/core-linux-x64-musl": "0.5.10", + "@opentui/core-win32-arm64": "0.5.10", + "@opentui/core-win32-x64": "0.5.10" + }, + "peerDependencies": { + "web-tree-sitter": "0.25.10" } }, - "node_modules/@protobufjs/aspromise": { + "node_modules/@opentui/core-darwin-arm64": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core-darwin-arm64/-/core-darwin-arm64-0.5.10.tgz", + "integrity": "sha512-Vyb+nTbhab8ZcRy5gg1loEEGwRcIbjAeVRIBfHBcbFDqmITBOg7x2gqJ+x/TnoOy4uwMhCmICUN2wiyREw3r1Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@opentui/core-darwin-x64": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core-darwin-x64/-/core-darwin-x64-0.5.10.tgz", + "integrity": "sha512-tTFLcM7Oj1gTyhm/bUdAt3C6grZdCxPk6+/g2azcZBUlI3/62LwbeRS6HbQKFFmm+1fUmX8cq6kWrtul885mVg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@opentui/core-linux-arm64": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-arm64/-/core-linux-arm64-0.5.10.tgz", + "integrity": "sha512-ncJXcgudhBf2GdJyF3xVQN/Ec+1F7GOL+pRrURmgBYSj2v1w6EyoDQFAACtPTK2c3R38W6fvZwL4JSLlm4EFXQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@opentui/core-linux-arm64-musl": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-arm64-musl/-/core-linux-arm64-musl-0.5.10.tgz", + "integrity": "sha512-dGMphDKexSdeYqwl0wgoFBP88Ta/cdi1Zc1mk29/ENkSCGz+74zlCHgqTHRNGLmI8W5TfuUtCyktQH11/Z+TBQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@opentui/core-linux-x64": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-x64/-/core-linux-x64-0.5.10.tgz", + "integrity": "sha512-5qtYaOgwVycZD1GaGshTRsi0rXPAmVExO03N1JQaHu+NYxK/vXSOc7Bu4QW0sPXx3Sp0SpzpP+FHjXABfoK66g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@opentui/core-linux-x64-musl": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-x64-musl/-/core-linux-x64-musl-0.5.10.tgz", + "integrity": "sha512-Oj4H9hApuvuTKPWxh4SoZAgGJorR7vbvnrZA/cAkSMAk2VGSoHRRcqeXQbcH8IcdjVZ0KFpv8Zkl/D5Ye+2mew==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@opentui/core-win32-arm64": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core-win32-arm64/-/core-win32-arm64-0.5.10.tgz", + "integrity": "sha512-A9VhgvTxQoUdZ+8LmUumEng1sQNbj9QQQT3NYG9mSxI54qTANi7vOWNSphMiY6RMVsr22pgm6nUvSSvJXv7Jog==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@opentui/core-win32-x64": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core-win32-x64/-/core-win32-x64-0.5.10.tgz", + "integrity": "sha512-u3KHa7kEeWrmKVDRJYpxSGO+g5E9cMGlrmTsPN3GVPHUmQMiREUawLXUvsU8+IHaQnqG3Q5nuE1yf4fPBzS+Qw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@opentui/core/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@opentui/core/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@opentui/core/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@opentui/solid": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/solid/-/solid-0.5.10.tgz", + "integrity": "sha512-KrmMIsHiKBHOABTC0brOwqWm+sGq1ZX2sGCAx6WgtBbE3STMup9n8TAy/6gUYhwcjC9zugT53ytfSVwCwVWZUg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/core": "7.28.0", + "@babel/preset-typescript": "7.27.1", + "@opentui/core": "0.5.10", + "babel-plugin-module-resolver": "5.0.2", + "babel-preset-solid": "1.9.12", + "entities": "7.0.1", + "s-js": "^0.4.9" + }, + "peerDependencies": { + "solid-js": "1.9.12" + } + }, + "node_modules/@oven/bun-darwin-aarch64": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@oven/bun-darwin-aarch64/-/bun-darwin-aarch64-1.4.2.tgz", + "integrity": "sha512-MXdZkP1featqxZ+/VTXWG1BVjM4OGBehVY2Q88EeUj/7L0UMeCGItmyPYTN+wxvlGJ6F66JEtzsw+GvQWewnag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-darwin-x64": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64/-/bun-darwin-x64-1.4.2.tgz", + "integrity": "sha512-gZTxZuLjkUhAWjTETu3tw0WhsEdNkJ64daj60ybhPf835a2yollV3yTkK9JozvzKPx4TRFzLSl8C+U525pxVbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-freebsd-aarch64": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@oven/bun-freebsd-aarch64/-/bun-freebsd-aarch64-1.4.2.tgz", + "integrity": "sha512-SMNItMw1Z8QeeQVKnw8jA7xQNkeXdP+OPgin4Wi/QTx/B8RHHLnuZfqmFy7NtVeT2NF0kKYppW4WWd2CCYZjhQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@oven/bun-freebsd-x64": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@oven/bun-freebsd-x64/-/bun-freebsd-x64-1.4.2.tgz", + "integrity": "sha512-THbPKXhO54N0DpFRKZNDZpQ7dpbX0bWASuARckAUS9wRtFIHsiY+uULXJvxJGo2YD1YewvXQ4G8Fj7XT5oBCiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@oven/bun-linux-aarch64": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64/-/bun-linux-aarch64-1.4.2.tgz", + "integrity": "sha512-3BBP9ovJ2RGHFH6Ae1CAtxNtG1+YY6GD6rmYbsUosoAk9+OEl6zeDQ/k4fBkc6dYOJCtWnx8hUxzNzQATSmvYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-aarch64-android": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64-android/-/bun-linux-aarch64-android-1.4.2.tgz", + "integrity": "sha512-3mZKO2rhsNgbAUtAHC1UKUlF2zTxFraDZT/Elv8wzyH0fJL9h+Iv3TgB9lO63w89PRn3eFe+NRA1bhVgikKNPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oven/bun-linux-aarch64-musl": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64-musl/-/bun-linux-aarch64-musl-1.4.2.tgz", + "integrity": "sha512-+Sm6y+lSiSFBOtXmnekp5Q6n1tUKlyv71FCPWBc61Cgb14T5eBs8SN/nh4MUCOKzONkI3O+as3MGUgikS4aCBQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64/-/bun-linux-x64-1.4.2.tgz", + "integrity": "sha512-9/E/UXOTpSo3YsV5g+FhtTd/qTpiWoKuxS12cqtuYA1ssu9fRAoPQnipFgGyck3tWO63iUdxBiygq+kELFawng==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64-android": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-android/-/bun-linux-x64-android-1.4.2.tgz", + "integrity": "sha512-6HC5tzcC79113n2IHCTJMWv+HsQImv4ZFEK2XpYLxY6HbT8tM4cUM2Zv1bHZBQsS3jv/zYBamDJ1UX7If0d5tw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oven/bun-linux-x64-musl": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-musl/-/bun-linux-x64-musl-1.4.2.tgz", + "integrity": "sha512-vVTKUg1bnPhRP/Hp73jIVoFh2vPFNYEqYX0ERKfZBOQEEHitNAeukZzzuUDZS0SoDCIpuWUGSpd/CDMbjdR+Uw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-windows-aarch64": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@oven/bun-windows-aarch64/-/bun-windows-aarch64-1.4.2.tgz", + "integrity": "sha512-8EJ1ST7339WJE3poPW5nBgVW/lWf9HBz4W27ZUNhburKmcBLOByPyE6DP9fHD8FQGm5c+ilUN2hX1mrW0jxq9Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oven/bun-windows-x64": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@oven/bun-windows-x64/-/bun-windows-x64-1.4.2.tgz", + "integrity": "sha512-+bN6OuVld/9diT/RLSXSW7JE6CvNE3gL9XsAEjULi1nUsXd6DNO6GuA9jNdNb3r8PdJFnYHr5aypNV1Oj3Rd9g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", @@ -2165,12 +3096,12 @@ } }, "node_modules/@smithy/core": { - "version": "3.33.3", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.33.3.tgz", - "integrity": "sha512-CsOeKq/9kA3y6VJHt+/+VTCtBaxJ4OTFpgrjIUhPpDIKxBci1k2bJaQASF2h/ELWrulGp+t97DZ0mevfAD8idg==", + "version": "3.34.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.34.1.tgz", + "integrity": "sha512-dLcOUxz8YCv1RZUMKq6GbyUf95pLbrqh34bPvpCZ1+CByFF31BEAFewZjsGCnVsZTKdThNENfGyAgk2TJqVwSw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.17.2", + "@smithy/types": "^4.18.0", "tslib": "^2.6.2" }, "engines": { @@ -2713,6 +3644,166 @@ "integrity": "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g==", "license": "MIT" }, + "node_modules/babel-plugin-jsx-dom-expressions": { + "version": "0.40.10", + "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.40.10.tgz", + "integrity": "sha512-lxve6Y02YiZTldB7efKpnbf1BH00XCFZNYYW235jSGsYaJNFtHrYlKV6/O+miHbjqpIr9FTe5+0no4hofAMbfA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "7.18.6", + "@babel/plugin-syntax-jsx": "^7.18.6", + "@babel/types": "^7.20.7", + "html-entities": "2.3.3", + "parse5": "^7.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.20.12" + } + }, + "node_modules/babel-plugin-jsx-dom-expressions/node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/babel-plugin-module-resolver": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-module-resolver/-/babel-plugin-module-resolver-5.0.2.tgz", + "integrity": "sha512-9KtaCazHee2xc0ibfqsDeamwDps6FZNo5S0Q81dUqEuFzVwPhcT4J5jOqIVvgCA3Q/wO9hKYxN/Ds3tIsp5ygg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "find-babel-config": "^2.1.1", + "glob": "^9.3.3", + "pkg-up": "^3.1.0", + "reselect": "^4.1.7", + "resolve": "^1.22.8" + } + }, + "node_modules/babel-plugin-module-resolver/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/babel-plugin-module-resolver/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/babel-plugin-module-resolver/node_modules/glob": { + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "devOptional": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/babel-plugin-module-resolver/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/babel-plugin-module-resolver/node_modules/minimatch": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.7.tgz", + "integrity": "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/babel-plugin-module-resolver/node_modules/minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-module-resolver/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "devOptional": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/babel-plugin-module-resolver/node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "devOptional": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/babel-preset-solid": { + "version": "1.9.12", + "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.9.12.tgz", + "integrity": "sha512-LLqnuKVDlKpyBlMPcH6qEvs/wmS9a+NczppxJ3ryS/c0O5IiSFOIBQi9GzyiGDSbcJpx4Gr87jyFTos1MyEuWg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jsx-dom-expressions": "^0.40.6" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "solid-js": "^1.9.12" + }, + "peerDependenciesMeta": { + "solid-js": { + "optional": true + } + } + }, "node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", @@ -2742,6 +3833,19 @@ ], "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.24", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.24.tgz", + "integrity": "sha512-hYrgxie335U08WqICoGqKRzV1HFXv6zdxwJE4ekCb80CM9a0SVVsN4QPwT67RraRo+9h8IATk6uxHJw7QSkdOg==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/bignumber.js": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", @@ -2785,12 +3889,93 @@ "node": "20 || >=22" } }, + "node_modules/browserslist": { + "version": "4.29.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.0.tgz", + "integrity": "sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA==", + "devOptional": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.23", + "caniuse-lite": "^1.0.30001810", + "electron-to-chromium": "^1.5.427", + "node-releases": "^2.0.55", + "update-browserslist-db": "^1.3.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, + "node_modules/bun": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/bun/-/bun-1.4.2.tgz", + "integrity": "sha512-TrSXo6HJfIEaczpb3kjX82I2pL47vK1QUNmHRCUdz9IzaOwa9lzOXSWwu2l18YHE3sNfGRapVLd4nNm+22vVVA==", + "cpu": [ + "arm64", + "x64" + ], + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "os": [ + "darwin", + "linux", + "android", + "freebsd", + "win32" + ], + "bin": { + "bun": "bin/bun.exe", + "bunx": "bin/bunx.exe" + }, + "optionalDependencies": { + "@oven/bun-darwin-aarch64": "1.4.2", + "@oven/bun-darwin-x64": "1.4.2", + "@oven/bun-freebsd-aarch64": "1.4.2", + "@oven/bun-freebsd-x64": "1.4.2", + "@oven/bun-linux-aarch64": "1.4.2", + "@oven/bun-linux-aarch64-android": "1.4.2", + "@oven/bun-linux-aarch64-musl": "1.4.2", + "@oven/bun-linux-x64": "1.4.2", + "@oven/bun-linux-x64-android": "1.4.2", + "@oven/bun-linux-x64-musl": "1.4.2", + "@oven/bun-windows-aarch64": "1.4.2", + "@oven/bun-windows-x64": "1.4.2" + } + }, + "node_modules/bun-ffi-structs": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/bun-ffi-structs/-/bun-ffi-structs-0.3.1.tgz", + "integrity": "sha512-3gM7PpVWLyrwxWjcilSiGuhWanhZivvo6l0u573NziPH6f/gwk6McbaYgn7oJWov6pKGRTDbrg94W5DcJsKTtQ==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "typescript": "^5" + } + }, "node_modules/cacache": { "version": "20.0.4", "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.4.tgz", @@ -2826,6 +4011,27 @@ "qified": "^0.10.1" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "devOptional": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, "node_modules/chownr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", @@ -2909,6 +4115,13 @@ "url": "https://opencollective.com/express" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "devOptional": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2956,6 +4169,13 @@ "node": ">=4" } }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", @@ -2999,6 +4219,16 @@ "node": ">=8" } }, + "node_modules/diff": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-9.0.0.tgz", + "integrity": "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -3024,12 +4254,32 @@ "msgpackr": "^2.0.5" } }, + "node_modules/electron-to-chromium": { + "version": "1.5.429", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.429.tgz", + "integrity": "sha512-/1ENIE3cx4HTIx4IfPZFaOunJmsrSVTnj6coXoRVbiJUbkeTyFkJvBeWGkdgh08OhFbxYLMT1kbkwFVSarq6Ow==", + "devOptional": true, + "license": "ISC" + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "license": "MIT" }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "devOptional": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -3039,6 +4289,16 @@ "node": ">=6" } }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.28.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", @@ -3081,6 +4341,16 @@ "@esbuild/win32-x64": "0.28.2" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -3354,6 +4624,16 @@ "flat-cache": "^6.1.23" } }, + "node_modules/find-babel-config": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-2.1.2.tgz", + "integrity": "sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.3" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3430,6 +4710,13 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "devOptional": true, + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3445,6 +4732,16 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gaxios": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.3.1.tgz", @@ -3497,6 +4794,29 @@ "node": ">=14" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/glob": { "version": "13.0.5", "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.5.tgz", @@ -3599,6 +4919,19 @@ "node": ">=20" } }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hookified": { "version": "1.15.1", "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", @@ -3618,6 +4951,13 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/http-cache-semantics": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", @@ -3709,14 +5049,30 @@ } }, "node_modules/ip-address": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.0.tgz", - "integrity": "sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==", + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.2.tgz", + "integrity": "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w==", "license": "MIT", "engines": { "node": ">= 12" } }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3773,6 +5129,26 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "devOptional": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -3820,6 +5196,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "devOptional": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", @@ -3940,6 +5329,19 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/marked": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.1.tgz", + "integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==", + "devOptional": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/mime": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz", @@ -4263,6 +5665,16 @@ "node": ">=18.17" } }, + "node_modules/node-releases": { + "version": "2.0.55", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.55.tgz", + "integrity": "sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/nopt": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", @@ -4424,9 +5836,9 @@ } }, "node_modules/p-map": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.7.tgz", - "integrity": "sha512-VaWRu2i4FJNRtiRWCuuQRgfQ1B7a6+gMSrO+3j0EQi/k0ULfS9kosRxGoiqwzIjZTDI02tGfk5mXXltLg6QtfQ==", + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.8.tgz", + "integrity": "sha512-MitaVsCuCFIvOLLPIU7NnfrZvS9H9h7kwMUkDo+T2pEISaJD48IV9S8iIdXB7PsvvdxyYcsSTTrr90XKsbulNw==", "license": "MIT", "engines": { "node": ">=18" @@ -4435,6 +5847,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -4486,6 +5908,32 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "devOptional": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4505,6 +5953,13 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "devOptional": true, + "license": "MIT" + }, "node_modules/path-scurry": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", @@ -4521,6 +5976,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "devOptional": true, + "license": "ISC" + }, "node_modules/picomatch": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", @@ -4533,6 +5995,85 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-up/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-up/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-selector-parser": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", @@ -4704,6 +6245,35 @@ "node": ">= 20.0.0" } }, + "node_modules/reselect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", + "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -4735,9 +6305,9 @@ "license": "MIT" }, "node_modules/rimraf/node_modules/brace-expansion": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", - "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -4801,6 +6371,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/s-js": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/s-js/-/s-js-0.4.9.tgz", + "integrity": "sha512-RtpOm+cM6O0sHg6IA70wH+UC3FZcND+rccBZpBAHzlUgNO2Bm5BN+FnM8+OBxzXdwpKWFwX11JGF0MFRkhSoIQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4840,6 +6417,29 @@ "node": ">=10" } }, + "node_modules/seroval": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.5.6.tgz", + "integrity": "sha512-rVQVWjjSvlINzaQPZH5JFqsqEsIWdTxY3iJZCnTL/5gQbXIRooVZKI60tVCkOVfzcRPejboxO2t0P89dg5mQaA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/seroval-plugins": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.5.6.tgz", + "integrity": "sha512-HXuLAX2pu/UByPpaeo/TaMfvMIi+1QqIoPJYCcAtU8QkVNwgR6MPlGuCQTErV1JwraaMbYaWVIBX7mppzGLATQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "seroval": "^1.0" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -4928,6 +6528,18 @@ "node": ">= 14" } }, + "node_modules/solid-js": { + "version": "1.9.12", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.12.tgz", + "integrity": "sha512-QzKaSJq2/iDrWR1As6MHZQ8fQkdOBf8GReYb7L5iKwMGceg7HxDcaOHk0at66tNgn9U2U7dXo8ZZpLIAmGMzgw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.1.0", + "seroval": "~1.5.0", + "seroval-plugins": "~1.5.0" + } + }, "node_modules/spdx-exceptions": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", @@ -4945,9 +6557,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.23", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", - "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "version": "3.0.24", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.24.tgz", + "integrity": "sha512-cLS9TtWkIQFyLkJ3/5aFQAOHOSKTlOs/7WDut/XPSdjom1fJhUdkepeJAKXa9Y05+FabHd0sti+Et559vDtkpQ==", "license": "CC0-1.0" }, "node_modules/ssri": { @@ -5058,6 +6670,19 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/tar": { "version": "7.5.22", "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", @@ -5168,7 +6793,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -5217,6 +6842,37 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, + "node_modules/update-browserslist-db": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz", + "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==", + "devOptional": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -5260,6 +6916,22 @@ "node": ">= 8" } }, + "node_modules/web-tree-sitter": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.25.10.tgz", + "integrity": "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA==", + "devOptional": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@types/emscripten": "^1.40.0" + }, + "peerDependenciesMeta": { + "@types/emscripten": { + "optional": true + } + } + }, "node_modules/which": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", diff --git a/package.json b/package.json index 6b05249..82eb700 100644 --- a/package.json +++ b/package.json @@ -33,26 +33,37 @@ "typecheck": "tsc -p tsconfig.test.json", "lint": "eslint src test scripts eslint.config.mjs --max-warnings 0", "test": "node --import tsx --test test/*.test.ts test/*.test.mjs", - "check": "npm run lint && npm run typecheck && npm test && npm run build", + "check": "npm run lint && npm run typecheck && npm test && npm run test:tui && npm run build", "package:check": "node scripts/package-check.mjs", - "install-local": "bash scripts/install-local.sh" + "install-local": "bash scripts/install-local.sh", + "test:tui": "bun --conditions=browser test/fixtures/sidebar-render.ts" }, "dependencies": { - "@opencode/client": "0.0.0-beta-19398", - "@opencode/plugin": "0.0.0-beta-19398", + "@opencode/client": "2.0.6", + "@opencode/plugin": "2.0.6", "proper-lockfile": "^4.1.2", "zod": "^4.1.0" }, "devDependencies": { "@eslint/js": "^10.0.1", + "@opencode/theme": "2.0.6", + "@opentui/core": "0.5.10", + "@opentui/solid": "0.5.10", "@types/node": "^22.0.0", "@types/proper-lockfile": "^4.1.4", + "bun": "1.4.2", "eslint": "^10.10.0", "globals": "^17.12.0", "semver": "^7.8.5", + "solid-js": "1.9.12", "tsx": "^4.20.0", "typescript": "^5.9.0", "typescript-eslint": "^8.70.0" }, - "private": true + "private": true, + "peerDependencies": { + "@opentui/core": ">=0.5.10", + "@opentui/solid": ">=0.5.10", + "solid-js": ">=1.9.12" + } } diff --git a/prompts/bot.md b/prompts/bot.md index d3d8cb4..0a39c3b 100644 --- a/prompts/bot.md +++ b/prompts/bot.md @@ -36,6 +36,9 @@ repository inspection in the implementation session. - Preserve existing work, including changes from earlier rounds. Inspect the current state before editing; do not assume a fresh checkout. - Work only in the assigned worktree and retain its branch and pinned base. + "Repository root" means that worktree's root for edits, builds, and tests. + Do not substitute the primary checkout's build or executable. Automatic file + permission within the repository does not change the assigned worktree. - Do not switch branches, push, merge, open PRs, or post directly to GitHub. The dispatcher owns publication and appends the configured message signature. - Do not change automation configuration, credentials, or permissions merely @@ -57,6 +60,9 @@ repository inspection in the implementation session. verification, or delegation while waiting. - A delegated worker that asks must return control to the main agent. The dispatcher delivers the reply to the main session. +- A repository may enable automatic file-access approval for its checkout and + assigned worktree. The runtime applies it; do not ask again for an operation + it already permits. This does not grant blanket shell approval or bypass denials. - If permission approval is pending, stop. Never bypass a denied operation. Approval requires an authorized user's exact `/allow QUESTION_ID` or `/deny QUESTION_ID` reply in the issue. Never supply that approval yourself. @@ -143,7 +149,13 @@ repository inspection in the implementation session. - Check correctness, regressions, scope, missing tests, documentation, and unintended files or debug artifacts. - Address actionable findings and rerun verification affected by further edits. -- Update relevant documentation and workflow diagrams when behavior changes. +- Documentation is part of the change. If implementation affects described + behavior, update every affected reference, example, command and workflow + diagram in the same worktree before finishing. Do not defer documentation to + another task or release. Review cross-linked pages and repository instructions, + validate diagram syntax and links, and describe actual implemented behavior. +- In the final report, identify documentation updated or explain why no documented + behavior was affected. Do not claim completion while descriptions are stale. - Before finishing, ensure delegated work is resolved and no worker or background command remains able to modify the worktree. - Do not declare completion while a question, required decision, or material @@ -176,8 +188,23 @@ repository inspection in the implementation session. - Do not repeat an action with uncertain results until its state is reconciled. - Preserve question and permission boundaries after compaction or restart. +## Cancelled rounds + +An operator may cancel one round while keeping issue/PR tracking. Do not resume a +cancelled session or act on its former permission request. A later round receives +new feedback in a fresh worktree based on the published PR or pinned base; keep +that local branch and leave archived worktrees unchanged. Do not reapply the +cancelled scope or copy archived changes unless the new request asks for them. +The dispatcher still publishes to the existing remote PR branch. + ## Final report +The dispatcher copies the final public text of a successfully completed session +into the PR description with Markdown preserved. Write a review-ready report of +completed work, not an acknowledgement or a promise to begin. On follow-ups, report +what changed in this round; the original report remains in the PR and this report +becomes its Latest update. Do not include private reasoning or raw tool transcripts. + Finish an implementation session with a concise English summary covering: - The behavior delivered. diff --git a/scripts/package-check.mjs b/scripts/package-check.mjs index 8780a95..56ee03b 100644 --- a/scripts/package-check.mjs +++ b/scripts/package-check.mjs @@ -18,11 +18,11 @@ try { const [archive] = JSON.parse(packed.stdout); assert.equal(archive.version, pkg.version, "Packed version must match package.json"); assert.equal(archive.filename, basename(archive.filename), "Archive name must not contain a directory"); - for (const required of ["dist/index.js", "dist/tui.js", "dist/setup.js", "dist/install.js", "scripts/postinstall.mjs", "prompts/bot.md", "CHANGELOG.md"]) { + for (const required of ["dist/index.js", "dist/tui.js", "dist/setup.js", "dist/install.js", "dist/repositories.js", "dist/repository-report.js", "scripts/postinstall.mjs", "prompts/bot.md", "CHANGELOG.md"]) { assert.ok(archive.files.some(file => file.path === required), `Missing packaged file: ${required}`); } const file = join(output, archive.filename), prefix = join(temporary, "prefix"), config = join(temporary, "config"); - const env = { ...process.env, OPENCODE_CONFIG_DIR: config, XDG_CONFIG_HOME: join(temporary, "xdg") }; + const env = { ...process.env, OPENCODE_CONFIG_DIR: config, XDG_CONFIG_HOME: join(temporary, "xdg"), XDG_STATE_HOME: join(temporary, "state") }; // Reuse cached downloads, allowing metadata lookups absent from npm ci's cache. await exec("npm", ["install", "--global", "--prefix", prefix, "--prefer-offline", "--ignore-scripts=false", "--no-audit", "--no-fund", file], { env, timeout: 120_000, maxBuffer: 8 * 1024 * 1024, @@ -35,6 +35,8 @@ try { assert.deepEqual(await readdir(config), ["plugins"], "Installation must not create project configuration"); const help = await exec(join(prefix, "bin", pkg.name), ["--help"], { env, cwd: temporary, timeout: 15_000 }); assert.match(help.stdout, /init/); + const inventory = await exec(join(prefix, "bin", pkg.name), ["list", "--json"], { env, cwd: temporary, timeout: 15_000 }); + assert.deepEqual(JSON.parse(inventory.stdout), { entries: [], warnings: [] }, "Inventory works outside Git without starting a service"); const digest = createHash("sha256").update(await readFile(file)).digest("hex"); await writeFile(`${file}.sha256`, `${digest} ${archive.filename}\n`); if (process.env.GITHUB_OUTPUT) await appendFile(process.env.GITHUB_OUTPUT, `filename=${archive.filename}\n`); diff --git a/scripts/release-pipeline.mjs b/scripts/release-pipeline.mjs index 3fec311..85356bc 100644 --- a/scripts/release-pipeline.mjs +++ b/scripts/release-pipeline.mjs @@ -1,6 +1,7 @@ import { execFile } from "node:child_process"; import { createHash } from "node:crypto"; -import { mkdir, readFile, writeFile } from "node:fs/promises"; +import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; import { join } from "node:path"; import { pathToFileURL } from "node:url"; import { promisify } from "node:util"; @@ -19,6 +20,9 @@ export function releaseRequest(event, repository) { if (event.repository?.full_name !== repository) throw new Error("Release event belongs to another repository."); if (event.action === "closed" && event.pull_request?.merged === true && event.pull_request.base?.ref === "release") { const pr = event.pull_request; + if (pr.head?.ref !== "devel" || pr.head.repo?.full_name !== repository) { + throw new Error("Automatic publication requires a same-repository devel-to-release PR."); + } if (!Number.isSafeInteger(pr.number) || pr.number <= 0 || !/^[a-f0-9]{40}$/.test(pr.merge_commit_sha)) { throw new Error("Merged PR must provide its number and merge commit."); } @@ -120,6 +124,49 @@ export async function buildPackage({ cwd, directory, version }) { return { archive, checksum }; } +// Merge the exact published release head, never copy files over newer development. +// Isolate merge attempts from the publisher checkout; a normal push protects races. +export async function syncDevel({ cwd, releaseHead }) { + const git = gitAt(cwd); + const ref = "refs/remotes/origin/devel"; + for (let attempt = 0; attempt < 3; attempt++) { + await git("fetch", "origin", "refs/heads/devel:refs/remotes/origin/devel"); + const before = await git("rev-parse", ref); + try { + await git("merge-base", "--is-ancestor", releaseHead, before); + return { head: before, changed: false }; + } catch {} + const temporary = await mkdtemp(join(tmpdir(), "oc2-sync-devel-")); + const checkout = join(temporary, "checkout"); + let added = false; + try { + await git("worktree", "add", "--detach", checkout, before); + added = true; + const merge = gitAt(checkout); + try { + await merge(...identity, "merge", "--no-edit", releaseHead); + } catch { + throw new Error("Automatic release-to-devel merge conflicted. Remote devel was not changed. Resolve the conflict on devel, then rerun this Release job; no sync PR is created."); + } + const head = await merge("rev-parse", "HEAD"); + try { + await merge("push", "origin", "HEAD:refs/heads/devel"); + return { head, changed: true }; + } catch (error) { + await git("fetch", "origin", "refs/heads/devel:refs/remotes/origin/devel"); + if (await git("rev-parse", ref) === before) { + throw new Error("Automatic devel sync push failed. Check publisher write permission and devel branch rules, then rerun this Release job.", { cause: error }); + } + // A concurrent feature merge won the race. Re-merge its new tip, never force. + } + } finally { + if (added) await git("worktree", "remove", "--force", checkout); + await rm(temporary, { recursive: true, force: true }); + } + } + throw new Error("devel kept changing during synchronization. Rerun this Release job to retry without rebuilding or bumping the version."); +} + export async function runRelease({ cwd, repository, event, github, directory, build = buildPackage }) { const request = releaseRequest(event, repository); const git = gitAt(cwd); @@ -173,7 +220,8 @@ export async function runRelease({ cwd, repository, event, github, directory, bu title: `Release ${tag}`, body: `Publish ${tag} to main with all released changes and the updated package download.\n\nRelease: ${published.html_url}\n\n${notes}\nMerge this PR with a merge commit to preserve the long-lived release branch.`, }); - return { tag, pullRequest: pull.html_url }; + const devel = await syncDevel({ cwd, releaseHead: await git("rev-parse", "HEAD") }); + return { tag, pullRequest: pull.html_url, devel }; } export async function verifyPromotion({ cwd, repository, event, github }) { @@ -192,7 +240,11 @@ export async function verifyPromotion({ cwd, repository, event, github }) { return `Release ${published.tag_name} and README are ready for review.`; } -export async function verifyFeature(cwd) { +export async function verifyFeature(cwd, event, repository) { + if (event?.pull_request?.base?.ref === "release" && + (event.pull_request.head?.ref !== "devel" || event.pull_request.head.repo?.full_name !== repository)) { + throw new Error("Feature PRs must target devel. Only same-repository devel can target release."); + } const git = gitAt(cwd); const pkg = await manifest(git, "HEAD"); const version = semver.inc(pkg.version, "patch"); @@ -235,7 +287,8 @@ export function githubClient(repository, token) { if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { try { if (process.argv[2] === "verify-feature") { - console.log(await verifyFeature(process.cwd())); + const event = process.env.GITHUB_EVENT_PATH ? JSON.parse(await readFile(process.env.GITHUB_EVENT_PATH, "utf8")) : undefined; + console.log(await verifyFeature(process.cwd(), event, process.env.GITHUB_REPOSITORY)); } else { const repository = process.env.GITHUB_REPOSITORY; if (!repository || !process.env.GH_TOKEN || !process.env.GITHUB_EVENT_PATH) throw new Error("Run this script through GitHub Actions with its repository, token, and event file."); diff --git a/src/activity.ts b/src/activity.ts index 33f51e9..cbe75c4 100644 --- a/src/activity.ts +++ b/src/activity.ts @@ -7,15 +7,38 @@ export const Activity = z.object({ worktree: z.string().optional(), sessionReady: z.boolean(), error: z.string().optional(), prURL: z.string().optional(), prState: z.string().optional(), sessionIDs: z.array(z.string()).optional(), + controlVersion: z.number().optional(), localBranch: z.string().optional(), + historicalError: z.string().optional(), cancelledRound: z.number().optional(), cancelledWorktree: z.string().optional(), + closeRequestedAt: z.number().optional(), closedAt: z.number().optional(), + branch: z.string().optional(), baseBranch: z.string().optional(), model: z.string().optional(), + attempts: z.number().optional(), nextAt: z.number().optional(), pendingFeedback: z.number().optional(), + helpers: z.number().optional(), recovery: z.boolean().optional(), + question: z.enum(["permission", "analysis", "base", "implementation"]).optional(), + prNumber: z.number().optional(), }); export type Activity = z.infer; export function activityOf(task: Task): Activity { + const historical = task.status === "closed" || task.status === "watching"; + const error = task.cancellation?.error ?? task.closeError ?? task.error ?? task.mergeError; return { key: task.key, repo: task.repo, issueNumber: task.issue.number, round: task.round ?? 1, phase: task.merged ? "merged" : task.pr?.state === "closed" ? "pr_closed" : task.phase, status: task.status, sessionIDs: [...new Set([...task.sessionIDs ?? [], ...[task.previousSessionID, task.sessionID].filter((id): id is string => Boolean(id)), ...task.helpers?.map(h => h.id) ?? []])], ...(task.sessionID ? { sessionID: task.sessionID } : {}), ...(task.worktree ? { worktree: task.worktree } : {}), sessionReady: task.sessionReady ?? Boolean(task.promptAttempted), - ...(task.error || task.mergeError ? { error: task.error ?? task.mergeError } : {}), - ...(task.pr ? { prURL: task.pr.html_url, prState: task.pr.state } : {}) }; + ...(!historical && error ? { error } : task.status === "watching" && task.mergeError ? { error: task.mergeError } : {}), + ...(historical && (task.error ?? task.cancelledRounds?.at(-1)?.error) ? { historicalError: task.error ?? task.cancelledRounds?.at(-1)?.error } : {}), + ...(task.controlVersion !== undefined ? { controlVersion: task.controlVersion } : {}), + ...(task.cancelledRounds?.length ? { cancelledRound: task.cancelledRounds.at(-1)!.round } : {}), + ...(task.cancelledRounds?.at(-1)?.worktree ? { cancelledWorktree: task.cancelledRounds.at(-1)!.worktree } : {}), + ...(task.localBranch ? { localBranch: task.localBranch } : {}), + ...(task.closeRequestedAt !== undefined ? { closeRequestedAt: task.closeRequestedAt } : {}), + ...(task.closedAt !== undefined ? { closedAt: task.closedAt } : {}), + ...(task.branch ? { branch: task.branch } : {}), + ...(task.baseBranch ? { baseBranch: task.baseBranch } : {}), + ...(task.route ? { model: `${task.route.model.providerID}/${task.route.model.id}` } : {}), + ...(!historical && task.attempts !== undefined ? { attempts: task.attempts } : {}), ...(task.nextAt !== undefined ? { nextAt: task.nextAt } : {}), pendingFeedback: task.pendingFeedback?.length ?? 0, + helpers: task.helpers?.filter(h => h.parentID === task.sessionID).length ?? 0, recovery: Boolean(task.recovery), + ...(task.question && !task.question.delivered ? { question: task.question.permission ? "permission" as const : task.question.purpose ?? "implementation" as const } : {}), + ...(task.pr ? { prURL: task.pr.html_url, prState: task.pr.state, ...(task.pr.number ? { prNumber: task.pr.number } : {}) } : {}) }; } diff --git a/src/approval.ts b/src/approval.ts index 2cc3c2e..770320f 100644 --- a/src/approval.ts +++ b/src/approval.ts @@ -12,7 +12,10 @@ export function approvalAuthors(reviews: Review[], comments: DatedComment[], hea } // An outstanding request for changes takes precedence over a merge comment. if ([...latest.values()].some(r => r.state === "CHANGES_REQUESTED")) return []; - const authors = [...latest.values()].filter(r => r.state === "APPROVED" && r.commit_id === head && Date.parse(r.submitted_at ?? "") > since).map(r => r.user.login); + // Reviews are bound to an exact commit. Description/publication retries must + // not invalidate an approval already submitted for that same verified head. + // Plain merge comments have no commit binding and still need the time window. + const authors = [...latest.values()].filter(r => r.state === "APPROVED" && r.commit_id === head && Number.isFinite(Date.parse(r.submitted_at ?? ""))).map(r => r.user.login); for (const comment of comments) { if (comment.user.type === "Bot" || comment.body.includes("`; + requireTracked(task); const commentID = await this.github.ensureComment(task.repo, task.issue.number, marker, decision.comment); await this.update(task, { commentID, phase: "commented", attempts: 0, ...(task.question?.purpose === "analysis" ? { question: undefined } : {}) }); } @@ -313,28 +443,31 @@ export class Dispatcher { private async mergeOnce() { if (!this.options.autoMerge.enabled || !this.github.mergeApproved) return; for (const task of this.queue.tasks) { - if (task.status !== "done" || !task.pr || task.pr.state === "closed" || !task.commit || task.merged || task.pendingFeedback?.length || (task.mergeNextAt ?? 0) > this.now()) continue; + const head = task.status === "watching" ? task.publishedHead : task.commit ? { commit: task.commit, at: task.publishedAt } : undefined; + if (!["done", "watching"].includes(task.status) || !task.pr || task.pr.state === "closed" || !head || task.merged || task.pendingFeedback?.length || (task.mergeNextAt ?? 0) > this.now()) continue; const repo = this.options.repositories.find(r => r.repo === task.repo); if (!repo) continue; - // Older queues start watching now; historical approvals must not trigger an unexpected merge. - if (!task.publishedAt) { await this.update(task, { publishedAt: this.now() }); continue; } + // Older queues need a fresh window for merge comments, which have no SHA. + if (!head.at) { await this.update(task, { publishedAt: this.now() }); continue; } try { await this.scan(); // Pick up issue feedback before considering a completed task for merge. - if (task.pendingFeedback?.length || task.pr.state === "closed") continue; - const merged = await this.github.mergeApproved(task.repo, task.pr.number, task.commit, task.publishedAt, repo.allowedAuthors, this.options.autoMerge); + if (suspended(task) || task.pendingFeedback?.length || task.pr.state === "closed") continue; + this.publishing.add(task.key); + const merged = await this.github.mergeApproved(task.repo, task.pr.number, head.commit, head.at, repo.allowedAuthors, this.options.autoMerge); if (merged) { await this.github.ensureComment(task.repo, task.pr.number, ``, "Pull request merged."); await this.update(task, { merged: true, pr: { ...task.pr, state: "closed" }, mergeError: undefined }); } else await this.update(task, { mergeError: undefined, mergeNextAt: this.now() + 60_000 }); } catch (error) { - if (this.signal.aborted) return; + if (this.signal.aborted || suspended(task)) return; await this.update(task, { mergeError: redact(error, this.secrets), mergeNextAt: Math.max(this.now() + 60_000, error instanceof GithubError ? error.retryAt ?? 0 : 0) }); } + finally { this.publishing.delete(task.key); } } } runtime(sessionID: string) { const task = this.queue.tasks.find(t => t.sessionID === sessionID || t.helpers?.some(h => h.id === sessionID && h.parentID === t.sessionID)); - if (!task) return null; + if (!task || suspended(task) || task.status === "watching") return null; const result = JSON.parse(JSON.stringify(task)) as Task; const matching = Object.values(this.options.routes).filter(r => r.agent === task.route?.agent && r.model.id === task.route?.model.id && r.model.providerID === task.route?.model.providerID); const configured = matching.length === 1 ? matching[0] : undefined; @@ -349,6 +482,7 @@ export class Dispatcher { private async askTask(task: Task, input: z.infer) { let question!: z.infer; await this.serial.run(async () => { + requireTracked(task); if (!task.question || task.question.delivered) task.question = input; question = task.question; await this.store.save(this.queue); }); @@ -356,6 +490,7 @@ export class Dispatcher { return { id: question.id }; } private async publishQuestion(task: Task, question: z.infer) { + requireTracked(task); if (!question.commentID) { const body = `Question (${question.id})\n\n${question.text}\n\n${question.permission ? `Reply with /allow ${question.id} or /deny ${question.id}.` : "Reply in this issue to continue. Only configured authors can answer."}`; const marker = ``; @@ -372,9 +507,10 @@ export class Dispatcher { } async helper(sessionID: string, callID: string, capability: "vision" | "audio") { const task = this.queue.tasks.find(t => t.sessionID === sessionID); - if (!task || task.phase !== "running" || task.question && !task.question.delivered) throw new Error("No active main bot session available for delegation"); + if (!task || suspended(task) || task.status === "watching" || task.phase !== "running" || task.question && !task.question.delivered) throw new Error("No active main bot session available for delegation"); const id = `ses_${createHash("sha256").update(`${sessionID}:${callID}`).digest("hex").slice(0, 32)}`; await this.serial.run(async () => { + requireTracked(task); if (!task.helpers?.some(h => h.id === id)) task.helpers = [...task.helpers ?? [], { id, parentID: sessionID, capability }]; await this.store.save(this.queue); }); @@ -386,12 +522,32 @@ export class Dispatcher { try { return await this.maintenance; } finally { this.maintenance = undefined; } } + async restartWorkflow(key: string) { + return this.serial.run(async () => { + this.signal.throwIfAborted(); + const task = this.queue.tasks.find(t => t.key === key); + if (!task) throw new Error("Task not found in this project"); + if (closing(task)) throw new Error("Task tracking is closed; use Resume issue tracking to watch future comments."); + if (task.status === "cancelling") throw new Error("Wait for round cancellation to finish."); + if (task.question && !task.question.delivered) throw new Error("Answer the pending question or permission request in the GitHub issue first"); + if (task.merged || task.pr?.state === "closed") throw new Error("The original PR is closed or merged; reopen it or create a new issue"); + if (!["blocked", "failed"].includes(task.status)) return false; + if (task.phase === "running" && !stoppedSession(task)) throw new Error("Inspect the session error before retrying; workflow restart cannot bypass uncertain prompt delivery or execution configuration errors"); + if (!task.route) throw new Error("Fix the execution route and use retry before restarting the workflow"); + Object.assign(task, { + status: "ready", attempts: 0, nextAt: this.now(), error: undefined, + ...(task.phase === "running" ? { recovery: { id: randomUUID() } } : {}), + }); + await this.store.save(this.queue); + return true; + }); + } private async retryOnce(key: string, restartSession: boolean) { const task = this.queue.tasks.find(t => t.key === key); if (!task || !["blocked", "failed"].includes(task.status)) return false; if (restartSession) { await this.executor.cancel(task); - await this.update(task, { sessionID: undefined, promptAttempted: undefined, phase: task.commentID ? "commented" : "queued", analysis: task.commentID ? task.analysis : undefined }); + await this.update(task, { sessionID: undefined, completion: undefined, promptAttempted: undefined, phase: task.commentID ? "commented" : "queued", analysis: task.commentID ? task.analysis : undefined }); } if (!task.route) { const latest = await this.github.issue(task.repo, task.issue.number); @@ -400,5 +556,119 @@ export class Dispatcher { await this.update(task, { status: "ready", attempts: 0, nextAt: this.now(), error: undefined }); return true; } - async settle() { await Promise.allSettled([this.scanning, this.working, this.maintenance]); } + async closeTask(key: string) { + const task = await this.serial.run(async () => { + this.signal.throwIfAborted(); + const task = this.queue.tasks.find(t => t.key === key); + if (!task) throw new Error("Task not found in this project"); + if (task.status === "closed") return undefined; + if (task.status === "cancelling") throw new Error("Round cancellation is still in progress; wait before ending tracking."); + if (this.publishing.has(key)) throw new Error("Publication or merge is already in flight. Wait for it to finish, then close the task."); + Object.assign(task, { controlVersion: (task.controlVersion ?? 0) + 1, status: "closing", closeRequestedAt: task.closeRequestedAt ?? this.now(), closeError: undefined, nextAt: this.now() }); + await this.store.save(this.queue); + return task; + }); + if (!task) return false; + await this.notify(activityOf(task)).catch(() => {}); + this.startClosing(task); + return true; + } + private startClosing(task: Task) { + if (this.closures.has(task.key) || this.signal.aborted) return; + const work = this.activeTask === task.key ? this.working : undefined; + const operation = (async () => { + try { + // The durable status is saved before interruption. A replacement owner + // resumes this operation and never retries implementation/publication. + await this.executor.cancel(task, true); + await work; + await Promise.allSettled([...this.questionPosts].filter(([key]) => key.startsWith(``, end: `` }; +} +function summaryText(summary: CompletionSummary) { + return bounded(summary.text ?? `Summary unavailable: ${summary.unavailable ?? "No saved completion report."}`, 22000) + .replaceAll("