Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/client/src/effect/api/api.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/client/src/effect/generated/client.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/client/src/promise/generated/client.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

52 changes: 46 additions & 6 deletions packages/client/src/promise/generated/types.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 42 additions & 2 deletions packages/core/schema.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"version": "7",
"dialect": "sqlite",
"id": "e5daa0af-cae7-4057-919a-ebf44dfced0a",
"prevIds": ["dc16dd12-cb76-432f-8108-0c3aadaf54bb"],
"id": "0ac88673-a402-45a0-b931-e694d59ccefa",
"prevIds": ["e5daa0af-cae7-4057-919a-ebf44dfced0a"],
"ddl": [
{
"name": "account_state",
Expand Down Expand Up @@ -792,6 +792,16 @@
"entityType": "columns",
"table": "schedule"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "check",
"entityType": "columns",
"table": "schedule"
},
{
"type": "text",
"notNull": true,
Expand Down Expand Up @@ -862,6 +872,26 @@
"entityType": "columns",
"table": "schedule"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": "0",
"generated": null,
"name": "checks",
"entityType": "columns",
"table": "schedule"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": "0",
"generated": null,
"name": "failures",
"entityType": "columns",
"table": "schedule"
},
{
"type": "integer",
"notNull": false,
Expand Down Expand Up @@ -902,6 +932,16 @@
"entityType": "columns",
"table": "schedule"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "state",
"entityType": "columns",
"table": "schedule"
},
{
"type": "integer",
"notNull": true,
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/database/migration.gen.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration.js"

const migration: DatabaseMigration.Migration = {
id: "20261029165540_schedule_checks",
up(tx) {
return Effect.gen(function* () {
yield* tx.run(`ALTER TABLE \`schedule\` ADD \`check\` text;`)
yield* tx.run(`ALTER TABLE \`schedule\` ADD \`checks\` integer DEFAULT 0 NOT NULL;`)
yield* tx.run(`ALTER TABLE \`schedule\` ADD \`failures\` integer DEFAULT 0 NOT NULL;`)
yield* tx.run(`ALTER TABLE \`schedule\` ADD \`state\` text;`)
})
},
}

export default migration
4 changes: 4 additions & 0 deletions packages/core/src/database/schema.gen.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 24 additions & 2 deletions packages/core/src/permission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,23 @@ export interface AskResult {

export { Event } from "@opencode/schema/permission"

/**
* Rules that settle a request which would otherwise ask, for work nobody is present to answer, such as a schedule's
* check. A request they do not allow is denied. Configured denies still win, as they are evaluated first.
*/
export const Unattended = Context.Reference<Permission.Ruleset | undefined>("@opencode/Permission/Unattended", {
defaultValue: () => undefined,
})

export class DeclinedError extends Schema.TaggedError<DeclinedError>()("Permission.DeclinedError", {}) {}

export class CorrectedError extends Schema.TaggedError<CorrectedError>()("Permission.CorrectedError", {
feedback: Schema.String,
}) {}
}) {
override get message() {
return this.feedback
}
}

export class BlockedError extends Schema.TaggedError<BlockedError>()("Permission.BlockedError", {
rules: Permission.Ruleset,
Expand Down Expand Up @@ -174,7 +186,17 @@ const layer = Layer.effect(
source: input.source,
effect,
})
return { effect: event.effect, message: event.message, rules: all }
const unattended = yield* Unattended
if (event.effect !== "ask" || unattended === undefined)
return { effect: event.effect, message: event.message, rules: all }
const settled = [...all, ...unattended]
const allowed = input.resources.every((resource) => evaluate(input.action, resource, settled).effect === "allow")
if (allowed) return { effect: "allow" as const, rules: all }
return {
effect: "deny" as const,
message: `Permission ${input.action} needs approval, and nobody is present to give it`,
rules: all,
}
})

function request(input: AssertInput, message?: string): Request {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/plugin/command/schedule.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Request: $ARGUMENTS

Use the schedule tools in the `opencode` namespace. They only see this session's schedules.

- To schedule something, call `schedule_create`. Choose exactly one `when`: `{ in: "20 minutes" }`, `{ at: "2026-10-27T09:00" }`, `{ every: "1 hour" }` or `{ cron: "0 9 * * 1-5" }`. Wall-clock times use the configured time zone unless you pass `tz`. To stop a repeating schedule, add `ends`: `{ after: "8 hours" }`, `{ at: "..." }` or `{ times: 3 }`. Write `prompt` as the instruction you will receive when it fires, with enough context to act on it without this conversation.
- To schedule something, call `schedule_create`. Choose `when` (`{ in: "20 minutes" }`, `{ at: "2026-10-27T09:00" }`, `{ every: "1 hour" }` or `{ cron: "0 9 * * 1-5" }`, optional with `until` which defaults to every 30s). Add `until: { run: "..." }` to wait once for a shell condition (exit 0, `is`, `matches`, or `changes: true`, defaulting to a 24h timeout), or `if: { run: "..." }` to gate each recurring slot without a model call. To stop a repeating schedule, add `ends`: `{ after: "8 hours" }`, `{ at: "..." }` or `{ times: 3 }`. Write `prompt` as the instruction you will receive when it wakes, with enough context to act on it without this conversation.
- To pause, resume or delete one, find its id with `schedule_list`, then call `schedule_pause`, `schedule_resume` or `schedule_delete`.
- If the request is empty, call `schedule_list` and show this session's schedules: name, when, status and next run.

Expand Down
Loading
Loading