@fungi.computer/shiitake-extensions 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +43 -13
  2. package/dist/almanac/contract.d.ts +94 -0
  3. package/dist/almanac/contract.js +71 -5
  4. package/dist/goal/client.d.ts +5 -2
  5. package/dist/goal/client.js +66 -72
  6. package/dist/goal/contract.d.ts +181 -64
  7. package/dist/goal/contract.js +102 -12
  8. package/dist/goal/host.js +188 -113
  9. package/dist/home/contract.d.ts +18 -0
  10. package/dist/home/contract.js +24 -3
  11. package/dist/home/host.d.ts +3 -0
  12. package/dist/home/host.js +1 -2
  13. package/dist/home/routing.js +13 -24
  14. package/dist/internal/history.d.ts +16 -0
  15. package/dist/internal/history.js +8 -0
  16. package/dist/loop/client.d.ts +1 -1
  17. package/dist/loop/client.js +28 -34
  18. package/dist/loop/contract.d.ts +113 -84
  19. package/dist/loop/contract.js +77 -9
  20. package/dist/loop/host.d.ts +1 -0
  21. package/dist/loop/host.js +17 -30
  22. package/dist/plan/client.d.ts +1 -1
  23. package/dist/plan/contract.d.ts +33 -0
  24. package/dist/plan/contract.js +28 -3
  25. package/dist/plan/host.js +12 -7
  26. package/dist/question/client.d.ts +1 -1
  27. package/dist/question/contract.d.ts +158 -0
  28. package/dist/question/contract.js +92 -7
  29. package/dist/question/host.js +34 -20
  30. package/dist/roster/conversation-agents.d.ts +31 -0
  31. package/dist/roster/conversation-agents.js +114 -0
  32. package/dist/roster.js +1 -0
  33. package/dist/skills/host.d.ts +1 -0
  34. package/dist/skills/host.js +22 -17
  35. package/dist/skills/source.d.ts +34 -0
  36. package/dist/subagents/client.d.ts +1 -1
  37. package/dist/subagents/client.js +43 -12
  38. package/dist/subagents/contract.d.ts +46 -8
  39. package/dist/subagents/contract.js +38 -8
  40. package/dist/subagents/host.d.ts +20 -8
  41. package/dist/subagents/host.js +58 -12
  42. package/docs/api/README.md +35 -0
  43. package/docs/api/almanac/contract.md +90 -0
  44. package/docs/api/almanac/host.md +19 -0
  45. package/docs/api/goal/client.md +26 -0
  46. package/docs/api/goal/contract.md +247 -0
  47. package/docs/api/goal/host.md +29 -0
  48. package/docs/api/home/contract.md +58 -0
  49. package/docs/api/home/host.md +55 -0
  50. package/docs/api/loop/client.md +25 -0
  51. package/docs/api/loop/contract.md +159 -0
  52. package/docs/api/loop/host.md +30 -0
  53. package/docs/api/plan/client.md +26 -0
  54. package/docs/api/plan/contract.md +163 -0
  55. package/docs/api/plan/host.md +19 -0
  56. package/docs/api/question/client.md +25 -0
  57. package/docs/api/question/contract.md +377 -0
  58. package/docs/api/question/host.md +19 -0
  59. package/docs/api/roster/conversation-agents.md +80 -0
  60. package/docs/api/roster.md +40 -0
  61. package/docs/api/skills/host.md +39 -0
  62. package/docs/api/skills/source.md +104 -0
  63. package/docs/api/subagents/client.md +26 -0
  64. package/docs/api/subagents/contract.md +131 -0
  65. package/docs/api/subagents/host.md +115 -0
  66. package/package.json +14 -8
@@ -1,4 +1,3 @@
1
- import { historySchema } from "../internal/history.js";
2
1
  import { ExtensionId } from "@fungi.computer/shiitake/extensions";
3
2
  import * as Schema from "effect/Schema";
4
3
  import { CatalogStep } from "../internal/catalog.js";
@@ -11,19 +10,26 @@ const Prompt = Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.ch
11
10
  const RequestId = Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.isMaxLength(200)));
12
11
  /** A named cron cadence evaluated in one IANA time zone. */
13
12
  export const LoopCron = Schema.Struct({
13
+ /** Cron expression for the fixed cadence. */
14
14
  expression: Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.isMaxLength(256))),
15
+ /** IANA time zone in which the expression is evaluated. */
15
16
  timeZone: Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.isMaxLength(128))),
16
17
  });
17
18
  /** One Session's loop as every client sees it. */
18
19
  export const Loop = Schema.Struct({
20
+ /** Identity of the loop whose occurrences are being tracked. */
19
21
  loopId: Schema.String,
22
+ /** Whether a person or the model started this loop. */
20
23
  origin: Schema.Literals(["human", "model"]),
24
+ /** Configured expiry timestamp, or null when absent. */
21
25
  expiresAt: Schema.NullOr(Schema.Int),
22
26
  /** Original instruction repeated for every occurrence. */
23
27
  prompt: Prompt,
28
+ /** Whether the model or a fixed cron cadence schedules occurrences. */
24
29
  mode: Schema.Literals(["self_paced", "fixed_interval"]),
25
30
  /** Cadence of a fixed-interval loop; null when the model paces it. */
26
31
  cron: Schema.NullOr(LoopCron),
32
+ /** Current loop lifecycle state. */
27
33
  state: Schema.Literals([
28
34
  "active",
29
35
  "paused",
@@ -41,9 +47,13 @@ export const Loop = Schema.Struct({
41
47
  lastError: Schema.NullOr(Schema.String),
42
48
  });
43
49
  const StartResult = Schema.Struct({
50
+ /** Newly started loop. */
44
51
  loop: Loop,
52
+ /** Admission receipt for the start command. */
45
53
  accepted: Schema.Struct({
54
+ /** Successful admission discriminator. */
46
55
  status: Schema.Literal("accepted"),
56
+ /** Identity of the accepted start command. */
47
57
  commandId: Schema.String,
48
58
  }),
49
59
  });
@@ -54,41 +64,99 @@ const StartResult = Schema.Struct({
54
64
  */
55
65
  export const LoopCommandInput = Schema.Union([
56
66
  Schema.Struct({
67
+ /** Select the loop-starting control. */
57
68
  action: Schema.Literal("start"),
69
+ /** Human prompt text, optionally prefixed with an interval. */
58
70
  text: Prompt,
71
+ /** Idempotency key for this start command. */
59
72
  requestId: RequestId,
60
73
  }),
61
74
  Schema.Struct({
75
+ /** Select an existing-loop control or open new-loop entry. */
62
76
  action: Schema.Literals(["status", "pause", "resume", "stop", "new"]),
63
77
  }),
64
78
  Schema.Struct({}),
65
79
  ]);
66
80
  /** Loop's contract: Session-scope controls and one projection slot. */
67
81
  export const LoopProjection = Schema.Struct({
68
- loop: Schema.NullOr(Loop),
69
- history: historySchema(Loop),
82
+ /** Loop currently visible to Session clients. */
83
+ loop: Loop,
70
84
  });
85
+ /** Session-owned loop controls and their visible state. */
71
86
  export const loopContract = {
87
+ /** Stable extension identity. */
72
88
  id: ExtensionId.make("botanical-session-loop"),
89
+ /** Loop operations admitted by the Session owner. */
73
90
  commands: {
74
- loop: { scope: "session", input: LoopCommandInput, output: CatalogStep },
91
+ /** Open or answer the human loop controls. */
92
+ loop: {
93
+ /** The Session owns this loop. */
94
+ scope: "session",
95
+ /** Decode the selected human action. */
96
+ input: LoopCommandInput,
97
+ /** Return the server-owned conversation step. */
98
+ output: CatalogStep,
99
+ },
100
+ /** Start a loop whose next occurrence is chosen by the model. */
75
101
  startSelfPaced: {
102
+ /** The Session owns this loop. */
76
103
  scope: "session",
77
- input: Schema.Struct({ prompt: Prompt, requestId: RequestId }),
104
+ /** Decode the repeated prompt and command identity. */
105
+ input: Schema.Struct({
106
+ /** Instruction repeated on every occurrence. */
107
+ prompt: Prompt,
108
+ /** Idempotency key for the start command. */
109
+ requestId: RequestId,
110
+ }),
111
+ /** Return the loop and admission receipt. */
78
112
  output: StartResult,
79
113
  },
114
+ /** Start a loop with a fixed cron cadence. */
80
115
  startFixedInterval: {
116
+ /** The Session owns this loop. */
81
117
  scope: "session",
118
+ /** Decode the prompt, cadence and command identity. */
82
119
  input: Schema.Struct({
120
+ /** Instruction repeated on every occurrence. */
83
121
  prompt: Prompt,
122
+ /** Idempotency key for the start command. */
84
123
  requestId: RequestId,
85
124
  ...LoopCron.fields,
86
125
  }),
126
+ /** Return the loop and admission receipt. */
87
127
  output: StartResult,
88
128
  },
89
- pause: { scope: "session", input: Schema.Struct({}), output: Loop },
90
- resume: { scope: "session", input: Schema.Struct({}), output: Loop },
91
- stop: { scope: "session", input: Schema.Struct({}), output: Loop },
129
+ /** Suspend the current loop. */
130
+ pause: {
131
+ /** The Session owns this loop. */
132
+ scope: "session",
133
+ /** No additional arguments are required. */
134
+ input: Schema.Struct({}),
135
+ /** Return the paused loop. */
136
+ output: Loop,
137
+ },
138
+ /** Resume the current paused loop. */
139
+ resume: {
140
+ /** The Session owns this loop. */
141
+ scope: "session",
142
+ /** No additional arguments are required. */
143
+ input: Schema.Struct({}),
144
+ /** Return the resumed loop. */
145
+ output: Loop,
146
+ },
147
+ /** Stop future occurrences of the current loop. */
148
+ stop: {
149
+ /** The Session owns this loop. */
150
+ scope: "session",
151
+ /** No additional arguments are required. */
152
+ input: Schema.Struct({}),
153
+ /** Return the stopped loop. */
154
+ output: Loop,
155
+ },
156
+ },
157
+ /** Current loop visible to Session clients. */
158
+ projection: {
159
+ /** Decoder for projected loop state. */
160
+ schema: LoopProjection,
92
161
  },
93
- projection: { schema: LoopProjection },
94
162
  };
@@ -1,4 +1,5 @@
1
1
  import { type RosteredExtensionV0 } from "@fungi.computer/shiitake/extensions";
2
2
  /** The commands this extension advertises to every client. */
3
3
  export declare const loopCatalog: import("@fungi.computer/shiitake/extensions").Json;
4
+ /** Durable Session loop implementation with prompts, controls and scheduled occurrences. */
4
5
  export declare const loopExtension: RosteredExtensionV0;
package/dist/loop/host.js CHANGED
@@ -1,4 +1,3 @@
1
- import { appendHistory, emptyHistory, historySchema, } from "../internal/history.js";
2
1
  import { 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";
@@ -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. 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
- guide: "Session loop tools in execute: loop.get({}), loop.start({prompt,cadence}), loop.schedule_next({delaySeconds,reason}), loop.stop({reason}). You may start loops yourself; one live loop per Session. Find them with forage.search({query:'loop'}) or 'schedule' and forage.describe. 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.",
22
- moduleDescription: "Session loop control and schedule tools. Start, get or stop a loop from any Session run; schedule_next requires the self-paced loop's current run.",
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. */
@@ -36,7 +40,6 @@ const LoopRow = Schema.Struct({
36
40
  })),
37
41
  });
38
42
  const collections = {
39
- history: defineCollection(Schema.Struct({ sessionId: Schema.String, history: historySchema(Loop) }), { key: (row) => row.sessionId }),
40
43
  loops: defineCollection(LoopRow, {
41
44
  key: (loop) => loop.sessionId,
42
45
  index: (loop) => loop.state,
@@ -62,19 +65,6 @@ function projected(row) {
62
65
  }
63
66
  function commitLoop(ctx, row) {
64
67
  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
68
  yield* ctx.state.loops.put(row);
79
69
  });
80
70
  }
@@ -582,6 +572,7 @@ export const loopCatalog = catalog([
582
572
  },
583
573
  },
584
574
  ]);
575
+ /** Durable Session loop implementation with prompts, controls and scheduled occurrences. */
585
576
  export const loopExtension = defineExtension({
586
577
  id: loopContract.id,
587
578
  version: "3.0.0",
@@ -592,13 +583,13 @@ export const loopExtension = defineExtension({
592
583
  prompt: (ctx, { sessionId }) => ctx.state.loops.get(sessionId).pipe(Effect.map((found) => [
593
584
  {
594
585
  name: "loop",
595
- text: (ctx.config.guide +
596
- "\n" +
597
- (Option.isSome(found)
598
- ? loopLine(projected(found.value)) +
599
- "; nextDueAt=" +
600
- String(found.value.nextDueAt)
601
- : "No current loop.")).slice(0, ctx.config.promptLimit),
586
+ text: Option.isSome(found)
587
+ ? (ctx.config.guide +
588
+ "\n" +
589
+ loopLine(projected(found.value)) +
590
+ "; nextDueAt=" +
591
+ String(found.value.nextDueAt)).slice(0, ctx.config.promptLimit)
592
+ : ctx.config.idle,
602
593
  },
603
594
  ])),
604
595
  catalog: loopCatalog,
@@ -606,13 +597,9 @@ export const loopExtension = defineExtension({
606
597
  schema: loopContract.projection.schema,
607
598
  read: (ctx, sessionId) => {
608
599
  const loop = Option.getOrUndefined(ctx.state.loops.peek(sessionId));
609
- const history = Option.getOrUndefined(ctx.state.history.peek(sessionId))?.history;
610
- return loop === undefined && history === undefined
600
+ return loop === undefined
611
601
  ? Option.none()
612
- : Option.some({
613
- loop: loop === undefined ? null : projected(loop),
614
- history: history ?? emptyHistory(),
615
- });
602
+ : Option.some({ loop: projected(loop) });
616
603
  },
617
604
  },
618
605
  hooks: {
@@ -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
- correlatedPrompts?: "note";
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
  }>;
@@ -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;
@@ -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({ action: Schema.Literal("clear") }),
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
- plan: { scope: "session", input: PlanCommandInput, output: CatalogStep },
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
- guide: "Session checklist tool in execute: plan.update_plan({explanation?,steps:[{step,status:'pending'|'in_progress'|'completed'}]}). Find it with forage.search({query:'plan'}) and forage.describe. 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.",
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: (ctx.config.guide +
97
- "\n" +
98
- (Option.isSome(found) && found.value.plan.steps.length > 0
99
- ? JSON.stringify(found.value.plan)
100
- : "No current plan.")).slice(0, ctx.config.promptLimit),
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: "Session checklist planning tools.",
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
- correlatedPrompts?: "note";
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
  }>;