@stackbone/cli 0.6.3 → 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.
@@ -0,0 +1,34 @@
1
+ -- 0096_voice_settings.sql — the voice settings an operator changed from Studio
2
+ -- for one deep agent (FDE-62 S5).
3
+ --
4
+ -- A row holds ONLY the fields Studio changed, as one JSON object in the shape of
5
+ -- `voiceSettingsSchema` (`@stackbone/validators`, `studio-voice.ts`): top-level
6
+ -- leaves, the `vad` / `turn` / `interruptions` groups with only their changed
7
+ -- leaves, `keyterms` whole, and `greeting: null` as a declared "wait for the
8
+ -- caller". The box lays it over the agent's code and the box defaults leaf by
9
+ -- leaf (Studio > code > box default, FDE-62 D2) each time it reads the agent or
10
+ -- dispatches a call, so a saved change reaches the next call with nothing
11
+ -- restarted.
12
+ --
13
+ -- Partial on purpose, never the full effective value: a leaf Studio did not
14
+ -- touch keeps following the code, so a redeploy that changes it still lands.
15
+ -- Resetting a field removes its leaf, and resetting the last one deletes the
16
+ -- row, so "no row" means "nothing changed in Studio".
17
+ --
18
+ -- NO VERSIONS and no `updated_by`, unlike 0073: the activity log records who
19
+ -- changed what and when, which is the history this table does not keep.
20
+ --
21
+ -- Identity is the deep agent's name alone. Only a deep agent takes a call, so
22
+ -- there is no `owner_kind` beside it. `owner_name` is free text for the reason
23
+ -- 0047 gives for a prompt's owner.
24
+ --
25
+ -- Deliberately NO CHECK on `overrides`, following 0073: a box running an older
26
+ -- build must still be able to STORE what a newer one wrote, and the rules
27
+ -- (bounds, the known model lists, the cross-field checks) change with the build.
28
+ -- The route parses a body before it writes, and the merge
29
+ -- (`resolveEffectiveVoiceSettings`) reads only the leaves its build knows.
30
+ CREATE TABLE IF NOT EXISTS stackbone_platform.voice_settings (
31
+ owner_name text PRIMARY KEY,
32
+ overrides jsonb NOT NULL,
33
+ updated_at timestamptz NOT NULL DEFAULT now()
34
+ );
@@ -0,0 +1,5 @@
1
+ -- A run can be projected after its workflow has already stored memory, so the
2
+ -- navigation link must accept a UUID before the corresponding run row exists.
3
+
4
+ ALTER TABLE stackbone_platform.memory_calls
5
+ DROP CONSTRAINT IF EXISTS memory_calls_run_id_fkey;
@@ -0,0 +1,31 @@
1
+ -- 0097_voice_recordings.sql — the recording of each voice call (FDE-62 S12).
2
+ --
3
+ -- One row per ROOM, keyed on it: a room is one call (`POST /api/voice/rooms`
4
+ -- opens a fresh `call-<agent>-<8 hex>` every time), and the box starts one
5
+ -- room-composite egress for it. A session can hold several calls, so
6
+ -- `session_id` is indexed, newest first, for the read Studio's session screen makes.
7
+ --
8
+ -- `storage_key` is relative to the agent's scope in the box bucket, the key the
9
+ -- Storage Browser shows; the egress writes it under the `<agentId>/` prefix.
10
+ --
11
+ -- `egress_id` is LiveKit's id for the egress, null until LiveKit accepts the
12
+ -- start. The status moves past `recording` lazily, when someone reads the row:
13
+ -- the box asks LiveKit for that egress then, because no webhook can reach a dev
14
+ -- box. A row still without one after the join window means the start never
15
+ -- went through (the room was never joined, or the box restarted mid-retry).
16
+ --
17
+ -- Deliberately NO foreign key to `sessions`: the row is written when the room
18
+ -- opens, and the call's first turn creates the session row later, the race 0019
19
+ -- describes.
20
+ CREATE TABLE IF NOT EXISTS stackbone_platform.voice_recordings (
21
+ room text PRIMARY KEY,
22
+ session_id uuid NOT NULL,
23
+ storage_key text NOT NULL,
24
+ status text NOT NULL CHECK (status IN ('recording', 'ready', 'failed')),
25
+ egress_id text NULL,
26
+ started_at timestamptz NOT NULL DEFAULT now(),
27
+ ended_at timestamptz NULL
28
+ );
29
+
30
+ CREATE INDEX IF NOT EXISTS voice_recordings_session_id_started_at_idx
31
+ ON stackbone_platform.voice_recordings (session_id, started_at DESC);
@@ -0,0 +1,21 @@
1
+ -- STA3-18: preserve the meaning of historical vectors and document versions
2
+ -- while new collections can choose another provider, model and dimension.
3
+ ALTER TABLE stackbone_platform.rag_collections
4
+ ADD COLUMN IF NOT EXISTS embedding_provider text NOT NULL DEFAULT 'default',
5
+ ADD COLUMN IF NOT EXISTS embedding_model text NOT NULL DEFAULT 'openai/text-embedding-3-small',
6
+ ADD COLUMN IF NOT EXISTS embedding_dimension integer NOT NULL DEFAULT 1536
7
+ CONSTRAINT rag_collections_embedding_dimension_check CHECK (embedding_dimension BETWEEN 1 AND 16000),
8
+ ADD COLUMN IF NOT EXISTS embedding_revision integer NOT NULL DEFAULT 1
9
+ CONSTRAINT rag_collections_embedding_revision_check CHECK (embedding_revision > 0),
10
+ ADD COLUMN IF NOT EXISTS embedding_state text NOT NULL DEFAULT 'ready'
11
+ CONSTRAINT rag_collections_embedding_state_check CHECK (embedding_state IN ('ready', 'rebuilding', 'failed')),
12
+ ADD CONSTRAINT rag_collections_embedding_identity_check
13
+ CHECK (length(trim(embedding_provider)) > 0 AND length(trim(embedding_model)) > 0);
14
+
15
+ DROP INDEX IF EXISTS stackbone_platform.rag_chunks_embedding_hnsw;
16
+ ALTER TABLE stackbone_platform.rag_chunks ALTER COLUMN embedding TYPE vector;
17
+
18
+ -- Other dimensions are initialized under the shared index-creation lock.
19
+ CREATE INDEX IF NOT EXISTS rag_chunks_embedding_1536_hnsw
20
+ ON stackbone_platform.rag_chunks USING hnsw ((embedding::vector(1536)) vector_cosine_ops)
21
+ WHERE vector_dims(embedding) = 1536;
@@ -0,0 +1,33 @@
1
+ CREATE TABLE stackbone_platform.rag_collection_rebuilds (
2
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
3
+ collection_id uuid NOT NULL REFERENCES stackbone_platform.rag_collections(id) ON DELETE CASCADE,
4
+ target_provider text NOT NULL CHECK (length(trim(target_provider)) > 0),
5
+ target_model text NOT NULL CHECK (length(trim(target_model)) > 0),
6
+ target_dimension integer NOT NULL CHECK (target_dimension BETWEEN 1 AND 16000),
7
+ target_revision integer NOT NULL CHECK (target_revision > 0),
8
+ attempt integer NOT NULL DEFAULT 1 CHECK (attempt > 0),
9
+ attempt_token uuid NOT NULL DEFAULT gen_random_uuid(),
10
+ status text NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'running', 'failed', 'succeeded')),
11
+ error text,
12
+ run_id text,
13
+ world_run_id text,
14
+ created_at timestamptz NOT NULL DEFAULT now(),
15
+ updated_at timestamptz NOT NULL DEFAULT now(),
16
+ finished_at timestamptz,
17
+ UNIQUE (collection_id, target_revision)
18
+ );
19
+ CREATE UNIQUE INDEX rag_collection_rebuilds_active_collection_uq
20
+ ON stackbone_platform.rag_collection_rebuilds(collection_id)
21
+ WHERE status IN ('pending', 'running', 'failed');
22
+
23
+ CREATE TABLE stackbone_platform.rag_collection_rebuild_items (
24
+ rebuild_id uuid NOT NULL REFERENCES stackbone_platform.rag_collection_rebuilds(id) ON DELETE CASCADE,
25
+ document_id uuid NOT NULL REFERENCES stackbone_platform.rag_documents(id) ON DELETE CASCADE,
26
+ ordinal integer NOT NULL CHECK (ordinal >= 0),
27
+ document_snapshot jsonb NOT NULL CHECK (jsonb_typeof(document_snapshot) = 'object'),
28
+ source_kind text NOT NULL CHECK (source_kind IN ('original', 'chunks')),
29
+ prepared_chunks jsonb NOT NULL CHECK (jsonb_typeof(prepared_chunks) = 'array' AND jsonb_array_length(prepared_chunks) > 0),
30
+ completed_at timestamptz,
31
+ PRIMARY KEY (rebuild_id, document_id),
32
+ UNIQUE (rebuild_id, ordinal)
33
+ );
@@ -0,0 +1,15 @@
1
+ -- Keep the original manifest size when a missing document cascades its item.
2
+ ALTER TABLE stackbone_platform.rag_collection_rebuilds
3
+ ADD COLUMN manifest_size integer NOT NULL DEFAULT 0;
4
+
5
+ UPDATE stackbone_platform.rag_collection_rebuilds r
6
+ SET manifest_size = (
7
+ SELECT count(*)::integer FROM stackbone_platform.rag_collection_rebuild_items i
8
+ WHERE i.rebuild_id = r.id
9
+ );
10
+
11
+ ALTER TABLE stackbone_platform.rag_collection_rebuilds
12
+ ADD CONSTRAINT rag_collection_rebuilds_manifest_size_check CHECK (manifest_size >= 0);
13
+
14
+ CREATE INDEX rag_collection_rebuild_items_document_idx
15
+ ON stackbone_platform.rag_collection_rebuild_items(document_id);
@@ -0,0 +1,16 @@
1
+ -- FDE-87: legacy rows have no reliable agent owner; the author approved erasure.
2
+ DELETE FROM stackbone_platform.memory_facts;
3
+ DELETE FROM stackbone_platform.memory_calls;
4
+ DELETE FROM stackbone_platform.memory_user_revisions;
5
+
6
+ ALTER TABLE stackbone_platform.memory_facts ADD COLUMN agent_id text NOT NULL;
7
+ ALTER TABLE stackbone_platform.memory_calls ADD COLUMN agent_id text NOT NULL;
8
+ ALTER TABLE stackbone_platform.memory_user_revisions ADD COLUMN agent_id text NOT NULL;
9
+ ALTER TABLE stackbone_platform.memory_user_revisions DROP CONSTRAINT memory_user_revisions_pkey;
10
+ ALTER TABLE stackbone_platform.memory_user_revisions ADD PRIMARY KEY (agent_id, user_id);
11
+
12
+ DROP INDEX stackbone_platform.memory_facts_user_idx;
13
+ CREATE INDEX memory_facts_user_idx ON stackbone_platform.memory_facts (agent_id, user_id);
14
+ DROP INDEX stackbone_platform.memory_calls_user_started_idx;
15
+ CREATE INDEX memory_calls_user_started_idx ON stackbone_platform.memory_calls
16
+ (agent_id, user_id, started_at DESC, created_at DESC);
@@ -0,0 +1,7 @@
1
+ CREATE TABLE stackbone_platform.memory_settings (
2
+ agent_id text PRIMARY KEY,
3
+ overrides jsonb NOT NULL DEFAULT '{}'::jsonb,
4
+ updated_at timestamptz NOT NULL DEFAULT now(),
5
+ updated_by text,
6
+ CONSTRAINT memory_settings_overrides_object CHECK (jsonb_typeof(overrides) = 'object')
7
+ );
@@ -0,0 +1,13 @@
1
+ CREATE TABLE stackbone_platform.memory_identity_bindings (
2
+ agent_id text PRIMARY KEY,
3
+ identity_key text NOT NULL CHECK (char_length(identity_key) BETWEEN 1 AND 100),
4
+ source text NOT NULL CHECK (source IN ('code', 'studio', 'legacy')),
5
+ fixed_at timestamptz NOT NULL DEFAULT now()
6
+ );
7
+
8
+ INSERT INTO stackbone_platform.memory_identity_bindings (agent_id, identity_key, source)
9
+ SELECT agent_id, 'customerId', 'legacy' FROM (
10
+ SELECT agent_id FROM stackbone_platform.memory_facts
11
+ UNION SELECT agent_id FROM stackbone_platform.memory_calls
12
+ UNION SELECT agent_id FROM stackbone_platform.memory_user_revisions
13
+ ) AS existing_agents;
@@ -0,0 +1,17 @@
1
+ -- 0109_trigger_subscriptions_conversation_key.sql — an agent link's conversation key (PLA-971)
2
+ -- A JSONata expression evaluated against each inbound event. Events that yield the
3
+ -- same value continue one agent session; an event that yields nothing usable starts a
4
+ -- fresh one. Nullable: NULL is "every event opens its own session", which every link
5
+ -- that existed before reads as.
6
+ --
7
+ -- No CHECK tying it to `target_kind = 'agent'`, as `target_kind` itself (0096): a box
8
+ -- running an older build must be able to store a row it cannot run rather than reject
9
+ -- the write. The validators and the subscriptions router refuse a conversation key on
10
+ -- a workflow link, and a correlation key on an agent link.
11
+ --
12
+ -- 0109 and not 0106: STA3-18 took 0103-0105, and open PR #785 (FDE-87) also claims
13
+ -- 0103-0105 and has to renumber into 0106-0108. Gaps are legal
14
+ -- (`scripts/check-migration-numbers.mjs`).
15
+
16
+ ALTER TABLE stackbone_platform.trigger_subscriptions
17
+ ADD COLUMN IF NOT EXISTS conversation_key text;
@@ -0,0 +1,52 @@
1
+ -- 0110_concurrency_limits.sql — named concurrency limits for workflows.
2
+ --
3
+ -- A workflow takes a slot (`acquireSlot`) before it starts scarce work, and
4
+ -- another workflow gives it back (`releaseSlot`) when that work ends. When no
5
+ -- slot is free, the run waits on a World hook; the box resumes the hook when it
6
+ -- grants a slot.
7
+ --
8
+ -- `concurrency_limiters`: one row per limit the workspace declares, synced from
9
+ -- the manifest at boot. `capacity` and `max_hold` are what the code declares;
10
+ -- `capacity_override` is what an operator saves in Studio, and wins when set.
11
+ --
12
+ -- `concurrency_slots`: one row per slot requested. `waiting` is in the queue,
13
+ -- `granted` is given to the run but not yet confirmed by it, `held` is
14
+ -- confirmed. Only `granted` and `held` count against the capacity.
15
+ -- `claim_deadline` bounds how long a granted slot waits for its run to confirm;
16
+ -- `expires_at` is the `max_hold` cut-off, after which the box takes the slot back.
17
+ -- `hook_token` is UNIQUE so a retried enqueue step finds its row instead of
18
+ -- queueing twice. `label` is an id the creator chooses, never personal data.
19
+ --
20
+ -- Every write locks the limiter row first (`SELECT ... FOR UPDATE`), then
21
+ -- touches its slots: one lock order, so READ COMMITTED is enough.
22
+ --
23
+ -- Deliberately NO foreign key from `run_id` to `runs`: the run row is projected
24
+ -- in the background by the run sweep and can land after the slot row.
25
+ -- Deliberately NO foreign key from `limiter` to `concurrency_limiters` either:
26
+ -- the boot sync keeps a limiter with slots, and the store refuses an unknown one.
27
+ CREATE TABLE IF NOT EXISTS stackbone_platform.concurrency_limiters (
28
+ name text PRIMARY KEY,
29
+ capacity integer NOT NULL CHECK (capacity > 0),
30
+ max_hold interval NOT NULL,
31
+ capacity_override integer NULL CHECK (capacity_override > 0),
32
+ updated_at timestamptz NOT NULL DEFAULT now()
33
+ );
34
+
35
+ CREATE TABLE IF NOT EXISTS stackbone_platform.concurrency_slots (
36
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
37
+ limiter text NOT NULL,
38
+ state text NOT NULL CHECK (state IN ('waiting', 'granted', 'held')),
39
+ run_id uuid NULL,
40
+ hook_token text NOT NULL UNIQUE,
41
+ label text NULL,
42
+ enqueued_at timestamptz NOT NULL DEFAULT now(),
43
+ granted_at timestamptz NULL,
44
+ claim_deadline timestamptz NULL,
45
+ expires_at timestamptz NULL
46
+ );
47
+
48
+ CREATE INDEX IF NOT EXISTS concurrency_slots_limiter_state_enqueued_at_idx
49
+ ON stackbone_platform.concurrency_slots (limiter, state, enqueued_at);
50
+
51
+ CREATE INDEX IF NOT EXISTS concurrency_slots_run_id_idx
52
+ ON stackbone_platform.concurrency_slots (run_id);
@@ -0,0 +1,21 @@
1
+ -- 0111_concurrency_slots_now_idx.sql — the indexes the Insights "now" metrics
2
+ -- over concurrency slots are read through.
3
+ --
4
+ -- Two metrics read `concurrency_slots` (0110) on every query, never from a
5
+ -- cache (the box runtime's `insights/engine/catalog/now-metrics.ts`):
6
+ --
7
+ -- - `slots_in_use`: slots `granted` or `held`, the ones that count against a
8
+ -- limit's capacity, by `limiter`;
9
+ -- - `slots_waiting`: slots `waiting` in the queue, by `limiter`.
10
+ --
11
+ -- As in 0086, each index is partial on the metric's predicate, spelled as the
12
+ -- metric spells it so the planner proves the index covers the read, and holds
13
+ -- the column its property reads, so a count by limit is an index-only scan over
14
+ -- the rows the metric counts and no others.
15
+ CREATE INDEX IF NOT EXISTS concurrency_slots_in_use_now_idx
16
+ ON stackbone_platform.concurrency_slots (limiter)
17
+ WHERE state IN ('granted', 'held');
18
+
19
+ CREATE INDEX IF NOT EXISTS concurrency_slots_waiting_now_idx
20
+ ON stackbone_platform.concurrency_slots (limiter)
21
+ WHERE state = 'waiting';
@@ -0,0 +1,15 @@
1
+ -- 0112_concurrency_max_hold_override.sql — an operator override for a limit's
2
+ -- max hold, next to the capacity override 0110 already carries.
3
+ --
4
+ -- `max_hold` is what the code declares, synced from the manifest at boot;
5
+ -- `max_hold_override` is what an operator saves in Studio or with
6
+ -- `stackbone limits max-hold`, and wins when set. The boot sync never touches it.
7
+ --
8
+ -- The effective hold, `coalesce(max_hold_override, max_hold)`, is read once,
9
+ -- when a slot is granted, and stamped into `concurrency_slots.expires_at`.
10
+ -- Changing the override never rewrites `expires_at` of a slot already granted
11
+ -- or held: lowering it does not take back running work, raising it does not
12
+ -- extend a slot granted before the change.
13
+ ALTER TABLE stackbone_platform.concurrency_limiters
14
+ ADD COLUMN IF NOT EXISTS max_hold_override interval NULL
15
+ CHECK (max_hold_override > interval '0');
@@ -0,0 +1,13 @@
1
+ -- 0113_trigger_subscriptions_debounce_ms.sql — an agent link's wait before it answers (STA3-25)
2
+ -- Milliseconds the box waits after an event for more events of the same conversation,
3
+ -- so a burst of chat messages from one customer gets one answer. Nullable: NULL is
4
+ -- "answer each event as it arrives", which every link that existed before reads as.
5
+ --
6
+ -- No CHECK on the range or tying it to `target_kind = 'agent'` and a conversation key,
7
+ -- as `conversation_key` (0109): a box running an older build must be able to store a
8
+ -- row it cannot run rather than reject the write. The validators and the subscriptions
9
+ -- router refuse a wait outside 1..10000, on a workflow link, and on an agent link
10
+ -- without a conversation key.
11
+
12
+ ALTER TABLE stackbone_platform.trigger_subscriptions
13
+ ADD COLUMN IF NOT EXISTS debounce_ms integer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackbone/cli",
3
- "version": "0.6.3",
3
+ "version": "0.7.0",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,6 +32,7 @@
32
32
  "ioredis": "^5.10.1",
33
33
  "jose": "^6.2.3",
34
34
  "libsodium-wrappers": "^0.8.4",
35
+ "livekit-server-sdk": "2.19.1",
35
36
  "open": "^11.0.0",
36
37
  "openai": "^6.42.0",
37
38
  "pg": "^8.20.0",