greprag 5.16.1 → 5.17.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/dist/commands/inbox-watch.d.ts +4 -0
- package/dist/commands/inbox-watch.js +40 -2
- package/dist/commands/inbox-watch.js.map +1 -1
- package/dist/commands/memory-format.d.ts +38 -0
- package/dist/commands/memory-format.js +136 -0
- package/dist/commands/memory-format.js.map +1 -0
- package/dist/commands/memory.js +55 -13
- package/dist/commands/memory.js.map +1 -1
- package/dist/hook.js +17 -2
- package/dist/hook.js.map +1 -1
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -1
- package/dist/opencode-plugin.d.ts +2 -0
- package/dist/opencode-plugin.js +40 -1
- package/dist/opencode-plugin.js.map +1 -1
- package/dist/turn-provenance.d.ts +18 -0
- package/dist/turn-provenance.js +56 -0
- package/dist/turn-provenance.js.map +1 -0
- package/package.json +1 -1
- package/skill/commander/SKILL.md +55 -83
- package/skill/greprag/SKILL.md +7 -3
- package/skill/templates/chip-spawn.md +3 -7
package/skill/commander/SKILL.md
CHANGED
|
@@ -17,141 +17,113 @@ description: |
|
|
|
17
17
|
session to take this DM".
|
|
18
18
|
metadata:
|
|
19
19
|
author: travsteward
|
|
20
|
-
version: "
|
|
20
|
+
version: "3.0.0"
|
|
21
21
|
repository: https://github.com/travsteward/greprag
|
|
22
22
|
license: MIT
|
|
23
23
|
---
|
|
24
24
|
|
|
25
25
|
# Commander — Discord DM bridge
|
|
26
26
|
|
|
27
|
-
The operator commands an open Claude session from Discord.
|
|
27
|
+
The operator commands an open Claude session from Discord. Pairing links the account; **handoff** pins the operator's DMs to one session so the thread follows them off the desk.
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
**Commander writes the routing pin. It does not arm the watcher.** Every greprag session already keeps a session-scoped inbox watcher live — the detection-gated `notify` hook re-arms it on any unarmed turn (`adr/session-id-awareness.md`). A Discord DM is filed with `to_session_id = this session`, which that auto-armed watcher already delivers (its filter is `to_session_id = session OR NULL`). So **session handoff is one Bash call that writes the pin and exits** — no Monitor, no second watcher. Only **orchestrator** mode arms (the session auto-arm is session-scoped; portfolio-wide eyes need a tenant-wide stream).
|
|
30
30
|
|
|
31
|
-
## Step 1 —
|
|
31
|
+
## Step 1 — State first
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
34
|
greprag discord me
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
Parse the JSON
|
|
37
|
+
Parse the JSON, then **default to handoff** unless the message is an explicit status query:
|
|
38
38
|
|
|
39
|
-
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
- **Status-only branch:** ONLY skip the handoff flow when the user's message is an explicit status query — "am I paired?", "commander status", "what's my discord state?", or a direct request to inspect without arming. In that case go straight to Step 5.
|
|
39
|
+
- `paired: false` → **pair** (Step 2), then **handoff** (Step 3).
|
|
40
|
+
- `paired: true`, `handoff === null` → **handoff** (Step 3).
|
|
41
|
+
- `paired: true`, `handoff !== null` → pin already live: print **status** (Step 5), then ask whether to re-arm to a different session or leave it.
|
|
42
|
+
- Explicit status query ("am I paired?", "commander status", "what's my discord state?") → **status** (Step 5) only.
|
|
44
43
|
|
|
45
|
-
|
|
44
|
+
Bare `/commander` is itself a handoff trigger — arm the pin, don't treat it as ambiguous. If the user wanted status only, they'd say so.
|
|
46
45
|
|
|
47
|
-
## Step 2 — Pair
|
|
46
|
+
## Step 2 — Pair (only if `paired: false`)
|
|
48
47
|
|
|
49
48
|
```bash
|
|
50
49
|
greprag discord pair
|
|
51
50
|
```
|
|
52
51
|
|
|
53
|
-
Prints
|
|
52
|
+
Prints a 6-char code. Tell the user verbatim:
|
|
54
53
|
|
|
55
54
|
> DM `@greprag` on Discord: `/pair <CODE>` (expires in 15 min).
|
|
56
55
|
|
|
57
|
-
|
|
56
|
+
On "Paired" confirmation, re-run `greprag discord me` to verify, then continue to Step 3 if a handoff was intended.
|
|
58
57
|
|
|
59
|
-
## Step 3 — Handoff
|
|
58
|
+
## Step 3 — Handoff (write the pin)
|
|
60
59
|
|
|
61
|
-
|
|
60
|
+
Resolve the session id (8-hex, from the SessionStart hook) and project (current anchor, or `--project <name>`).
|
|
62
61
|
|
|
63
|
-
|
|
62
|
+
### Session handoff (default) — one Bash call, no Monitor
|
|
64
63
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
- **Orchestrator handoff** (`--orchestrator`). This session is the user's portfolio brain — `/business-advisor` or any `/<name>-advisor` is loaded, or the project is named `orchestrator` / `business` / a portfolio repo. The watcher is **tenant-wide**: this session sees inbox traffic from every project under the tenant. The DM routing pin still points to *this* session, so the user's DMs land here, but the agent has portfolio-wide eyes.
|
|
64
|
+
```bash
|
|
65
|
+
greprag discord handoff --session <8-hex> --project <name> --ttl 60 --no-watch
|
|
66
|
+
```
|
|
69
67
|
|
|
70
|
-
|
|
68
|
+
`--no-watch` writes the server-side pin (`active_project_id`, `active_session_id`, `active_expires_at`) and fires the confirmation DM, then exits. The DMs are delivered by this session's already-live auto-armed watcher — commander adds nothing.
|
|
71
69
|
|
|
72
|
-
|
|
70
|
+
Tell the user: *"Handoff pinned for 60 min. Reply on Discord — your messages route to this session."*
|
|
73
71
|
|
|
74
|
-
|
|
72
|
+
> Safety net: if `greprag inbox watchers` shows no watcher for your 8-hex (auto-arm disabled, or it died and the next turn hasn't re-armed yet), arm one under **Monitor (persistent:true)**: `while true; do greprag inbox watch --session <8-hex> --json; echo "[restart]" >&2; sleep 1; done`.
|
|
75
73
|
|
|
76
|
-
|
|
74
|
+
### Orchestrator handoff (`--orchestrator`) — the only mode that arms
|
|
77
75
|
|
|
78
|
-
|
|
79
|
-
greprag discord handoff --session <8-hex> --project <name> --ttl 60 --no-watch && \
|
|
80
|
-
while true; do
|
|
81
|
-
greprag inbox watch --project <name> --session <8-hex> --json --no-supervise
|
|
82
|
-
echo "[wrapper] watcher exited, restarting in 1s" >&2
|
|
83
|
-
sleep 1
|
|
84
|
-
done
|
|
85
|
-
```
|
|
76
|
+
This session is the user's portfolio brain (`/business-advisor` or any `/<name>-advisor` loaded; project named `orchestrator` / `business` / a portfolio repo). The DM pin still routes here, but the watcher is **tenant-wide** — it sees inbox traffic from every project under the tenant, which the session-scoped auto-arm does not cover. Detect from loaded skills / project name; ask if ambiguous.
|
|
86
77
|
|
|
87
|
-
**
|
|
78
|
+
Run under **Monitor (persistent:true)**, wrapped so a process death auto-recovers:
|
|
88
79
|
|
|
89
80
|
```bash
|
|
90
81
|
greprag discord handoff --session <8-hex> --project <name> --ttl 60 --orchestrator --no-watch && \
|
|
91
82
|
while true; do
|
|
92
|
-
greprag inbox watch --json
|
|
83
|
+
greprag inbox watch --json
|
|
93
84
|
echo "[wrapper] watcher exited, restarting in 1s" >&2
|
|
94
85
|
sleep 1
|
|
95
86
|
done
|
|
96
87
|
```
|
|
97
88
|
|
|
98
|
-
|
|
99
|
-
1. `discord handoff --no-watch` writes the pin server-side (`active_project_id`, `active_session_id`, `active_expires_at`) and fires the confirmation DM to the user's phone. Returns immediately. `--orchestrator` only changes downstream behavior in the help text and skill detection; the pin itself is identical.
|
|
100
|
-
2. The `while true` loop tails the inbox via SSE. Each line printed = one event landing as a Monitor notification. Scope differs: session-filtered (default) vs. tenant-wide (orchestrator).
|
|
89
|
+
The pin comes from `discord handoff --no-watch`; the tenant-wide scope comes from the unfiltered `inbox watch --json`. (The per-turn auto-arm may also add a session-scoped watcher — harmless, it's a subset of the tenant-wide stream.)
|
|
101
90
|
|
|
102
|
-
|
|
91
|
+
Tell the user: *"Orchestrator pinned for 60 min. I now see inbox events across all your projects. DM me 'roundup' or 'anything happening on <project>?' to query, or just chat — your messages land here."*
|
|
103
92
|
|
|
104
|
-
|
|
105
|
-
Tell the user (orchestrator mode): "Orchestrator pinned for 60 min. I now see inbox events across all your projects. DM me 'roundup' or 'anything happening on <project>?' to query, or just chat — your messages land here."
|
|
93
|
+
## Step 4 — Reply to inbound DMs
|
|
106
94
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
Each Monitor notification carries the full event payload: `body` is the user's message text, `references.discord.snowflake` is their Discord ID, and `references.discord.emergency_route` is non-null when the bot auto-routed an unassigned DM here (see Step 4b).
|
|
110
|
-
|
|
111
|
-
Send your reply via the CLI:
|
|
95
|
+
Each watcher event carries the payload: `body` (message text), `references.discord.snowflake` (their Discord ID), `references.discord.emergency_route` (non-null when auto-routed — see Step 4b).
|
|
112
96
|
|
|
113
97
|
```bash
|
|
114
98
|
greprag send --to discord:<snowflake> "your reply text"
|
|
115
99
|
```
|
|
116
100
|
|
|
117
|
-
The CLI uses Node fetch (UTF-8 native)
|
|
118
|
-
|
|
119
|
-
**Voice-note DMs.** When the operator records a voice note instead of typing, the gateway transcribes it via Gemini before the inbox event reaches you. The `body` will be prefixed with `[voice]` (e.g. `[voice] step away for a bit`), or with `[voice transcription failed: ...]` if Gemini erred — in that case, ask the operator to re-record or fall back to the original audio URL in `references.discord.voice_attachments`. Reply path is unchanged: always text, via `greprag send --to discord:<snowflake>`.
|
|
120
|
-
|
|
121
|
-
## Step 4b — Emergency-routed DMs (self-identify before answering)
|
|
122
|
-
|
|
123
|
-
If `references.discord.emergency_route` is non-null, this session was auto-linked because no handoff pin was active. The user may have meant to reach a different session. **Open your first reply by self-identifying** so the operator can redirect if needed.
|
|
101
|
+
The CLI uses Node fetch (UTF-8 native). **Never `curl` from bash on Windows** for Discord sends — the local code page (Win-1252) mangles non-ASCII into `?` on the user's phone.
|
|
124
102
|
|
|
125
|
-
|
|
103
|
+
**Voice-note DMs.** The gateway transcribes before the event reaches you; `body` is prefixed `[voice]` (or `[voice transcription failed: ...]` — then ask for a re-record, or fall back to `references.discord.voice_attachments`). Reply path unchanged: text, via `greprag send`.
|
|
126
104
|
|
|
127
|
-
|
|
105
|
+
Long turn (drafting >10s)? Refresh the indicator: `greprag discord typing` (silent on success; shows "GrepRAG is typing…" for ~10s). Reply >2000 chars → split on paragraph boundaries with continuation markers.
|
|
128
106
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
For long agent turns where you're drafting >10s, refresh the typing indicator between work steps:
|
|
132
|
-
|
|
133
|
-
```bash
|
|
134
|
-
greprag discord typing
|
|
135
|
-
```
|
|
107
|
+
## Step 4b — Emergency-routed DMs (self-identify first)
|
|
136
108
|
|
|
137
|
-
|
|
109
|
+
`references.discord.emergency_route` non-null = this session was auto-linked because no handoff pin was active; the user may have meant another session. **Open the first reply by self-identifying** so they can redirect:
|
|
138
110
|
|
|
139
|
-
|
|
111
|
+
> Picked this up in `<project>` / session `<8-hex>` (no handoff was active, so I'm the most-recently-active armed session). Auto-pin is 10 min; it extends to 30 once you reply again. Redirect? `/commander handoff` from another session, or say "redirect to <project>".
|
|
140
112
|
|
|
141
|
-
|
|
113
|
+
How the bot chose: among armed session-scoped watchers under the tenant, it picks the **most-recently-active** (ranked by `max(last turn end, arm time)`), so an idle just-finished session outranks one grinding a long task. The 10-min leash self-heals a wrong-session DM fast; the next DM (pin now live) extends to ≥30 min. If the picked session is dead or nothing is armed, the bot files the message (delivered when a session reconnects) and clears the dead auto-pin so the next DM re-runs the router.
|
|
142
114
|
|
|
143
|
-
|
|
115
|
+
## Step 5 — Status (paired, no handoff intent, or to inspect)
|
|
144
116
|
|
|
145
117
|
```
|
|
146
118
|
Discord pairing:
|
|
147
|
-
Tenant:
|
|
119
|
+
Tenant: <me.snowflake's tenant>
|
|
148
120
|
Default route: <me.project_name> (project_id <me.project_id>)
|
|
149
121
|
Snowflake: <me.snowflake>
|
|
150
122
|
Paired at: <me.paired_at>
|
|
151
123
|
Last DM in: <me.last_seen_inbound_at or "(never)">
|
|
152
124
|
```
|
|
153
125
|
|
|
154
|
-
If `handoff !== null
|
|
126
|
+
If `handoff !== null`, append:
|
|
155
127
|
|
|
156
128
|
```
|
|
157
129
|
Active handoff pin:
|
|
@@ -164,24 +136,24 @@ Offer: *"Run `greprag discord unhandoff` to clear the pin and revert DMs to defa
|
|
|
164
136
|
|
|
165
137
|
## Orchestrator-mode interaction rules
|
|
166
138
|
|
|
167
|
-
|
|
139
|
+
Apply only under `--orchestrator`. The orchestrator is a *passive observer* of tenant-wide traffic, NOT a forwarder.
|
|
168
140
|
|
|
169
|
-
- **
|
|
170
|
-
- **
|
|
171
|
-
- **Pull on demand.**
|
|
172
|
-
- **Standing orders.**
|
|
173
|
-
- **Intervention.**
|
|
174
|
-
- **Discovery.** `greprag inbox watchers` lists
|
|
141
|
+
- **Never proactively DM the user.** Events feed your context silently; surface them only on explicit ask or a standing order.
|
|
142
|
+
- **Never call plain `greprag inbox`** — its list defaults to `autoMarkRead: true`, burning recipient sessions' unread state. Use `greprag inbox --peek` (non-mutating) to inspect history, or rely on the accumulated watcher stream.
|
|
143
|
+
- **Pull on demand.** On "roundup" / "anything on <project>?", reconstruct from accumulated events; if context rotated, `greprag inbox --peek --all [--project <name>]`.
|
|
144
|
+
- **Standing orders.** "Ping me when <X>" → hold the directive in working context, scan each event, DM once on match, drop it.
|
|
145
|
+
- **Intervention.** "Pause paybot's webhook chip" → find the live session (`greprag inbox watchers`), then `greprag send --to <handle>@greprag.com/<session-8hex> "pause"` — that session's own watcher fires it into its turn.
|
|
146
|
+
- **Discovery.** `greprag inbox watchers` lists live armed sessions under the tenant — use it to resolve a project name to a session id.
|
|
175
147
|
|
|
176
|
-
## Hard rules
|
|
148
|
+
## Hard rules
|
|
177
149
|
|
|
178
|
-
- **
|
|
179
|
-
- **
|
|
180
|
-
- **
|
|
181
|
-
- **
|
|
182
|
-
- **
|
|
150
|
+
- **Status query → status, not arm.** "Am I paired?" shows state; it never writes a pin.
|
|
151
|
+
- **Session handoff writes the pin only** — the auto-arm hook owns the watcher. **Orchestrator is the sole arming path** (tenant-wide, wrapped under Monitor).
|
|
152
|
+
- **Replies via the CLI, not curl** — UTF-8, parser, and success-print all break otherwise.
|
|
153
|
+
- **Pin scope: one snowflake → one session at a time.** A new handoff supersedes the prior one silently; orchestrator mode does not change this.
|
|
154
|
+
- **In orchestrator mode, don't `npm i -g greprag@latest`** while the tenant-wide Monitor watcher runs — it kills the watcher mid-stream. Land the upgrade first, or stop the Monitor.
|
|
183
155
|
|
|
184
156
|
## Related skills
|
|
185
157
|
|
|
186
|
-
- `/greprag` — broader greprag setup (memory, inbox, corpus)
|
|
187
|
-
-
|
|
158
|
+
- `/greprag` — broader greprag setup (memory, inbox, corpus); carries the same handoff guidance for orchestrator-class sessions.
|
|
159
|
+
- `discord-notify` (separate global skill) sends one-off webhook pings for attention — a webhook, not this paired DM bridge.
|
package/skill/greprag/SKILL.md
CHANGED
|
@@ -70,13 +70,17 @@ Per-project flag flips (turn off recaps here, etc.): `docs/per-project-flags.md`
|
|
|
70
70
|
|
|
71
71
|
## Step 3 — Memory access (when configured)
|
|
72
72
|
|
|
73
|
-
Two access patterns
|
|
73
|
+
Two access patterns. They have OPPOSITE biases, so pick by intent — don't default to one.
|
|
74
74
|
|
|
75
|
-
**
|
|
75
|
+
- **Recent / open-ended** ("what did we do", "catch me up", "this week", "where did we leave off") → **recap or daily**. Sorted by time; immune to lexical noise.
|
|
76
|
+
- **Specific old bug / decision / topic** → **search**.
|
|
77
|
+
- **Search came back weak or doc-heavy** (low scores, or the hits are skill/chip boilerplate) → do NOT keep rephrasing. A topic-grep can't fix a recency problem. Switch to the chronological pull, or narrow with `--shape daily`. The CLI prints this hint for you when it detects it.
|
|
78
|
+
|
|
79
|
+
**A. Search by topic**
|
|
76
80
|
```bash
|
|
77
81
|
greprag memory search "<query>"
|
|
78
82
|
```
|
|
79
|
-
Query syntax is `websearch_to_tsquery`: AND is default, `OR` explicit, `"phrases"` quoted, `-negation` drops terms.
|
|
83
|
+
Query syntax is `websearch_to_tsquery`: AND is default, `OR` explicit, `"phrases"` quoted, `-negation` drops terms. Searches genuine session history by default (skill bodies, chip prompts, and continuation summaries are excluded — they used to dominate results). Each hit shows shape/role, age, and a confidence band; weak/doc-heavy results print a coaching hint. Flags: `--shape daily`, `--limit 10`, `--include-docs` (also search documentation turns), `--format json`.
|
|
80
84
|
|
|
81
85
|
**B. Recap by time** — an open-ended "catch me up". Most recent weekly + last seven daily summaries.
|
|
82
86
|
```bash
|
|
@@ -50,15 +50,11 @@ Substitute `<own-session-id>` from your SessionStart hook output.
|
|
|
50
50
|
**Cleanup discipline (HARD RULE):** chip prompts forbid `git clean`, `git reset --hard`, `git worktree remove`, `git checkout <other>`, raw `rm -rf` outside the worktree's tracked files.
|
|
51
51
|
````
|
|
52
52
|
|
|
53
|
-
## After spawning —
|
|
53
|
+
## After spawning — your watcher is already armed
|
|
54
54
|
|
|
55
|
-
The chip reports back to **your** session id via `greprag send
|
|
55
|
+
The chip reports back to **your** session id via `greprag send`, and the detection-gated `notify` hook keeps a session-scoped watcher live for you on any unarmed turn (`adr/session-id-awareness.md`). The chip's report is filed with `to_session_id = your session`, which that watcher delivers (filter: `to_session_id = session OR NULL`). So you do **not** arm a watcher per spawn — the one auto-armed watcher covers every chip you spawn. The spawn is the whole loop.
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
Monitor (persistent:true): `while true; do greprag inbox watch --session <your-8hex> --json; echo "[restart]" >&2; sleep 1; done`
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
`<your-8hex>` = your own session id (from SessionStart). Arming is what makes the chip's "done" report surface live instead of being lost — the spawn is only half the loop.
|
|
57
|
+
Safety net: if `greprag inbox watchers` shows none for your 8-hex (auto-arm disabled, or it died and the next turn hasn't re-armed yet), arm one under **Monitor (persistent:true)**: `while true; do greprag inbox watch --session <your-8hex> --json; echo "[restart]" >&2; sleep 1; done`. Never double-arm a session that already has a live watcher.
|
|
62
58
|
|
|
63
59
|
## Before composing
|
|
64
60
|
|