@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/README.md +89 -74
- package/dist/boombox.js +1095 -146
- package/dist/index.js +153 -58
- package/package.json +1 -1
- package/ui/README.md +10 -8
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
|
-
|
|
28048
|
-
|
|
28049
|
-
|
|
28050
|
-
|
|
28051
|
-
|
|
28052
|
-
|
|
28053
|
-
|
|
28054
|
-
|
|
28055
|
-
|
|
28056
|
-
|
|
28057
|
-
|
|
28058
|
-
|
|
28059
|
-
|
|
28060
|
-
|
|
28061
|
-
|
|
28062
|
-
|
|
28063
|
-
|
|
28064
|
-
|
|
28065
|
-
|
|
28066
|
-
|
|
28067
|
-
|
|
28068
|
-
|
|
28069
|
-
|
|
28070
|
-
|
|
28071
|
-
|
|
28072
|
-
|
|
28073
|
-
|
|
28074
|
-
|
|
28075
|
-
|
|
28076
|
-
|
|
28077
|
-
|
|
28078
|
-
}
|
|
28079
|
-
|
|
28080
|
-
|
|
28081
|
-
|
|
28082
|
-
|
|
28083
|
-
|
|
28084
|
-
|
|
28085
|
-
|
|
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 '
|
|
28223
|
-
thinking_level: external_exports2.enum(["off", "minimal", "low", "medium", "high", "xhigh"]).optional().describe("Reasoning effort override. Default '
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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
|
-
\`\`\`${
|
|
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.
|
|
34672
|
+
var version = "0.3.0";
|
|
34578
34673
|
|
|
34579
34674
|
// src/lib/version.ts
|
|
34580
34675
|
var BOOMBOX_VERSION = version;
|
package/package.json
CHANGED
package/ui/README.md
CHANGED
|
@@ -50,12 +50,14 @@ ui/
|
|
|
50
50
|
## Scripts
|
|
51
51
|
|
|
52
52
|
```bash
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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.**
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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).
|