@fungi.computer/shiitake-extensions 0.0.2 → 0.1.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/README.md +43 -13
- package/dist/almanac/contract.d.ts +94 -0
- package/dist/almanac/contract.js +71 -5
- package/dist/goal/client.d.ts +5 -2
- package/dist/goal/client.js +66 -72
- package/dist/goal/contract.d.ts +181 -64
- package/dist/goal/contract.js +102 -12
- package/dist/goal/host.js +206 -124
- package/dist/home/contract.d.ts +18 -0
- package/dist/home/contract.js +24 -3
- package/dist/home/host.d.ts +3 -0
- package/dist/home/host.js +1 -2
- package/dist/home/routing.js +13 -24
- package/dist/internal/history.d.ts +16 -0
- package/dist/internal/history.js +8 -0
- package/dist/loop/client.d.ts +1 -1
- package/dist/loop/client.js +28 -34
- package/dist/loop/contract.d.ts +129 -84
- package/dist/loop/contract.js +79 -9
- package/dist/loop/host.d.ts +1 -0
- package/dist/loop/host.js +89 -48
- package/dist/plan/client.d.ts +1 -1
- package/dist/plan/contract.d.ts +33 -0
- package/dist/plan/contract.js +28 -3
- package/dist/plan/host.js +12 -7
- package/dist/question/client.d.ts +1 -1
- package/dist/question/contract.d.ts +158 -0
- package/dist/question/contract.js +92 -7
- package/dist/question/host.js +34 -20
- package/dist/roster/conversation-agents.d.ts +31 -0
- package/dist/roster/conversation-agents.js +114 -0
- package/dist/roster.js +1 -0
- package/dist/skills/host.d.ts +1 -0
- package/dist/skills/host.js +22 -17
- package/dist/skills/source.d.ts +34 -0
- package/dist/subagents/client.d.ts +1 -1
- package/dist/subagents/client.js +43 -12
- package/dist/subagents/contract.d.ts +46 -8
- package/dist/subagents/contract.js +38 -8
- package/dist/subagents/host.d.ts +20 -8
- package/dist/subagents/host.js +58 -12
- package/docs/api/README.md +35 -0
- package/docs/api/almanac/contract.md +90 -0
- package/docs/api/almanac/host.md +19 -0
- package/docs/api/goal/client.md +26 -0
- package/docs/api/goal/contract.md +247 -0
- package/docs/api/goal/host.md +29 -0
- package/docs/api/home/contract.md +58 -0
- package/docs/api/home/host.md +55 -0
- package/docs/api/loop/client.md +25 -0
- package/docs/api/loop/contract.md +161 -0
- package/docs/api/loop/host.md +30 -0
- package/docs/api/plan/client.md +26 -0
- package/docs/api/plan/contract.md +163 -0
- package/docs/api/plan/host.md +19 -0
- package/docs/api/question/client.md +25 -0
- package/docs/api/question/contract.md +377 -0
- package/docs/api/question/host.md +19 -0
- package/docs/api/roster/conversation-agents.md +80 -0
- package/docs/api/roster.md +40 -0
- package/docs/api/skills/host.md +39 -0
- package/docs/api/skills/source.md +104 -0
- package/docs/api/subagents/client.md +26 -0
- package/docs/api/subagents/contract.md +131 -0
- package/docs/api/subagents/host.md +115 -0
- package/package.json +14 -8
package/dist/loop/host.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Mycelium } from "@fungi.computer/mycelium";
|
|
1
|
+
import { CapabilityFailed, Mycelium, } from "@fungi.computer/mycelium";
|
|
3
2
|
import { defineCollection, defineExtension, ExtensionCommandError, ExtensionStop, } from "@fungi.computer/shiitake/extensions";
|
|
4
3
|
import * as Cause from "effect/Cause";
|
|
5
4
|
import * as Clock from "effect/Clock";
|
|
@@ -12,14 +11,19 @@ import { z } from "zod";
|
|
|
12
11
|
import { ask, catalog, done, freshRequestId } from "../internal/catalog.js";
|
|
13
12
|
import { nextOccurrence } from "../internal/cron.js";
|
|
14
13
|
import { Loop, LoopCommandInput, loopContract } from "./contract.js";
|
|
14
|
+
/** The rules the model follows; the describe page and a live loop's prompt share them. */
|
|
15
|
+
const rules = "You may start loops yourself; one live loop per Session. Failed loop ticks retry with backoff; loop.get shows consecutiveFailures, lastError, and nextDueAt. schedule_next is allowed only in the active self-paced loop's own run, clamped to 60–3600 seconds. Omitting it ends that loop. stop works in any Session run. Human pause/resume/stop and Esc remain user controls.";
|
|
15
16
|
/** Hashed prose and policy: every constant loop reads comes from here. */
|
|
16
17
|
const config = {
|
|
17
18
|
minDelaySeconds: 60,
|
|
18
19
|
maxDelaySeconds: 3_600,
|
|
19
20
|
fixedLifetimeMs: 7 * 24 * 60 * 60 * 1_000,
|
|
20
21
|
promptLimit: 1_024,
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
idle: "Recurring or scheduled work? forage.describe({path:'loop'}) first.",
|
|
23
|
+
guide: "Session loop tools: loop.get, loop.start, loop.schedule_next, loop.stop. " +
|
|
24
|
+
rules,
|
|
25
|
+
moduleDescription: "Session loop tools: get({}), start({prompt,cadence}), schedule_next({delaySeconds,reason}), stop({reason}). " +
|
|
26
|
+
rules,
|
|
23
27
|
scheduleDescription: "Schedule the original loop prompt after this run settles successfully. delaySeconds is clamped between 60 and 3600. The reason is stored as why now and does not change the loop prompt. Do not call this when the loop should end.",
|
|
24
28
|
};
|
|
25
29
|
/** One Session's durable loop row. */
|
|
@@ -34,9 +38,9 @@ const LoopRow = Schema.Struct({
|
|
|
34
38
|
delaySeconds: Schema.Number,
|
|
35
39
|
reason: Schema.String,
|
|
36
40
|
})),
|
|
41
|
+
consecutiveFailures: Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)), Schema.withDecodingDefaultKey(Effect.succeed(0))),
|
|
37
42
|
});
|
|
38
43
|
const collections = {
|
|
39
|
-
history: defineCollection(Schema.Struct({ sessionId: Schema.String, history: historySchema(Loop) }), { key: (row) => row.sessionId }),
|
|
40
44
|
loops: defineCollection(LoopRow, {
|
|
41
45
|
key: (loop) => loop.sessionId,
|
|
42
46
|
index: (loop) => loop.state,
|
|
@@ -58,23 +62,11 @@ function projected(row) {
|
|
|
58
62
|
reason: row.reason,
|
|
59
63
|
nextDueAt: row.nextDueAt,
|
|
60
64
|
lastError: row.lastError,
|
|
65
|
+
consecutiveFailures: row.consecutiveFailures,
|
|
61
66
|
};
|
|
62
67
|
}
|
|
63
68
|
function commitLoop(ctx, row) {
|
|
64
69
|
return Effect.gen(function* () {
|
|
65
|
-
const previous = Option.getOrUndefined(yield* ctx.state.loops.get(row.sessionId));
|
|
66
|
-
if (previous?.state !== row.state &&
|
|
67
|
-
(row.state === "active" ||
|
|
68
|
-
row.state === "finished" ||
|
|
69
|
-
row.state === "cancelled" ||
|
|
70
|
-
row.state === "failed")) {
|
|
71
|
-
const history = Option.getOrUndefined(yield* ctx.state.history.get(row.sessionId))
|
|
72
|
-
?.history ?? emptyHistory();
|
|
73
|
-
yield* ctx.state.history.put({
|
|
74
|
-
sessionId: row.sessionId,
|
|
75
|
-
history: appendHistory(history, projected(row), row.commandId || null, yield* Clock.currentTimeMillis),
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
70
|
yield* ctx.state.loops.put(row);
|
|
79
71
|
});
|
|
80
72
|
}
|
|
@@ -137,6 +129,7 @@ function start(ctx, sessionId, input, cron, origin = "human") {
|
|
|
137
129
|
lastError: null,
|
|
138
130
|
startRequestId: input.requestId,
|
|
139
131
|
requested: null,
|
|
132
|
+
consecutiveFailures: 0,
|
|
140
133
|
};
|
|
141
134
|
const stored = cron === null
|
|
142
135
|
? row
|
|
@@ -160,6 +153,8 @@ function requireLoop(ctx, sessionId) {
|
|
|
160
153
|
}
|
|
161
154
|
/** Where a resumed loop picks up: its cadence, or its recorded next instant. */
|
|
162
155
|
function resumeAt(loop, now) {
|
|
156
|
+
if (loop.consecutiveFailures > 0)
|
|
157
|
+
return Effect.succeed(loop.nextDueAt === null ? null : Math.max(now, loop.nextDueAt));
|
|
163
158
|
return loop.cron === null
|
|
164
159
|
? Effect.succeed(loop.nextDueAt)
|
|
165
160
|
: nextOccurrence(loop.cron, now);
|
|
@@ -175,6 +170,27 @@ function settle(ctx, run) {
|
|
|
175
170
|
return;
|
|
176
171
|
if (run.workId !== loop.commandId)
|
|
177
172
|
return;
|
|
173
|
+
if (run.outcome === "failed") {
|
|
174
|
+
const consecutiveFailures = Math.min(Number.MAX_SAFE_INTEGER, loop.consecutiveFailures + 1);
|
|
175
|
+
const backoffExponent = Math.min(consecutiveFailures - 1, 5);
|
|
176
|
+
const delayMs = Math.min(15 * 60_000, 30_000 * 2 ** backoffExponent);
|
|
177
|
+
const now = yield* Clock.currentTimeMillis;
|
|
178
|
+
const dueAt = now + delayMs;
|
|
179
|
+
const retry = {
|
|
180
|
+
...loop,
|
|
181
|
+
state: loop.state,
|
|
182
|
+
requested: null,
|
|
183
|
+
reason: null,
|
|
184
|
+
nextDueAt: dueAt,
|
|
185
|
+
lastError: "Loop work ended with outcome failed.",
|
|
186
|
+
consecutiveFailures,
|
|
187
|
+
};
|
|
188
|
+
if (loop.state === "paused")
|
|
189
|
+
yield* commitLoop(ctx, retry);
|
|
190
|
+
else
|
|
191
|
+
yield* arm(ctx, retry, dueAt);
|
|
192
|
+
return yield* ctx.observe(loop.sessionId);
|
|
193
|
+
}
|
|
178
194
|
if (run.outcome !== "completed" || run.terminal !== "completed") {
|
|
179
195
|
yield* ctx.timers.cancel(loop.sessionId);
|
|
180
196
|
yield* commitLoop(ctx, {
|
|
@@ -186,12 +202,22 @@ function settle(ctx, run) {
|
|
|
186
202
|
lastError: run.outcome === "completed"
|
|
187
203
|
? "Completed loop work has no exact terminal."
|
|
188
204
|
: `Loop work ended with outcome ${run.outcome}.`,
|
|
205
|
+
consecutiveFailures: loop.consecutiveFailures,
|
|
189
206
|
});
|
|
190
207
|
return yield* ctx.observe(loop.sessionId);
|
|
191
208
|
}
|
|
192
209
|
// A fixed interval loop's cadence is armed independently of its runs.
|
|
193
|
-
if (loop.mode === "fixed_interval")
|
|
210
|
+
if (loop.mode === "fixed_interval") {
|
|
211
|
+
if (loop.consecutiveFailures !== 0 || loop.lastError !== null) {
|
|
212
|
+
yield* commitLoop(ctx, {
|
|
213
|
+
...loop,
|
|
214
|
+
consecutiveFailures: 0,
|
|
215
|
+
lastError: null,
|
|
216
|
+
});
|
|
217
|
+
yield* ctx.observe(loop.sessionId);
|
|
218
|
+
}
|
|
194
219
|
return;
|
|
220
|
+
}
|
|
195
221
|
// Omitting schedule_next during the run ends a self-paced loop.
|
|
196
222
|
if (loop.requested?.workId !== run.workId) {
|
|
197
223
|
yield* commitLoop(ctx, {
|
|
@@ -199,6 +225,8 @@ function settle(ctx, run) {
|
|
|
199
225
|
state: "finished",
|
|
200
226
|
reason: "no next wake requested",
|
|
201
227
|
nextDueAt: null,
|
|
228
|
+
lastError: null,
|
|
229
|
+
consecutiveFailures: 0,
|
|
202
230
|
});
|
|
203
231
|
return yield* ctx.observe(loop.sessionId);
|
|
204
232
|
}
|
|
@@ -208,6 +236,8 @@ function settle(ctx, run) {
|
|
|
208
236
|
reason: loop.requested.reason,
|
|
209
237
|
requested: null,
|
|
210
238
|
nextDueAt: now + loop.requested.delaySeconds * 1_000,
|
|
239
|
+
lastError: null,
|
|
240
|
+
consecutiveFailures: 0,
|
|
211
241
|
};
|
|
212
242
|
// A paused loop keeps the instant; resume arms it.
|
|
213
243
|
if (loop.state === "paused")
|
|
@@ -283,17 +313,24 @@ function fire(ctx, sessionId, dueAt, now) {
|
|
|
283
313
|
yield* ctx.observe(sessionId);
|
|
284
314
|
});
|
|
285
315
|
}
|
|
286
|
-
/** Run one tool transaction and
|
|
287
|
-
function toolCall(ctx, body) {
|
|
316
|
+
/** Run one tool transaction and preserve its owner-admitted diagnostic. */
|
|
317
|
+
function toolCall(ctx, operation, body) {
|
|
288
318
|
return Effect.runPromiseExit(ctx.transact(body)).then((exit) => {
|
|
289
319
|
if (Exit.isSuccess(exit))
|
|
290
320
|
return exit.value;
|
|
291
321
|
const failure = Cause.squash(exit.cause);
|
|
292
|
-
return Promise.reject(new
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
: "
|
|
322
|
+
return Promise.reject(new CapabilityFailed({
|
|
323
|
+
namespace: "loop",
|
|
324
|
+
operation,
|
|
325
|
+
cause: {
|
|
326
|
+
kind: "OperationFailed",
|
|
327
|
+
message: failure instanceof ExtensionStop
|
|
328
|
+
? failure.reason
|
|
329
|
+
: failure instanceof Error
|
|
330
|
+
? failure.message
|
|
331
|
+
: "Loop tool failed",
|
|
332
|
+
},
|
|
333
|
+
}));
|
|
297
334
|
});
|
|
298
335
|
}
|
|
299
336
|
// Mycelium's maintained operation interface takes Zod for model-tool I/O.
|
|
@@ -315,6 +352,7 @@ const toolLoop = z.strictObject({
|
|
|
315
352
|
reason: z.string().nullable(),
|
|
316
353
|
nextDueAt: z.number().nullable(),
|
|
317
354
|
lastError: z.string().nullable(),
|
|
355
|
+
consecutiveFailures: z.number().int().nonnegative(),
|
|
318
356
|
});
|
|
319
357
|
/** The self-paced `schedule_next` tool for exactly the loop's current run. */
|
|
320
358
|
function loopModule(ctx, sessionId, workId) {
|
|
@@ -328,7 +366,7 @@ function loopModule(ctx, sessionId, workId) {
|
|
|
328
366
|
description: "Get the current Session loop and its next scheduled wake.",
|
|
329
367
|
input: z.strictObject({}),
|
|
330
368
|
output: toolLoop.nullable(),
|
|
331
|
-
execute: () => toolCall(ctx, ctx.state.loops
|
|
369
|
+
execute: () => toolCall(ctx, "get", ctx.state.loops
|
|
332
370
|
.get(sessionId)
|
|
333
371
|
.pipe(Effect.map((found) => Option.isSome(found) ? projected(found.value) : null))),
|
|
334
372
|
}),
|
|
@@ -353,7 +391,7 @@ function loopModule(ctx, sessionId, workId) {
|
|
|
353
391
|
]),
|
|
354
392
|
}),
|
|
355
393
|
output: toolLoop,
|
|
356
|
-
execute: ({ prompt, cadence }) => toolCall(ctx, Effect.gen(function* () {
|
|
394
|
+
execute: ({ prompt, cadence }) => toolCall(ctx, "start", Effect.gen(function* () {
|
|
357
395
|
const requestId = yield* freshRequestId;
|
|
358
396
|
const cron = cadence.kind === "self_paced"
|
|
359
397
|
? null
|
|
@@ -372,7 +410,7 @@ function loopModule(ctx, sessionId, workId) {
|
|
|
372
410
|
description: "Stop this Session's loop from any run, with a visible reason. Cancel its next scheduled wake.",
|
|
373
411
|
input: z.strictObject({ reason: z.string().trim().min(1).max(1_000) }),
|
|
374
412
|
output: toolLoop,
|
|
375
|
-
execute: ({ reason }) => toolCall(ctx, stopLoop(ctx, sessionId, reason)),
|
|
413
|
+
execute: ({ reason }) => toolCall(ctx, "stop", stopLoop(ctx, sessionId, reason)),
|
|
376
414
|
}),
|
|
377
415
|
schedule_next: Mycelium.operation({
|
|
378
416
|
replay: "unsafe",
|
|
@@ -383,13 +421,16 @@ function loopModule(ctx, sessionId, workId) {
|
|
|
383
421
|
dueAt: z.number(),
|
|
384
422
|
reason: z.string().min(1).max(4_000),
|
|
385
423
|
}),
|
|
386
|
-
execute: ({ delaySeconds, reason }) => toolCall(ctx, Effect.gen(function* () {
|
|
424
|
+
execute: ({ delaySeconds, reason }) => toolCall(ctx, "schedule_next", Effect.gen(function* () {
|
|
387
425
|
const found = yield* ctx.state.loops.get(sessionId);
|
|
388
|
-
if (Option.isNone(found)
|
|
389
|
-
|
|
426
|
+
if (Option.isNone(found))
|
|
427
|
+
return yield* Effect.fail(conflict("schedule_next only works inside this loop's own tick run"));
|
|
428
|
+
if (found.value.state === "failed")
|
|
429
|
+
return yield* Effect.fail(conflict("loop is failed; start a new one with loop.start"));
|
|
430
|
+
if (found.value.state !== "active" ||
|
|
390
431
|
found.value.mode !== "self_paced" ||
|
|
391
432
|
found.value.commandId !== workId)
|
|
392
|
-
return yield* Effect.fail(conflict("
|
|
433
|
+
return yield* Effect.fail(conflict("schedule_next only works inside this loop's own tick run"));
|
|
393
434
|
const clamped = Math.max(ctx.config.minDelaySeconds, Math.min(ctx.config.maxDelaySeconds, delaySeconds));
|
|
394
435
|
yield* commitLoop(ctx, {
|
|
395
436
|
...found.value,
|
|
@@ -582,23 +623,27 @@ export const loopCatalog = catalog([
|
|
|
582
623
|
},
|
|
583
624
|
},
|
|
584
625
|
]);
|
|
626
|
+
/** Durable Session loop implementation with prompts, controls and scheduled occurrences. */
|
|
585
627
|
export const loopExtension = defineExtension({
|
|
586
628
|
id: loopContract.id,
|
|
587
|
-
version: "
|
|
629
|
+
version: "4.0.0",
|
|
588
630
|
})
|
|
589
631
|
.config(config)
|
|
590
|
-
.state({
|
|
632
|
+
.state({
|
|
633
|
+
version: 3,
|
|
634
|
+
collections,
|
|
635
|
+
})
|
|
591
636
|
.define({
|
|
592
637
|
prompt: (ctx, { sessionId }) => ctx.state.loops.get(sessionId).pipe(Effect.map((found) => [
|
|
593
638
|
{
|
|
594
639
|
name: "loop",
|
|
595
|
-
text: (
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
640
|
+
text: Option.isSome(found)
|
|
641
|
+
? (ctx.config.guide +
|
|
642
|
+
"\n" +
|
|
643
|
+
loopLine(projected(found.value)) +
|
|
644
|
+
"; nextDueAt=" +
|
|
645
|
+
String(found.value.nextDueAt)).slice(0, ctx.config.promptLimit)
|
|
646
|
+
: ctx.config.idle,
|
|
602
647
|
},
|
|
603
648
|
])),
|
|
604
649
|
catalog: loopCatalog,
|
|
@@ -606,13 +651,9 @@ export const loopExtension = defineExtension({
|
|
|
606
651
|
schema: loopContract.projection.schema,
|
|
607
652
|
read: (ctx, sessionId) => {
|
|
608
653
|
const loop = Option.getOrUndefined(ctx.state.loops.peek(sessionId));
|
|
609
|
-
|
|
610
|
-
return loop === undefined && history === undefined
|
|
654
|
+
return loop === undefined
|
|
611
655
|
? Option.none()
|
|
612
|
-
: Option.some({
|
|
613
|
-
loop: loop === undefined ? null : projected(loop),
|
|
614
|
-
history: history ?? emptyHistory(),
|
|
615
|
-
});
|
|
656
|
+
: Option.some({ loop: projected(loop) });
|
|
616
657
|
},
|
|
617
658
|
},
|
|
618
659
|
hooks: {
|
package/dist/plan/client.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { type ClientContributionV1 } from "@fungi.computer/shiitake/client-entry
|
|
|
6
6
|
declare const _default: Readonly<{
|
|
7
7
|
protocol: "shiitake.client/v1";
|
|
8
8
|
id: import("@fungi.computer/shiitake/extensions").ExtensionId;
|
|
9
|
-
|
|
9
|
+
promptPresentation?: "footer";
|
|
10
10
|
attach?: (context: import("@fungi.computer/shiitake/client-entry").ClientExtensionContextV1) => import("effect/Effect").Effect<void, Error, import("effect/Scope").Scope>;
|
|
11
11
|
contribute?: (slot: import("@fungi.computer/shiitake").ExtensionSlotV0 | undefined) => ClientContributionV1;
|
|
12
12
|
}>;
|
package/dist/plan/contract.d.ts
CHANGED
|
@@ -4,31 +4,47 @@ import * as Schema from "effect/Schema";
|
|
|
4
4
|
* steps means no plan: replacing it with an empty checklist clears it.
|
|
5
5
|
*/
|
|
6
6
|
export declare const Plan: Schema.Struct<{
|
|
7
|
+
/** Optional explanation for this checklist replacement. */
|
|
7
8
|
readonly explanation: Schema.optionalKey<Schema.String>;
|
|
9
|
+
/** Ordered checklist; an empty array clears the plan. */
|
|
8
10
|
readonly steps: Schema.$Array<Schema.Struct<{
|
|
11
|
+
/** Work described by this checklist step. */
|
|
9
12
|
readonly step: Schema.String;
|
|
13
|
+
/** Current progress state of the step. */
|
|
10
14
|
readonly status: Schema.Literals<readonly ["pending", "in_progress", "completed"]>;
|
|
11
15
|
}>>;
|
|
12
16
|
}>;
|
|
17
|
+
/** Checklist replacement that permits at most one in-progress step. */
|
|
13
18
|
export declare const PlanInput: Schema.Struct<{
|
|
19
|
+
/** Optional explanation for this checklist replacement. */
|
|
14
20
|
readonly explanation: Schema.optionalKey<Schema.String>;
|
|
21
|
+
/** Ordered checklist; an empty array clears the plan. */
|
|
15
22
|
readonly steps: Schema.$Array<Schema.Struct<{
|
|
23
|
+
/** Work described by this checklist step. */
|
|
16
24
|
readonly step: Schema.String;
|
|
25
|
+
/** Current progress state of the step. */
|
|
17
26
|
readonly status: Schema.Literals<readonly ["pending", "in_progress", "completed"]>;
|
|
18
27
|
}>>;
|
|
19
28
|
}>;
|
|
29
|
+
/** One Session's ordered checklist and optional explanation. */
|
|
20
30
|
export type Plan = Schema.Schema.Type<typeof Plan>;
|
|
21
31
|
/** Open work remains: some step is not completed. Cleared and finished plans are closed. */
|
|
22
32
|
export declare function planIsOpen(plan: Plan): boolean;
|
|
23
33
|
/** The current checklist and every committed replacement, clears included. */
|
|
24
34
|
export declare const PlanProjection: Schema.Struct<{
|
|
35
|
+
/** Latest checklist replacement. */
|
|
25
36
|
readonly plan: Schema.Struct<{
|
|
37
|
+
/** Optional explanation for this checklist replacement. */
|
|
26
38
|
readonly explanation: Schema.optionalKey<Schema.String>;
|
|
39
|
+
/** Ordered checklist; an empty array clears the plan. */
|
|
27
40
|
readonly steps: Schema.$Array<Schema.Struct<{
|
|
41
|
+
/** Work described by this checklist step. */
|
|
28
42
|
readonly step: Schema.String;
|
|
43
|
+
/** Current progress state of the step. */
|
|
29
44
|
readonly status: Schema.Literals<readonly ["pending", "in_progress", "completed"]>;
|
|
30
45
|
}>>;
|
|
31
46
|
}>;
|
|
47
|
+
/** Committed replacements, including explicit clears. */
|
|
32
48
|
readonly history: Schema.Struct<{
|
|
33
49
|
readonly nextSequence: Schema.Int;
|
|
34
50
|
readonly omitted: Schema.Int;
|
|
@@ -55,28 +71,45 @@ export declare const PlanProjection: Schema.Struct<{
|
|
|
55
71
|
}>;
|
|
56
72
|
/** Arguments of the human `/plan` command: a bare call views, `clear` empties it. */
|
|
57
73
|
export declare const PlanCommandInput: Schema.Union<readonly [Schema.Struct<{
|
|
74
|
+
/** Clear the checklist instead of viewing it. */
|
|
58
75
|
readonly action: Schema.Literal<"clear">;
|
|
59
76
|
}>, Schema.Struct<{}>]>;
|
|
77
|
+
/** Session-owned human plan control and checklist projection. */
|
|
60
78
|
export declare const planContract: {
|
|
79
|
+
/** Stable extension identity. */
|
|
61
80
|
id: string & import("effect/Brand").Brand<"ExtensionId">;
|
|
81
|
+
/** Human checklist controls. */
|
|
62
82
|
commands: {
|
|
83
|
+
/** View or clear the current checklist. */
|
|
63
84
|
plan: {
|
|
85
|
+
/** The Session owns the checklist. */
|
|
64
86
|
scope: "session";
|
|
87
|
+
/** Decode the selected human control. */
|
|
65
88
|
input: Schema.Union<readonly [Schema.Struct<{
|
|
89
|
+
/** Clear the checklist instead of viewing it. */
|
|
66
90
|
readonly action: Schema.Literal<"clear">;
|
|
67
91
|
}>, Schema.Struct<{}>]>;
|
|
92
|
+
/** Return the server-owned conversation step. */
|
|
68
93
|
output: Schema.declare<import("@fungi.computer/whistle/catalog").WhistleCatalogStepV1, import("@fungi.computer/whistle/catalog").WhistleCatalogStepV1>;
|
|
69
94
|
};
|
|
70
95
|
};
|
|
96
|
+
/** Current checklist and its replacement history. */
|
|
71
97
|
projection: {
|
|
98
|
+
/** Decoder for visible plan state. */
|
|
72
99
|
schema: Schema.Struct<{
|
|
100
|
+
/** Latest checklist replacement. */
|
|
73
101
|
readonly plan: Schema.Struct<{
|
|
102
|
+
/** Optional explanation for this checklist replacement. */
|
|
74
103
|
readonly explanation: Schema.optionalKey<Schema.String>;
|
|
104
|
+
/** Ordered checklist; an empty array clears the plan. */
|
|
75
105
|
readonly steps: Schema.$Array<Schema.Struct<{
|
|
106
|
+
/** Work described by this checklist step. */
|
|
76
107
|
readonly step: Schema.String;
|
|
108
|
+
/** Current progress state of the step. */
|
|
77
109
|
readonly status: Schema.Literals<readonly ["pending", "in_progress", "completed"]>;
|
|
78
110
|
}>>;
|
|
79
111
|
}>;
|
|
112
|
+
/** Committed replacements, including explicit clears. */
|
|
80
113
|
readonly history: Schema.Struct<{
|
|
81
114
|
readonly nextSequence: Schema.Int;
|
|
82
115
|
readonly omitted: Schema.Int;
|
package/dist/plan/contract.js
CHANGED
|
@@ -7,12 +7,17 @@ import { CatalogStep } from "../internal/catalog.js";
|
|
|
7
7
|
* steps means no plan: replacing it with an empty checklist clears it.
|
|
8
8
|
*/
|
|
9
9
|
export const Plan = Schema.Struct({
|
|
10
|
+
/** Optional explanation for this checklist replacement. */
|
|
10
11
|
explanation: Schema.optionalKey(Schema.String.pipe(Schema.check(Schema.isMaxLength(2_000)))),
|
|
12
|
+
/** Ordered checklist; an empty array clears the plan. */
|
|
11
13
|
steps: Schema.Array(Schema.Struct({
|
|
14
|
+
/** Work described by this checklist step. */
|
|
12
15
|
step: Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.isMaxLength(500))),
|
|
16
|
+
/** Current progress state of the step. */
|
|
13
17
|
status: Schema.Literals(["pending", "in_progress", "completed"]),
|
|
14
18
|
})).pipe(Schema.check(Schema.isMaxLength(50))),
|
|
15
19
|
});
|
|
20
|
+
/** Checklist replacement that permits at most one in-progress step. */
|
|
16
21
|
export const PlanInput = Plan.pipe(Schema.check(Schema.makeFilter((plan) => plan.steps.filter((step) => step.status === "in_progress").length <=
|
|
17
22
|
1 || "At most one plan step may be in progress")));
|
|
18
23
|
/** Open work remains: some step is not completed. Cleared and finished plans are closed. */
|
|
@@ -21,18 +26,38 @@ export function planIsOpen(plan) {
|
|
|
21
26
|
}
|
|
22
27
|
/** The current checklist and every committed replacement, clears included. */
|
|
23
28
|
export const PlanProjection = Schema.Struct({
|
|
29
|
+
/** Latest checklist replacement. */
|
|
24
30
|
plan: Plan,
|
|
31
|
+
/** Committed replacements, including explicit clears. */
|
|
25
32
|
history: historySchema(Plan),
|
|
26
33
|
});
|
|
27
34
|
/** Arguments of the human `/plan` command: a bare call views, `clear` empties it. */
|
|
28
35
|
export const PlanCommandInput = Schema.Union([
|
|
29
|
-
Schema.Struct({
|
|
36
|
+
Schema.Struct({
|
|
37
|
+
/** Clear the checklist instead of viewing it. */
|
|
38
|
+
action: Schema.Literal("clear"),
|
|
39
|
+
}),
|
|
30
40
|
Schema.Struct({}),
|
|
31
41
|
]);
|
|
42
|
+
/** Session-owned human plan control and checklist projection. */
|
|
32
43
|
export const planContract = {
|
|
44
|
+
/** Stable extension identity. */
|
|
33
45
|
id: ExtensionId.make("botanical-session-plan"),
|
|
46
|
+
/** Human checklist controls. */
|
|
34
47
|
commands: {
|
|
35
|
-
|
|
48
|
+
/** View or clear the current checklist. */
|
|
49
|
+
plan: {
|
|
50
|
+
/** The Session owns the checklist. */
|
|
51
|
+
scope: "session",
|
|
52
|
+
/** Decode the selected human control. */
|
|
53
|
+
input: PlanCommandInput,
|
|
54
|
+
/** Return the server-owned conversation step. */
|
|
55
|
+
output: CatalogStep,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
/** Current checklist and its replacement history. */
|
|
59
|
+
projection: {
|
|
60
|
+
/** Decoder for visible plan state. */
|
|
61
|
+
schema: PlanProjection,
|
|
36
62
|
},
|
|
37
|
-
projection: { schema: PlanProjection },
|
|
38
63
|
};
|
package/dist/plan/host.js
CHANGED
|
@@ -8,9 +8,14 @@ import * as Schema from "effect/Schema";
|
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
import { catalog, done } from "../internal/catalog.js";
|
|
10
10
|
import { Plan, PlanInput, planContract, planIsOpen } from "./contract.js";
|
|
11
|
+
/** The rules the model follows; the describe page and an open plan's prompt share them. */
|
|
12
|
+
const rules = "Use it for multi-step work, with at most one in-progress step; replace the whole plan on each update, and pass steps:[] to clear a plan that no longer applies. A completed checklist is not proof of goal completion.";
|
|
11
13
|
const config = Object.freeze({
|
|
12
14
|
promptLimit: 2_000,
|
|
13
|
-
|
|
15
|
+
idle: "Multi-step work? forage.describe({path:'plan'}) first.",
|
|
16
|
+
moduleDescription: "Session checklist tool: update_plan({explanation?,steps:[{step,status:'pending'|'in_progress'|'completed'}]}). " +
|
|
17
|
+
rules,
|
|
18
|
+
guide: "Session checklist tool: plan.update_plan. " + rules,
|
|
14
19
|
});
|
|
15
20
|
const collections = {
|
|
16
21
|
history: defineCollection(Schema.Struct({ sessionId: Schema.String, history: historySchema(Plan) }), { key: (row) => row.sessionId }),
|
|
@@ -93,17 +98,17 @@ export const planExtension = defineExtension({
|
|
|
93
98
|
prompt: (ctx, { sessionId }) => ctx.state.plans.get(sessionId).pipe(Effect.map((found) => [
|
|
94
99
|
{
|
|
95
100
|
name: "plan",
|
|
96
|
-
text: (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
+
text: Option.isSome(found) && found.value.plan.steps.length > 0
|
|
102
|
+
? (ctx.config.guide +
|
|
103
|
+
"\n" +
|
|
104
|
+
JSON.stringify(found.value.plan)).slice(0, ctx.config.promptLimit)
|
|
105
|
+
: ctx.config.idle,
|
|
101
106
|
},
|
|
102
107
|
])),
|
|
103
108
|
tools: (ctx, { sessionId, workId }) => Effect.succeed(Option.some(Mycelium.module({
|
|
104
109
|
id: "botanical.session-plan",
|
|
105
110
|
name: "plan",
|
|
106
|
-
description:
|
|
111
|
+
description: ctx.config.moduleDescription,
|
|
107
112
|
operations: {
|
|
108
113
|
update_plan: Mycelium.operation({
|
|
109
114
|
replay: "safe",
|
|
@@ -3,7 +3,7 @@ import { type ClientContributionV1 } from "@fungi.computer/shiitake/client-entry
|
|
|
3
3
|
declare const _default: Readonly<{
|
|
4
4
|
protocol: "shiitake.client/v1";
|
|
5
5
|
id: import("@fungi.computer/shiitake/extensions").ExtensionId;
|
|
6
|
-
|
|
6
|
+
promptPresentation?: "footer";
|
|
7
7
|
attach?: (context: import("@fungi.computer/shiitake/client-entry").ClientExtensionContextV1) => import("effect/Effect").Effect<void, Error, import("effect/Scope").Scope>;
|
|
8
8
|
contribute?: (slot: import("@fungi.computer/shiitake").ExtensionSlotV0 | undefined) => ClientContributionV1;
|
|
9
9
|
}>;
|