@cfbender/cesium 0.6.2 → 0.7.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 (35) hide show
  1. package/CHANGELOG.md +82 -1
  2. package/package.json +1 -1
  3. package/src/index.ts +2 -0
  4. package/src/prompt/system-fragment.md +68 -8
  5. package/src/render/annotate-frozen.ts +90 -0
  6. package/src/render/blocks/render.ts +20 -0
  7. package/src/render/blocks/renderers/callout.ts +3 -2
  8. package/src/render/blocks/renderers/code.ts +17 -2
  9. package/src/render/blocks/renderers/compare-table.ts +3 -2
  10. package/src/render/blocks/renderers/diagram.ts +3 -2
  11. package/src/render/blocks/renderers/diff.ts +23 -9
  12. package/src/render/blocks/renderers/hero.ts +3 -2
  13. package/src/render/blocks/renderers/kv.ts +3 -2
  14. package/src/render/blocks/renderers/list.ts +5 -4
  15. package/src/render/blocks/renderers/pill-row.ts +3 -2
  16. package/src/render/blocks/renderers/prose.ts +8 -2
  17. package/src/render/blocks/renderers/raw-html.ts +8 -2
  18. package/src/render/blocks/renderers/risk-table.ts +3 -2
  19. package/src/render/blocks/renderers/section.ts +4 -2
  20. package/src/render/blocks/renderers/timeline.ts +3 -2
  21. package/src/render/blocks/renderers/tldr.ts +3 -2
  22. package/src/render/client-js.ts +804 -6
  23. package/src/render/critique.ts +5 -335
  24. package/src/render/theme.ts +431 -6
  25. package/src/render/validate.ts +353 -97
  26. package/src/render/wrap.ts +67 -9
  27. package/src/server/api.ts +162 -3
  28. package/src/storage/index-gen.ts +4 -2
  29. package/src/storage/mutate.ts +433 -27
  30. package/src/tools/annotate.ts +336 -0
  31. package/src/tools/ask.ts +2 -6
  32. package/src/tools/critique.ts +15 -45
  33. package/src/tools/publish.ts +16 -56
  34. package/src/tools/styleguide.ts +7 -1
  35. package/src/tools/wait.ts +77 -24
package/src/tools/wait.ts CHANGED
@@ -6,7 +6,14 @@ import { tool } from "@opencode-ai/plugin";
6
6
  import type { PluginInput } from "@opencode-ai/plugin";
7
7
  import { loadConfig, type CesiumConfig } from "../config.ts";
8
8
  import { loadIndex } from "../storage/index-cache.ts";
9
- import type { AnswerValue, InteractiveData } from "../render/validate.ts";
9
+ import type {
10
+ AnswerValue,
11
+ Comment,
12
+ Verdict,
13
+ InteractiveAskData,
14
+ InteractiveAnnotateData,
15
+ } from "../render/validate.ts";
16
+ import { coerceInteractiveData } from "../render/validate.ts";
10
17
  import { readEmbeddedMetadata } from "../storage/write.ts";
11
18
  import { readFile } from "node:fs/promises";
12
19
 
@@ -16,17 +23,13 @@ export interface WaitToolOverrides {
16
23
 
17
24
  export type WaitStatus = "complete" | "incomplete" | "expired" | "cancelled" | "not-found";
18
25
 
19
- const TOOL_DESCRIPTION = `cesium_wait — Block until the user completes a cesium_ask interactive artifact, or
20
- until timeout. Returns the user's answers as a map keyed by question id.
26
+ const TOOL_DESCRIPTION = `cesium_wait — Block until the user completes a cesium_ask or cesium_annotate interactive artifact, or until timeout.
21
27
 
22
- Pass the id returned from cesium_ask. Default timeout is 10 minutes. Polls the
23
- artifact file every 500ms (no server-side coordination needed — disk is the source
24
- of truth).
28
+ Pass the id returned from cesium_ask or cesium_annotate. Default timeout is 10 minutes. Polls the artifact file every 500ms (no server-side coordination needed — disk is the source of truth).
25
29
 
26
- Use this immediately after cesium_ask when you need the user's input to continue.
27
- If the user doesn't finish within the timeout, you'll get status: "incomplete"
28
- with whatever they answered so far — handle that case (re-prompt, give up, or
29
- publish a partial artifact).`;
30
+ For cesium_ask sessions, returns { status, answers, remaining } where answers is a map keyed by question id. For cesium_annotate sessions, additionally returns { status, comments, verdict, kind: "annotate" } where comments is an array of { id, anchor, selectedText, comment, createdAt } and verdict is { value: "approve" | "request_changes" | "comment", decidedAt } or null if the user hasn't decided yet.
31
+
32
+ Use this immediately after cesium_ask or cesium_annotate when you need the user's input to continue. If the user doesn't finish within the timeout, you'll get status: "incomplete" with whatever they've done so far — handle that case (re-prompt, give up, or publish a partial artifact).`;
30
33
 
31
34
  export function createWaitTool(
32
35
  _ctx: PluginInput,
@@ -105,8 +108,13 @@ async function resolveArtifactPath(stateDir: string, id: string): Promise<string
105
108
 
106
109
  interface PollResult {
107
110
  status: WaitStatus;
111
+ // Ask-mode fields (empty for annotate)
108
112
  answers: Record<string, AnswerValue>;
109
113
  remaining: string[];
114
+ // Annotate-mode fields (omitted/empty for ask)
115
+ comments?: Comment[];
116
+ verdict?: { value: Verdict; decidedAt: string } | null;
117
+ kind?: "ask" | "annotate";
110
118
  }
111
119
 
112
120
  async function pollLoop(
@@ -128,12 +136,26 @@ async function pollLoop(
128
136
  }
129
137
 
130
138
  const meta = readEmbeddedMetadata(html);
131
- if (meta === null || !isInteractiveData(meta["interactive"])) {
139
+ const interactive = coerceInteractiveData(meta === null ? null : meta["interactive"]);
140
+ if (interactive === null) {
132
141
  return { status: "not-found", answers: {}, remaining: [] };
133
142
  }
134
143
 
135
- const interactive = meta["interactive"] as InteractiveData;
144
+ if (interactive.kind === "ask") {
145
+ return pollAsk(interactive, artifactPath, timeoutMs, pollIntervalMs, startTime);
146
+ }
136
147
 
148
+ // kind === "annotate"
149
+ return buildAnnotateResult(interactive, artifactPath, timeoutMs, pollIntervalMs, startTime);
150
+ }
151
+
152
+ async function pollAsk(
153
+ interactive: InteractiveAskData,
154
+ artifactPath: string,
155
+ timeoutMs: number,
156
+ pollIntervalMs: number,
157
+ startTime: number,
158
+ ): Promise<PollResult> {
137
159
  const answers = extractAnswers(interactive);
138
160
  const remaining = interactive.questions
139
161
  .map((q) => q.id)
@@ -166,21 +188,52 @@ async function pollLoop(
166
188
  return pollLoop(artifactPath, timeoutMs, pollIntervalMs, startTime);
167
189
  }
168
190
 
169
- // ─── Helpers ───────────────────────────────────────────────────────────────────
191
+ async function buildAnnotateResult(
192
+ interactive: InteractiveAnnotateData,
193
+ artifactPath: string,
194
+ timeoutMs: number,
195
+ pollIntervalMs: number,
196
+ startTime: number,
197
+ ): Promise<PollResult> {
198
+ const { comments, verdict } = interactive;
199
+ const base = {
200
+ answers: {} as Record<string, AnswerValue>,
201
+ remaining: [] as string[],
202
+ comments,
203
+ verdict,
204
+ kind: "annotate" as const,
205
+ };
170
206
 
171
- function isInteractiveData(v: unknown): v is InteractiveData {
172
- if (v === null || typeof v !== "object" || Array.isArray(v)) return false;
173
- const raw = v as Record<string, unknown>;
174
- return (
175
- (raw["status"] === "open" ||
176
- raw["status"] === "complete" ||
177
- raw["status"] === "expired" ||
178
- raw["status"] === "cancelled") &&
179
- Array.isArray(raw["questions"])
180
- );
207
+ switch (interactive.status) {
208
+ case "complete":
209
+ return { status: "complete", ...base };
210
+ case "expired":
211
+ return { status: "expired", ...base };
212
+ case "cancelled":
213
+ return { status: "cancelled", ...base };
214
+ case "open":
215
+ break;
216
+ }
217
+
218
+ // Check timeout
219
+ if (Date.now() - startTime >= timeoutMs) {
220
+ return { status: "incomplete", ...base };
221
+ }
222
+
223
+ // Sleep then recurse
224
+ await sleep(pollIntervalMs);
225
+
226
+ // Check timeout again after sleep (in case sleep took longer)
227
+ if (Date.now() - startTime >= timeoutMs) {
228
+ return { status: "incomplete", ...base };
229
+ }
230
+
231
+ return pollLoop(artifactPath, timeoutMs, pollIntervalMs, startTime);
181
232
  }
182
233
 
183
- function extractAnswers(interactive: InteractiveData): Record<string, AnswerValue> {
234
+ // ─── Helpers ───────────────────────────────────────────────────────────────────
235
+
236
+ function extractAnswers(interactive: InteractiveAskData): Record<string, AnswerValue> {
184
237
  const answers: Record<string, AnswerValue> = {};
185
238
  for (const [id, entry] of Object.entries(interactive.answers)) {
186
239
  answers[id] = entry.value;