@animalabs/connectome-host 0.3.10 → 0.4.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/CHANGELOG.md CHANGED
@@ -1,7 +1,88 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Changed
6
+
7
+ - **context-manager ^0.6.0** — the fatal coverage invariant: a compile
8
+ refuses (`OverBudgetError` / `UncoveredDropError`) rather than shipping a
9
+ context with silently-dropped messages, and recall-pair pricing includes
10
+ reasoning carriers (fixes the permanent compile wedge / silent middle loss
11
+ on carrier-bearing stores). Default `overBudgetGraceRatio` is now 0.02.
12
+ - **agent-framework ^0.7.0** — host-side recovery for context refusals: the
13
+ OverBudget drain breaker also kicks for `UncoveredDropError`, and a
14
+ `context-refusal` ops alert fires immediately (fleet-watch) with the
15
+ recovery knobs named. Plus the context-settings preview surface and the
16
+ workspace read cap.
17
+
18
+ ### Added
19
+
20
+ - `compressionMaxTokens` recipe passthrough — cap compression output for
21
+ models with low output ceilings (2c78936).
22
+
3
23
  ## Unreleased
4
24
 
25
+ ### Fixed
26
+
27
+ - **TUI bug sweep** (#64): operator-safety and observability fixes.
28
+ - `/quit` confirm no longer treats arbitrary input as consent — only an
29
+ explicit `y`/`yes` (or re-typed `/quit`) kills fleet children, `d`
30
+ detaches, anything else cancels; a typed-through message is restored to
31
+ the input (paste referents intact) instead of discarded. Ctrl+C now goes
32
+ through the same confirmation; a second Ctrl+C force-quits.
33
+ - `/checkpoint` records the message position and `/restore` branches back
34
+ to it (previously restored to the branch head — rolling back nothing);
35
+ repeat restores at the same position are a no-op, and an unreachable
36
+ position degrades to the branch head with an explicit note.
37
+ - Session switch fully resets TUI observability state (tree aggregator,
38
+ stream subscriptions, per-agent caches) — fleet subtrees no longer
39
+ freeze after `/session switch`.
40
+ - Memory: peek logs / transcripts / scrollback capped, and detached
41
+ renderables are `destroy()`ed so their native text buffers are actually
42
+ freed (the fleet view leaked one buffer per line per 500ms repaint).
43
+ - Agent-name resolution is exact (`shortAgentName`, fork `-d{depth}`
44
+ scheme included) instead of substring matching that cross-wired agents
45
+ with prefix-overlapping names; peek tails no longer clip the newest
46
+ lines; fleet-view kill/restart failures are surfaced; per-round context
47
+ size (`ctx:`) and session totals (`Σ`) are separate status segments;
48
+ synesthete summaries moved off the render path and back off 30s after
49
+ a failed call instead of retrying at 2 Hz.
50
+ - Smaller UX: peek works on finished subagents (final runtime shown),
51
+ fork `done` summaries always print a chat line, Esc/Ctrl+B work from
52
+ the fleet view, paste placeholders survive `]` in the pasted text,
53
+ `/help` documents `/find` and `/branchto`, `/clear` with arguments
54
+ clears.
55
+
56
+ ### Docs
57
+
58
+ - Synced stale documentation with the current build: repos marked public
59
+ (AGENT-ONBOARDING), `forking-knowledge-miner` → `connectome-host`
60
+ naming, webui default port corrected to 7340, DEV-ENVIRONMENT
61
+ branch/version table refreshed (all feature branches merged),
62
+ LOCUS-ROUTING and both root plan docs marked implemented.
63
+
64
+ ### Changed
65
+
66
+ - **Tool-bloat reduction**: subscription-gc's `set_channel_idle_limit` /
67
+ `list_channel_idle_limits` tools folded into `agent_settings` as the
68
+ `channel_idle_limits` field (per-entry merge; number / `"off"` /
69
+ `"default"`-or-null to clear), following the reasoning-controls
70
+ precedent. The old tool names remain routable (undeclared), so agent
71
+ muscle memory keeps working; agents just no longer carry the two extra
72
+ tool schemas. `get` also reports read-only `channel_idle_default`,
73
+ `channel_idle_counters`, and `channel_idle_pinned`, preserving what
74
+ `list_channel_idle_limits` exposed. Updates are all-or-nothing: a patch
75
+ with any invalid entry applies none of its entries.
76
+ - **GC pins split from agent overrides**: ChannelModeModule now holds
77
+ debounced channels open via an internal `pin_channel_idle_limit` verb
78
+ and a separate pins layer, instead of writing an `"off"` override.
79
+ Consequences: a blanket `agent_settings reset` clears only agent-set
80
+ limits — it can no longer silently re-enable auto-close on a channel in
81
+ debounced mode — and a pre-existing agent override now survives a
82
+ debounced→mentions round-trip rather than being reset to default.
83
+ (Pins persisted by earlier builds as `"off"` overrides stay agent-level
84
+ until the next mode change re-asserts them as pins.)
85
+
5
86
  ## 0.3.10 — 2026-07-21
6
87
 
7
88
  ### Added
@@ -1,6 +1,6 @@
1
1
  # Headless Daemon Mode & Fleet Orchestration — Plan
2
2
 
3
- Status: draft • Authors: conversation between @tengro and Claude, 2026-04-21
3
+ Status: implemented (all phases shipped — see [UNIFIED-TREE-PLAN.md](./UNIFIED-TREE-PLAN.md) "Implementation outcome") • Authors: conversation between @tengro and Claude, 2026-04-21
4
4
 
5
5
  ## Goal
6
6
 
@@ -1,5 +1,7 @@
1
1
  # Unified Tree Plan: Fleet Children as Subagents in the TUI
2
2
 
3
+ **Status: shipped** — all phases landed; see "Implementation outcome" below. The per-phase headers preserve the plan as written.
4
+
3
5
  Goal: collapse the two-paradigm UI (`SubagentModule`'s in-process tree vs. `FleetModule`'s flat child list) into a single tree-rendering paradigm where fleet children appear and behave like subagents — same readouts (context tokens, phase, tool-call count), same unfold-children affordance, same row component.
4
6
 
5
7
  Companion to [HEADLESS-FLEET-PLAN.md](./HEADLESS-FLEET-PLAN.md), which defines the underlying IPC.
@@ -22,7 +22,7 @@ A connectome agent is:
22
22
  - an **`.env`** with secrets and per-deploy config;
23
23
  - a **chronicle** (`data/`) — the event-sourced memory store, optionally seeded by importing
24
24
  a prior conversation;
25
- - run by the **connectome-host** (`forking-knowledge-miner`, run via `bun src/index.ts <recipe> --headless`);
25
+ - run by the **connectome-host** (`bun src/index.ts <recipe> --headless`);
26
26
  - under a **systemd --user** service, with a sibling **terminal-sessions** daemon for the shell tool.
27
27
 
28
28
  It reaches the model through **membrane** (Anthropic-format adapter; can target a gateway via
@@ -164,10 +164,10 @@ shell. First, the **hard prerequisites** (install / confirm before anything else
164
164
  or their box owner if unsure):
165
165
  - **git**, **Bun** (`curl -fsSL https://bun.sh/install | bash`), **node ≥ 20** (via `nvm`);
166
166
  - a **Rust toolchain** (`rustup`) — `chronicle` has a native (napi) component built from Rust;
167
- - **access to the private `anima-research` / `antra-tess` GitHub orgs.** The connectome
168
- packages are **not public**. If `git clone` / `bun install` can't reach them, *stop* — the
169
- user must be granted org access (or handed the code) first. There's no way around this; it's
170
- the gate before all the mechanics.
167
+ - the `anima-research` / `antra-tess` GitHub repos. These are **public** (as of 2026-07;
168
+ they previously required org access), and the `@animalabs/*` npm packages install without
169
+ auth. If a `git clone` / `bun install` fails on access, check whether the repo moved
170
+ rather than assuming a permissions gate.
171
171
 
172
172
  The repos (clone all as siblings under one dir, canonically `~/connectome-local/`):
173
173
 
@@ -203,7 +203,7 @@ errors and logs `[webui] listening`. Then continue with the per-agent install di
203
203
 
204
204
  **Pick non-colliding ports.** Every agent on the box needs a unique:
205
205
  - **shell-daemon port** (`SESSION_SERVER_PORT`, default 3100)
206
- - **webui port** (default 7342)
206
+ - **webui port** (module default 7340; binds `0.0.0.0` and requires `basicAuth` unless set to loopback)
207
207
 
208
208
  Check what's taken (`ss -tlnp`) and pick the next pair (e.g. 3101 / 7343).
209
209
 
@@ -381,7 +381,7 @@ Two `systemd --user` units in `~/.config/systemd/user/`:
381
381
 
382
382
  1. **`terminal-sessions.service`** — the shell daemon, `--host localhost --headless --token
383
383
  ${SESSION_SERVER_TOKEN}`, env `SESSION_SERVER_PORT`. `enable` + `start` it first.
384
- 2. **`<agent>-agent.service`** — `bun .../forking-knowledge-miner/src/index.ts
384
+ 2. **`<agent>-agent.service`** — `bun .../connectome-host/src/index.ts
385
385
  .../recipes/<agent>.json --headless`, `EnvironmentFile=...env`, `Environment=PATH=<node bin>:<bun bin>:...`,
386
386
  `After=/Wants=terminal-sessions.service`, `Restart=always`.
387
387
 
@@ -472,7 +472,8 @@ robust fallback strategy.
472
472
 
473
473
  ## Appendix — quick reference
474
474
 
475
- - **Run host:** `bun forking-knowledge-miner/src/index.ts <recipe.json> --headless`
475
+ - **Run host:** `bun connectome-host/src/index.ts <recipe.json> --headless` (legacy
476
+ checkouts may still name the directory `forking-knowledge-miner`)
476
477
  - **Reach loopback webui:** `ssh -L <port>:localhost:<port> <login-user>@<box>` → `http://localhost:<port>`
477
478
  - **Live makeup:** `GET /debug/context/makeup` (basic auth) — segments + exact total tokens
478
479
  - **Compiled context:** `GET /debug/context`
@@ -9,9 +9,9 @@ end-to-end, see [`AGENT-ONBOARDING.md`](./AGENT-ONBOARDING.md).
9
9
 
10
10
  ## TL;DR
11
11
 
12
- An agent deployment is the Connectome stack (`forking-knowledge-miner` host +
12
+ An agent deployment is the Connectome stack (`connectome-host` +
13
13
  `agent-framework` + MCPL servers) plus a per-agent install dir. Agents run
14
- **headless** (`bun forking-knowledge-miner/src/index.ts <recipe> --headless`),
14
+ **headless** (`bun connectome-host/src/index.ts <recipe> --headless`),
15
15
  each as its own OS user, supervised by launchd (macOS) or systemd-user (Linux).
16
16
 
17
17
  > **Model integrity matters.** Each agent has a *correct* model and must stay on
@@ -1,6 +1,6 @@
1
1
  # Connectome Dev Environment — Setup Guide
2
2
 
3
- **Status:** Working notes, uncommitted. Snapshot of the dev layout as of 2026-05-30.
3
+ **Status:** Working notes. Snapshot of the dev layout as of 2026-07-22 (originally 2026-05-30; all feature branches in the original table have since merged to `main`).
4
4
  **Goal:** Reproduce the current development state — every component as a local git
5
5
  checkout, wired together so the host runs against editable source.
6
6
 
@@ -24,17 +24,22 @@ that holds a recipe + `.env` + chronicle data and references the code by absolut
24
24
 
25
25
  All cloned as siblings under `~/connectome-local/`:
26
26
 
27
- | Dir | GitHub repo | Branch | Ver | Role |
27
+ | Dir | GitHub repo | Branch | Ver (2026-07-22) | Role |
28
28
  |---|---|---|---|---|
29
- | `forking-knowledge-miner` | `anima-research/connectome-host` | `feat/tui-mem-stats-and-strategy-passthrough` | 0.3.0 | the host app (run via **bun**) |
30
- | `agent-framework` | `anima-research/agent-framework` | `fix/lazy-register-locus-and-send-failure-marker` | 0.5.0 | host runtime: gate, MCPL orchestration, locus routing, `think` |
31
- | `discord-mcpl` | `anima-research/discord-mcpl` | `main` | 0.1.0 | Discord surface (MCPL server) |
32
- | `heartbeat-mcpl` | `anima-research/heartbeat-mcpl` | `main` | 0.1.0 | periodic self-wake (MCPL server) |
33
- | `terminal-sessions-mcp` | `antra-tess/terminal-sessions-mcp` ⚠️ | `main` | 1.0.1 | shell: session daemon (ws://localhost:3100) + per-agent MCP stdio frontend |
34
- | `membrane` | `antra-tess/membrane` ⚠️ | `fix/before-request-hook-on-stream-paths` | 0.5.47 | LLM client lib — **single shared instance required** |
35
- | `context-manager` | `anima-research/context-manager` | `feat/adaptive-resolution` | 0.4.0 | context compilation / autobiographical memory |
36
- | `chronicle` | `anima-research/chronicle` | `main` | 0.1.1 | record / chronicle store |
37
- | `mcpl-core-ts` | `anima-research/mcpl-core-ts` | `main` | 0.1.0 | MCPL protocol types (discord-mcpl path dep) |
29
+ | `forking-knowledge-miner` | `anima-research/connectome-host` | `main` | 0.3.10 | the host app (run via **bun**) |
30
+ | `agent-framework` | `anima-research/agent-framework` | `main` | 0.6.10 | host runtime: gate, MCPL orchestration, locus routing, `think` |
31
+ | `discord-mcpl` | `anima-research/discord-mcpl` | `main` | 0.1.4 | Discord surface (MCPL server) |
32
+ | `heartbeat-mcpl` | `anima-research/heartbeat-mcpl` | `main` | 0.1.3 | periodic self-wake (MCPL server) |
33
+ | `terminal-sessions-mcp` | `antra-tess/terminal-sessions-mcp` ⚠️ | `main` | 1.6.0 | shell: session daemon (ws://localhost:3100) + per-agent MCP stdio frontend |
34
+ | `membrane` | `antra-tess/membrane` ⚠️ | `main` | 0.5.74 | LLM client lib — **single shared instance required** |
35
+ | `context-manager` | `anima-research/context-manager` | `main` | 0.5.14 | context compilation / autobiographical memory |
36
+ | `chronicle` | `anima-research/chronicle` | `main` | 0.2.7 | record / chronicle store |
37
+ | `mcpl-core-ts` | `anima-research/mcpl-core-ts` | `main` | 0.2.1 | MCPL protocol types |
38
+
39
+ > Versions drift; treat the column as a dated snapshot. The published npm
40
+ > releases now track `main` closely (typically within a patch), so the
41
+ > stock `bun install` path is sufficient for host-level work — use the
42
+ > checkout layout below when editing the libraries themselves.
38
43
 
39
44
  > ⚠️ **Org note:** `terminal-sessions-mcp` and `membrane` still live under the
40
45
  > personal `antra-tess` org, not `anima-research`. Consider migrating them for
@@ -51,19 +56,15 @@ All cloned as siblings under `~/connectome-local/`:
51
56
  ```bash
52
57
  mkdir -p ~/connectome-local && cd ~/connectome-local
53
58
 
54
- git clone -b feat/tui-mem-stats-and-strategy-passthrough \
55
- git@github.com:anima-research/connectome-host.git forking-knowledge-miner
56
- git clone -b fix/lazy-register-locus-and-send-failure-marker \
57
- git@github.com:anima-research/agent-framework.git
58
- git clone -b main git@github.com:anima-research/discord-mcpl.git
59
- git clone -b main git@github.com:anima-research/heartbeat-mcpl.git
60
- git clone -b main git@github.com:antra-tess/terminal-sessions-mcp.git
61
- git clone -b fix/before-request-hook-on-stream-paths \
62
- git@github.com:antra-tess/membrane.git
63
- git clone -b feat/adaptive-resolution \
64
- git@github.com:anima-research/context-manager.git
65
- git clone -b main git@github.com:anima-research/chronicle.git
66
- git clone -b main git@github.com:anima-research/mcpl-core-ts.git
59
+ git clone git@github.com:anima-research/connectome-host.git forking-knowledge-miner
60
+ git clone git@github.com:anima-research/agent-framework.git
61
+ git clone git@github.com:anima-research/discord-mcpl.git
62
+ git clone git@github.com:anima-research/heartbeat-mcpl.git
63
+ git clone git@github.com:antra-tess/terminal-sessions-mcp.git
64
+ git clone git@github.com:antra-tess/membrane.git
65
+ git clone git@github.com:anima-research/context-manager.git
66
+ git clone git@github.com:anima-research/chronicle.git
67
+ git clone git@github.com:anima-research/mcpl-core-ts.git
67
68
  ```
68
69
 
69
70
  ---
@@ -82,8 +83,9 @@ terminal-sessions-mcp
82
83
 
83
84
  In each: `npm install && npm run build` (build script is `tsc`).
84
85
 
85
- > `discord-mcpl` depends on `@connectome/mcpl-core` via `file:../mcpl-core-ts`
86
- > (a path dep), so build `mcpl-core-ts` first.
86
+ > `discord-mcpl` now depends on `@animalabs/mcpl-core` as a regular npm
87
+ > dependency (the old `file:../mcpl-core-ts` path dep is gone). A sibling
88
+ > `mcpl-core-ts` checkout is only needed when changing mcpl-core itself.
87
89
 
88
90
  ---
89
91
 
@@ -1,7 +1,10 @@
1
1
  # Conversational Locus & Output Routing — Design Note
2
2
 
3
- **Status:** Agreed design, implementation deferred.
4
- **Date:** 2026-05-29
3
+ **Status:** Implemented the locus now lives in `agent-framework`
4
+ (`src/mcpl/channel-registry.ts`; `routeSpeech` fires at turn completion in
5
+ `framework.ts`) and has grown beyond this note (global-locus fallback for
6
+ heartbeats, fork/home-channel semantics). Kept as design rationale.
7
+ **Date:** 2026-05-29 (status updated 2026-07-21)
5
8
  **Context:** Surfaced while wiring periodic heartbeats for an agent instance
6
9
  running headless on a Linux VPS.
7
10
 
@@ -95,6 +98,9 @@ discord-mcpl patch.
95
98
 
96
99
  ## Bugs found in passing (separate fixes, worth upstreaming)
97
100
 
101
+ *(Update 2026-07-21: #3 has been fixed upstream — `agent-framework`'s
102
+ feature-set-manager now normalizes array→record on ingest.)*
103
+
98
104
  1. **discord-mcpl `connect()` race** — resolved on `login()` (before gateway
99
105
  READY), so `registerDiscordChannels` enumerated an empty `guilds.cache`.
100
106
  *Fixed*: `connect()` now awaits the `ready` event. (Not yet
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@animalabs/connectome-host",
3
- "version": "0.3.10",
3
+ "version": "0.4.0",
4
4
  "description": "General-purpose agent TUI host with recipe-based configuration",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -14,10 +14,10 @@
14
14
  "postinstall": "test -d web && npm --prefix web install && npm --prefix web run build || true"
15
15
  },
16
16
  "dependencies": {
17
- "@animalabs/agent-framework": "^0.6.8",
17
+ "@animalabs/agent-framework": "^0.7.0",
18
18
  "@animalabs/chronicle": "^0.2.0",
19
- "@animalabs/context-manager": "^0.5.13",
20
- "@animalabs/membrane": "^0.5.73",
19
+ "@animalabs/context-manager": "^0.6.0",
20
+ "@animalabs/membrane": "^0.5.75",
21
21
  "@opentui/core": "^0.1.82"
22
22
  },
23
23
  "devDependencies": {
package/src/commands.ts CHANGED
@@ -151,7 +151,9 @@ export function handleCommand(command: string, app: AppContext): CommandResult {
151
151
  { text: ' /restore <name> Restore to checkpoint', style: 'system' },
152
152
  { text: ' /branches List Chronicle branches', style: 'system' },
153
153
  { text: ' /checkout <name> Switch to branch', style: 'system' },
154
- { text: ' /history Show state transitions', style: 'system' },
154
+ { text: ' /history [n] Show state transitions (last n)', style: 'system' },
155
+ { text: ' /find <text> Search messages for text', style: 'system' },
156
+ { text: ' /branchto <msgId> Branch from a specific message', style: 'system' },
155
157
  { text: ' /mcp list List MCPL servers', style: 'system' },
156
158
  { text: ' /mcp add <id> <cmd> Add/overwrite server', style: 'system' },
157
159
  { text: ' /mcp remove <id> Remove a server', style: 'system' },
@@ -747,7 +749,13 @@ function handleCheckpoint(app: AppContext, name?: string): CommandResult {
747
749
  if (!cm) return { lines: [{ text: 'No agent context manager.', style: 'system' }] };
748
750
 
749
751
  const branch = cm.currentBranch();
750
- app.branchState.checkpoints.set(name, { branchName: branch.name });
752
+ // A checkpoint is a *position*, not just a branch: record the id of the
753
+ // last message so /restore can branch back to this exact point even after
754
+ // the branch head has moved on. branchName alone would restore to the
755
+ // branch HEAD — i.e. not roll anything back.
756
+ const { messages } = cm.queryMessages({});
757
+ const messageId = messages.length > 0 ? messages[messages.length - 1]!.id : undefined;
758
+ app.branchState.checkpoints.set(name, { branchName: branch.name, messageId });
751
759
 
752
760
  return { lines: [{ text: `Checkpoint "${name}" saved at branch ${branch.name} (head: ${branch.head}).`, style: 'system' }] };
753
761
  }
@@ -774,12 +782,50 @@ function handleRestore(app: AppContext, name?: string): CommandResult {
774
782
  const cm = getAgentCM(app.framework);
775
783
  if (!cm) return { lines: [{ text: 'No agent context manager.', style: 'system' }] };
776
784
 
777
- const target = point.branchName;
785
+ // Restore to the recorded *position*. If the checkpoint captured a message
786
+ // id, branch at that message (same machinery as /undo) — switching to the
787
+ // stored branch name alone would land on its head, which by now may
788
+ // include everything the user wanted to roll back. Checkpoints from before
789
+ // the messageId field (or taken on an empty branch) fall back to the head.
790
+ let target = point.branchName;
791
+ let exact = false;
792
+ if (point.messageId) {
793
+ // A checkpoint is a POSITION, so position equality is the whole
794
+ // "already there" test. Comparing branch names too would kill the guard
795
+ // after the first restore (you'd be on restore-{name}-{ts}, never the
796
+ // original branch again) and every repeat /restore would mint another
797
+ // branch pointing at the same message.
798
+ const { messages } = cm.queryMessages({});
799
+ const atCheckpoint = messages.length > 0
800
+ && messages[messages.length - 1]!.id === point.messageId;
801
+ if (atCheckpoint) {
802
+ return { lines: [{ text: `Already at checkpoint "${name}".`, style: 'system' }] };
803
+ }
804
+ try {
805
+ // branchAt returns the new branch's NAME (the only thing switchBranch accepts).
806
+ target = cm.branchAt(point.messageId, `restore-${name}-${Date.now()}`);
807
+ exact = true;
808
+ // The record follows the position: keep the newest branch that holds
809
+ // the checkpoint message as the fallback for future restores.
810
+ app.branchState.checkpoints.set(name, { branchName: target, messageId: point.messageId });
811
+ } catch {
812
+ // branchAt resolves ids against the CURRENT branch's view of the log;
813
+ // after e.g. /undo the checkpoint message may not be reachable from
814
+ // here. Degrade to the checkpoint's branch head (the pre-messageId
815
+ // behavior) rather than failing the restore outright.
816
+ target = point.branchName;
817
+ }
818
+ }
778
819
 
779
820
  // switchBranch is async — strategy reinit needs to complete before next op.
780
821
  const asyncWork = Promise.resolve(cm.switchBranch(target))
781
822
  .then(() => ({
782
- lines: [{ text: `Restored to checkpoint "${name}" (branch: ${target}).`, style: 'system' as const }],
823
+ lines: [
824
+ { text: `Restored to checkpoint "${name}" (branch: ${target}).`, style: 'system' as const },
825
+ ...(point.messageId && !exact
826
+ ? [{ text: ' (note: exact checkpoint position unreachable from the current branch — restored to the branch head instead)', style: 'system' as const }]
827
+ : []),
828
+ ],
783
829
  branchChanged: true,
784
830
  }))
785
831
  .catch(err => ({
@@ -55,5 +55,8 @@ export function buildFrameworkAgentConfig(
55
55
  ...(recipe.agent.sameRoundThinkTextPolicy !== undefined
56
56
  ? { sameRoundThinkTextPolicy: recipe.agent.sameRoundThinkTextPolicy }
57
57
  : {}),
58
+ ...(recipe.agent.proseRouting !== undefined
59
+ ? { proseRouting: recipe.agent.proseRouting }
60
+ : {}),
58
61
  };
59
62
  }
@@ -67,6 +67,9 @@ export function buildFrameworkStrategy(
67
67
  headWindowTokens: strategyConfig?.headWindowTokens ?? 4000,
68
68
  recentWindowTokens: strategyConfig?.recentWindowTokens ?? 30000,
69
69
  compressionModel: strategyConfig?.compressionModel ?? model,
70
+ ...(strategyConfig?.compressionMaxTokens !== undefined
71
+ ? { compressionMaxTokens: strategyConfig.compressionMaxTokens }
72
+ : {}),
70
73
  autoTickOnNewMessage: true,
71
74
  maxMessageTokens: strategyConfig?.maxMessageTokens ?? 10000,
72
75
  ...(strategyType === 'frontdesk' ? { timeZone } : {}),
package/src/index.ts CHANGED
@@ -46,6 +46,7 @@ import { ChannelModeModule } from './modules/channel-mode-module.js';
46
46
  import { WebUiModule } from './modules/web-ui-module.js';
47
47
  import { ObserversModule } from './modules/observers-module.js';
48
48
  import { McplAdminModule } from './modules/mcpl-admin-module.js';
49
+ import { TtsRelayModule } from './modules/tts-relay-module.js';
49
50
  import { loadMcplServers, applyAgentOverlay, DEFAULT_CONFIG_PATH, DEFAULT_AGENT_OVERLAY_PATH } from './mcpl-config.js';
50
51
  import { SessionManager } from './session-manager.js';
51
52
  import { resolveAgentName } from './agent-name.js';
@@ -357,6 +358,14 @@ async function createFramework(
357
358
  moduleInstances.push(new ObserversModule({ path: observersPath }));
358
359
  }
359
360
 
361
+ // TTS relay tap — opt-in per recipe. Pure trace-bus consumer: mirrors the
362
+ // agent's streaming generation to a melodeus-tts-relay /bot endpoint and
363
+ // trims posted messages on voice interruptions. See modules.ttsRelay in
364
+ // recipe.ts for the config contract (url/token validated at recipe load).
365
+ if (modules.ttsRelay) {
366
+ moduleInstances.push(new TtsRelayModule(modules.ttsRelay));
367
+ }
368
+
360
369
  // Extension-registered modules. Instantiated last so built-in modules keep
361
370
  // their historical positions; each factory gets the minimal runtime context
362
371
  // plus its declaring extension's config blob.
@@ -200,11 +200,13 @@ export class ChannelModeModule implements Module {
200
200
  }
201
201
 
202
202
  // 3. Pin subscription-gc off so a chatty channel doesn't auto-close
203
- // itself back out of debounced mode.
203
+ // itself back out of debounced mode. The pin layer sits above
204
+ // agent-level overrides, so agent_settings reset/update can't undo it
205
+ // and any pre-existing override survives the mode round-trip.
204
206
  steps.push(
205
- await this.callToolStep('gc-off', `${this.gcModuleName}--set_channel_idle_limit`, {
207
+ await this.callToolStep('gc-pin', `${this.gcModuleName}--pin_channel_idle_limit`, {
206
208
  channelId,
207
- limit: 'off',
209
+ pinned: true,
208
210
  }),
209
211
  );
210
212
 
@@ -225,11 +227,12 @@ export class ChannelModeModule implements Module {
225
227
  // 2. Close ambient traffic through the generic host lifecycle.
226
228
  steps.push(await this.callToolStep('close', 'channel_close', { channelId, serverId: this.serverId }));
227
229
 
228
- // 3. Restore the default auto-close limit.
230
+ // 3. Release the pin; the channel's agent override (if any) or the
231
+ // default limit applies again.
229
232
  steps.push(
230
- await this.callToolStep('gc-default', `${this.gcModuleName}--set_channel_idle_limit`, {
233
+ await this.callToolStep('gc-unpin', `${this.gcModuleName}--pin_channel_idle_limit`, {
231
234
  channelId,
232
- limit: 'default',
235
+ pinned: false,
233
236
  }),
234
237
  );
235
238