feat(tool): add automate_manage lifecycle actions#1278
Conversation
📝 WalkthroughWalkthroughThis PR adds a new deferred tool, Changesautomate_manage Tool Implementation & Integration
Sequence DiagramsequenceDiagram
participant User
participant Tool as automate_manage Tool
participant AutomationService as Automation.Service
participant Context as ctx.ask
participant Scheduler as AutomationScheduler
User->>Tool: delete { action: "delete", id: "automation-id" }
activate Tool
Tool->>Scheduler: settle owner
Tool->>AutomationService: get(id)
AutomationService-->>Tool: automation
Tool->>Context: ask permission for delete (title,id)
activate Context
User-->>Context: approve
Context-->>Tool: permission granted
Tool->>AutomationService: remove(id)
AutomationService-->>Tool: { tombstone, stoppedRun? }
Tool->>Scheduler: cancel scheduled work for id
Scheduler-->>Tool: cancelled
Tool->>Tool: publishDefinitionDeleted / publishRunUpdated
Tool-->>User: { tombstone, metadata }
deactivate Tool
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces the automate_manage tool, which allows listing, pausing, resuming, and deleting existing PawWork Automations. It includes the tool definition, integration into the tool registry and info modules, and comprehensive unit tests. The feedback suggests improving error handling in the tool's execution flow by leveraging the Effect ecosystem's error channel (using Effect.fail and Effect.succeed instead of throwing synchronous errors).
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/opencode/src/tool/tool-info.ts`:
- Around line 69-75: The tool_info entry for the automate_manage tool does not
fully document the activation-time contract: update the description string for
the automate_manage object (the one with id "automate_manage" and parameters
cast to Tool.Def["parameters"]) to explicitly state that "list" operations are
limited to the current context only and that pause, resume, and delete
operations require an exact automation id (no fuzzy matching); keep the existing
user-facing guidance but extend the deferred description to enumerate these
constraints so the pre-activation model cannot assume global listing or allow
fuzzy id targets.
In `@packages/opencode/test/tool/automate-manage.test.ts`:
- Around line 1-196: The suite currently creates a manual ManagedRuntime
(ManagedRuntime, runtime, and the await runtime.runPromise(...) that yields
automation) and uses plain bun:test test(...) cases; replace that wiring by
converting tests to use the repository test harness (testEffect) and run
OS-backed scenarios with it.live(...), remove the
ManagedRuntime/runtime/automation manual setup and instead obtain the Automation
service via the testEffect provided layer (use Automation.defaultLayer or the
harness's layer composition), and update each test case to run as testEffect +
it.live so Effect services (e.g., createAutomateManageDefinition,
AutomationScheduler interactions, Flock/tmpdir) are executed under the standard
Effect test environment rather than custom runtime.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 4c3ad70b-c5f8-45d3-b4dd-bdfb747c3037
📒 Files selected for processing (6)
packages/opencode/src/tool/automate-manage.tspackages/opencode/src/tool/registry.tspackages/opencode/src/tool/tool-info.tspackages/opencode/test/tool/automate-manage.test.tspackages/opencode/test/tool/registry.test.tspackages/opencode/test/tool/tool-info.test.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/opencode/src/tool/automate-manage.ts`:
- Around line 101-103: The update path for pause/resume currently calls
automation.update(id, { paused: ... }) after reading previous and can surface a
raw NotFound; wrap or replace that call with the same readable stale-id mapping
used elsewhere so a removed definition produces the friendly "automate_manage
list" guidance instead of a raw error—i.e., intercept errors from
automation.update (in automate-manage.ts around the params.action ===
"pause"/"resume" branch), detect the not-found/stale-id case and re-throw or
return the mapped, human-friendly error message used elsewhere (preserving the
existing revision check against previous.revision).
In `@packages/opencode/test/session/prompt-effect.test.ts`:
- Around line 1040-1044: Add an explicit assertion on the number of emitted
requests before indexing into requests: after collecting const requests = yield*
llm.inputs, assert the expected count (e.g., expect(requests).toHaveLength(2) or
expect(requests.length).toBeGreaterThanOrEqual(2)) so the subsequent positional
checks that call requestToolNames(requests[0]) and requestToolNames(requests[1])
fail with a clear message if fewer requests were emitted.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: c8f27aba-d8cb-4d92-8b42-541de9f443e4
📒 Files selected for processing (16)
packages/app/e2e/snap/fixtures/permission-dock-fixture.tsxpackages/app/e2e/snap/permission-dock.snap.tspackages/app/src/i18n/en.tspackages/app/src/i18n/zh.tspackages/app/src/pages/session/composer/session-permission-dock.test.tspackages/app/src/pages/session/composer/session-permission-dock.tsxpackages/opencode/src/acp/agent.tspackages/opencode/src/session/prompt/pawwork.txtpackages/opencode/src/tool/automate-manage.tspackages/opencode/src/tool/shell.txtpackages/opencode/src/tool/tool-info.tspackages/opencode/test/acp/event-subscription.test.tspackages/opencode/test/server/automation-scheduler.test.tspackages/opencode/test/session/prompt-effect.test.tspackages/opencode/test/tool/automate-manage.test.tspackages/opencode/test/tool/registry.test.ts
✅ Files skipped from review due to trivial changes (3)
- packages/opencode/src/tool/shell.txt
- packages/app/src/i18n/zh.ts
- packages/app/src/i18n/en.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/opencode/src/tool/tool-info.ts
- packages/opencode/test/tool/registry.test.ts
| if (params.action === "pause" || params.action === "resume") { | ||
| const definition = yield* automation.update(id, { paused: params.action === "pause" }) | ||
| if (definition.revision !== previous.revision) { |
There was a problem hiding this comment.
Apply readable stale-id mapping to the pause/resume update call.
Line 102 performs automation.update(...) after a separate read on Line 100. If the definition is removed between those calls, the update path can leak a raw not-found error instead of the automate_manage list guidance used elsewhere.
💡 Suggested fix
- const definition = yield* automation.update(id, { paused: params.action === "pause" })
+ const definition = yield* readableAutomationEffect(
+ automation.update(id, { paused: params.action === "pause" }),
+ id,
+ )🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/opencode/src/tool/automate-manage.ts` around lines 101 - 103, The
update path for pause/resume currently calls automation.update(id, { paused: ...
}) after reading previous and can surface a raw NotFound; wrap or replace that
call with the same readable stale-id mapping used elsewhere so a removed
definition produces the friendly "automate_manage list" guidance instead of a
raw error—i.e., intercept errors from automation.update (in automate-manage.ts
around the params.action === "pause"/"resume" branch), detect the
not-found/stale-id case and re-throw or return the mapped, human-friendly error
message used elsewhere (preserving the existing revision check against
previous.revision).
| const requests = yield* llm.inputs | ||
| expect(requestToolNames(requests[0])).toContain("tool_info") | ||
| expect(requestToolNames(requests[0])).not.toContain("automate_manage") | ||
| expect(requestToolNames(requests[1])).toContain("automate_manage") | ||
|
|
There was a problem hiding this comment.
Add an explicit request-count assertion before positional tool-list checks.
Lines 1041-1044 index requests[0] / requests[1] directly. If the loop emits fewer requests in a regression, this throws before asserting the activation contract and makes failures harder to diagnose.
💡 Suggested fix
const requests = yield* llm.inputs
- expect(requestToolNames(requests[0])).toContain("tool_info")
- expect(requestToolNames(requests[0])).not.toContain("automate_manage")
- expect(requestToolNames(requests[1])).toContain("automate_manage")
+ expect(requests.length).toBeGreaterThanOrEqual(2)
+ const [firstRequest, secondRequest] = requests
+ expect(requestToolNames(firstRequest)).toContain("tool_info")
+ expect(requestToolNames(firstRequest)).not.toContain("automate_manage")
+ expect(requestToolNames(secondRequest)).toContain("automate_manage")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const requests = yield* llm.inputs | |
| expect(requestToolNames(requests[0])).toContain("tool_info") | |
| expect(requestToolNames(requests[0])).not.toContain("automate_manage") | |
| expect(requestToolNames(requests[1])).toContain("automate_manage") | |
| const requests = yield* llm.inputs | |
| expect(requests.length).toBeGreaterThanOrEqual(2) | |
| const [firstRequest, secondRequest] = requests | |
| expect(requestToolNames(firstRequest)).toContain("tool_info") | |
| expect(requestToolNames(firstRequest)).not.toContain("automate_manage") | |
| expect(requestToolNames(secondRequest)).toContain("automate_manage") |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/opencode/test/session/prompt-effect.test.ts` around lines 1040 -
1044, Add an explicit assertion on the number of emitted requests before
indexing into requests: after collecting const requests = yield* llm.inputs,
assert the expected count (e.g., expect(requests).toHaveLength(2) or
expect(requests.length).toBeGreaterThanOrEqual(2)) so the subsequent positional
checks that call requestToolNames(requests[0]) and requestToolNames(requests[1])
fail with a clear message if fewer requests were emitted.
Summary
Adds a deferred
automate_managetool for listing, pausing, resuming, and deleting existing PawWork Automations by exact id.Also tightens the permission surfaces exposed by this new delete flow: delete confirmation shows the automation title/id, one-time-only prompts no longer show an unavailable "allow always" choice in the app or ACP, and model routing now points existing automation management requests to
automate_manageviatool_info.Why
Users can create Automations from conversation with
automate, but issue #1249 tracks the missing management side: seeing existing automation ids, reversible pause/resume, and deletion without falling back to shell or OS scheduler workflows.Related Issue
Closes #1249
Human Review Status
Pending
Review Focus
Please focus on the management contract:
liststays current-scope, pause/resume do not ask, delete asks throughctx.askbefore removal, live active-run deletion preserves the existing 409-style behavior as a readable tool error, and once-only delete prompts do not offer persistent approval.Risk Notes
Deletion is destructive, so the tool gates it through
ctx.askand exact ids. The delete permission is intentionally one-time-only becauseautomate_managedelete passes noalwayspatterns. The app permission dock and ACP now hide the persistent approval option whenever a request cannot persist approval.Pause/resume are reversible and follow the existing server route behavior by publishing definition updates instead of adding a separate scheduler path. The visible UI change is limited to the permission dock copy/buttons. The platform/permissions impact is shared app renderer and ACP behavior only; no OS-specific packaging, updater, signing, installer, or filesystem path behavior changed.
How To Verify
Screenshots or Recordings
Local snap generated and visually reviewed at
docs/design/preview/screenshots/permission-dock.png. It showsautomate_managedelete confirmation with automation title/id and noAllow alwaysbutton, plus a normal persistable shell permission that still showsAllow always.Checklist
bug,enhancement,task,documentation. Type labels are author-added; the labeler bot does NOT assign them. Add the label in the GitHub UI, then tick this.app,ui,platform,harness,ci. The labeler bot assigns these on PR open based on changed paths. Confirm the bot's choice (or override if wrong), then tick this.P0,P1,P2,P3. The priority-triage bot suggests one on PR open. Confirm or override, then tick this.Pending,Approved by @<reviewer>, orNot required: <reason>(default isPending; "not required" is restricted to bot-authored low-risk PRs).dev, and my PR title and commit messages use Conventional Commits in English.Summary by CodeRabbit
New Features
UI
Localization
Tests