@carbon-ni/pi-schedule 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/DEVELOPMENT.md ADDED
@@ -0,0 +1,91 @@
1
+ # Development
2
+
3
+ ## One command per lifecycle stage
4
+
5
+ Use Node.js 22.18+ (Nix provides 24), npm 10.9.9, Git, and Pi 1.5.x. Use
6
+ `nix develop` if Node is unavailable. If your npm differs, use
7
+ `npx --yes npm@10.9.9 ci` before installing hooks.
8
+
9
+ | Stage | Command | Enforcement |
10
+ | ----------------- | -------------------------------------- | ----------------------------------------------------------------- |
11
+ | Discover commands | `make help` | Copyable local commands |
12
+ | Install | `make setup` | Locked dependencies, install scripts disabled, versioned hooks |
13
+ | Verify hooks | `make hooks-check` | Checks hook path and executability |
14
+ | During work | `fzz --log-file .tmp/funzzy/tests.log` | Same normal gate on changes |
15
+ | Normal gate | `make check` | Pre-commit, pre-push, watcher, CI |
16
+ | Format | `make format` / `make format-write` | Explicit manual/release check |
17
+ | Coverage | `make coverage` | Explicit budgets: 85% lines/functions, 75% branches |
18
+ | Security | `make security` | Native dependency audit; high severity blocks |
19
+ | Real Pi smoke | `node scripts/smoke.mjs` | Explicit isolated RPC test with local deterministic HTTP provider |
20
+ | Release | `make release-check` | Normal gate + format, coverage, audit, package dry run |
21
+
22
+ Coverage preloads every production module so newly added, unimported code does
23
+ not disappear from the report. Keep module evaluation free of external effects;
24
+ start resources through explicit lifecycle adapters.
25
+
26
+ The normal gate is lint, typecheck, and deterministic tests only. Success prints
27
+ exactly `true`. Failure prints `false` first, then at most 40 diagnostic lines.
28
+ Hooks strip Git redirect variables so scratch repositories cannot touch the
29
+ submodule index. Commit subjects follow Conventional Commits.
30
+
31
+ CI invokes the same normal gate on main pushes and pull requests. Pushing a stable
32
+ `v<version>` tag runs the release gate, then publishes `@carbon-ni/pi-schedule` to
33
+ npm through trusted publishing. Tag creation is manual. See [releasing](docs/RELEASING.md)
34
+ for initial npm setup, copyable release commands, and failure recovery.
35
+
36
+ Review the package dry run before publishing. Pi is a host peer dependency;
37
+ TypeBox is also host-provided. Development versions and direct tooling are
38
+ pinned; the lockfile pins their dependency graph.
39
+
40
+ ## Definition of done
41
+
42
+ - Behavior and failure paths have deterministic tests; use fake time, not sleeps.
43
+ - The watcher normal gate passes for the current revision.
44
+ - Coverage and audit pass for the changed scope; uncovered risk is recorded.
45
+ - Runtime changes pass the explicit Pi smoke test; this uses no real credentials
46
+ and no user session. Human TUI review remains a separate check.
47
+ - Formatting passes and docs describe actual behavior and recovery.
48
+ - Commit/push the submodule before updating its parent pointer. Never amend.
49
+ - Update TASK-0007 on the separate planning board when its criteria are met.
50
+
51
+ ## Boundaries and configuration
52
+
53
+ Domain state transitions do not import infrastructure, Pi, TypeBox, or Node
54
+ modules. ESLint enforces inward imports for production domain files. Changes to
55
+ that rule require both a passing baseline and a forbidden-import probe that
56
+ fails lint. Tests import external test helpers; they are not production layers.
57
+
58
+ There is one versioned watcher config and one lint/type/test gate. Runtime
59
+ defaults are defined in source; Pi's agent-directory default is overridden only
60
+ by Pi's standard `PI_CODING_AGENT_DIR`. Budget changes use the manual command;
61
+ there is no competing environment or JSON budget policy.
62
+
63
+ `skipLibCheck` skips inconsistent third-party declaration internals, including
64
+ Pi's generated JSON model declarations under NodeNext. Our source, fixtures,
65
+ and integration calls remain strictly typechecked.
66
+
67
+ ## Do not
68
+
69
+ - Do not use shell sleeps for scheduling, run a hidden daemon, steer active work,
70
+ or silently execute missed/uncertain tasks.
71
+ - Do not reconstruct terminal scheduler state from an active history branch.
72
+ - Do not let model parameters set creator, session ownership, root, or budget.
73
+ - Do not claim that a submitted prompt proves a successful check.
74
+ - Do not force-add runtime state, credentials, logs, or coverage output.
75
+ - Do not bypass a failing implementation hook. Fix it and use the same gate as CI.
76
+
77
+ ## Failure → recovery
78
+
79
+ - Dependencies unavailable → `npm ci` (network is required for installation).
80
+ - Hooks absent → `make setup && make hooks-check`.
81
+ - Format failure → `make format-write && make format`.
82
+ - Type/lint/test failure → `make check`; read bounded diagnostics and run the
83
+ relevant individual command from `make help`.
84
+ - Watcher unavailable → `fzz run -- @agent-final`; it uses the same normal gate.
85
+ - Missing Pi → install Pi 1.5.x, then `make try`.
86
+ - Runtime/storage/lock failure → use [security recovery](docs/SECURITY.md), then
87
+ `/reload`. Never delete an active process's lock.
88
+ - Audit failure → `npm audit`; review advisories and pin a targeted correction.
89
+ Do not use `npm audit fix --force`.
90
+ - Coverage failure → `make coverage`; add behavioral cases, not config-string
91
+ assertions or a lower threshold.
package/README.md CHANGED
@@ -1,3 +1,125 @@
1
- # Temporary Holding Version
1
+ # pi-schedule
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Schedule one-off follow-ups in your [Pi](https://pi.dev) conversation. Ask Pi to
4
+ check CI later, or set a deadline yourself. Results appear in the same chat.
5
+
6
+ **Keep the Pi session open and the computer awake.** There is no background
7
+ service, recurring schedule, or automatic retry.
8
+
9
+ ## Install
10
+
11
+ Requires Pi 1.5.x, Node.js 22.18+, and a persistent TUI or RPC session.
12
+ Storage relies on Unix file permissions and filesystem behavior; use macOS or Linux.
13
+ Pi extensions run with your user permissions, not in a sandbox.
14
+
15
+ ```sh
16
+ git clone https://github.com/carbon-ni/pi-schedule.git
17
+ cd pi-schedule
18
+ npm ci
19
+ pi install "$PWD"
20
+ ```
21
+
22
+ Start Pi, or run `/reload` in an existing session. If the repository is already
23
+ at `~/.pi/agent/extensions/pi-schedule`, Pi discovers it without `pi install`.
24
+
25
+ After the first npm release, install without cloning:
26
+
27
+ ```sh
28
+ pi install npm:@carbon-ni/pi-schedule
29
+ ```
30
+
31
+ ## Schedule a follow-up
32
+
33
+ Ask Pi with an exact target and a clear scope:
34
+
35
+ > In 30 minutes, check CI for owner/repo#123 and report failures. Do not modify code.
36
+
37
+ Pi confirms the task ID, due time with timezone, target, and cancellation command.
38
+ It can schedule further checks needed for the current request, with a reason for
39
+ each check. A status check does not authorize code changes, pushes, or merges.
40
+
41
+ To schedule without a model call, use `/schedule`:
42
+
43
+ ```text
44
+ /schedule in 30m Check CI for owner/repo#123 and report failures; do not modify code
45
+ /schedule at 2026-11-09T14:30:00+01:00 Check deployment status for owner/repo
46
+ /schedule list
47
+ ```
48
+
49
+ Use an explicit timezone offset for `at`. Replace `S-<id>` below with the ID from
50
+ the confirmation or task list:
51
+
52
+ ```text
53
+ /schedule show S-<id>
54
+ /schedule cancel S-<id>
55
+ /schedule help
56
+ ```
57
+
58
+ Tasks wait while Pi is busy, messages are queued, or extension UI is blocking.
59
+ They remain cancellable until dispatch starts. **Dispatched means Pi started the
60
+ turn, not that the check succeeded.** Read the conversation for findings or blockers.
61
+
62
+ ## Recover missed work
63
+
64
+ Closing Pi stops execution. Reloading or resuming keeps future tasks, but missed
65
+ tasks become overdue. Sleep or forward-clock gaps over two minutes also require
66
+ review; missed work does not run automatically.
67
+
68
+ Inspect the saved request, then explicitly run overdue work when Pi is idle:
69
+
70
+ ```text
71
+ /schedule show S-<id>
72
+ /schedule run S-<id>
73
+ ```
74
+
75
+ If dispatch is uncertain, inspect the conversation and any external effects first.
76
+ Then acknowledge it without replay:
77
+
78
+ ```text
79
+ /schedule dismiss S-<id>
80
+ ```
81
+
82
+ Create a fresh task only if another check is needed. For storage or lock errors,
83
+ follow [security and recovery](docs/SECURITY.md) before using `/reload`.
84
+
85
+ Each confirmation includes `pi --session-id <id>` to resume from the same project.
86
+ Use it if a task was scheduled before the first chat message: Pi creates session
87
+ files lazily, so the session may not appear in the picker yet.
88
+
89
+ ## Control follow-up limits
90
+
91
+ Agent-created checks share a default budget of **three checks within two hours**.
92
+ Follow-ups cannot reset that budget or silently recreate a cancelled chain. A new
93
+ explicit user request starts a new authorization scope.
94
+
95
+ Only you can change a chain's total run limit and expiry from now:
96
+
97
+ ```text
98
+ /schedule budget S-<id> 5 3h
99
+ ```
100
+
101
+ An expired chain needs a budget extension before it can run. Maximum explicit
102
+ budget: 20 checks within 24 hours. Each session allows 20 pending or overdue tasks,
103
+ 8 KiB per saved instruction, and 1,000 history records.
104
+
105
+ For agent integrations, `schedule_task` supports `create`, `list`, `show`, and
106
+ `cancel`. Creation requires an instruction, exact target, reason, and either
107
+ `delay_seconds` or timezone-qualified `run_at`.
108
+
109
+ ## Local state
110
+
111
+ Tasks stay in `~/.pi/agent/schedule/`, or `<PI_CODING_AGENT_DIR>/schedule/` when
112
+ Pi's agent directory is overridden. There is no separate scheduler configuration.
113
+
114
+ State files use mode 600; directories and locks use mode 700. Each session has one
115
+ exclusive owner. Forked sessions get separate stores. Rewinding history does not
116
+ restore cancelled or dispatched tasks. Corrupt state is retained for recovery;
117
+ a write failure stops scheduling until reload.
118
+
119
+ ## Development
120
+
121
+ See [development](DEVELOPMENT.md) for setup, isolated loading, and checks;
122
+ [releasing](docs/RELEASING.md) for npm setup and publishing;
123
+ [architecture](docs/ARCHITECTURE.md) for implementation boundaries;
124
+ [security and recovery](docs/SECURITY.md) for permissions and failure handling;
125
+ and [design baseline](docs/PLAN.md) for the original requirements.
@@ -0,0 +1,59 @@
1
+ # Architecture
2
+
3
+ ## Ownership
4
+
5
+ ```text
6
+ src/index.ts Pi package entrypoint
7
+ └─ src/infra/extension.ts Pi lifecycle, commands/tools, UI, timer wiring
8
+ ├─ src/cli/ Manual command parsing
9
+ ├─ src/lib/ Shared task presentation and output bounds
10
+ ├─ src/application/ Runtime orchestration and Store boundary
11
+ │ └─ src/domain/ Pure task/chain state transitions
12
+ └─ src/infra/ FileStore, lock ownership, state decoding
13
+ └─ domain types
14
+ fixtures/ Isolated smoke-test controls, never auto-loaded
15
+ scripts/ Gates, hooks, native-test discovery, RPC smoke
16
+ docs/ Design, architecture, security contracts
17
+ ```
18
+
19
+ Tests are colocated with their subject. Add a shared helper to `src/lib` only
20
+ when it has a real shared responsibility. Do not add workflow-engine or service
21
+ layers for a one-session capability.
22
+
23
+ ## Dependency direction
24
+
25
+ The domain owns tasks, chains, and transitions. It has no infrastructure imports.
26
+ The application owns timing policy, trusted parent association, and the Store
27
+ interface. Infrastructure implements that interface and validates JSON before
28
+ passing state inward. The Pi adapter composes the runtime with the file store
29
+ and translates host events, user commands, and tool calls. Presentation depends
30
+ only on domain values. CLI parsing has no runtime dependencies.
31
+
32
+ ## Creation and delivery
33
+
34
+ 1. CLI parsing or the tool adapter provides a saved instruction and deadline.
35
+ The entrypoint assigns creator/session/project; the runtime assigns a trusted
36
+ chain root and parent.
37
+ 2. Pure transitions validate and produce a new snapshot. FileStore validates
38
+ schema and graph invariants, writes a private temporary file, fsyncs it,
39
+ atomically renames, then fsyncs the directory. Only then acknowledge creation.
40
+ 3. The timer asks the runtime to evaluate UTC deadlines. Host idle, queue, and
41
+ blocking-UI checks gate dispatch. Due tasks remain cancellable in our store.
42
+ 4. Persist uncertain dispatch intent before calling Pi. Only Pi's matching
43
+ `before_agent_start` event acknowledges the scheduled turn. An asynchronous
44
+ send failure leaves uncertainty instead of a false success or automatic retry.
45
+ 5. A scheduled turn binds descendants to its persisted chain. Only a new explicit
46
+ human-input run clears that association; an agent cannot omit its parent to
47
+ acquire a fresh budget.
48
+
49
+ ## Why not session entries?
50
+
51
+ Pi histories can branch or fork, while cancellation and dispatch tombstones must
52
+ not rewind. Session entries also do not provide exclusive cross-process ownership.
53
+ The authoritative state is therefore one per-session sidecar plus an exclusive
54
+ lock, not a branch projection or duplicated session ledger. Forks have different
55
+ session IDs and do not inherit runnable work.
56
+
57
+ Pi can delay writing a new session file until chat starts. Scheduler state is
58
+ still durable; confirmations include `pi --session-id <id>` for explicit resume.
59
+ No task runs in another conversation merely because the project path matches.
package/docs/PLAN.md ADDED
@@ -0,0 +1,84 @@
1
+ # Scheduling design baseline
2
+
3
+ > Original design for TASK-0007, captured 2026-11-08. This is not the task board.
4
+ > README and DEVELOPMENT describe implemented behavior and verification; the
5
+ > historical criteria below preserve the design intent, not live progress.
6
+
7
+ ## Problem
8
+
9
+ Waiting for CI, deployments, or another external operation requires the user to remember to return and ask Pi to check again. Long-running shell sleeps hide this intent, occupy execution, and cannot provide reliable listing, cancellation, or recovery.
10
+
11
+ ## Decision
12
+
13
+ Build `pi-schedule`, a Pi extension for one-off follow-ups in the original session. The user explicitly selected session-open execution as the MVP. This is a plan, not an active schedule.
14
+
15
+ Example: “In 30 minutes check CI again.” Pi resolves the relevant repository and PR, records the action, and confirms its ID and exact local due time with timezone. At the due time it initiates a normal Pi turn in that session. Scheduling must not merely display a reminder.
16
+
17
+ No daemon, cron, external service, spawned Pi process, recurring schedules, worktrees, or cross-session delivery in v1. No intray dependency: this runs inside the target session.
18
+
19
+ ## Reference
20
+
21
+ [Codex scheduled tasks](https://developers.openai.com/codex/app/automations), accessed 2026-11-08, documents in-chat scheduling, standalone runs, reviewable results, and the requirement to keep the computer and app running for local projects. Borrow explicit action + schedule + destination + visible result, not its full automation platform. The current page is titled “Scheduled tasks | ChatGPT Learn”; product naming has changed.
22
+
23
+ ## User contract
24
+
25
+ - Natural language is interpreted by the agent into structured tool input. The scheduler validates inputs deterministically; it does not use an LLM to decide whether work is due.
26
+ - Both manual and agent-initiated scheduling are first-class. The user may schedule directly through a command or natural language. The agent may schedule a follow-up without asking each time when it is necessary to finish the current authorized task (e.g. CI is still running). This is not permission to invent unrelated monitoring or new work.
27
+ - Agent-created tasks must visibly explain why a follow-up is needed, show their due time and target, and remain listable/cancellable. User cancellation stops that follow-up chain; the agent must not silently recreate it. Scheduling does not extend the original action permissions.
28
+ - Bound agent follow-up chains with a root task ID, remaining run budget, and expiry shared by every descendant. Proposed default: at most three agent-initiated checks within two hours per root task; the user can explicitly change the budget. Each scheduled check consumes one run; reaching a limit reports the unresolved state and asks the user rather than resetting the budget. A pending-count cap alone is not sufficient.
29
+ - Tools: `schedule_task` with create/list/cancel actions. Create takes an explicit instruction and exactly one of positive integer `delay_seconds` or timezone-qualified `run_at`. Return ID, due time, resolved scope, and execution limitation.
30
+ - `/schedule in 30m <instruction>` and `/schedule at <ISO-8601-with-offset> <instruction>` create tasks directly without an LLM round trip. `/schedule help`, `/schedule list`, `/schedule cancel <id>`, and `/schedule run <id>` provide discovery, inspection, cancellation, and explicit overdue recovery. Natural-language cancellation uses the same application logic. Repeating run/cancel cannot dispatch twice.
31
+ - Support “in 30 minutes” first. Absolute dates require an explicit offset; ask about ambiguous time or target rather than guess. Resolve relative duration against an injected clock at successful creation, not model request start.
32
+ - Save a durable instruction: e.g. check CI for `owner/repo` PR 123 and report status/failures. Do not save only “check that again.” Resolve repo/PR identity before create; omit credentials and unnecessary transcript text.
33
+ - Confirmation: “Scheduled S-123 for <date/time/timezone> (in 30 minutes): check CI for owner/repo#123. Runs only while this Pi session is open and computer is awake.”
34
+ - Current permissions and instructions still apply at execution. “Check CI” does not authorize fixing code, pushing, merging, or cancelling workflow runs. Report blockers; never silently grant access.
35
+ - Show pending count/next due time and visible task lifecycle records. Distinguish scheduled, due/waiting, dispatched, cancelled, overdue, and uncertain. Do not claim a check succeeded merely because a prompt was submitted.
36
+
37
+ ## Timing and lifecycle
38
+
39
+ 1. Persist creation before acknowledging it; timer is only a wake-up mechanism. Compare absolute UTC deadlines on every wake, with bounded periodic rechecks to handle clock changes and sleep.
40
+ 2. Dispatch only when the original session is idle, with no pre-existing queued user work. Recheck eligibility immediately before dispatch. Keep due work in the scheduler until then so cancellation remains meaningful; do not insert uncancellable messages behind an active turn.
41
+ 3. Order simultaneous due tasks by due time then stable ID. Run one at a time and recheck session/user queue state between runs. Use non-steering delivery defensively; verify actual Pi queue semantics in a spike before implementation.
42
+ 4. Define a two-minute lateness grace for ordinary event-loop delay. A task delayed beyond that without an observed continuous busy-session wait becomes overdue (e.g. sleep or large forward clock jump). Never run early after a backward clock change. Make this policy explicit in help.
43
+ 5. Clean up timers on every session shutdown, including reload/new/resume/fork. Restore pending tasks on reload/resume of the same session. If a restored deadline has passed, mark overdue and require `/schedule run <id>`; do not execute a surprise catch-up.
44
+ 6. Session replacement never redirects a task to the new conversation. Forks do not inherit runnable tasks; branch navigation does not resurrect dispatched or cancelled tasks. Verify session ID and history behavior against the installed Pi API.
45
+ 7. Mark dispatch intent durably before calling Pi. A crash between persistence and delivery is uncertain: show it and require explicit recovery, never automatically replay. Do not promise exactly-once external effects. Concurrent opens of the same session must fail closed for scheduling unless exclusive ownership is proven.
46
+ 8. Closing Pi stops execution. Starting another session does not run old tasks. Pending records remain available when the original session is resumed. The feature cannot wake a sleeping computer.
47
+
48
+ ## Design
49
+
50
+ Keep changes under `pi/agent/extensions/pi-schedule/`, following nearby package conventions and current `@earendil-works/*` imports.
51
+
52
+ Separate deterministic task validation/state transitions from Pi lifecycle and tool/command adapters. Inject clock, timer, persistence, and delivery at the boundary; avoid building a generic workflow framework.
53
+
54
+ Proposed record: schema version, task ID, owning session ID, project path and resolved target, instruction, creator (`user` or `agent`), scheduling reason, root/parent task IDs, shared follow-up budget and expiry, created/due timestamps, state, lifecycle timestamps, and failure/uncertainty reason. Creator attribution must come from the command/tool entry point, not a model-supplied authority claim. Explicit user changes to autonomous budgets need a user-controlled command or equivalent verified input; the agent cannot grant itself a larger budget. Limit instruction length and pending count (proposed 8 KiB and 20 per session). Validate persisted records; malformed state disables affected tasks with a visible error rather than silently discarding them.
55
+
56
+ First investigate whether session custom entries (`pi.appendEntry`) can provide authoritative lifecycle reconstruction across branches and exclusive ownership. They already persist extension data outside model context. Use them if guarantees hold; otherwise use a small private per-session store with atomic writes and an exclusive lock. Choose one source of truth, document retention/deletion and permissions, and do not create a second shadow ledger. Cancelled/dispatched tombstones must not vanish on branch rewind.
57
+
58
+ Pi integration evidence: `pi.sendUserMessage` initiates a turn when idle and supports `deliverAs: "followUp"` while streaming. `session_start`/`session_shutdown` cover reload and session replacement. Existing `pi-auto` exercises queued messages; `pi-intray` handles cross-process messages but is unnecessary here. Exact API behavior and compatibility must be checked before implementation; no harness changes are proposed.
59
+
60
+ ## Delivery plan (test first)
61
+
62
+ 1. **Prove the boundary:** inspect current Pi docs/examples in full as needed; spike idle/queued-user semantics, session identity, forks, persistence replay, and duplicate session ownership. Finalize storage and delivery policy before coding the scheduler.
63
+ 2. **Implement deterministic core:** fake-clock tests first for validation, due ordering, cancellation, state transitions, overdue classification, and crash recovery. Add persistence adapter and ownership handling.
64
+ 3. **Wire Pi:** expose tools/commands, lifecycle cleanup, idle dispatch, and visible task state. Test adapters with fake Pi events; include integration checks for session replacement and reload.
65
+ 4. **Verify user flow:** short real-session timer checks CI in the same conversation; cancel prevents dispatch; active work is not interrupted; close/resume yields an overdue task without execution. Document help, prerequisites, recovery commands, storage, and limitations.
66
+
67
+ ## Acceptance criteria
68
+
69
+ - [ ] Direct manual commands and natural-language requests create durable tasks through the same validated scheduling logic, with resolved target and exact due-time confirmation; no shell sleep or background process.
70
+ - [ ] The agent can schedule a necessary follow-up to the current task without per-task approval, with visible reason and creator attribution; unrelated work remains outside scope.
71
+ - [ ] Agent-created follow-ups share a bounded run budget and expiry across descendants. Cancellation, exhausted budget, and expiry prevent silent recreation; tests cover both allowed follow-ups and rejected scope/budget expansion.
72
+ - [ ] An open, awake, idle session runs the specified check at/after due time and returns an ordinary reviewable response in the original conversation.
73
+ - [ ] Busy-session work and queued user messages take priority; cancel remains effective until dispatch begins.
74
+ - [ ] List/cancel/help/explicit-overdue-run work without relying on natural-language inference; invalid/past/ambiguous inputs have actionable errors.
75
+ - [ ] Close, reload, resume, sleep, clock jumps, session replacement, forks, branch rewind, corrupted persistence, and duplicate ownership follow the stated policy.
76
+ - [ ] Crash windows are reported as uncertain; no silent retry and no exactly-once claim.
77
+ - [ ] Task text is treated as stored user-request data, not system authority; untrusted CI output cannot create further schedules or expand authorization.
78
+ - [ ] Tests cover happy and failure paths with fake time and fake delivery; no wall-clock waits or live network dependencies in automated tests.
79
+ - [ ] Localized coverage is measured for validation/state transitions and lifecycle adapters; record uncovered risk rather than use source-string tests as coverage.
80
+ - [ ] Manual short-timer smoke check and focused regression checks pass; no full repository check is required for planning.
81
+
82
+ ## Future scope (not committed)
83
+
84
+ General recurring schedules beyond bounded current-task follow-ups; standalone/background execution; results inbox; OS notifications. Each requires its own lifecycle and authorization design.
@@ -0,0 +1,105 @@
1
+ # Publish to npm
2
+
3
+ Package: `@carbon-ni/pi-schedule`.
4
+ Repository: `carbon-ni/pi-schedule`.
5
+
6
+ Pushing a stable `v<version>` tag starts `.github/workflows/release.yml`.
7
+ The workflow requires the tagged commit to be in `main` history and the tag to
8
+ match `package.json`. It runs the release gate before publishing with npm trusted
9
+ publishing (OIDC). No npm write token is stored in GitHub.
10
+
11
+ Only stable releases are supported. Prerelease tags are rejected rather than
12
+ published as `latest`.
13
+
14
+ ## One-time setup
15
+
16
+ You need publish access to the `@carbon-ni` npm scope and admin access to the
17
+ GitHub repository. The npm scope and GitHub owner do not need to have the same name.
18
+
19
+ 1. In GitHub, create an environment named `npm`. Add required reviewers if your
20
+ plan supports them, and restrict deployment tags to `v*`. Protect release tags
21
+ with a repository ruleset so only release maintainers can create them.
22
+ 2. If the npm package does not exist yet, perform the first publish locally as
23
+ shown below. Trusted publishing is configured in an existing package's settings.
24
+ 3. In the npm package settings, add a GitHub Actions trusted publisher:
25
+
26
+ | Field | Value |
27
+ | -------------------- | -------------------------- |
28
+ | Organization or user | `carbon-ni` |
29
+ | Repository | `pi-schedule` |
30
+ | Workflow filename | `release.yml` |
31
+ | Environment name | `npm` |
32
+ | Allowed actions | Allow direct `npm publish` |
33
+
34
+ 4. Complete an automated release within two days of creating the trusted
35
+ publisher. An unused configuration expires; delete and recreate it if needed.
36
+ 5. After the first automated publish succeeds, select npm publishing access
37
+ **Require two-factor authentication and disallow tokens**. No `NPM_TOKEN`
38
+ secret is needed.
39
+
40
+ ### First publish only
41
+
42
+ Run from a clean, reviewed checkout of `main`, after the release-process changes
43
+ have been pushed. Check scope access with your npm account; a registry 404 does
44
+ not prove you have permission to publish.
45
+
46
+ ```sh
47
+ npm ci
48
+ make release-check
49
+ node scripts/release.mjs "v$(node -p "require('./package.json').version")"
50
+ npm login --registry=https://registry.npmjs.org/
51
+ npm publish --ignore-scripts
52
+ ```
53
+
54
+ The first publish uses interactive npm authentication and any required 2FA.
55
+ It does not get GitHub Actions provenance. Later CI releases do.
56
+ Do not push a tag for this already-published version: the workflow would attempt
57
+ to publish it again. Configure the trusted publisher, then use the next patch
58
+ version for the first automated release.
59
+
60
+ ## Release a new version
61
+
62
+ Start with a clean `main` checkout. Keep npm 10.9.9 for local development.
63
+ `npm version` changes both manifests and creates a new commit and tag; it does
64
+ not publish. Replace `patch` with `minor` or `major` when appropriate.
65
+
66
+ ```sh
67
+ git switch main
68
+ git pull --ff-only
69
+ npm ci
70
+ make release-check
71
+ npm version patch -m "chore(release): %s"
72
+ node scripts/release.mjs "v$(node -p "require('./package.json').version")"
73
+ git push origin main
74
+ git push origin "v$(node -p "require('./package.json').version")"
75
+ ```
76
+
77
+ Approve the `npm` environment deployment if review is enabled. The workflow uses
78
+ npm 10.9.9 for verification and pinned npm 11.6.2 for OIDC publishing only.
79
+ It publishes public TypeScript sources and docs, without tests or development
80
+ tooling. There is no build step; Pi loads the TypeScript extension directly.
81
+
82
+ Check the workflow and registry after publication:
83
+
84
+ ```sh
85
+ gh run list --workflow release.yml --limit 5
86
+ npm view @carbon-ni/pi-schedule version dist-tags --json
87
+ pi install npm:@carbon-ni/pi-schedule
88
+ ```
89
+
90
+ ## Recover a failed release
91
+
92
+ - **Verification failed:** fix the cause on `main` and create a new version.
93
+ Do not move or replace a published release tag.
94
+ - **Authentication failed:** compare every trusted-publisher field above,
95
+ including the environment name and permission for direct publishing. Recreate
96
+ expired publisher configurations, then rerun the failed publish job with
97
+ `gh run rerun <run-id> --failed`.
98
+ - **Publish result uncertain:** inspect the workflow and run
99
+ `npm view @carbon-ni/pi-schedule@<version> version`. If it exists, do not retry
100
+ publishing it. If it is absent and the cause is resolved, rerun the failed job.
101
+ - **Version already exists:** npm versions are immutable. Use `npm version patch`
102
+ and follow the release steps again; never unpublish to make a retry succeed.
103
+
104
+ See [npm trusted publishing](https://docs.npmjs.com/trusted-publishers/) for
105
+ provider requirements and troubleshooting.
@@ -0,0 +1,77 @@
1
+ # Security and recovery
2
+
3
+ ## Trust boundaries
4
+
5
+ Pi extensions execute with user permissions. This scheduler is not a sandbox,
6
+ and it does not prevent a malicious extension or another process running as the
7
+ same user from modifying files or invoking commands.
8
+
9
+ The tool schema accepts action, instruction, target, reason, deadline, and ID.
10
+ It does not accept creator, project path, session identity, root/parent, or budget.
11
+ The Pi adapter assigns those at trusted entrypoints. Agent-created roots remain
12
+ associated with the current request across reloads; scheduled turns bind the
13
+ actual dispatched parent, so omitting a model-supplied parent cannot reset limits.
14
+
15
+ Checking CI does not grant permission to fix, push, merge, cancel jobs, or start
16
+ unrelated monitoring. The scheduled prompt and tool guidelines preserve this
17
+ boundary. Scope matching and scheduling reasons are not proof of user intent;
18
+ semantic authorization still depends on the agent obeying current instructions.
19
+ Untrusted CI output is data, not a new scheduling authorization.
20
+
21
+ ## Assets and controls
22
+
23
+ - Saved instructions may contain private project details. State stays local;
24
+ do not include credentials or unnecessary transcript content.
25
+ - Directory and lock modes are 700; snapshots are 600. Final directory/file
26
+ symlinks and non-private files are rejected. Parent paths belong to trusted Pi
27
+ configuration. Atomic writes do not follow a supplied state-file symlink.
28
+ - A session has one exclusive owner. No automatic stale-lock deletion or replay.
29
+ - JSON schema, task/chain relationships, session/project identity, text bounds,
30
+ history counts, and budgets are validated before restored state can run.
31
+ - A write failure stops scheduling until reload. A crash after a write but
32
+ before delivery is uncertain; exactly-once external effects are not promised.
33
+ - Task summaries escape control characters; tool output is bounded to 50 KiB
34
+ with a truncation notice. All active work appears before terminal history.
35
+ - No credentials, telemetry, external scheduler, or additional third-party
36
+ service is used. The model handling a scheduled turn sees its saved request
37
+ under the user's existing Pi provider settings.
38
+
39
+ ## Lock and corruption recovery
40
+
41
+ Start with the exact path printed in the runtime error. The lock contains
42
+ `owner.json` with PID/token; the corresponding JSON snapshot is authoritative.
43
+
44
+ 1. Close the other Pi session normally; reload this one.
45
+ 2. After a crash, inspect `owner.json` and verify its PID no longer exists.
46
+ For example, `ps -p <pid> -o pid=,command=` (PID reuse is possible, so inspect
47
+ the process, not only whether a PID exists).
48
+ 3. Only after verifying no live owner, remove that lock's `owner.json` using
49
+ `rm -- <lock-path>/owner.json`, then `rmdir -- <lock-path>`, then `/reload`.
50
+ Do not remove a lock just to silence an error.
51
+ 4. For corrupt JSON, stop the owner and retain a private backup. Restore a known
52
+ good snapshot or explicitly discard the affected session's schedules. The
53
+ extension never overwrites corrupt data with an empty state automatically.
54
+
55
+ Uncertain work: `/schedule show <id>`, inspect the conversation/external effects,
56
+ then `/schedule dismiss <id>` without replay. If a new check is needed, create it
57
+ explicitly after inspection.
58
+
59
+ ## Retention and deletion
60
+
61
+ History and cancellation/dispatch tombstones remain for the session's lifetime
62
+ (up to 1,000 records). No automatic pruning or transcript export occurs. Cancel
63
+ pending work before closing the session. To delete saved instructions, close its
64
+ owner and delete the corresponding JSON snapshot after checking its task/session
65
+ IDs. Do not delete active snapshots or tombstones to reset agent limits.
66
+
67
+ The Pi agent directory may be a symlink into a config repository. That repository
68
+ must ignore its `schedule/` directory, just as it ignores session/auth data.
69
+ This parent checkout explicitly ignores `pi/agent/schedule/`.
70
+
71
+ ## Supply chain
72
+
73
+ Direct development dependencies are exact-pinned; npm's lockfile pins transitives.
74
+ Install scripts are disabled. Host Pi and TypeBox are peer dependencies, not a
75
+ second bundled runtime. `make security` audits the full installed graph.
76
+ The minimatch 11.0.4 override addresses GHSA-g3hv-75wg-m249 in lint dependencies;
77
+ remove it only when upstream constraints require patched versions.
package/package.json CHANGED
@@ -1,6 +1,62 @@
1
1
  {
2
2
  "name": "@carbon-ni/pi-schedule",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/carbon-ni/pi-schedule.git"
7
+ },
8
+ "publishConfig": {
9
+ "access": "public",
10
+ "registry": "https://registry.npmjs.org/"
11
+ },
12
+ "keywords": [
13
+ "pi-package",
14
+ "pi",
15
+ "scheduler"
16
+ ],
17
+ "type": "module",
18
+ "description": "Session-scoped scheduled follow-ups for Pi",
19
+ "packageManager": "npm@10.9.9",
20
+ "pi": {
21
+ "extensions": [
22
+ "./src/index.ts"
23
+ ]
24
+ },
25
+ "files": [
26
+ "src/**/*.ts",
27
+ "!src/**/*.test.ts",
28
+ "README.md",
29
+ "DEVELOPMENT.md",
30
+ "docs"
31
+ ],
32
+ "peerDependencies": {
33
+ "@earendil-works/pi-coding-agent": ">=1.5.0 <2",
34
+ "typebox": "1.3.15"
35
+ },
36
+ "overrides": {
37
+ "minimatch": "11.0.4"
38
+ },
39
+ "scripts": {
40
+ "test": "node scripts/test.mjs",
41
+ "test:coverage": "node scripts/test.mjs --coverage",
42
+ "typecheck": "tsc --noEmit",
43
+ "lint": "eslint .",
44
+ "format": "prettier --check .",
45
+ "format:write": "prettier --write .",
46
+ "check": "sh scripts/gate.sh"
47
+ },
48
+ "engines": {
49
+ "node": ">=22.18"
50
+ },
51
+ "devDependencies": {
52
+ "@earendil-works/pi-coding-agent": "1.5.0",
53
+ "@eslint/js": "10.0.1",
54
+ "@types/node": "22.20.5",
55
+ "eslint": "10.8.1",
56
+ "globals": "17.11.0",
57
+ "prettier": "3.9.6",
58
+ "typebox": "1.3.15",
59
+ "typescript": "6.0.3",
60
+ "typescript-eslint": "8.67.0"
61
+ }
62
+ }