@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 +17 -0
- package/main.js +19 -10
- package/package.json +1 -1
- package/stackbone-cli-0.9.1.tgz +0 -0
- package/voice-worker/main.js +44 -9
- package/voice-worker/main.js.map +2 -2
- package/stackbone-cli-0.9.0.tgz +0 -0
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.
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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 —
|
|
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 —
|
|
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
|
Binary file
|
package/voice-worker/main.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
2240
|
+
let waitedLong = false;
|
|
2241
|
+
let wait;
|
|
2227
2242
|
let playing = false;
|
|
2228
2243
|
const sync = () => {
|
|
2229
|
-
const play =
|
|
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
|
-
|
|
2243
|
-
|
|
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();
|