@automagik/genie 4.260408.5 → 4.260409.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.
Files changed (43) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.genie/wishes/agent-stability-hardening/WISH.md +346 -0
  3. package/dist/genie.js +71 -30
  4. package/package.json +1 -1
  5. package/packages/genie-app/lib/subjects.ts +10 -0
  6. package/packages/genie-app/src/App.tsx +24 -0
  7. package/packages/genie-app/src-backend/index.ts +16 -1
  8. package/packages/genie-app/src-backend/ipc.ts +16 -0
  9. package/packages/genie-app/src-backend/pg-bridge.ts +13 -1
  10. package/packages/genie-app/views/shared/ApprovalMessage.tsx +273 -0
  11. package/packages/genie-app/views/shared/ApprovalToast.tsx +182 -0
  12. package/plugins/genie/.claude-plugin/plugin.json +1 -1
  13. package/plugins/genie/package.json +1 -1
  14. package/scripts/tmux/genie.tmux.conf +1 -1
  15. package/scripts/tmux/osc52-copy.sh +4 -0
  16. package/scripts/tmux/tui-tmux.conf +1 -1
  17. package/src/__tests__/mini-wizard.test.ts +2 -2
  18. package/src/__tests__/sdk-integration.test.ts +3 -2
  19. package/src/__tests__/tmux-config.test.ts +2 -2
  20. package/src/db/migrations/031_mailbox_delivery_status.sql +24 -0
  21. package/src/db/migrations/032_approvals.sql +51 -0
  22. package/src/db/migrations/033_approval_request_notify.sql +28 -0
  23. package/src/db/migrations/034_approvals_omni_message_id.sql +11 -0
  24. package/src/genie.ts +2 -0
  25. package/src/lib/defaults.ts +1 -1
  26. package/src/lib/inbox-watcher.test.ts +2 -1
  27. package/src/lib/inbox-watcher.ts +3 -1
  28. package/src/lib/mailbox.ts +46 -0
  29. package/src/lib/omni-approval-handler.ts +256 -0
  30. package/src/lib/protocol-router.ts +10 -2
  31. package/src/lib/providers/__tests__/claude-sdk.test.ts +2 -1
  32. package/src/lib/providers/claude-sdk-remote-approval.ts +316 -0
  33. package/src/lib/providers/claude-sdk.ts +40 -13
  34. package/src/lib/scheduler-daemon.ts +60 -1
  35. package/src/lib/sdk-directory-types.ts +8 -1
  36. package/src/lib/team-auto-spawn.ts +22 -0
  37. package/src/lib/workspace.ts +18 -0
  38. package/src/templates/genie-agents.md +1 -1
  39. package/src/templates/index.ts +1 -1
  40. package/src/term-commands/agent/inbox.ts +4 -0
  41. package/src/term-commands/approval.ts +150 -0
  42. package/src/term-commands/msg.ts +4 -0
  43. package/src/term-commands/serve.ts +27 -3
@@ -10,7 +10,7 @@
10
10
  "plugins": [
11
11
  {
12
12
  "name": "genie",
13
- "version": "4.260408.5",
13
+ "version": "4.260409.1",
14
14
  "source": "./plugins/genie",
15
15
  "description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, wish them into plans, make with parallel agents, ship as one team. A coding genie that grows with your project."
16
16
  }
@@ -0,0 +1,346 @@
1
+ # Wish: Agent Stability Hardening + Remote Approval
2
+
3
+ | Field | Value |
4
+ |-------|-------|
5
+ | **Status** | APPROVED |
6
+ | **Slug** | `agent-stability-hardening` |
7
+ | **Date** | 2026-04-08 |
8
+ | **Design** | [DESIGN.md](../../brainstorms/agent-stability-hardening/DESIGN.md) |
9
+ | **GitHub Issues** | #1094, #1093, #1064 |
10
+ | **Repo** | automagik-dev/genie |
11
+
12
+ ## Summary
13
+
14
+ Fix three agent runtime reliability bugs (permission deadlock, clipboard breakage, silent message loss) and introduce a new `remoteApproval` permission mode that routes tool-use approval requests to humans via Omni (WhatsApp) and the Genie desktop app. This transforms the broken permission system from a source of deadlocks into a powerful human-in-the-loop control plane.
15
+
16
+ ## Scope
17
+
18
+ ### IN
19
+ - Fix `permissionMode` spread override that causes agent deadlock (#1094)
20
+ - Change scaffold default from `permissionMode: default` to `bypassPermissions`
21
+ - Defense-in-depth: strip `permissionMode` from `translateSdkConfig`, call `ensureTeammateBypassPermissions` at SDK spawn
22
+ - New `permissionMode: remoteApproval` with PG-backed approval queue
23
+ - Omni approval frontend: WhatsApp message + reaction/text reply approval
24
+ - App approval frontend: toast notification + interactive chat message with Approve/Deny/Preview
25
+ - Configurable approve/deny tokens in `workspace.json`
26
+ - Fix `osc52-copy.sh` to use `$SSH_TTY` as primary clipboard target (#1093)
27
+ - Add `GENIE_TMUX_MOUSE=off` env var opt-out
28
+ - Inbox delivery retry (3x) with escalation to team-lead (#1064)
29
+ - Fix `isTeamActive` to check per-agent pane liveness
30
+
31
+ ### OUT
32
+ - WhatsApp interactive buttons (requires Business API — text reply + reactions are universal)
33
+ - Per-agent approval config (workspace-level is sufficient)
34
+ - Approval audit dashboard UI (PG table is queryable, UI deferred)
35
+ - Full inbox-watcher redesign (incremental fix only)
36
+ - OSC52 terminal compatibility matrix testing (fix the script, document Shift workaround)
37
+
38
+ ## Decisions
39
+
40
+ | # | Decision | Rationale |
41
+ |---|----------|-----------|
42
+ | D1 | Strip `permissionMode` from `translateSdkConfig()` | SDK executor must always bypass — frontmatter `permissionMode` only meaningful for tmux CLI path |
43
+ | D2 | Scaffold default `bypassPermissions` | Current `default` causes deadlock for every new agent |
44
+ | D3 | PG LISTEN/NOTIFY for approval resolution | Proven pattern (mailbox delivery), works cross-process |
45
+ | D4 | Accept both text reply and reaction emoji | Channel-agnostic — works on WhatsApp, Telegram, Slack |
46
+ | D5 | Configurable approve/deny tokens in `workspace.json` | Multilingual support ("sim"/"nao"), custom emoji |
47
+ | D6 | Timeout 300s default, `defaultAction: deny` | Safe default — no unauthorized tool use |
48
+ | D7 | Mode name: `remoteApproval` | Transport-agnostic — works via Omni, app, future channels |
49
+ | D8 | Toast + in-chat for app UI | Toast alerts from any tab, chat message is persistent and actionable |
50
+ | D9 | Medium preview for Omni, full via app Preview button | WhatsApp can't handle 500-line diffs |
51
+ | D10 | `$SSH_TTY` as primary OSC52 target | More reliable than `who -m` in nested tmux |
52
+ | D11 | `GENIE_TMUX_MOUSE=off` env opt-out, default on | Preserves behavior, escape hatch for SSH users |
53
+ | D12 | 3 delivery retries then escalate to team-lead | Matches existing `spawnFailures` pattern |
54
+
55
+ ## Success Criteria
56
+
57
+ - [ ] Agent with `permissionMode: bypassPermissions` (new scaffold default) executes tools without approval prompt
58
+ - [ ] Agent with `permissionMode: remoteApproval` blocks on tool use, writes approval to PG
59
+ - [ ] Approval request appears in genie-app chat as interactive message with Approve/Deny/Preview
60
+ - [ ] Approval request delivered via Omni to configured WhatsApp chat
61
+ - [ ] Human approves via WhatsApp reaction (configurable, default 👍) — agent resumes within 2s
62
+ - [ ] Human approves via WhatsApp text reply (configurable, default "y") — agent resumes within 2s
63
+ - [ ] Human approves via app Approve button — agent resumes within 1s
64
+ - [ ] Timeout (300s default) with no response — auto-deny
65
+ - [ ] Custom approve/deny tokens work from `workspace.json`
66
+ - [ ] `osc52-copy.sh` uses `$SSH_TTY` as primary clipboard target
67
+ - [ ] `GENIE_TMUX_MOUSE=off` disables mouse capture, native Cmd+C works
68
+ - [ ] `deliverToPane()` failure triggers retry (3x) then escalation to team-lead
69
+ - [ ] `translateSdkConfig()` never copies `permissionMode` into SDK options
70
+ - [ ] `ensureTeammateBypassPermissions()` called at SDK executor spawn path
71
+ - [ ] Existing tests pass (`tsc --noEmit` + `biome check`)
72
+
73
+ ## Execution Strategy
74
+
75
+ ### Wave 1 (parallel — independent bug fixes)
76
+ | Group | Agent | Description |
77
+ |-------|-------|-------------|
78
+ | 1 | engineer | Permission fix: spread order, strip translateSdkConfig, scaffold default, ensureTeammate |
79
+ | 5 | engineer | OSC52 clipboard: `$SSH_TTY` in osc52-copy.sh, `GENIE_TMUX_MOUSE` env opt-out |
80
+ | 6 | engineer | Inbox retry: delivery_status column, retry loop, escalation, isAgentAlive |
81
+
82
+ ### Wave 2 (after Group 1 — approval core depends on permission system)
83
+ | Group | Agent | Description |
84
+ |-------|-------|-------------|
85
+ | 2 | engineer | Remote approval core: PG table, migration, hook factory, workspace config, CLI command |
86
+ | review-w1 | reviewer | Review Groups 1, 5, 6 |
87
+
88
+ ### Wave 3 (after Group 2 — frontends depend on approval core)
89
+ | Group | Agent | Description |
90
+ |-------|-------|-------------|
91
+ | 3 | engineer | Omni approval frontend: message handler, reaction/text matching, rate limiting |
92
+ | 4 | engineer | App approval frontend: toast, chat message component, sidecar NATS subjects |
93
+ | review-w2 | reviewer | Review Group 2 |
94
+
95
+ ### Wave 4 (final)
96
+ | Group | Agent | Description |
97
+ |-------|-------|-------------|
98
+ | review-w3 | reviewer | Review Groups 3, 4 |
99
+ | qa | qa | End-to-end QA: all success criteria |
100
+
101
+ ## Execution Groups
102
+
103
+ ### Group 1: Permission Fix
104
+ **Goal:** Eliminate the `permissionMode` spread override that causes agent deadlock.
105
+
106
+ **Deliverables:**
107
+ 1. `src/lib/providers/claude-sdk.ts` — Delete line that copies `permissionMode` from `translateSdkConfig()` (line 42). Reorder options spread so `permissionMode: 'bypassPermissions'` and `allowDangerouslySkipPermissions: true` come AFTER `...translatedSdk` and `...extraOptions`.
108
+ 2. `src/lib/providers/claude-sdk.ts` — Call `ensureTeammateBypassPermissions()` at top of `runQuery()`.
109
+ 3. `src/templates/index.ts` — Change `permissionMode: default` to `permissionMode: bypassPermissions` (line ~93).
110
+ 4. `src/templates/genie-agents.md` — Change `permissionMode: default` to `permissionMode: bypassPermissions` (line ~9).
111
+ 5. `src/lib/defaults.ts` — Change `permissionMode: 'default'` to `permissionMode: 'bypassPermissions'` (line ~27).
112
+ 6. Update existing tests that assert `permissionMode: default` or the old spread order.
113
+
114
+ **Acceptance Criteria:**
115
+ - [ ] `translateSdkConfig()` output never contains `permissionMode` key
116
+ - [ ] Options object has `permissionMode: 'bypassPermissions'` after all spreads
117
+ - [ ] New scaffolded agent has `permissionMode: bypassPermissions` in frontmatter
118
+ - [ ] `ensureTeammateBypassPermissions()` called before SDK query
119
+
120
+ **Validation:**
121
+ ```bash
122
+ cd repos/genie && npx tsc --noEmit && npx biome check src/
123
+ grep -r "permissionMode: 'default'" src/templates/ src/lib/defaults.ts && echo "FAIL: default still present" && exit 1 || echo "PASS"
124
+ ```
125
+
126
+ **depends-on:** none
127
+
128
+ ---
129
+
130
+ ### Group 2: Remote Approval Core
131
+ **Goal:** Build the PG-backed approval queue and SDK hook that blocks until a human decides.
132
+
133
+ **Deliverables:**
134
+ 1. `src/db/migrations/030_approvals.sql` — Create `approvals` table with columns: `id`, `executor_id`, `agent_name`, `tool_name`, `tool_input_preview`, `decision` (pending/allow/deny), `decided_by`, `decided_at`, `timeout_at`, `created_at`. Add trigger `notify_approval_resolved` that fires `pg_notify('genie_approval_resolved', id)` on decision change from pending.
135
+ 2. `src/lib/providers/claude-sdk-remote-approval.ts` — New file. Export `createRemoteApprovalGate(config)` that returns a PreToolUse hook. Hook inserts approval row, subscribes to LISTEN channel, safety-net polls every 5s, returns allow/deny on resolution or timeout.
136
+ 3. `src/lib/providers/claude-sdk.ts` — When `sdkConfig.permissionMode === 'remoteApproval'`, wire `createRemoteApprovalGate` as PreToolUse hook instead of the standard permission gate. Keep `permissionMode: 'bypassPermissions'` in SDK options (the hook handles blocking).
137
+ 4. `src/lib/workspace.ts` — Add `permissions` section to workspace schema: `approveTokens`, `denyTokens`, `timeout`, `defaultAction`, `omniChat`, `omniInstance`.
138
+ 5. `src/term-commands/approval.ts` — New CLI command `genie approval request --tool <name> --input <preview> --agent <name> --wait` for tmux-path agents. Writes to same PG approvals table, blocks until resolved. Also `genie approval resolve <id> --decision allow|deny --by <actor>`.
139
+ 6. Register approval CLI commands in `src/genie.ts`.
140
+
141
+ **Acceptance Criteria:**
142
+ - [ ] `approvals` table created by migration
143
+ - [ ] `genie approval request --tool Write --input "test" --agent test-agent --wait` blocks and returns on resolve
144
+ - [ ] `genie approval resolve <id> --decision allow --by human` resolves pending approval within 2s
145
+ - [ ] Timeout auto-resolves with configured `defaultAction`
146
+ - [ ] Hook returns `{ permissionDecision: 'allow' }` or `'deny'` correctly
147
+
148
+ **Validation:**
149
+ ```bash
150
+ cd repos/genie && npx tsc --noEmit && npx biome check src/
151
+ ```
152
+
153
+ **depends-on:** Group 1
154
+
155
+ ---
156
+
157
+ ### Group 3: Omni Approval Frontend
158
+ **Goal:** Route approval requests to WhatsApp and accept human decisions via text reply or reaction.
159
+
160
+ **Deliverables:**
161
+ 1. `src/lib/providers/claude-sdk-remote-approval.ts` — Add Omni send on approval creation: format message with tool name + target + ~200 char preview. Read `omniChat` and `omniInstance` from workspace config. Use `omni send` CLI.
162
+ 2. Omni incoming message handler — Match incoming text replies against `approveTokens`/`denyTokens` from workspace config. Match reaction emoji on the approval message. On match, call `genie approval resolve <id> --decision allow|deny --by <sender>`.
163
+ 3. Rate limiting — If >3 pending approvals within 10s, batch into single summary message with count.
164
+
165
+ **Acceptance Criteria:**
166
+ - [ ] Approval request sends formatted WhatsApp message via Omni
167
+ - [ ] Reply "y" on WhatsApp resolves approval as allow
168
+ - [ ] Reply "n" resolves as deny
169
+ - [ ] Reaction 👍 resolves as allow
170
+ - [ ] Reaction 👎 resolves as deny
171
+ - [ ] Custom tokens from workspace.json work
172
+ - [ ] >3 rapid approvals are batched
173
+
174
+ **Validation:**
175
+ ```bash
176
+ cd repos/genie && npx tsc --noEmit && npx biome check src/
177
+ ```
178
+
179
+ **depends-on:** Group 2
180
+
181
+ ---
182
+
183
+ ### Group 4: App Approval Frontend
184
+ **Goal:** Show approval requests in the Genie desktop app as toasts and interactive chat messages.
185
+
186
+ **Deliverables:**
187
+ 1. `packages/genie-app/src-backend/index.ts` — Add NATS subjects: `genie.approval.request` (subscribe to PG LISTEN, publish to frontend), `genie.approval.resolve` (req/reply — update PG), `genie.approval.list` (req/reply — list pending).
188
+ 2. `packages/genie-app/views/shared/ApprovalToast.tsx` — New component. Non-blocking toast notification when approval arrives. Shows agent name + tool name + "View in Chat" link. Auto-dismisses after 10s.
189
+ 3. `packages/genie-app/views/` (chat view) — New `ApprovalMessage.tsx` component. Renders in chat as interactive card: tool info, Approve/Deny buttons, Preview expand. Buttons send NATS `genie.approval.resolve` request.
190
+ 4. Wire toast into `App.tsx` — subscribe to `events.approval_request` NATS event, render `ApprovalToast`.
191
+
192
+ **Acceptance Criteria:**
193
+ - [ ] Toast appears when approval request fires
194
+ - [ ] Chat message renders with Approve/Deny/Preview buttons
195
+ - [ ] Approve button resolves approval, agent resumes within 1s
196
+ - [ ] Deny button resolves approval, agent gets deny
197
+ - [ ] Preview button shows full tool input
198
+ - [ ] Agent state shows `permission` in AgentsView during pending approval
199
+
200
+ **Validation:**
201
+ ```bash
202
+ cd repos/genie && npx tsc --noEmit && npx biome check src/ packages/
203
+ ```
204
+
205
+ **depends-on:** Group 2
206
+
207
+ ---
208
+
209
+ ### Group 5: OSC52 Clipboard Fix
210
+ **Goal:** Fix clipboard copy over SSH and add mouse capture opt-out.
211
+
212
+ **Deliverables:**
213
+ 1. `scripts/tmux/osc52-copy.sh` — Add `$SSH_TTY` as primary target before `who -m` fallback:
214
+ ```bash
215
+ if [ -n "$SSH_TTY" ]; then
216
+ printf '%s' "$seq" > "$SSH_TTY" 2>/dev/null || true
217
+ fi
218
+ ```
219
+ 2. `scripts/tmux/genie.tmux.conf` — Wrap mouse setting: `if-shell '[ "$GENIE_TMUX_MOUSE" != "off" ]' 'set -g mouse on'`
220
+ 3. `scripts/tmux/tui-tmux.conf` — Same conditional mouse wrapping.
221
+ 4. `src/term-commands/serve.ts` — Conditional `set-option mouse on` only when `GENIE_TMUX_MOUSE !== 'off'`.
222
+ 5. Update `src/__tests__/tmux-config.test.ts` if tests assert unconditional `mouse on`.
223
+
224
+ **Acceptance Criteria:**
225
+ - [ ] `osc52-copy.sh` tries `$SSH_TTY` before `who -m`
226
+ - [ ] `GENIE_TMUX_MOUSE=off genie serve` starts without mouse capture
227
+ - [ ] Default (no env var) preserves current mouse-on behavior
228
+ - [ ] Existing tmux config tests pass
229
+
230
+ **Validation:**
231
+ ```bash
232
+ cd repos/genie && grep -q 'SSH_TTY' scripts/tmux/osc52-copy.sh && echo "PASS: SSH_TTY present" || exit 1
233
+ grep -q 'GENIE_TMUX_MOUSE' scripts/tmux/genie.tmux.conf && echo "PASS: mouse opt-out present" || exit 1
234
+ npx tsc --noEmit && npx biome check src/
235
+ ```
236
+
237
+ **depends-on:** none
238
+
239
+ ---
240
+
241
+ ### Group 6: Inbox Delivery Retry + Escalation
242
+ **Goal:** Stop messages from silently vanishing when target agent is unreachable.
243
+
244
+ **Deliverables:**
245
+ 1. `src/db/migrations/031_mailbox_delivery_status.sql` — Add columns to `mailbox` table: `delivery_status TEXT DEFAULT 'pending'` (pending/delivered/failed/escalated), `delivery_attempts INT DEFAULT 0`. Backfill: set existing rows with `delivered_at IS NOT NULL` to `delivery_status = 'delivered'`.
246
+ 2. `src/lib/mailbox.ts` — Add `markFailed(messageId)` (increment attempts, set status=failed), `getRetryable(maxAttempts)` (return failed messages with attempts < max), `markEscalated(messageId)`.
247
+ 3. `src/lib/protocol-router.ts` — In `deliverToPane()`, on failure call `mailbox.markFailed()` instead of silent return.
248
+ 4. `src/lib/scheduler-daemon.ts` — New retry loop every 60s: `getRetryable(3)` → attempt `deliverToPane()` → on 3rd failure call `mailbox.markEscalated()` + send escalation to team-lead via mailbox.
249
+ 5. `src/lib/team-auto-spawn.ts` — Add `isAgentAlive(agentName): Promise<boolean>` that checks if the specific agent's pane exists (not just the team window). Export for use by inbox-watcher.
250
+ 6. `src/lib/inbox-watcher.ts` — Use `isAgentAlive` for per-recipient check alongside existing `isTeamActive`.
251
+
252
+ **Acceptance Criteria:**
253
+ - [ ] Failed delivery increments `delivery_attempts` and sets `delivery_status = 'failed'`
254
+ - [ ] Retry loop picks up failed messages and re-attempts delivery
255
+ - [ ] After 3 failures, message is escalated to team-lead
256
+ - [ ] `isAgentAlive` correctly detects dead agent panes
257
+ - [ ] Existing mailbox tests pass
258
+
259
+ **Validation:**
260
+ ```bash
261
+ cd repos/genie && npx tsc --noEmit && npx biome check src/
262
+ ```
263
+
264
+ **depends-on:** none
265
+
266
+ ---
267
+
268
+ ## QA Criteria
269
+
270
+ _What must be verified on dev after merge._
271
+
272
+ - [ ] Spawn agent with default frontmatter → tools execute without approval prompt (no deadlock)
273
+ - [ ] Spawn agent with `permissionMode: remoteApproval` → tool use triggers approval request in PG
274
+ - [ ] Approve via app chat → agent resumes, tool executes
275
+ - [ ] Approve via WhatsApp reply "y" → agent resumes within 2s
276
+ - [ ] Deny via WhatsApp reaction 👎 → agent gets deny, continues gracefully
277
+ - [ ] Timeout with no response → auto-deny after configured timeout
278
+ - [ ] SSH session: drag-select text, Cmd+C copies to clipboard (via OSC52)
279
+ - [ ] `GENIE_TMUX_MOUSE=off` → Cmd+C works natively without OSC52
280
+ - [ ] Send message to dead agent → message retried 3x → escalated to team-lead
281
+ - [ ] No regressions: `tsc --noEmit`, `biome check`, existing test suite green
282
+
283
+ ## Assumptions / Risks
284
+
285
+ | Risk | Severity | Mitigation |
286
+ |------|----------|------------|
287
+ | PG LISTEN missed → hook blocks forever | High | 300s timeout + safety-net polling every 5s |
288
+ | Multiple approvals flood WhatsApp | Medium | Rate limit: batch if >3 pending within 10s |
289
+ | Approval latency slows agent execution | Medium | Only `remoteApproval` mode — `bypassPermissions` remains default |
290
+ | PG approvals table grows unbounded | Low | TTL cleanup: resolved approvals >7d auto-purged by scheduler |
291
+ | `$SSH_TTY` not set in all SSH configs | Low | Fallback chain: `$SSH_TTY` → `who -m` → stdout passthrough |
292
+ | Inbox retry storms on dead agents | Low | Max 3 retries then escalate once and stop |
293
+ | Omni not configured | Low | App UI works independently. If neither configured, timeout auto-denies. |
294
+
295
+ ## Review Results
296
+
297
+ _Populated by `/review` after execution completes._
298
+
299
+ ---
300
+
301
+ ## Files to Create/Modify
302
+
303
+ ```
304
+ # Group 1 — Permission Fix
305
+ src/lib/providers/claude-sdk.ts (modify — spread order, strip permissionMode, ensureTeammate)
306
+ src/templates/index.ts (modify — scaffold default)
307
+ src/templates/genie-agents.md (modify — scaffold default)
308
+ src/lib/defaults.ts (modify — builtin default)
309
+ src/lib/providers/__tests__/claude-sdk.test.ts (modify — update assertions)
310
+ src/__tests__/defaults.test.ts (modify — update assertions)
311
+ src/__tests__/mini-wizard.test.ts (modify — update assertions)
312
+
313
+ # Group 2 — Remote Approval Core
314
+ src/db/migrations/030_approvals.sql (create)
315
+ src/lib/providers/claude-sdk-remote-approval.ts (create)
316
+ src/lib/providers/claude-sdk.ts (modify — wire remoteApproval hook)
317
+ src/lib/workspace.ts (modify — permissions schema)
318
+ src/term-commands/approval.ts (create)
319
+ src/genie.ts (modify — register approval commands)
320
+
321
+ # Group 3 — Omni Approval Frontend
322
+ src/lib/providers/claude-sdk-remote-approval.ts (modify — add Omni send)
323
+ src/lib/omni-approval-handler.ts (create — incoming message matching)
324
+
325
+ # Group 4 — App Approval Frontend
326
+ packages/genie-app/src-backend/index.ts (modify — approval NATS subjects)
327
+ packages/genie-app/views/shared/ApprovalToast.tsx (create)
328
+ packages/genie-app/views/shared/ApprovalMessage.tsx (create)
329
+ packages/genie-app/src/App.tsx (modify — wire toast subscription)
330
+ packages/genie-app/lib/subjects.ts (modify — add approval subjects)
331
+
332
+ # Group 5 — OSC52 Clipboard Fix
333
+ scripts/tmux/osc52-copy.sh (modify — SSH_TTY primary)
334
+ scripts/tmux/genie.tmux.conf (modify — conditional mouse)
335
+ scripts/tmux/tui-tmux.conf (modify — conditional mouse)
336
+ src/term-commands/serve.ts (modify — conditional mouse)
337
+ src/__tests__/tmux-config.test.ts (modify — update assertions)
338
+
339
+ # Group 6 — Inbox Delivery Retry
340
+ src/db/migrations/031_mailbox_delivery_status.sql (create)
341
+ src/lib/mailbox.ts (modify — markFailed, getRetryable, markEscalated)
342
+ src/lib/protocol-router.ts (modify — call markFailed on delivery failure)
343
+ src/lib/scheduler-daemon.ts (modify — retry loop)
344
+ src/lib/team-auto-spawn.ts (modify — isAgentAlive)
345
+ src/lib/inbox-watcher.ts (modify — per-agent check)
346
+ ```