@konstantdotcloud/boombox 0.2.0 → 0.3.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.
package/dist/index.js CHANGED
@@ -28044,47 +28044,55 @@ function registerAskGary(server, gateway) {
28044
28044
  const streamedArtifacts = /* @__PURE__ */ new Map();
28045
28045
  await emit(`asking Gary: "${input.question.slice(0, 80)}${input.question.length > 80 ? "\u2026" : ""}"${input.thread ? ` (thread=${input.thread})` : ""}${input.thread_id ? ` (thread_id=${input.thread_id})` : ""}${input.artifact_ref ? ` (artifact_ref=${input.artifact_ref})` : ""}${input.fresh ? " [fresh]" : ""}`);
28046
28046
  try {
28047
- await gateway.stream("/gateway/homebase/ask", body, async (evt) => {
28048
- const evType = typeof evt.event === "string" ? evt.event : "";
28049
- if (evType === "started") {
28050
- await emit(`session ${evt.session_id ?? "?"} thread ${evt.thread_id ?? "?"} \u2014 running\u2026`);
28051
- } else if (evType === "tool_start") {
28052
- await emit(`tool start: ${typeof evt.tool === "string" ? evt.tool : "unknown"}`);
28053
- } else if (evType === "tool_end") {
28054
- const toolName = typeof evt.tool === "string" ? evt.tool : "unknown";
28055
- const dur = typeof evt.duration_ms === "number" ? `${evt.duration_ms}ms` : "?ms";
28056
- const isError = evt.is_error === true;
28057
- await emit(`tool ${isError ? "failed" : "done"}: ${toolName} (${dur})`);
28058
- } else if (evType === "artifact_ready") {
28059
- const artifactId = typeof evt.artifact_id === "string" ? evt.artifact_id : "";
28060
- const viewUrl = typeof evt.view_url === "string" ? evt.view_url : "";
28061
- if (artifactId && viewUrl) {
28062
- streamedArtifacts.set(artifactId, {
28063
- artifact_id: artifactId,
28064
- view_url: viewUrl,
28065
- title: typeof evt.title === "string" ? evt.title : void 0,
28066
- type: typeof evt.type === "string" ? evt.type : void 0,
28067
- tool: typeof evt.tool === "string" ? evt.tool : void 0
28068
- });
28069
- await emit(`artifact ready: ${viewUrl}`);
28070
- }
28071
- } else if (evType === "text_delta") {
28072
- const delta = typeof evt.delta === "string" ? evt.delta : "";
28073
- const before = streamedAnswer.length;
28074
- streamedAnswer += delta;
28075
- const crossedBoundary = Math.floor(before / 200) !== Math.floor(streamedAnswer.length / 200);
28076
- if (crossedBoundary) {
28077
- await emit(`writing answer\u2026 (${streamedAnswer.length} chars)`);
28078
- }
28079
- } else if (evType === "message_end_text") {
28080
- const text2 = typeof evt.text === "string" ? evt.text : "";
28081
- if (text2.length > streamedAnswer.length) streamedAnswer = text2;
28082
- } else if (evType === "error") {
28083
- await emit(`error: ${typeof evt.message === "string" ? evt.message : "unknown"}`);
28084
- } else if (evType === "final") {
28085
- final = evt;
28086
- }
28087
- });
28047
+ if (input.depth === "quick") {
28048
+ await emit("using quick ask-fast path");
28049
+ final = await gateway.call("/gateway/homebase/ask-fast", body);
28050
+ if (typeof final.answer === "string") streamedAnswer = final.answer;
28051
+ } else {
28052
+ await gateway.stream("/gateway/homebase/ask", body, async (evt) => {
28053
+ const evType = typeof evt.event === "string" ? evt.event : "";
28054
+ if (evType === "started") {
28055
+ await emit(`session ${evt.session_id ?? "?"} thread ${evt.thread_id ?? "?"} \u2014 running\u2026`);
28056
+ } else if (evType === "tool_start") {
28057
+ await emit(`tool start: ${typeof evt.tool === "string" ? evt.tool : "unknown"}`);
28058
+ } else if (evType === "tool_end") {
28059
+ const toolName = typeof evt.tool === "string" ? evt.tool : "unknown";
28060
+ const dur = typeof evt.duration_ms === "number" ? `${evt.duration_ms}ms` : "?ms";
28061
+ const isError = evt.is_error === true;
28062
+ await emit(`tool ${isError ? "failed" : "done"}: ${toolName} (${dur})`);
28063
+ } else if (evType === "keepalive") {
28064
+ await emit("still working...");
28065
+ } else if (evType === "artifact_ready") {
28066
+ const artifactId = typeof evt.artifact_id === "string" ? evt.artifact_id : "";
28067
+ const viewUrl = typeof evt.view_url === "string" ? evt.view_url : "";
28068
+ if (artifactId && viewUrl) {
28069
+ streamedArtifacts.set(artifactId, {
28070
+ artifact_id: artifactId,
28071
+ view_url: viewUrl,
28072
+ title: typeof evt.title === "string" ? evt.title : void 0,
28073
+ type: typeof evt.type === "string" ? evt.type : void 0,
28074
+ tool: typeof evt.tool === "string" ? evt.tool : void 0
28075
+ });
28076
+ await emit(`artifact ready: ${viewUrl}`);
28077
+ }
28078
+ } else if (evType === "text_delta") {
28079
+ const delta = typeof evt.delta === "string" ? evt.delta : "";
28080
+ const before = streamedAnswer.length;
28081
+ streamedAnswer += delta;
28082
+ const crossedBoundary = Math.floor(before / 200) !== Math.floor(streamedAnswer.length / 200);
28083
+ if (crossedBoundary) {
28084
+ await emit(`writing answer\u2026 (${streamedAnswer.length} chars)`);
28085
+ }
28086
+ } else if (evType === "message_end_text") {
28087
+ const text2 = typeof evt.text === "string" ? evt.text : "";
28088
+ if (text2.length > streamedAnswer.length) streamedAnswer = text2;
28089
+ } else if (evType === "error") {
28090
+ await emit(`error: ${typeof evt.message === "string" ? evt.message : "unknown"}`);
28091
+ } else if (evType === "final") {
28092
+ final = evt;
28093
+ }
28094
+ });
28095
+ }
28088
28096
  } catch (error2) {
28089
28097
  const text2 = renderErrorText(error2);
28090
28098
  process.stderr.write(`[ask_gary] ${text2.split("\n")[0]}
@@ -28219,8 +28227,9 @@ var init_ask_gary = __esm({
28219
28227
  thread_id: external_exports2.string().optional().describe("Concrete thread_id to use instead of resolving a named alias. Useful when continuing an artifact discussion thread."),
28220
28228
  artifact_ref: external_exports2.string().optional().describe("Artifact id to pin as Gary context. Gary loads the artifact body and tags the resulting thread with artifact_ref."),
28221
28229
  fresh: external_exports2.boolean().optional().describe("If true, rotate the thread alias to a brand-new conversation, discarding prior history under that alias."),
28222
- model_tier: external_exports2.enum(["auto", "fast", "nano", "mini", "reason", "mid", "explore", "premium", "opus"]).optional().describe("Model tier override. Default 'explore' (Sonnet 4.6). Use 'premium' for Opus 4.8 at max thinking, 'opus' for Opus 4.8 standard."),
28223
- thinking_level: external_exports2.enum(["off", "minimal", "low", "medium", "high", "xhigh"]).optional().describe("Reasoning effort override. Default 'medium'."),
28230
+ model_tier: external_exports2.enum(["auto", "fast", "nano", "mini", "reason", "mid", "explore", "premium", "opus"]).optional().describe("Model tier override. Default 'fast' (Sonnet 4.6 with low reasoning). Use 'premium' for Opus 4.8 at max thinking, 'opus' for Opus 4.8 standard."),
28231
+ thinking_level: external_exports2.enum(["off", "minimal", "low", "medium", "high", "xhigh"]).optional().describe("Reasoning effort override. Default 'low'."),
28232
+ depth: external_exports2.enum(["standard", "quick"]).optional().describe("'quick' routes to the direct map->retrieve->single-synthesis fast path (~5-10s, no agent loop); default 'standard' runs the full V2 chat-session turn."),
28224
28233
  intent: external_exports2.enum(["cassette_author"]).optional().describe("Set to cassette_author to load Gary cassette-authoring mode prompt/doctrine.")
28225
28234
  };
28226
28235
  }
@@ -29392,45 +29401,57 @@ function registerCassetteAuthoringVerbs(server, gateway) {
29392
29401
  "cassette_draft",
29393
29402
  "Start Gary cassette-authoring mode: create a cassette_drafts record, emit YAML, validate it, and ask one next question.",
29394
29403
  draftSchema,
29395
- async (input) => renderGatewayResult("cassette_draft", await gateway.call("/gateway/cassette/draft", { ...input, intent: "cassette_author" }))
29404
+ async (input) => callAndRender("cassette_draft", () => gateway.call("/gateway/cassette/draft", { ...input, intent: "cassette_author" }))
29396
29405
  );
29397
29406
  server.tool(
29398
29407
  "cassette_refine",
29399
29408
  "Refine an owned cassette draft with answers or a YAML patch. Does not mutate published cassettes.",
29400
29409
  refineSchema,
29401
- async (input) => renderGatewayResult("cassette_refine", await gateway.call("/gateway/cassette/refine", input))
29410
+ async (input) => {
29411
+ const refineInput = { ...input, yaml_patch: input.yaml_patch ?? input.yaml };
29412
+ delete refineInput.yaml;
29413
+ return callAndRender("cassette_refine", () => gateway.call("/gateway/cassette/refine", refineInput));
29414
+ }
29402
29415
  );
29403
29416
  server.tool(
29404
29417
  "cassette_dry_run",
29405
29418
  "Dry-run a cassette draft against sample inputs. Returns a DryRunReport and never writes WorkflowRunRecord.",
29406
29419
  dryRunSchema,
29407
- async (input) => renderGatewayResult("cassette_dry_run", await gateway.call("/gateway/cassette/dry-run", input))
29420
+ async (input) => callAndRender("cassette_dry_run", () => gateway.call("/gateway/cassette/dry-run", input))
29408
29421
  );
29409
29422
  server.tool(
29410
29423
  "cassette_publish",
29411
- 'Publish a dry-run-approved cassette draft. Gateway must enforce session.canDo("cassette.publish").',
29424
+ 'Publish a dry-run-approved cassette draft. Gateway must enforce session.canDo("cassette.publish"); name defaults to the draft spec id.',
29412
29425
  publishSchema,
29413
- async (input) => renderGatewayResult("cassette_publish", await gateway.call("/gateway/cassette/publish", input))
29426
+ async (input) => callAndRender("cassette_publish", () => gateway.call("/gateway/cassette/publish", input))
29414
29427
  );
29415
29428
  server.tool(
29416
29429
  "cassette_get",
29417
29430
  "Get a cassette draft or published cassette as YAML plus describe payload.",
29418
29431
  getSchema,
29419
- async (input) => renderGatewayResult("cassette_get", await gateway.call("/gateway/cassette/get", input))
29432
+ async (input) => callAndRender("cassette_get", () => gateway.call("/gateway/cassette/get", input))
29420
29433
  );
29421
29434
  server.tool(
29422
29435
  "cassette_list_drafts",
29423
29436
  "List cassette drafts visible to the caller.",
29424
29437
  listDraftsSchema,
29425
- async (input) => renderGatewayResult("cassette_list_drafts", await gateway.call("/gateway/cassette/list-drafts", input))
29438
+ async (input) => callAndRender("cassette_list_drafts", () => gateway.call("/gateway/cassette/list-drafts", input))
29426
29439
  );
29427
29440
  server.tool(
29428
29441
  "cassette_list_capabilities",
29429
29442
  "List tenant capabilities Gary may use while authoring. Call before inventing or refining steps.",
29430
29443
  listCapabilitiesSchema,
29431
- async (input) => renderGatewayResult("cassette_list_capabilities", await gateway.call("/gateway/cassette/list-capabilities", input))
29444
+ async (input) => callAndRender("cassette_list_capabilities", () => gateway.call("/gateway/cassette/list-capabilities", input))
29432
29445
  );
29433
29446
  }
29447
+ async function callAndRender(verb, call) {
29448
+ try {
29449
+ return renderGatewayResult(verb, await call());
29450
+ } catch (error2) {
29451
+ if (error2 instanceof GatewayError) return renderGatewayError(verb, error2);
29452
+ throw error2;
29453
+ }
29454
+ }
29434
29455
  function renderGatewayResult(verb, result) {
29435
29456
  const record2 = isRecord(result) ? result : { value: result };
29436
29457
  if (record2.ok === false || typeof record2.code === "string" && record2.ok !== true) {
@@ -29446,21 +29467,93 @@ function renderGatewayResult(verb, result) {
29446
29467
  function renderText2(verb, record2) {
29447
29468
  if (typeof record2.markdown === "string") return record2.markdown;
29448
29469
  if (typeof record2.text === "string") return record2.text;
29470
+ if (verb === "cassette_dry_run" && isRecord(record2.report)) return renderDryRunReport(record2.report);
29449
29471
  if (typeof record2.yaml === "string") {
29450
29472
  const title = typeof record2.draft_id === "string" ? `${verb}: ${record2.draft_id}` : verb;
29451
- const next = typeof record2.next_question === "string" ? `
29452
-
29453
- Next question: ${record2.next_question}` : "";
29454
29473
  return `# ${title}
29455
29474
 
29456
29475
  \`\`\`yaml
29457
29476
  ${record2.yaml}
29458
- \`\`\`${next}`;
29477
+ \`\`\`${renderOpenQuestions(record2)}`;
29459
29478
  }
29460
29479
  return `# ${verb}
29461
29480
 
29462
29481
  ${JSON.stringify(record2, null, 2)}`;
29463
29482
  }
29483
+ function renderGatewayError(verb, error2) {
29484
+ const parsed = parseGatewayErrorBody(error2.body);
29485
+ const diagnostics = parsed ? extractDiagnostics(parsed) : [];
29486
+ const text = diagnostics.length > 0 ? `# ${verb} failed
29487
+
29488
+ ${diagnostics.map(formatDiagnostic).join("\n")}` : `${verb} failed: ${error2.message}`;
29489
+ return {
29490
+ content: [{ type: "text", text }],
29491
+ ...parsed ? { structuredContent: parsed } : {},
29492
+ isError: true
29493
+ };
29494
+ }
29495
+ function renderOpenQuestions(record2) {
29496
+ const questions = Array.isArray(record2.open_questions) ? record2.open_questions.filter((question) => typeof question === "string" && question.length > 0) : [];
29497
+ if (questions.length === 0) return "";
29498
+ return `
29499
+
29500
+ Next questions:
29501
+ ${questions.map((question) => `- ${question}`).join("\n")}`;
29502
+ }
29503
+ function renderDryRunReport(report) {
29504
+ const lines = [`Dry-run verdict: ${typeof report.verdict === "string" ? report.verdict : "unknown"}`];
29505
+ if (Array.isArray(report.blockers) && report.blockers.length > 0) {
29506
+ lines.push("", "Blockers:");
29507
+ for (const blocker of report.blockers) {
29508
+ if (isRecord(blocker)) lines.push(`- ${formatBlocker(blocker)}`);
29509
+ }
29510
+ }
29511
+ if (isRecord(report.artifact_preview)) {
29512
+ lines.push("", "Artifact preview:", "```text", formatArtifactPreview(report.artifact_preview), "```");
29513
+ }
29514
+ return lines.join("\n");
29515
+ }
29516
+ function formatBlocker(blocker) {
29517
+ const detail = typeof blocker.detail === "string" ? blocker.detail : "blocked";
29518
+ const parts = [typeof blocker.kind === "string" ? `${blocker.kind}: ${detail}` : detail];
29519
+ for (const key of ["capability_id", "scope", "path"]) {
29520
+ if (typeof blocker[key] === "string") parts.push(`${key}=${blocker[key]}`);
29521
+ }
29522
+ return parts.join(" ");
29523
+ }
29524
+ function formatArtifactPreview(preview) {
29525
+ const title = typeof preview.title === "string" ? preview.title : "Untitled artifact";
29526
+ const template = typeof preview.template === "string" ? preview.template : "unknown";
29527
+ const wordCount = typeof preview.word_count === "number" ? preview.word_count : 0;
29528
+ const content = typeof preview.content === "string" ? preview.content.slice(0, 600) : "";
29529
+ return [`${title}`, `template: ${template}`, `word_count: ${wordCount}`, "", content].join("\n");
29530
+ }
29531
+ function parseGatewayErrorBody(body) {
29532
+ try {
29533
+ const parsed = JSON.parse(body);
29534
+ return isRecord(parsed) ? parsed : null;
29535
+ } catch {
29536
+ return null;
29537
+ }
29538
+ }
29539
+ function extractDiagnostics(record2) {
29540
+ const details = isRecord(record2.details) ? record2.details : null;
29541
+ const diagnostics = details && Array.isArray(details.diagnostics) ? details.diagnostics : [];
29542
+ return diagnostics.flatMap((diagnostic) => {
29543
+ if (!isRecord(diagnostic) || typeof diagnostic.message !== "string") return [];
29544
+ return [{
29545
+ message: diagnostic.message,
29546
+ ...typeof diagnostic.line === "number" ? { line: diagnostic.line } : {},
29547
+ ...typeof diagnostic.column === "number" ? { column: diagnostic.column } : {},
29548
+ ...typeof diagnostic.path === "string" ? { path: diagnostic.path } : {}
29549
+ }];
29550
+ });
29551
+ }
29552
+ function formatDiagnostic(diagnostic) {
29553
+ const location = typeof diagnostic.line === "number" ? `L${diagnostic.line}${typeof diagnostic.column === "number" ? `:${diagnostic.column}` : ""} ` : "";
29554
+ const path4 = diagnostic.path ? ` (${diagnostic.path})` : "";
29555
+ return `- ${location}${diagnostic.message}${path4}`;
29556
+ }
29464
29557
  function isRecord(value) {
29465
29558
  return value !== null && typeof value === "object" && !Array.isArray(value);
29466
29559
  }
@@ -29470,6 +29563,7 @@ var init_cassette_authoring = __esm({
29470
29563
  "use strict";
29471
29564
  init_esm_shims();
29472
29565
  init_zod();
29566
+ init_gateway_client();
29473
29567
  recordSchema = external_exports2.record(external_exports2.unknown());
29474
29568
  draftSchema = {
29475
29569
  description: external_exports2.string().min(1).describe("Natural-language description of the cassette to author."),
@@ -29481,7 +29575,8 @@ var init_cassette_authoring = __esm({
29481
29575
  refineSchema = {
29482
29576
  draft_id: external_exports2.string().min(1),
29483
29577
  answers: recordSchema.optional(),
29484
- yaml_patch: external_exports2.string().min(1).optional()
29578
+ yaml_patch: external_exports2.string().min(1).optional().describe("Full YAML replacement for the draft, not a partial patch."),
29579
+ yaml: external_exports2.string().min(1).optional().describe("Full YAML replacement for the draft (alias of yaml_patch).")
29485
29580
  };
29486
29581
  dryRunSchema = {
29487
29582
  draft_id: external_exports2.string().min(1),
@@ -29489,7 +29584,7 @@ var init_cassette_authoring = __esm({
29489
29584
  };
29490
29585
  publishSchema = {
29491
29586
  draft_id: external_exports2.string().min(1),
29492
- name: external_exports2.string().min(1),
29587
+ name: external_exports2.string().min(1).optional(),
29493
29588
  version: external_exports2.number().int().min(1).optional()
29494
29589
  };
29495
29590
  getSchema = {
@@ -34574,7 +34669,7 @@ init_esm_shims();
34574
34669
  init_esm_shims();
34575
34670
 
34576
34671
  // package.json
34577
- var version = "0.2.0";
34672
+ var version = "0.3.0";
34578
34673
 
34579
34674
  // src/lib/version.ts
34580
34675
  var BOOMBOX_VERSION = version;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@konstantdotcloud/boombox",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Local Boombox runtime for Konstant cassettes — CLI, stdio MCP server, and local Hono proxy.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
package/ui/README.md CHANGED
@@ -50,12 +50,14 @@ ui/
50
50
  ## Scripts
51
51
 
52
52
  ```bash
53
- pnpm install
54
- pnpm dev # vite dev server, http://localhost:5173
55
- pnpm build # tsc --noEmit && vite build → dist/
56
- pnpm test # vitest run
53
+ npm install
54
+ npm run dev # vite dev server, http://localhost:5173
55
+ npm run build # tsc --noEmit && vite build → dist/
56
+ npm test # vitest run
57
57
  ```
58
58
 
59
+ (The parent package builds this UI via `build:ui` = `cd ui && npm install && npm run build`.)
60
+
59
61
  ## Theme system
60
62
 
61
63
  `<ThemeProvider>` writes `data-theme` to `<html>` and persists to
@@ -111,10 +113,10 @@ that follow-up waves need to wire:
111
113
  already injects `Authorization: Bearer <api_key>` for `/api/*`. The UI
112
114
  should hit the proxy directly with no token, since the proxy adds it.
113
115
  `client.ts` accepts a null token for that path.
114
- 3. **`boombox serve` static-serve integration.** Currently `pnpm build`
115
- produces `ui/dist/`. The CLI's `boombox serve --http` should mount that
116
- directory (or fetch the latest UI bundle from the gateway). Tracked as a
117
- TODO in `src/cli/serve.ts`.
116
+ 3. **`boombox serve` static-serve integration.** DONE — `boombox serve --http`
117
+ mounts `ui/dist/` from the local Hono proxy, serving the SPA at `/` and returning
118
+ `503 ui_not_built` when the bundle is absent. Run `npm run build` here (or the
119
+ parent's `build:ui`) to produce `ui/dist/`.
118
120
  4. **Cloud API endpoint shapes.** `api/types.ts` mirrors the cassette
119
121
  substrate. Once Wave 5b-ApprovalGate endpoints stabilize, regenerate the
120
122
  types so this package stays a faithful client (instead of duplicating).