@stackbone/cli 0.9.0 → 0.9.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/CHANGELOG.md CHANGED
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.9.1] - 2026-11-10
11
+
12
+ ### Changed
13
+
14
+ - **The thinking sound waits for a long reply.** The voice worker, the one
15
+ `stackbone dev` runs too, starts the thinking sound at once for a box tool
16
+ call, and for any other wait only once it passes the
17
+ `backgroundAudio.thinkingDelayMs` voice setting (default 2000 ms). Before,
18
+ it played on every reply, since every reply waits for the knowledge search
19
+ and the model's first token. Studio's Voice tab gets a **Thinking delay
20
+ (ms)** field. Deploy the worker on LiveKit Cloud before the boxes: an older
21
+ worker refuses a call whose settings carry the delay.
22
+ - **The box's missing-credentials messages name the Voice provider screen.**
23
+ A call or a voice status check on a box with no readable LiveKit
24
+ credentials now sends you to Studio's **Voice provider** screen, where the
25
+ credentials moved (STA3-89).
26
+
10
27
  ## [0.9.0] - 2026-11-10
11
28
 
12
29
  ### Added
package/main.js CHANGED
@@ -3673,13 +3673,21 @@ var init_studio_voice = __esmMin((() => {
3673
3673
  ambientSound: z.enum(VOICE_AMBIENT_SOUNDS),
3674
3674
  ambientVolume: volumeSchema,
3675
3675
  thinkingSound: z.enum(VOICE_THINKING_SOUNDS),
3676
- thinkingVolume: volumeSchema
3676
+ thinkingVolume: volumeSchema,
3677
+ /**
3678
+ * How long a reply's wait runs before the thinking sound starts, when no box tool
3679
+ * call is open (a tool call starts it at once). Every turn waits for the knowledge
3680
+ * search and the model's first token, about 1.4 s at the median on a voice box
3681
+ * (2026-11-09), so a delay under that plays the sound on nearly every reply.
3682
+ */
3683
+ thinkingDelayMs: millisecondsSchema
3677
3684
  });
3678
3685
  VOICE_BACKGROUND_AUDIO_DEFAULTS = Object.freeze({
3679
3686
  ambientSound: "office-ambience",
3680
3687
  ambientVolume: .7,
3681
3688
  thinkingSound: "keyboard-typing",
3682
- thinkingVolume: .6
3689
+ thinkingVolume: .6,
3690
+ thinkingDelayMs: 2e3
3683
3691
  });
3684
3692
  effectiveVoiceSettingsObjectSchema = z.strictObject({
3685
3693
  /** BCP-47-ish tag the STT is hinted with and the TTS speaks (`es`, `en-US`), or `multi`. */
@@ -3749,7 +3757,7 @@ var init_studio_voice = __esmMin((() => {
3749
3757
  recording: true,
3750
3758
  rag: true,
3751
3759
  refusalPhrase: true
3752
- }).extend({ backgroundAudio: backgroundAudioSchema.default(() => ({ ...VOICE_BACKGROUND_AUDIO_DEFAULTS })) });
3760
+ }).extend({ backgroundAudio: backgroundAudioSchema.extend({ thinkingDelayMs: millisecondsSchema.default(VOICE_BACKGROUND_AUDIO_DEFAULTS.thinkingDelayMs) }).default(() => ({ ...VOICE_BACKGROUND_AUDIO_DEFAULTS })) });
3753
3761
  callVoiceSettingsSchema = callVoiceSettingsObjectSchema.superRefine(checkCrossFieldRules);
3754
3762
  toCallVoiceSettings = ({ recording: _recording, rag: _rag, refusalPhrase: _refusalPhrase, ...call }) => ({
3755
3763
  ...call,
@@ -3779,6 +3787,7 @@ var init_studio_voice = __esmMin((() => {
3779
3787
  "backgroundAudio.ambientVolume",
3780
3788
  "backgroundAudio.thinkingSound",
3781
3789
  "backgroundAudio.thinkingVolume",
3790
+ "backgroundAudio.thinkingDelayMs",
3782
3791
  "recording",
3783
3792
  "rag.collection",
3784
3793
  "rag.minScore",
@@ -22665,7 +22674,7 @@ var init_role = __esmMin((() => {
22665
22674
  var STACKBONE_CLI_BUILD_VERSION, EMULATOR_CAPABILITY_ROUTES, EMULATOR_CAPABILITIES, buildContractResponse;
22666
22675
  var init_contract = __esmMin((() => {
22667
22676
  init_src$26();
22668
- STACKBONE_CLI_BUILD_VERSION = "0.9.0";
22677
+ STACKBONE_CLI_BUILD_VERSION = "0.9.1";
22669
22678
  EMULATOR_CAPABILITY_ROUTES = {
22670
22679
  "database.postgres_direct": "/api/db",
22671
22680
  "rag.basic": "/api/rag",
@@ -91331,9 +91340,9 @@ var lookUpLiveKitCredentials = async (opts) => {
91331
91340
  credentials = await readLiveKitCredentials(opts);
91332
91341
  } catch (err) {
91333
91342
  if (!(err instanceof SecretUndecryptableError)) throw err;
91334
- return voiceNotConfigured("This box cannot read the saved LiveKit credentials: they were saved with another key. Open the Voice settings screen and save them again.");
91343
+ return voiceNotConfigured("This box cannot read the saved LiveKit credentials: they were saved with another key. Open the Voice provider screen and save them again.");
91335
91344
  }
91336
- if (!credentials) return voiceNotConfigured("This box has no LiveKit credentials. Save the URL, API key and API secret on the Voice settings screen.");
91345
+ if (!credentials) return voiceNotConfigured("This box has no LiveKit credentials. Save the URL, API key and API secret on the Voice provider screen.");
91337
91346
  return { credentials };
91338
91347
  };
91339
91348
  /**
@@ -104326,7 +104335,7 @@ var calls_workflow_ts_default = "import { z } from \"zod\";\nimport { streamDeep
104326
104335
  var plain_workflow_ts_default = "import { z } from \"zod\";\n\n/**\n * Workflow \"{{name}}\" — a durable pipeline.\n *\n * Each `\"use step\"` is a durable checkpoint: kill the runtime mid-run and it\n * resumes from the last completed step instead of replaying everything.\n */\n\n// THE contract — sibling exports next to the bare workflow function. The build\n// harvests these (by convention) into the manifest + a live validator. The\n// parameter type is DERIVED from `inputSchema` and the RETURN type from\n// `outputSchema` (both `z.infer`), so neither side can drift from the contract\n// the build harvests. Without the return annotation the schema is published to\n// Studio and the generated types and never compared to what the body returns\n// (PLA-1272).\nexport const inputSchema = z.object({\n message: z.string().describe(\"The input to process\"),\n});\n\nexport const outputSchema = z.object({\n message: z.string(),\n processed: z.boolean(),\n});\n\ntype {{Name}}Input = z.infer<typeof inputSchema>;\n\nexport async function {{export}}(input: {{Name}}Input): Promise<z.infer<typeof outputSchema>> {\n \"use workflow\";\n\n const result = await processMessage(input); // step 1 — durable checkpoint\n return result;\n}\n\nasync function processMessage(input: {{Name}}Input) {\n \"use step\";\n // Replace this with the deterministic work of your step (DB write, HTTP call,\n // an LLM call, …). Returning a value records it as the step's checkpoint.\n return { message: input.message, processed: true };\n}\n";
104327
104336
  //#endregion
104328
104337
  //#region src/services/scaffold/template-loader.ts
104329
- var SDK_TEMPLATE_VERSION = "0.10.0";
104338
+ var SDK_TEMPLATE_VERSION = "0.10.1";
104330
104339
  var PREFIX = "./templates/";
104331
104340
  var templates = new Map(Object.entries(/* @__PURE__ */ Object.assign({
104332
104341
  "./templates/deep-agents/index.ts.tmpl": index_ts_default,
@@ -106449,7 +106458,7 @@ var buildWorkspaceBundle = async (options) => {
106449
106458
  const artifactPath = resolve(outDir, CONFIG_SCHEMA_ARTIFACT_PATH);
106450
106459
  await mkdir(dirname(artifactPath), { recursive: true });
106451
106460
  await writeFile(artifactPath, JSON.stringify(configSchema), "utf8");
106452
- } else logger?.warn(`This workspace declares ${CONFIG_SCHEMA_SOURCE}, but no config JSON Schema was produced for the bundle. Studio's config editor will fall back to a raw JSON field and writes will not be validated.`);
106461
+ } else logger?.warn(`This workspace declares ${CONFIG_SCHEMA_SOURCE}, but no config JSON Schema was produced for the bundle. Studio shows no Dynamic config section (edit the values with \`stackbone config\`) and writes will not be validated.`);
106453
106462
  }
106454
106463
  const manifest = deriveWorkspaceManifest({
106455
106464
  workspace,
@@ -106552,7 +106561,7 @@ var extractConfigSchema = async (projectRoot, opts = {}) => {
106552
106561
  try {
106553
106562
  loaded = await loader(projectRoot);
106554
106563
  } catch (err) {
106555
- emit(`[config-schema] config schema extraction bundle failed; no config schema is available — the Config editor will fall back to a raw JSON field. Cause: ${errMessage$3(err)}`);
106564
+ emit(`[config-schema] config schema extraction bundle failed; no config schema is available — Studio shows no Dynamic config section; edit the values with \`stackbone config\`. Cause: ${errMessage$3(err)}`);
106556
106565
  return {
106557
106566
  schema: null,
106558
106567
  warnings
@@ -106568,7 +106577,7 @@ var extractConfigSchema = async (projectRoot, opts = {}) => {
106568
106577
  warnings
106569
106578
  };
106570
106579
  } catch (err) {
106571
- emit(`[config-schema] config.schema.ts could not be converted to JSON Schema — the Config editor will fall back to a raw JSON field. Cause: ${errMessage$3(err)}`);
106580
+ emit(`[config-schema] config.schema.ts could not be converted to JSON Schema — Studio shows no Dynamic config section; edit the values with \`stackbone config\`. Cause: ${errMessage$3(err)}`);
106572
106581
  return {
106573
106582
  schema: null,
106574
106583
  warnings
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackbone/cli",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "bin": {
Binary file
@@ -995,13 +995,21 @@ var backgroundAudioSchema = z8.strictObject({
995
995
  ambientSound: z8.enum(VOICE_AMBIENT_SOUNDS),
996
996
  ambientVolume: volumeSchema,
997
997
  thinkingSound: z8.enum(VOICE_THINKING_SOUNDS),
998
- thinkingVolume: volumeSchema
998
+ thinkingVolume: volumeSchema,
999
+ /**
1000
+ * How long a reply's wait runs before the thinking sound starts, when no box tool
1001
+ * call is open (a tool call starts it at once). Every turn waits for the knowledge
1002
+ * search and the model's first token, about 1.4 s at the median on a voice box
1003
+ * (2026-11-09), so a delay under that plays the sound on nearly every reply.
1004
+ */
1005
+ thinkingDelayMs: millisecondsSchema
999
1006
  });
1000
1007
  var VOICE_BACKGROUND_AUDIO_DEFAULTS = Object.freeze({
1001
1008
  ambientSound: "office-ambience",
1002
1009
  ambientVolume: 0.7,
1003
1010
  thinkingSound: "keyboard-typing",
1004
- thinkingVolume: 0.6
1011
+ thinkingVolume: 0.6,
1012
+ thinkingDelayMs: 2e3
1005
1013
  });
1006
1014
  var effectiveVoiceSettingsObjectSchema = z8.strictObject({
1007
1015
  /** BCP-47-ish tag the STT is hinted with and the TTS speaks (`es`, `en-US`), or `multi`. */
@@ -1078,7 +1086,12 @@ var callVoiceSettingsObjectSchema = effectiveVoiceSettingsObjectSchema.omit({
1078
1086
  rag: true,
1079
1087
  refusalPhrase: true
1080
1088
  }).extend({
1081
- backgroundAudio: backgroundAudioSchema.default(() => ({ ...VOICE_BACKGROUND_AUDIO_DEFAULTS }))
1089
+ // A box older than `thinkingDelayMs` dispatches the group without it.
1090
+ backgroundAudio: backgroundAudioSchema.extend({
1091
+ thinkingDelayMs: millisecondsSchema.default(
1092
+ VOICE_BACKGROUND_AUDIO_DEFAULTS.thinkingDelayMs
1093
+ )
1094
+ }).default(() => ({ ...VOICE_BACKGROUND_AUDIO_DEFAULTS }))
1082
1095
  });
1083
1096
  var callVoiceSettingsSchema = callVoiceSettingsObjectSchema.superRefine(checkCrossFieldRules);
1084
1097
  var VOICE_SETTINGS_LEAF_PATHS = [
@@ -1105,6 +1118,7 @@ var VOICE_SETTINGS_LEAF_PATHS = [
1105
1118
  "backgroundAudio.ambientVolume",
1106
1119
  "backgroundAudio.thinkingSound",
1107
1120
  "backgroundAudio.thinkingVolume",
1121
+ "backgroundAudio.thinkingDelayMs",
1108
1122
  "recording",
1109
1123
  "rag.collection",
1110
1124
  "rag.minScore",
@@ -2220,13 +2234,14 @@ var CALL_CLOSING_PHRASE = Object.freeze({
2220
2234
  });
2221
2235
  var MAX_FAILED_TURNS_IN_A_ROW = 3;
2222
2236
  var inCallLanguage = (phrase, language) => language.split("-")[0]?.toLowerCase() === "es" ? phrase.es : phrase.en;
2223
- var thinkingCue = () => {
2237
+ var thinkingCue = (delayMs) => {
2224
2238
  let cue;
2225
2239
  let open = 0;
2226
- let thinking = false;
2240
+ let waitedLong = false;
2241
+ let wait;
2227
2242
  let playing = false;
2228
2243
  const sync = () => {
2229
- const play = thinking || open > 0;
2244
+ const play = waitedLong || open > 0;
2230
2245
  if (play === playing || !cue) return;
2231
2246
  playing = play;
2232
2247
  if (play) cue.start();
@@ -2239,13 +2254,32 @@ var thinkingCue = () => {
2239
2254
  sync();
2240
2255
  },
2241
2256
  onAgentState: (state) => {
2242
- thinking = state === "thinking";
2243
- sync();
2257
+ if (state !== "thinking") {
2258
+ clearTimeout(wait);
2259
+ wait = void 0;
2260
+ waitedLong = false;
2261
+ sync();
2262
+ return;
2263
+ }
2264
+ if (wait !== void 0 || waitedLong) return;
2265
+ wait = setTimeout(() => {
2266
+ wait = void 0;
2267
+ waitedLong = true;
2268
+ sync();
2269
+ }, delayMs);
2244
2270
  },
2245
2271
  onToolCall: (phase) => {
2246
2272
  if (phase === "start") open += 1;
2247
2273
  else if (open > 0) open -= 1;
2248
2274
  sync();
2275
+ },
2276
+ /** The call is ending: a pending delay must not start the sound in a closed room. */
2277
+ close: () => {
2278
+ clearTimeout(wait);
2279
+ wait = void 0;
2280
+ waitedLong = false;
2281
+ open = 0;
2282
+ sync();
2249
2283
  }
2250
2284
  };
2251
2285
  };
@@ -2290,7 +2324,7 @@ var runVoiceJob = async (ctx, deps) => {
2290
2324
  deps
2291
2325
  );
2292
2326
  const settings = dispatch.voiceSettings;
2293
- const cue = thinkingCue();
2327
+ const cue = thinkingCue(settings.backgroundAudio.thinkingDelayMs);
2294
2328
  const fallback = inCallLanguage(TURN_FALLBACK_PHRASE, settings.language);
2295
2329
  let failedInARow = 0;
2296
2330
  let ending = false;
@@ -2391,6 +2425,7 @@ var runVoiceJob = async (ctx, deps) => {
2391
2425
  const finishCall = (outcome = "disconnected") => {
2392
2426
  if (completion) return completion;
2393
2427
  ending = true;
2428
+ cue.close();
2394
2429
  completion = Promise.resolve().then(async () => {
2395
2430
  await hangUp.pendingResume();
2396
2431
  await session.close();