@fungi.computer/shiitake-extensions 0.0.0-stage → 0.0.1

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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +262 -2
  3. package/dist/almanac/contract.d.ts +213 -0
  4. package/dist/almanac/contract.js +77 -0
  5. package/dist/almanac/host.d.ts +3 -0
  6. package/dist/almanac/host.js +171 -0
  7. package/dist/goal/client.d.ts +10 -0
  8. package/dist/goal/client.js +88 -0
  9. package/dist/goal/contract.d.ts +172 -0
  10. package/dist/goal/contract.js +76 -0
  11. package/dist/goal/host.d.ts +5 -0
  12. package/dist/goal/host.js +542 -0
  13. package/dist/home/contract.d.ts +33 -0
  14. package/dist/home/contract.js +25 -0
  15. package/dist/home/host.d.ts +18 -0
  16. package/dist/home/host.js +136 -0
  17. package/dist/home/routing.d.ts +3 -0
  18. package/dist/home/routing.js +80 -0
  19. package/dist/internal/catalog.d.ts +20 -0
  20. package/dist/internal/catalog.js +33 -0
  21. package/dist/internal/cron.d.ts +5 -0
  22. package/dist/internal/cron.js +14 -0
  23. package/dist/internal/history.d.ts +29 -0
  24. package/dist/internal/history.js +41 -0
  25. package/dist/loop/client.d.ts +10 -0
  26. package/dist/loop/client.js +89 -0
  27. package/dist/loop/contract.d.ts +342 -0
  28. package/dist/loop/contract.js +94 -0
  29. package/dist/loop/host.d.ts +4 -0
  30. package/dist/loop/host.js +637 -0
  31. package/dist/plan/client.d.ts +13 -0
  32. package/dist/plan/client.js +51 -0
  33. package/dist/plan/contract.d.ts +105 -0
  34. package/dist/plan/contract.js +38 -0
  35. package/dist/plan/host.d.ts +3 -0
  36. package/dist/plan/host.js +154 -0
  37. package/dist/question/client.d.ts +10 -0
  38. package/dist/question/client.js +28 -0
  39. package/dist/question/contract.d.ts +251 -0
  40. package/dist/question/contract.js +114 -0
  41. package/dist/question/host.d.ts +3 -0
  42. package/dist/question/host.js +997 -0
  43. package/dist/roster.d.ts +9 -0
  44. package/dist/roster.js +80 -0
  45. package/dist/skills/host.d.ts +7 -0
  46. package/dist/skills/host.js +223 -0
  47. package/dist/skills/source.d.ts +28 -0
  48. package/dist/skills/source.js +1 -0
  49. package/dist/subagents/client.d.ts +10 -0
  50. package/dist/subagents/client.js +53 -0
  51. package/dist/subagents/contract.d.ts +54 -0
  52. package/dist/subagents/contract.js +38 -0
  53. package/dist/subagents/host.d.ts +50 -0
  54. package/dist/subagents/host.js +176 -0
  55. package/package.json +117 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jamstack Consulting LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,263 @@
1
- # Temporary Holding Version
1
+ # Shiitake product extensions
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
+ `@fungi.computer/shiitake-extensions` supplies Fungi's questions, goals, loops,
4
+ plans, Almanac schedules, skills and Home. Hub and Local choose the shared
5
+ roster and explicitly grant its capabilities. Shiitake core supplies the generic
6
+ extension API.
7
+
8
+ ## Set up
9
+
10
+ This is a package for host and client maintainers. Use its exported subpaths
11
+ from the configured workspace, not an invented npm install. From the repository
12
+ root:
13
+
14
+ ```sh
15
+ pnpm turbo:run run build --filter=@fungi.computer/shiitake-extensions
16
+ ```
17
+
18
+ A host passes `botanicalAgentExtensions` as its Shiitake `extensions` option. It
19
+ still supplies the SQLite owner, runtime wake, model and instruction sources and
20
+ any tools sandbox. The roster does not supply provider credentials or resource
21
+ permissions.
22
+
23
+ A Local host uses `botanicalExtensionsWithSkills(source)` instead. The read-only
24
+ `SkillSource` owns discovery and file access. `skillsExtension({source})` uses
25
+ the `prompt`, `run_tools` and `commands` grants: metadata enters the prompt,
26
+ while `skill.load({name, file?})` returns the body or a supporting file through
27
+ the existing `execute` tool. It is a forage operation, not a top-level tool. The
28
+ human `/skills` command lists each discovered skill beside its `SKILL.md` path,
29
+ plus the reasons other entries were not loaded; choosing one shows its
30
+ description. Commands run inside a synchronous core transaction, so `/skills`
31
+ reads the latest discovery, taken at activation and before each model request.
32
+ Empty catalogs say that no skills are installed; invalid entries report a
33
+ redacted cause. Tool discovery summaries stay within Forage's metadata bounds
34
+ and mark shortened listings; the prompt still lists every admitted skill. The
35
+ Hub uses the shared roster without Skills and adds its own scoped Home
36
+ extension.
37
+
38
+ ## Quick start: read the committed goal
39
+
40
+ Illustrative: this helper receives an already open Agent with the product roster
41
+ and reads the goal slot for one Session. A fresh Session has no goal value. The
42
+ helper requests neither model work nor a goal mutation.
43
+
44
+ ```ts
45
+ import {
46
+ bindSessionExtension,
47
+ type ShiitakeAgent,
48
+ } from "@fungi.computer/shiitake/client";
49
+ import { goalContract } from "@fungi.computer/shiitake-extensions/goal";
50
+ import { botanicalAgentExtensions } from "@fungi.computer/shiitake-extensions/roster";
51
+
52
+ export const extensions = botanicalAgentExtensions;
53
+
54
+ export async function readGoal(agent: ShiitakeAgent) {
55
+ const session = agent.session("example-session");
56
+ const goal = bindSessionExtension(session, goalContract);
57
+ return goal.view(await session.read());
58
+ }
59
+ ```
60
+
61
+ The bound view distinguishes `unavailable`, `stopped` with a reason, and
62
+ `running` with a value or `null`. Existing [goal tests](test/goal.test.ts) bind
63
+ that same contract, read observed state, issue commands and close the owner. The
64
+ helper borrows the Agent. Its owner closes it when the host's lifetime ends.
65
+
66
+ ## Core concepts
67
+
68
+ | Extension | Ownership and behavior |
69
+ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
70
+ | Ask | Up to four open decisions per Session. The model asks or withdraws; humans answer or decline. A live waiter receives the answer, while a parked question resumes through one follow-up. |
71
+ | Goal | One objective per Session. The model creates, reads and completes it through `goal.*` tools; humans set, pause, resume or clear it. Three tool-less continuations pause it as stalled. |
72
+ | Loop | One repeated prompt per Session. It runs on a fixed cron cadence or lets its own active run request the next occurrence. Commands start, pause, resume and stop it. |
73
+ | Plan | One checklist per Session. `plan.update_plan` replaces it whole, with at most one step in progress and at most 50 steps; `steps: []` clears it. The human clears it with `/plan clear` or the footer row's Clear plan action, through the same owner. `/plan` opens the full checklist in the footer; updates keep only the current step and progress visible, and a finished or cleared plan leaves the footer while its history stays in state. |
74
+ | Almanac | Agent-scope one-shot or recurring schedules. Each schedule targets a Session and tracks admission, retry and its next due time. |
75
+ | Skills | Host-supplied, read-only skill discovery. Metadata enters the prompt; `skill.load` reads a skill body or supporting file; `/skills` lists them. |
76
+ | Home | Agent-scope choice of an attached Computer. Each run captures its scoped file tools and current attachment choice. |
77
+ | Subagents | A read-only footer view: one row per Agent this Session sent work to or created, with its live status (working, waiting on you, idle, failed, unavailable). A row opens that Agent's conversation. It owns no relationship and no durable state. |
78
+
79
+ An active Goal is quiet system context on every run. Continuation instructions
80
+ apply only to runs admitted by Goal with its continuation correlation; human
81
+ messages receive the Goal context without that directive. Both sections are
82
+ bounded by UTF-8 bytes. Correlated Goal turns appear as compact transcript
83
+ notes; other extensions keep their own display policy.
84
+
85
+ Known limitation: Session forks copy transcript entries without their original
86
+ command-admission rows. Inherited Goal turns therefore lose the extension-origin
87
+ metadata used for compact notes and appear as ordinary message bubbles.
88
+
89
+ Goal pause stops continuation, not already accepted work. Model-requested
90
+ completion or blocking is provisional until successful settlement. Human
91
+ replacement uses the observed revision to avoid replacing a newer goal.
92
+
93
+ Loop tools are offered in every run. `loop.get`, `loop.start` and `loop.stop`
94
+ work from any run of the Session, and the model may start a loop itself.
95
+ `loop.schedule_next` is accepted only in the active self-paced loop's own run;
96
+ omitting it ends that loop. A fixed cadence uses the core timer and expires
97
+ seven days after it starts. Almanac retries a failed admission per schedule, so
98
+ one refused occurrence does not become another schedule's failure. Use the
99
+ recorded error and the owner's resume command when recovering.
100
+
101
+ The human `/goal`, `/loop` and `/plan` flows are advertised as
102
+ `whistle.catalog/v1` catalogs. A compatible client renders the same server-owned
103
+ interactions. A human command catalog, a per-run tool module and model tool
104
+ discovery are separate contracts. Inspect the host's exposed tool catalog before
105
+ relying on a model-facing operation.
106
+
107
+ ## State and lifetime
108
+
109
+ ### Question
110
+
111
+ `question.ask` creates one to four decision questions. Its namespace is
112
+ `question`, and its extension identity is `botanical-session-question`. Inspect
113
+ `forage.describe({path: "question.ask"}).inputSchema` for the validated bounds,
114
+ including the 12-character question header. A waiting call receives the human's
115
+ answer in-turn; `mode: "continue"` parks it and returns its `askId`. The live
116
+ wait leaves sixty seconds of the actual execute deadline and is capped at ten
117
+ minutes. Pending steering also parks the question so Mule can deliver the steer.
118
+ Each Session holds at most four open Asks, with one live Ask per run.
119
+
120
+ Bind `questionContract` from `/question` with `bindSessionExtension` to read its
121
+ decoded projection and call `list`, `answer` or `decline`. Human responses
122
+ include an `askId` and a stable `requestId`. Reuse that request identity after
123
+ an uncertain response: an exact retry returns its original receipt, while a
124
+ changed payload conflicts. Answer every question with offered labels or nonempty
125
+ free text. The model has no answer operation.
126
+
127
+ The `/answer` catalog conversation selects a waiting Ask and collects an answer
128
+ for each question. Multiple selections use comma-separated labels; quote a label
129
+ that contains a comma. Every choice also permits free text.
130
+
131
+ Optional `onExpire` specifies `afterMinutes` (5 through 10,080) and an action:
132
+ `stop` explicitly tells the model not to proceed; `proceed_with_recommended`
133
+ selects the first option of each question. A parked answer or expiry admits one
134
+ self-contained follow-up. The Ask owner clears attention when it closes, and the
135
+ live waiter and durable timer share the same expiry transition. Restart recovery
136
+ reconciles committed answers before allowing another delivery. An in-turn answer
137
+ retains its delivery receipt until Mule saves a successful tool outcome. If a
138
+ crash loses that outcome and no re-ask receives it, settlement sends the answer
139
+ once as a follow-up. A cancelled run suppresses that redelivery.
140
+
141
+ Client cards, goal/loop attention policy and external notifications are separate
142
+ contributions; the Ask owner alone does not provide those surfaces.
143
+
144
+ ### Owner transactions
145
+
146
+ Collections are namespaced to the extension. Schema, version, migrations and
147
+ configuration contribute to its revision. Settled hooks, timer hooks and command
148
+ handlers commit through core transactions. They must finish synchronously and
149
+ must not perform external I/O.
150
+
151
+ Extensions send prompts through the accepted-command door and wake through core
152
+ timers. An accepted prompt is queued work, not a finished model reply. Core owns
153
+ replay cursors and publication after commit. A stopped extension exposes its
154
+ reason; the host can resume it without inventing a separate background loop.
155
+
156
+ ## API reference and examples
157
+
158
+ This package documents contracts beside source and has no generated API command.
159
+ [package.json](package.json) owns these exported subpaths:
160
+
161
+ | Entry | Source |
162
+ | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
163
+ | `/goal`, `/goal/host` | [Goal contract](src/goal/contract.ts) and [definition](src/goal/host.ts). |
164
+ | `/loop`, `/loop/host` | [Loop contract](src/loop/contract.ts) and [definition](src/loop/host.ts). |
165
+ | `/almanac`, `/almanac/host` | [Schedule contract](src/almanac/contract.ts) and [definition](src/almanac/host.ts). |
166
+ | `/home`, `/home/host` | [Home contract](src/home/contract.ts) and [definition](src/home/host.ts). |
167
+ | `/question`, `/question/host` | [Question contract](src/question/contract.ts) and [definition](src/question/host.ts). |
168
+ | `/plan`, `/plan/host` | [Plan contract](src/plan/contract.ts) and [definition](src/plan/host.ts). |
169
+ | `/skills`, `/skills/source` | [Skills definition](src/skills/host.ts) and [host-owned source](src/skills/source.ts). |
170
+ | `/subagents`, `/subagents/host` | [Subagents contract](src/subagents/contract.ts) and [definition](src/subagents/host.ts). |
171
+ | `/subagents/client` | [Subagents client entry](src/subagents/client.ts). |
172
+ | `/goal/client`, `/loop/client`, `/plan/client` | Client entries for [Goal](src/goal/client.ts), [Loop](src/loop/client.ts) and [Plan](src/plan/client.ts). |
173
+ | `/roster` | [Product roster and grants](src/roster.ts). |
174
+
175
+ [Loop tests](test/loop.test.ts) exercise scheduling and mid-run pause.
176
+ [Almanac tests](test/almanac.test.ts) exercise admission and retry.
177
+ [Catalog tests](test/catalog.test.ts) check the human interaction contracts.
178
+ These fixtures use the actual extension definitions on Shiitake with fake
179
+ models.
180
+
181
+ ## Home
182
+
183
+ The host creates `homeExtension({ computers })`, where `computers.read()`
184
+ synchronously returns the current attachments as `{ name, attachmentId, port }`.
185
+ Each `port` is already scoped to that attachment by the host. Home never creates
186
+ credentials or bypasses the host gate. Add the returned extension and its
187
+ exported `homeGrant` (`state`, `files`, `prompt`, `projection`, `commands`).
188
+ Home is composed by the host, not included in `botanicalAgentExtensions`. The
189
+ Hub and demo add it with their own Computer ports and grants.
190
+
191
+ Home records one Agent-scope attachment choice. With no recorded choice, a run
192
+ adopts a sole attachment once. Zero or multiple attachments require a person to
193
+ choose `/home <name>`. Removing the chosen attachment keeps the choice and
194
+ offers no direct file tools until a person chooses again. Reordering attachments
195
+ never changes Home. The Session command changes Agent state, so other Sessions
196
+ see that choice on their next run.
197
+
198
+ Each run captures its table, Home and prompt once. The tools are Knapsack's
199
+ branded `read`, `list`, `glob` and `grep`, with `apply_patch` only when Home's
200
+ port has a native patch engine. Bare paths and `computer://home/` use Home;
201
+ `computer://<name>/` uses another captured attachment. Patches always use Home's
202
+ native engine and path jail. Returned file identity stays owned by the scoped
203
+ port. There is no Home model tool and no shared Files module.
204
+
205
+ The Agent-scope `seed` command is an unadvertised host migration operation. It
206
+ records an old attachment id only when no choice exists, including when that
207
+ attachment is detached; subsequent seed calls cannot overwrite a human choice.
208
+ Hosts keep the control door human/operator-authorized.
209
+ [Home tests](test/home.test.ts) exercise selection, capture, detachment, routing
210
+ and gating on real Shiitake.
211
+
212
+ ## Limits and errors
213
+
214
+ A roster entry is not an external-resource grant. The host still admits each
215
+ operation and owns its current Team, Computer and credential policy. A command
216
+ conflict requires a fresh observation; retry an uncertain request with the same
217
+ identity. A view with no goal is not evidence that a previous goal completed.
218
+ Closing a client view does not pause the goal, stop a loop or cancel a schedule.
219
+
220
+ ## Contribute and license
221
+
222
+ Add regressions beside the owning extension or catalog. From the root:
223
+
224
+ ```sh
225
+ pnpm --dir packages/shiitake-extensions run test
226
+ pnpm --dir packages/shiitake-extensions run typecheck
227
+ ```
228
+
229
+ MIT. See [LICENSE](LICENSE).
230
+
231
+ ## TypeScript consumer dependencies
232
+
233
+ Install `@modelcontextprotocol/sdk@1.25.2` alongside this package when compiling
234
+ its complete public declarations. The shared provider SDK imports those types
235
+ through an optional peer; the packed consumer supplies it explicitly. This does
236
+ not require a provider account or make a provider call.
237
+
238
+ ## Subagents
239
+
240
+ The host creates `subagentsExtension({ source })` and adds `subagentsGrant`
241
+ (`projection` only). `source.read(sessionId, limit)` returns the Agents that
242
+ Session sent work to or created, newest first, with a count of the rest;
243
+ `source.open` is the host's existing Agent access. Each child's status is its
244
+ owner's own Session state: `running` is working, any open attention (a question
245
+ or a tool approval) is waiting on you, `done` is idle and `errored` is failed. A
246
+ child whose state cannot be read is unavailable, never idle.
247
+
248
+ Rows refresh when a watcher reads the parent Session and they are at least a
249
+ second old. While a row is working or waiting, they poll every five seconds
250
+ until nobody has read the parent for thirty seconds. Like Home, the extension is
251
+ composed by the host, not included in `botanicalAgentExtensions`.
252
+
253
+ ## Packed consumer readiness
254
+
255
+ Run `pnpm run build`, `pnpm run pack` and `pnpm run stranger` in this package.
256
+ The maintained stranger proof installs only package tarballs into a fresh npm
257
+ consumer, checks the exact shipped files, runs the public runtime fixture and
258
+ compiles every JavaScript entrypoint with strict NodeNext and no library-check
259
+ skip. This qualification does not publish npm releases or push a repository
260
+ split.
261
+
262
+ See [LICENSE](LICENSE) for the license and the package manifest for its public
263
+ repository destination.
@@ -0,0 +1,213 @@
1
+ import * as Schema from "effect/Schema";
2
+ /** One durable schedule as every caller sees it. */
3
+ export declare const Schedule: Schema.Struct<{
4
+ readonly id: Schema.String;
5
+ readonly sessionId: Schema.String;
6
+ readonly prompt: Schema.String;
7
+ /** Cron expression and zone for a recurring schedule; null for one-shot. */
8
+ readonly cron: Schema.NullOr<Schema.Struct<{
9
+ readonly expression: Schema.String;
10
+ readonly timeZone: Schema.String;
11
+ }>>;
12
+ /** The occurrence that fires next (or fired last, once finished). */
13
+ readonly nextDueAt: Schema.Int;
14
+ readonly state: Schema.Literals<readonly ["active", "paused", "cancelled", "finished"]>;
15
+ /** Latest command admitted for this schedule. */
16
+ readonly lastCommandId: Schema.NullOr<Schema.String>;
17
+ /** Why the current occurrence has not been admitted yet, if it failed. */
18
+ readonly lastError: Schema.NullOr<Schema.String>;
19
+ /** Consecutive failed admissions of the current occurrence. */
20
+ readonly attempt: Schema.Int;
21
+ }>;
22
+ /** One durable schedule. */
23
+ export type Schedule = Schema.Schema.Type<typeof Schedule>;
24
+ /** Almanac's contract: agent-scope schedule CRUD, no Session projection. */
25
+ export declare const almanacContract: {
26
+ readonly id: string & import("effect/Brand").Brand<"ExtensionId">;
27
+ readonly commands: {
28
+ readonly createOneShot: {
29
+ readonly scope: "agent";
30
+ readonly input: Schema.Struct<{
31
+ readonly requestId: Schema.String;
32
+ readonly sessionId: Schema.String;
33
+ readonly prompt: Schema.String;
34
+ readonly dueAt: Schema.Int;
35
+ }>;
36
+ readonly output: Schema.Struct<{
37
+ readonly id: Schema.String;
38
+ readonly sessionId: Schema.String;
39
+ readonly prompt: Schema.String;
40
+ /** Cron expression and zone for a recurring schedule; null for one-shot. */
41
+ readonly cron: Schema.NullOr<Schema.Struct<{
42
+ readonly expression: Schema.String;
43
+ readonly timeZone: Schema.String;
44
+ }>>;
45
+ /** The occurrence that fires next (or fired last, once finished). */
46
+ readonly nextDueAt: Schema.Int;
47
+ readonly state: Schema.Literals<readonly ["active", "paused", "cancelled", "finished"]>;
48
+ /** Latest command admitted for this schedule. */
49
+ readonly lastCommandId: Schema.NullOr<Schema.String>;
50
+ /** Why the current occurrence has not been admitted yet, if it failed. */
51
+ readonly lastError: Schema.NullOr<Schema.String>;
52
+ /** Consecutive failed admissions of the current occurrence. */
53
+ readonly attempt: Schema.Int;
54
+ }>;
55
+ };
56
+ readonly createRecurring: {
57
+ readonly scope: "agent";
58
+ readonly input: Schema.Struct<{
59
+ readonly requestId: Schema.String;
60
+ readonly sessionId: Schema.String;
61
+ readonly prompt: Schema.String;
62
+ readonly expression: Schema.String;
63
+ readonly timeZone: Schema.String;
64
+ }>;
65
+ readonly output: Schema.Struct<{
66
+ readonly id: Schema.String;
67
+ readonly sessionId: Schema.String;
68
+ readonly prompt: Schema.String;
69
+ /** Cron expression and zone for a recurring schedule; null for one-shot. */
70
+ readonly cron: Schema.NullOr<Schema.Struct<{
71
+ readonly expression: Schema.String;
72
+ readonly timeZone: Schema.String;
73
+ }>>;
74
+ /** The occurrence that fires next (or fired last, once finished). */
75
+ readonly nextDueAt: Schema.Int;
76
+ readonly state: Schema.Literals<readonly ["active", "paused", "cancelled", "finished"]>;
77
+ /** Latest command admitted for this schedule. */
78
+ readonly lastCommandId: Schema.NullOr<Schema.String>;
79
+ /** Why the current occurrence has not been admitted yet, if it failed. */
80
+ readonly lastError: Schema.NullOr<Schema.String>;
81
+ /** Consecutive failed admissions of the current occurrence. */
82
+ readonly attempt: Schema.Int;
83
+ }>;
84
+ };
85
+ readonly read: {
86
+ readonly scope: "agent";
87
+ readonly input: Schema.Struct<{
88
+ readonly scheduleId: Schema.String;
89
+ }>;
90
+ readonly output: Schema.NullOr<Schema.Struct<{
91
+ readonly id: Schema.String;
92
+ readonly sessionId: Schema.String;
93
+ readonly prompt: Schema.String;
94
+ /** Cron expression and zone for a recurring schedule; null for one-shot. */
95
+ readonly cron: Schema.NullOr<Schema.Struct<{
96
+ readonly expression: Schema.String;
97
+ readonly timeZone: Schema.String;
98
+ }>>;
99
+ /** The occurrence that fires next (or fired last, once finished). */
100
+ readonly nextDueAt: Schema.Int;
101
+ readonly state: Schema.Literals<readonly ["active", "paused", "cancelled", "finished"]>;
102
+ /** Latest command admitted for this schedule. */
103
+ readonly lastCommandId: Schema.NullOr<Schema.String>;
104
+ /** Why the current occurrence has not been admitted yet, if it failed. */
105
+ readonly lastError: Schema.NullOr<Schema.String>;
106
+ /** Consecutive failed admissions of the current occurrence. */
107
+ readonly attempt: Schema.Int;
108
+ }>>;
109
+ };
110
+ readonly list: {
111
+ readonly scope: "agent";
112
+ readonly input: Schema.Struct<{
113
+ readonly after: Schema.optionalKey<Schema.String>;
114
+ readonly limit: Schema.optionalKey<Schema.Int>;
115
+ }>;
116
+ readonly output: Schema.$Array<Schema.Struct<{
117
+ readonly id: Schema.String;
118
+ readonly sessionId: Schema.String;
119
+ readonly prompt: Schema.String;
120
+ /** Cron expression and zone for a recurring schedule; null for one-shot. */
121
+ readonly cron: Schema.NullOr<Schema.Struct<{
122
+ readonly expression: Schema.String;
123
+ readonly timeZone: Schema.String;
124
+ }>>;
125
+ /** The occurrence that fires next (or fired last, once finished). */
126
+ readonly nextDueAt: Schema.Int;
127
+ readonly state: Schema.Literals<readonly ["active", "paused", "cancelled", "finished"]>;
128
+ /** Latest command admitted for this schedule. */
129
+ readonly lastCommandId: Schema.NullOr<Schema.String>;
130
+ /** Why the current occurrence has not been admitted yet, if it failed. */
131
+ readonly lastError: Schema.NullOr<Schema.String>;
132
+ /** Consecutive failed admissions of the current occurrence. */
133
+ readonly attempt: Schema.Int;
134
+ }>>;
135
+ };
136
+ readonly pause: {
137
+ readonly scope: "agent";
138
+ readonly input: Schema.Struct<{
139
+ readonly scheduleId: Schema.String;
140
+ }>;
141
+ readonly output: Schema.Struct<{
142
+ readonly id: Schema.String;
143
+ readonly sessionId: Schema.String;
144
+ readonly prompt: Schema.String;
145
+ /** Cron expression and zone for a recurring schedule; null for one-shot. */
146
+ readonly cron: Schema.NullOr<Schema.Struct<{
147
+ readonly expression: Schema.String;
148
+ readonly timeZone: Schema.String;
149
+ }>>;
150
+ /** The occurrence that fires next (or fired last, once finished). */
151
+ readonly nextDueAt: Schema.Int;
152
+ readonly state: Schema.Literals<readonly ["active", "paused", "cancelled", "finished"]>;
153
+ /** Latest command admitted for this schedule. */
154
+ readonly lastCommandId: Schema.NullOr<Schema.String>;
155
+ /** Why the current occurrence has not been admitted yet, if it failed. */
156
+ readonly lastError: Schema.NullOr<Schema.String>;
157
+ /** Consecutive failed admissions of the current occurrence. */
158
+ readonly attempt: Schema.Int;
159
+ }>;
160
+ };
161
+ readonly resume: {
162
+ readonly scope: "agent";
163
+ readonly input: Schema.Struct<{
164
+ readonly scheduleId: Schema.String;
165
+ }>;
166
+ readonly output: Schema.Struct<{
167
+ readonly id: Schema.String;
168
+ readonly sessionId: Schema.String;
169
+ readonly prompt: Schema.String;
170
+ /** Cron expression and zone for a recurring schedule; null for one-shot. */
171
+ readonly cron: Schema.NullOr<Schema.Struct<{
172
+ readonly expression: Schema.String;
173
+ readonly timeZone: Schema.String;
174
+ }>>;
175
+ /** The occurrence that fires next (or fired last, once finished). */
176
+ readonly nextDueAt: Schema.Int;
177
+ readonly state: Schema.Literals<readonly ["active", "paused", "cancelled", "finished"]>;
178
+ /** Latest command admitted for this schedule. */
179
+ readonly lastCommandId: Schema.NullOr<Schema.String>;
180
+ /** Why the current occurrence has not been admitted yet, if it failed. */
181
+ readonly lastError: Schema.NullOr<Schema.String>;
182
+ /** Consecutive failed admissions of the current occurrence. */
183
+ readonly attempt: Schema.Int;
184
+ }>;
185
+ };
186
+ readonly cancel: {
187
+ readonly scope: "agent";
188
+ readonly input: Schema.Struct<{
189
+ readonly scheduleId: Schema.String;
190
+ }>;
191
+ readonly output: Schema.Struct<{
192
+ readonly id: Schema.String;
193
+ readonly sessionId: Schema.String;
194
+ readonly prompt: Schema.String;
195
+ /** Cron expression and zone for a recurring schedule; null for one-shot. */
196
+ readonly cron: Schema.NullOr<Schema.Struct<{
197
+ readonly expression: Schema.String;
198
+ readonly timeZone: Schema.String;
199
+ }>>;
200
+ /** The occurrence that fires next (or fired last, once finished). */
201
+ readonly nextDueAt: Schema.Int;
202
+ readonly state: Schema.Literals<readonly ["active", "paused", "cancelled", "finished"]>;
203
+ /** Latest command admitted for this schedule. */
204
+ readonly lastCommandId: Schema.NullOr<Schema.String>;
205
+ /** Why the current occurrence has not been admitted yet, if it failed. */
206
+ readonly lastError: Schema.NullOr<Schema.String>;
207
+ /** Consecutive failed admissions of the current occurrence. */
208
+ readonly attempt: Schema.Int;
209
+ }>;
210
+ };
211
+ };
212
+ readonly projection: null;
213
+ };
@@ -0,0 +1,77 @@
1
+ import { ExtensionId } from "@fungi.computer/shiitake/extensions";
2
+ import * as Schema from "effect/Schema";
3
+ /**
4
+ * Almanac: durable one-shot and cron schedules that send ordinary Session
5
+ * prompts. This contract is shared by the server extension and its clients;
6
+ * its schemas are the only decoders of Almanac values.
7
+ */
8
+ /** Caller-chosen schedule identity; also its create idempotency key. */
9
+ const ScheduleId = Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.isMaxLength(200)), Schema.check(Schema.isPattern(/^[A-Za-z0-9._:-]+$/u)));
10
+ const SessionId = Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.isMaxLength(256)));
11
+ /** Human prompt grammar; the admission door re-checks the exact byte bound. */
12
+ const Prompt = Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.isMaxLength(256 * 1024)));
13
+ const Instant = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)));
14
+ const CronExpression = Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.isMaxLength(256)));
15
+ const TimeZone = Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.isMaxLength(128)));
16
+ /** One durable schedule as every caller sees it. */
17
+ export const Schedule = Schema.Struct({
18
+ id: ScheduleId,
19
+ sessionId: SessionId,
20
+ prompt: Prompt,
21
+ /** Cron expression and zone for a recurring schedule; null for one-shot. */
22
+ cron: Schema.NullOr(Schema.Struct({ expression: CronExpression, timeZone: TimeZone })),
23
+ /** The occurrence that fires next (or fired last, once finished). */
24
+ nextDueAt: Instant,
25
+ state: Schema.Literals(["active", "paused", "cancelled", "finished"]),
26
+ /** Latest command admitted for this schedule. */
27
+ lastCommandId: Schema.NullOr(Schema.String),
28
+ /** Why the current occurrence has not been admitted yet, if it failed. */
29
+ lastError: Schema.NullOr(Schema.String),
30
+ /** Consecutive failed admissions of the current occurrence. */
31
+ attempt: Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0))),
32
+ });
33
+ const ScheduleRef = Schema.Struct({ scheduleId: ScheduleId });
34
+ /** Almanac's contract: agent-scope schedule CRUD, no Session projection. */
35
+ export const almanacContract = {
36
+ id: ExtensionId.make("botanical-almanac"),
37
+ commands: {
38
+ createOneShot: {
39
+ scope: "agent",
40
+ input: Schema.Struct({
41
+ requestId: ScheduleId,
42
+ sessionId: SessionId,
43
+ prompt: Prompt,
44
+ dueAt: Instant,
45
+ }),
46
+ output: Schedule,
47
+ },
48
+ createRecurring: {
49
+ scope: "agent",
50
+ input: Schema.Struct({
51
+ requestId: ScheduleId,
52
+ sessionId: SessionId,
53
+ prompt: Prompt,
54
+ expression: CronExpression,
55
+ timeZone: TimeZone,
56
+ }),
57
+ output: Schedule,
58
+ },
59
+ read: {
60
+ scope: "agent",
61
+ input: ScheduleRef,
62
+ output: Schema.NullOr(Schedule),
63
+ },
64
+ list: {
65
+ scope: "agent",
66
+ input: Schema.Struct({
67
+ after: Schema.optionalKey(ScheduleId),
68
+ limit: Schema.optionalKey(Schema.Int.pipe(Schema.check(Schema.isBetween({ minimum: 1, maximum: 64 })))),
69
+ }),
70
+ output: Schema.Array(Schedule),
71
+ },
72
+ pause: { scope: "agent", input: ScheduleRef, output: Schedule },
73
+ resume: { scope: "agent", input: ScheduleRef, output: Schedule },
74
+ cancel: { scope: "agent", input: ScheduleRef, output: Schedule },
75
+ },
76
+ projection: null,
77
+ };
@@ -0,0 +1,3 @@
1
+ import { type RosteredExtensionV0 } from "@fungi.computer/shiitake/extensions";
2
+ /** Botanical's Almanac on Shiitake timers and the one admission door. */
3
+ export declare const almanacExtension: RosteredExtensionV0;