@automagik/genie 4.260410.2 → 4.260410.4
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/.claude-plugin/marketplace.json +1 -1
- package/.genie/brainstorm.md +61 -11
- package/.genie/wishes/_archive/agent-stability-hardening/WISH.md +346 -0
- package/.genie/wishes/_archive/brain-embeddings/WISH.md +72 -0
- package/.genie/wishes/_archive/brain-foundation/WISH.md +228 -0
- package/.genie/wishes/_archive/brain-identity-impl/WISH.md +72 -0
- package/.genie/wishes/_archive/brain-init-skill/WISH.md +57 -0
- package/.genie/wishes/_archive/brain-intelligence/WISH.md +83 -0
- package/.genie/wishes/_archive/brain-observability/WISH.md +70 -0
- package/.genie/wishes/_archive/brain-obsidian/WISH.md +1930 -0
- package/.genie/wishes/_archive/env-defaults-local-mode/WISH.md +148 -0
- package/.genie/wishes/_archive/genie-app/WISH.md +590 -0
- package/.genie/wishes/_archive/genie-layout-migration/WISH.md +292 -0
- package/.genie/wishes/_archive/genie-model-resolution/WISH.md +477 -0
- package/.genie/wishes/_archive/genie-onboarding-flow/WISH.md +453 -0
- package/.genie/wishes/_archive/omni-lifecycle-hardening/WISH.md +401 -0
- package/.genie/wishes/_archive/omni-skill-upgrade/WISH.md +309 -0
- package/.genie/wishes/_archive/omni-version-unify/WISH.md +257 -0
- package/.genie/wishes/_archive/os-services-orphan-leak/WISH.md +143 -0
- package/.genie/wishes/_archive/pgserve-daemon-ownership/WISH.md +140 -0
- package/.genie/wishes/_archive/remove-openclaw/WISH.md +178 -0
- package/.genie/wishes/_archive/rlmx-v02/WISH.md +478 -0
- package/.genie/wishes/_archive/rlmx-v03-cag/WISH.md +455 -0
- package/.genie/wishes/_archive/rlmx-v04-gemini3/WISH.md +533 -0
- package/.genie/wishes/_archive/session-capture-v2/WISH.md +452 -0
- package/.genie/wishes/_archive/session-ingester-perf/WISH.md +148 -0
- package/.genie/wishes/_archive/v4-database-layer/WISH.md +116 -0
- package/.genie/wishes/_archive/v4-hook-cli-safety/WISH.md +89 -0
- package/.genie/wishes/_archive/v4-message-routing/WISH.md +131 -0
- package/.genie/wishes/_archive/v4-session-executor/WISH.md +141 -0
- package/.genie/wishes/_archive/v4-spawn-resilience/WISH.md +176 -0
- package/.genie/wishes/_archive/v4-team-lifecycle/WISH.md +137 -0
- package/.genie/wishes/brain-benchmark-loop/WISH.md +130 -0
- package/.genie/wishes/brain-cag/WISH.md +37 -0
- package/.genie/wishes/brain-cag-v2-polish/WISH.md +177 -0
- package/.genie/wishes/brain-help-passthrough/WISH.md +159 -0
- package/.genie/wishes/brain-optimizer/WISH.md +59 -0
- package/.genie/wishes/crew-simplification/WISH.md +178 -0
- package/.genie/wishes/genie-app-v1/WISH.md +1098 -0
- package/.genie/wishes/genie-simulations/WISH.md +488 -0
- package/.genie/wishes/genie-skill-graph/WISH.md +354 -0
- package/.genie/wishes/omni-turn-based-dx/SESSION-LEARNINGS.md +53 -0
- package/.genie/wishes/omni-turn-based-dx/WISH.md +382 -0
- package/.genie/wishes/perfect-spawn-hierarchy/WISH.md +423 -0
- package/.genie/wishes/rlmx-dogfood/WISH.md +595 -0
- package/.genie/wishes/rlmx-integration/WISH.md +262 -0
- package/.genie/wishes/rlmx-readme/WISH.md +243 -0
- package/.genie/wishes/sac-agent/WISH.md +595 -0
- package/.genie/wishes/scaffold-auto-memory/WISH.md +168 -0
- package/.genie/wishes/sdk-executor-full/WISH.md +697 -0
- package/.genie/wishes/unified-executor-layer/WISH.md +732 -0
- package/.genie/wishes/workflow-action-engine/WISH.md +380 -0
- package/.genie/wishes/x-tool/WISH.md +254 -0
- package/package.json +1 -1
- package/plugins/genie/.claude-plugin/plugin.json +1 -1
- package/plugins/genie/package.json +1 -1
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "genie",
|
|
13
|
-
"version": "4.260410.
|
|
13
|
+
"version": "4.260410.4",
|
|
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
|
}
|
package/.genie/brainstorm.md
CHANGED
|
@@ -1,12 +1,62 @@
|
|
|
1
1
|
# Brainstorm Jar
|
|
2
|
-
|
|
3
|
-
##
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
2
|
+
|
|
3
|
+
## Sprint Backlog (next sprint — /review + /brainstorm queue)
|
|
4
|
+
|
|
5
|
+
### Tier 1: Autonomous dispatch (dream-eligible)
|
|
6
|
+
- [scaffold-auto-memory](wishes/scaffold-auto-memory/WISH.md) — READY. Auto-configure `autoMemoryEnabled`/`autoMemoryDirectory` in `.claude/settings.local.json` + seed `MEMORY.md` when `genie init agent <name>` scaffolds. Closes automagik-dev/genie#1106. Twin-genie reviewed: scope 2/5, risk 2/5, clarity 4/5.
|
|
7
|
+
- [brain-help-passthrough](wishes/brain-help-passthrough/WISH.md) — READY. Add `addHelpText` footer to `genie brain --help` so users see the forwarded subcommands (status, health, init, search, etc.). Passthrough already works at runtime; help text just doesn't advertise it. Closes automagik-dev/genie#1118. Twin-genie reviewed: scope 1/5, risk 1/5, clarity 5/5.
|
|
8
|
+
|
|
9
|
+
### Tier 2: Agent creation (brainstorm → wish)
|
|
10
|
+
- **brain-cag-v2** — NEW. Seamless brain→rlmx integration. Brain is the interface, rlmx is the engine. No direct `rlmx` CLI needed.
|
|
11
|
+
- **brain-optimizer-agent** — `/review` existing pipeline → create `.genie/agents/brain-optimizer/` sub-agent that uses traces+grades.
|
|
12
|
+
- **rlmx-dogfood-agent** — `/review` rlmx → create `.genie/agents/rlmx-dogfood/` sub-agent that uses rlmx on itself.
|
|
13
|
+
|
|
14
|
+
### Tier 3: Investigation first
|
|
15
|
+
- **dir-scope-architecture** (automagik-dev/genie#1107) — BLOCKED on architecture decision. Twin-genie trace revealed the PG `agents` table has NO scope column; `dir add --global` and `dir add` hit the same row. Fix requires either (a) adding a scope column with migration, or (b) removing the `--global` flag and false success messaging. Human decision needed before any code change. NOT autonomous-friendly.
|
|
16
|
+
- **rlmx-ship-polish** — Merge rlmx-integration + rlmx-readme. `/review` then `/brainstorm`. Live API tests, version fix, README.
|
|
17
|
+
- **genie-studio** — `/review` genie repo app code → compare with genie-app-v1 wish → consolidate/rename.
|
|
18
|
+
- [sdk-executor-full](wishes/sdk-executor-full/WISH.md) — APPROVED. `/review` current 132 executor matches → `/brainstorm` gaps.
|
|
19
|
+
- [agent-stability-hardening](wishes/_archive/agent-stability-hardening/WISH.md) — SHIPPED (PR #1112). Verify on dev.
|
|
20
|
+
- [brain-benchmark-loop](wishes/brain-benchmark-loop/WISH.md) — IN_PROGRESS. `/review` progress → decide next step.
|
|
21
|
+
|
|
22
|
+
## Deferred
|
|
23
|
+
- [omni-turn-based-dx](wishes/omni-turn-based-dx/WISH.md) — DRAFT. Foundational architecture, not sprint-sized.
|
|
24
|
+
- [unified-executor-layer](wishes/unified-executor-layer/WISH.md) — DRAFT. Depends on omni-turn-based-dx.
|
|
25
|
+
- [workflow-action-engine](wishes/workflow-action-engine/WISH.md) — DRAFT. Depends on genie-app-v1.
|
|
26
|
+
- [sac-agent](wishes/sac-agent/WISH.md) — DRAFT. Niche use case (Itaú Cartões).
|
|
27
|
+
- [crew-simplification](brainstorms/crew-simplification/DESIGN.md) — DRAFT. Housekeeping post-v1.
|
|
28
|
+
- [genie-skill-graph](wishes/genie-skill-graph/WISH.md) — DRAFT. Requires architecture work.
|
|
29
|
+
- tmux+sdk mode coexistence — flagged for future brainstorm.
|
|
30
|
+
|
|
31
|
+
## Non-engineering (parked)
|
|
32
|
+
- [The Agentic Shift — Mini-Documentary](brainstorms/agentic-shift-documentary/DESIGN.md) — CRYSTALLIZED. 40-shot Veo3 script. Schedule when ready.
|
|
33
|
+
- Viralizador — Recurring DevRel content loop, not a shippable feature.
|
|
34
|
+
|
|
35
|
+
## Poured (shipped)
|
|
36
|
+
|
|
37
|
+
All shipped wishes live in [`wishes/_archive/`](wishes/_archive/). Listed here for reference.
|
|
38
|
+
|
|
39
|
+
- [Agent Stability Hardening](wishes/_archive/agent-stability-hardening/WISH.md) — SHIPPED (PR #1112, 2026-04-09). Permission spread + remoteApproval + tmux mouse + inbox retry.
|
|
40
|
+
- [Session Capture v2](wishes/_archive/session-capture-v2/WISH.md) — SHIPPED (PR #825, 2026-04-09). Filewatch + lazy backfill + tool event extraction.
|
|
41
|
+
- [pgserve Daemon Ownership](wishes/_archive/pgserve-daemon-ownership/WISH.md) — SHIPPED (PR #827, 2026-04-09). Daemon owns PG, self-heal, doctor --fix.
|
|
42
|
+
- [os-services Orphan Leak](wishes/_archive/os-services-orphan-leak/WISH.md) — SHIPPED (PR #272 + #274, 2026-04-09). Layer 1+2 signal fix + orphan reaper + migration matchAll.
|
|
43
|
+
- [env-defaults-local-mode](wishes/_archive/env-defaults-local-mode/WISH.md) — SHIPPED (incremental, 2026-04-09). All 6 criteria met across multiple PRs.
|
|
44
|
+
- [Onboarding Overhaul](brainstorms/onboarding-overhaul/DRAFT.md) — ALL 3 SUB-WISHES SHIPPED:
|
|
45
|
+
- **1.** [genie-model-resolution](wishes/_archive/genie-model-resolution/WISH.md) — SHIPPED (QA 2026-04-08)
|
|
46
|
+
- **2.** [genie-onboarding-flow](wishes/_archive/genie-onboarding-flow/WISH.md) — SHIPPED (all 7 groups on dev)
|
|
47
|
+
- **3.** [genie-layout-migration](wishes/_archive/genie-layout-migration/WISH.md) — SHIPPED (in binary)
|
|
48
|
+
- [Brain Obsidian](wishes/_archive/brain-obsidian/WISH.md) — ALL 6 SUB-WISHES SHIPPED in @khal-os/brain v1.22.0:
|
|
49
|
+
- brain-foundation, brain-embeddings, brain-intelligence, brain-observability, brain-identity-impl, brain-init-skill
|
|
50
|
+
- [rlmx](wishes/_archive/rlmx-v04-gemini3/WISH.md) — v0.2 + v0.3 + v0.4 ALL SHIPPED (npm v0.260331.5)
|
|
51
|
+
- [Omni Lifecycle Hardening](wishes/_archive/omni-lifecycle-hardening/WISH.md) — SHIPPED (PR #359)
|
|
52
|
+
- [Omni Version Unify](brainstorms/omni-version-unify/DESIGN.md) — SHIPPED (PR #356)
|
|
53
|
+
- [Omni Skill Upgrade](wishes/_archive/omni-skill-upgrade/WISH.md) — SHIPPED (3-tier skills live)
|
|
54
|
+
- [remove-openclaw](wishes/_archive/remove-openclaw/WISH.md) — SHIPPED (absent from binary)
|
|
55
|
+
- **v4 Stability Sprint** — COMPLETE (2026-04-02). All 6 wishes merged: [v4-database-layer](wishes/_archive/v4-database-layer/WISH.md), [v4-hook-cli-safety](wishes/_archive/v4-hook-cli-safety/WISH.md), [v4-message-routing](wishes/_archive/v4-message-routing/WISH.md), [v4-session-executor](wishes/_archive/v4-session-executor/WISH.md), [v4-spawn-resilience](wishes/_archive/v4-spawn-resilience/WISH.md), [v4-team-lifecycle](wishes/_archive/v4-team-lifecycle/WISH.md).
|
|
56
|
+
- [Session Observability](brainstorms/session-observability/DESIGN.md) — CRYSTALLIZED → folds into session-capture-v2
|
|
57
|
+
- [X Tool](brainstorms/x-tool/DESIGN.md) — CRYSTALLIZED (WRS 100/100). Parked.
|
|
58
|
+
|
|
59
|
+
## Killed
|
|
60
|
+
- [genie-app](wishes/_archive/genie-app/WISH.md) — superseded by genie-app-v1 / genie-studio
|
|
61
|
+
- [session-ingester-perf](wishes/_archive/session-ingester-perf/WISH.md) — superseded by session-capture-v2
|
|
62
|
+
- [brain-obsidian](wishes/_archive/brain-obsidian/WISH.md) (parent spec) — all children shipped, reference only
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
# Wish: Agent Stability Hardening + Remote Approval
|
|
2
|
+
|
|
3
|
+
| Field | Value |
|
|
4
|
+
|-------|-------|
|
|
5
|
+
| **Status** | SHIPPED |
|
|
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
|
+
```
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Wish: brain-embeddings — Gemini Embedding 2, Vector Search, Multimodal
|
|
2
|
+
|
|
3
|
+
| Field | Value |
|
|
4
|
+
|-------|-------|
|
|
5
|
+
| **Status** | SHIPPED (verified 2026-04-08) |
|
|
6
|
+
| **Slug** | `brain-embeddings` |
|
|
7
|
+
| **Date** | 2026-03-27 |
|
|
8
|
+
| **Parent** | [brain-obsidian](../brain-obsidian/WISH.md) |
|
|
9
|
+
| **depends-on** | `brain-foundation` |
|
|
10
|
+
| **blocks** | `brain-intelligence` (semantic linking needs vectors) |
|
|
11
|
+
|
|
12
|
+
## Summary
|
|
13
|
+
|
|
14
|
+
Add Gemini Embedding 2 Preview as the embedding engine. All 8 task types. Multimodal: text, images (PNG/JPEG), video (MP4/MOV ≤120s), audio (MP3/WAV ≤80s), PDF (≤6 pages). pgvector + pg_trgm extensions. RRF fusion (BM25 + vector + trigram). Media processing pipeline (ffmpeg format conversion, frame extraction, audio extraction from video). Matryoshka dimensions (768/1536/3072). Batch API (50% cheaper). Intent detection (regex vs NL vs symbol vs question vs claim).
|
|
15
|
+
|
|
16
|
+
**After this ships:** `genie brain search` finds meaning, not just keywords. Images, videos, and PDFs are searchable. Cross-modal search works (text query → image result).
|
|
17
|
+
|
|
18
|
+
## Scope
|
|
19
|
+
|
|
20
|
+
### IN
|
|
21
|
+
- Migration `002-brain-embeddings.sql`: pgvector + pg_trgm extensions, add embedding/media columns to brain_chunks and brain_documents
|
|
22
|
+
- `src/lib/brain/embedding.ts` — Gemini Embedding 2 client: all 8 task types, Matryoshka, Batch API, normalization for <3072 dims
|
|
23
|
+
- `src/lib/brain/media.ts` — format conversion (ffmpeg), frame extraction, audio extraction from video, derivative files (.desc.md, .transcript.md, .frames/)
|
|
24
|
+
- `src/lib/brain/intent.ts` — query intent detection: regex vs NL vs symbol vs question vs claim → route to optimal backend
|
|
25
|
+
- Extend `update.ts` — multimodal processing: images, video, audio, PDF. Auto-describe via Gemini Vision. Transcription (Groq → Gemini fallback). Double indexing.
|
|
26
|
+
- Extend `search.ts` — vector search (pgvector cosine), trigram (pg_trgm), RRF fusion (4 backends), cross-modal search (--image, --audio, --video), --modality filter, --task code, --intent
|
|
27
|
+
- Extend `brain_chunks` — embedding vector(3072), embed_model, embed_task, embed_dims, modality, media_path columns
|
|
28
|
+
- Extend `brain_documents` — description column (Gemini Vision auto-descriptions)
|
|
29
|
+
- `genie brain search` gains: --semantic, --hybrid, --task, --intent, --image, --audio, --video, --modality, --refs, --outline
|
|
30
|
+
- Absorbed from qmd: RRF fusion (~80 lines), search pipeline orchestration (~200 lines)
|
|
31
|
+
- Absorbed from grepika: intent detection patterns (~60 lines)
|
|
32
|
+
|
|
33
|
+
### OUT
|
|
34
|
+
- rlmx / analyze (brain-intelligence)
|
|
35
|
+
- Wikilinks / link command (brain-intelligence)
|
|
36
|
+
- Traces / strategy (brain-observability)
|
|
37
|
+
- Full identity (brain-identity-impl)
|
|
38
|
+
|
|
39
|
+
## Success Criteria
|
|
40
|
+
|
|
41
|
+
- [ ] pgvector and pg_trgm extensions loaded in pgserve
|
|
42
|
+
- [ ] `genie brain update` embeds text chunks via Gemini Embedding 2 (3072 dims stored in pgvector)
|
|
43
|
+
- [ ] `genie brain update` processes images (PNG/JPEG), generates .desc.md, embeds image + description
|
|
44
|
+
- [ ] `genie brain update` processes video (MP4/MOV ≤120s): embeds raw video, extracts audio separately, transcribes, embeds transcript
|
|
45
|
+
- [ ] `genie brain update` processes audio (MP3/WAV ≤80s): embeds raw audio, transcribes, embeds transcript
|
|
46
|
+
- [ ] `genie brain update` processes PDF (≤6 pages): embeds raw PDF, extracts text, embeds text chunks
|
|
47
|
+
- [ ] Unsupported formats auto-converted (OGG→MP3, WebP→PNG, etc.)
|
|
48
|
+
- [ ] `genie brain search "query" --semantic` returns vector cosine results
|
|
49
|
+
- [ ] `genie brain search "query" --hybrid` combines BM25 + vector + trigram via RRF
|
|
50
|
+
- [ ] `genie brain search screenshot.png` returns cross-modal results (text docs matching image)
|
|
51
|
+
- [ ] `genie brain search --task code "how does dispatch work"` uses CODE_RETRIEVAL_QUERY
|
|
52
|
+
- [ ] Intent auto-detection: questions → Q&A, claims → FACT_VERIFICATION, docids → similar
|
|
53
|
+
- [ ] `genie brain update --estimate` shows cost before processing
|
|
54
|
+
- [ ] `genie brain update --budget 0.50` caps spending
|
|
55
|
+
- [ ] `genie brain update --batch` uses Batch API (50% cheaper)
|
|
56
|
+
- [ ] Matryoshka: --dims flag on init, L2 normalization for <3072
|
|
57
|
+
- [ ] `bun run check` passes
|
|
58
|
+
|
|
59
|
+
## Files to Create/Modify
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
CREATE repos/genie-brain/src/db/migrations/002-brain-embeddings.sql
|
|
63
|
+
CREATE repos/genie-brain/src/lib/brain/embedding.ts
|
|
64
|
+
CREATE repos/genie-brain/src/lib/brain/media.ts
|
|
65
|
+
CREATE repos/genie-brain/src/lib/brain/intent.ts
|
|
66
|
+
MODIFY repos/genie-brain/src/lib/brain/update.ts (multimodal processing)
|
|
67
|
+
MODIFY repos/genie-brain/src/lib/brain/search.ts (vector + trigram + RRF + cross-modal)
|
|
68
|
+
MODIFY repos/genie-brain/package.json (add google-genai SDK)
|
|
69
|
+
|
|
70
|
+
CREATE repos/genie-brain/src/lib/brain/embedding.test.ts
|
|
71
|
+
CREATE repos/genie-brain/src/lib/brain/media.test.ts
|
|
72
|
+
```
|