@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.
- package/LICENSE +21 -0
- package/README.md +262 -2
- package/dist/almanac/contract.d.ts +213 -0
- package/dist/almanac/contract.js +77 -0
- package/dist/almanac/host.d.ts +3 -0
- package/dist/almanac/host.js +171 -0
- package/dist/goal/client.d.ts +10 -0
- package/dist/goal/client.js +88 -0
- package/dist/goal/contract.d.ts +172 -0
- package/dist/goal/contract.js +76 -0
- package/dist/goal/host.d.ts +5 -0
- package/dist/goal/host.js +542 -0
- package/dist/home/contract.d.ts +33 -0
- package/dist/home/contract.js +25 -0
- package/dist/home/host.d.ts +18 -0
- package/dist/home/host.js +136 -0
- package/dist/home/routing.d.ts +3 -0
- package/dist/home/routing.js +80 -0
- package/dist/internal/catalog.d.ts +20 -0
- package/dist/internal/catalog.js +33 -0
- package/dist/internal/cron.d.ts +5 -0
- package/dist/internal/cron.js +14 -0
- package/dist/internal/history.d.ts +29 -0
- package/dist/internal/history.js +41 -0
- package/dist/loop/client.d.ts +10 -0
- package/dist/loop/client.js +89 -0
- package/dist/loop/contract.d.ts +342 -0
- package/dist/loop/contract.js +94 -0
- package/dist/loop/host.d.ts +4 -0
- package/dist/loop/host.js +637 -0
- package/dist/plan/client.d.ts +13 -0
- package/dist/plan/client.js +51 -0
- package/dist/plan/contract.d.ts +105 -0
- package/dist/plan/contract.js +38 -0
- package/dist/plan/host.d.ts +3 -0
- package/dist/plan/host.js +154 -0
- package/dist/question/client.d.ts +10 -0
- package/dist/question/client.js +28 -0
- package/dist/question/contract.d.ts +251 -0
- package/dist/question/contract.js +114 -0
- package/dist/question/host.d.ts +3 -0
- package/dist/question/host.js +997 -0
- package/dist/roster.d.ts +9 -0
- package/dist/roster.js +80 -0
- package/dist/skills/host.d.ts +7 -0
- package/dist/skills/host.js +223 -0
- package/dist/skills/source.d.ts +28 -0
- package/dist/skills/source.js +1 -0
- package/dist/subagents/client.d.ts +10 -0
- package/dist/subagents/client.js +53 -0
- package/dist/subagents/contract.d.ts +54 -0
- package/dist/subagents/contract.js +38 -0
- package/dist/subagents/host.d.ts +50 -0
- package/dist/subagents/host.js +176 -0
- 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
|
-
#
|
|
1
|
+
# Shiitake product extensions
|
|
2
2
|
|
|
3
|
-
|
|
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
|
+
};
|