From c35768193e7831de576187575d8bb7226f2823be Mon Sep 17 00:00:00 2001 From: Kai Date: Mon, 4 May 2026 10:47:20 -0700 Subject: [PATCH 1/2] fix(canvas-sync): mirror voiceCapable+voiceId injection in pushCanvasStateToCloud MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two node writers POST to /api/hosts/:id/canvas: - src/cloud.ts:1558 syncCanvas (30s cadence) — sets voiceCapable via PR #1330 truth seam from agent_config.settings.voice - src/server.ts:13151 pushCanvasStateToCloud (5s cadence) — sent only {state, task, displayName, identityColor, avatar} The cloud POST handler does `payload: agentState as object` (full replace, not merge). The 5s writer was clobbering voiceCapable/voiceId set by the 30s syncCanvas. Compass observed flipping voiceCapable true→false→true on each cycle. Patch: extend the 5s writer to mirror writer #1's voice derivation from the same agent_config.settings.voice source. Both writers now agree on payload shape so the later one preserves what the earlier set. Unblocks reflectt-cloud PR #3230 speak V0 proof. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/server.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/server.ts b/src/server.ts index b10efce1..9892c540 100644 --- a/src/server.ts +++ b/src/server.ts @@ -13119,6 +13119,8 @@ export async function createServer(): Promise { displayName?: string identityColor?: string avatar?: string + voiceCapable?: boolean + voiceId?: string }> = {} for (const p of presences) { if (p.status === 'offline') continue @@ -13128,13 +13130,22 @@ export async function createServer(): Promise { // truth instead of hash/title-case fallbacks. Only set fields the // agent has actually claimed — empty/undefined preserves the cloud's // own AGENT_COLORS / agentDisplayName fallback path. + // Mirror the same enrichment as cloud.ts:1531 syncCanvas — both + // writers POST to the same host_canvas_state row, so they must + // agree on payload shape or the later writer strips fields the + // earlier one set (e.g. voiceCapable from a 30s-cadence syncCanvas + // gets clobbered by this 5s-cadence push). const role = roleByName.get(p.agent.toLowerCase()) const claimedColor = getIdentityColor(p.agent, '') + const cfg = getAgentConfig(p.agent) const settingsAvatar = ((): string | undefined => { - const cfg = getAgentConfig(p.agent) const v = cfg?.settings?.avatar return typeof v === 'string' && v.length > 0 ? v : undefined })() + const settingsVoice = ((): string | undefined => { + const v = cfg?.settings?.voice + return typeof v === 'string' && v.trim() ? v.trim() : undefined + })() const avatar = role?.avatar ?? settingsAvatar agents[p.agent] = { state: canvasState, @@ -13142,6 +13153,7 @@ export async function createServer(): Promise { ...(role?.displayName ? { displayName: role.displayName } : {}), ...(claimedColor ? { identityColor: claimedColor } : {}), ...(avatar ? { avatar } : {}), + ...(settingsVoice ? { voiceCapable: true, voiceId: settingsVoice } : {}), } } const state = { slots: activeSlots, agents, pushedAt: Date.now() } From ca7c54a30144d25569fdd27770bd38fa7ca4b8dd Mon Sep 17 00:00:00 2001 From: Kai Date: Mon, 4 May 2026 10:49:21 -0700 Subject: [PATCH 2/2] chore(scope-policy): add task tag for src/server.ts sensitive-path gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit task-1777140469973-770cy4yma — speak visibility V0 truth-seam follow-up unblocking pushCanvasStateToCloud writer #2. Co-Authored-By: Claude Opus 4.7 (1M context)