@gonzih/cc-wire 0.1.7 → 0.3.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/README.md +226 -202
- package/dist/cjs/channels.cjs +71 -4
- package/dist/cjs/index.cjs +1 -0
- package/dist/cjs/runtime.cjs +166 -0
- package/dist/esm/channels.d.ts +50 -3
- package/dist/esm/channels.d.ts.map +1 -1
- package/dist/esm/channels.js +60 -3
- package/dist/esm/channels.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/runtime.d.ts +90 -0
- package/dist/esm/runtime.d.ts.map +1 -0
- package/dist/esm/runtime.js +164 -0
- package/dist/esm/runtime.js.map +1 -0
- package/dist/esm/types.d.ts +9 -4
- package/dist/esm/types.d.ts.map +1 -1
- package/package.json +17 -4
package/README.md
CHANGED
|
@@ -1,70 +1,178 @@
|
|
|
1
1
|
# @gonzih/cc-wire
|
|
2
2
|
|
|
3
|
-
Single source of truth for Redis channel names, key patterns,
|
|
3
|
+
Single source of truth for Redis channel names, key patterns, message shapes, and
|
|
4
|
+
storage runtime across the cc-suite (`cc-agent`, `cc-discord`, `cc-tg`, `cc-agent-ui`).
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
v0.3.0+: ships a typed storage runtime — services call `createCcWire(redis)` and
|
|
7
|
+
never touch raw Redis or import key builders directly.
|
|
6
8
|
|
|
7
9
|
## Install
|
|
8
10
|
|
|
9
11
|
```sh
|
|
10
|
-
npm install @gonzih/cc-wire
|
|
12
|
+
npm install @gonzih/cc-wire ioredis
|
|
11
13
|
```
|
|
12
14
|
|
|
13
|
-
##
|
|
15
|
+
## Architecture — Service Ownership
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
┌──────────────────────────────────────────────────────────────────┐
|
|
19
|
+
│ cc-wire (this package) │
|
|
20
|
+
│ Owns: Redis key contracts, type definitions │
|
|
21
|
+
└─────────────────────────────────┬────────────────────────────────┘
|
|
22
|
+
│ imported by
|
|
23
|
+
┌───────────────────────┼───────────────────────┐
|
|
24
|
+
│ │ │
|
|
25
|
+
▼ ▼ ▼
|
|
26
|
+
┌─────────────────┐ ┌─────────────────────┐ ┌──────────────────┐
|
|
27
|
+
│ cc-discord │ │ cc-tg │ │ cc-agent │
|
|
28
|
+
│ │ │ │ │ │
|
|
29
|
+
│ Owns: │ │ Owns: │ │ Owns: │
|
|
30
|
+
│ per-namespace │ │ money-brain session│ │ job execution │
|
|
31
|
+
│ Claude sessions│ │ (single session) │ │ (spawn_agent) │
|
|
32
|
+
│ Discord bridge │ │ Telegram bridge │ │ no meta-agent │
|
|
33
|
+
│ │ │ │ │ lifecycle │
|
|
34
|
+
│ Workspace: │ │ Workspace: │ │ │
|
|
35
|
+
│ ~/cc-discord- │ │ ~/money-brain │ │ │
|
|
36
|
+
│ workspace/{ns} │ │ │ │ │
|
|
37
|
+
└─────────────────┘ └─────────────────────┘ └──────────────────┘
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Key principle:** cc-tg and cc-discord are completely isolated — they share no Redis keys,
|
|
41
|
+
no sessions, and no state. cc-agent is a pure job runner; it no longer manages meta-agent
|
|
42
|
+
lifecycle.
|
|
43
|
+
|
|
44
|
+
## Runtime — `createCcWire(redis)`
|
|
45
|
+
|
|
46
|
+
Pass an [ioredis](https://github.com/redis/ioredis) `Redis` instance and get back
|
|
47
|
+
fully-typed storage APIs per service. No raw Redis commands, no key string management.
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { createClient } from "ioredis";
|
|
51
|
+
import { createCcWire } from "@gonzih/cc-wire";
|
|
52
|
+
|
|
53
|
+
const redis = new Redis(process.env.REDIS_URL);
|
|
54
|
+
const wire = createCcWire(redis);
|
|
55
|
+
|
|
56
|
+
// cc-discord: per-namespace session management
|
|
57
|
+
await wire.discord.enqueue("simorgh", { id: "...", source: "discord", ... });
|
|
58
|
+
const msg = await wire.discord.dequeue("simorgh"); // ChatMessage | null
|
|
59
|
+
await wire.discord.publishOutgoing("simorgh", msg); // PUBLISH + log + LTRIM
|
|
60
|
+
wire.discord.subscribeOutgoing("simorgh", (msg) => console.log(msg)); // live stream
|
|
61
|
+
await wire.discord.setStatus("simorgh", { isTyping: true, ... });
|
|
62
|
+
const status = await wire.discord.getStatus("simorgh"); // MetaAgentStatus | null
|
|
63
|
+
await wire.discord.writeChatLog("simorgh", msg);
|
|
64
|
+
const history = await wire.discord.getChatLog("simorgh", 50); // chronological
|
|
65
|
+
await wire.discord.notify("simorgh", { text: "job done" });
|
|
66
|
+
const notif = await wire.discord.pollNotify("simorgh"); // NotificationPayload | null
|
|
67
|
+
await wire.discord.registerChannel("1234567890", "simorgh", repoUrl);
|
|
68
|
+
const chan = await wire.discord.getChannel("1234567890");
|
|
69
|
+
const channels = await wire.discord.listChannels();
|
|
70
|
+
|
|
71
|
+
// cc-tg: single money-brain session
|
|
72
|
+
await wire.tg.publishOutgoing(msg);
|
|
73
|
+
wire.tg.subscribeOutgoing((msg) => console.log(msg));
|
|
74
|
+
await wire.tg.notify({ text: "job done" });
|
|
75
|
+
const tgNotif = await wire.tg.pollNotify();
|
|
76
|
+
|
|
77
|
+
// cc-agent: job queue
|
|
78
|
+
await wire.jobs.enqueue({ id: "job-001", repoUrl: "...", task: "..." });
|
|
79
|
+
const job = await wire.jobs.getStatus("job-001");
|
|
80
|
+
await wire.jobs.publishDone("job-001", { status: "done", score: 1.0 });
|
|
81
|
+
|
|
82
|
+
// shared master token
|
|
83
|
+
await wire.token.setMaster("my-token");
|
|
84
|
+
const token = await wire.token.getMaster();
|
|
85
|
+
|
|
86
|
+
// raw redis escape hatch (migration only)
|
|
87
|
+
const raw = wire._redis;
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Key builders (v0.2.x compat, use runtime in new code)
|
|
14
91
|
|
|
15
92
|
```typescript
|
|
16
93
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
94
|
+
// cc-discord service keys
|
|
95
|
+
discordMetaInputKey,
|
|
96
|
+
discordMetaStatusKey,
|
|
97
|
+
discordChatOutgoing,
|
|
98
|
+
discordChatLog,
|
|
99
|
+
discordChatIncoming,
|
|
100
|
+
discordNotify,
|
|
101
|
+
// cc-tg service keys
|
|
102
|
+
tgChatOutgoing,
|
|
103
|
+
tgChatIncoming,
|
|
104
|
+
tgNotify,
|
|
105
|
+
// Service ownership constants
|
|
106
|
+
CC_DISCORD_WORKSPACE_ROOT,
|
|
107
|
+
CC_TG_WORKSPACE,
|
|
108
|
+
// Job keys (cc-agent)
|
|
22
109
|
jobKey,
|
|
23
110
|
jobIndexKey,
|
|
24
|
-
|
|
111
|
+
// Shared utility
|
|
112
|
+
notifyPublishCommand,
|
|
25
113
|
TTL,
|
|
26
114
|
CAP,
|
|
27
|
-
type
|
|
28
|
-
type
|
|
115
|
+
type ChatMessage,
|
|
116
|
+
type SpawnParams,
|
|
29
117
|
} from "@gonzih/cc-wire";
|
|
30
118
|
|
|
31
|
-
//
|
|
32
|
-
const
|
|
33
|
-
|
|
119
|
+
// cc-discord — per-namespace session keys
|
|
120
|
+
const inputQueue = discordMetaInputKey("simorgh-mobile-app");
|
|
121
|
+
// => "cca:discord:meta:simorgh-mobile-app:input"
|
|
122
|
+
const statusKey = discordMetaStatusKey("simorgh-mobile-app");
|
|
123
|
+
// => "cca:discord:meta:simorgh-mobile-app:status"
|
|
124
|
+
const notifyChan = discordNotify("simorgh-mobile-app");
|
|
125
|
+
// => "cca:discord:notify:simorgh-mobile-app"
|
|
126
|
+
|
|
127
|
+
// cc-tg — single dedicated money-brain session
|
|
128
|
+
const tgOut = tgChatOutgoing(); // "cca:tg:chat:outgoing"
|
|
129
|
+
const tgIn = tgChatIncoming(); // "cca:tg:chat:incoming"
|
|
130
|
+
const tgNtfy = tgNotify(); // "cca:tg:notify"
|
|
131
|
+
|
|
132
|
+
// Workspace paths
|
|
133
|
+
const discordWs = `${process.env.HOME}/${CC_DISCORD_WORKSPACE_ROOT}/simorgh-mobile-app`;
|
|
134
|
+
const tgWs = `${process.env.HOME}/${CC_TG_WORKSPACE}`;
|
|
135
|
+
|
|
136
|
+
// ChatMessage — with service field
|
|
137
|
+
const msg: ChatMessage = {
|
|
138
|
+
id: crypto.randomUUID(),
|
|
139
|
+
source: "discord",
|
|
140
|
+
service: "cc-discord",
|
|
141
|
+
role: "user",
|
|
142
|
+
content: "deploy the app",
|
|
143
|
+
namespace: "simorgh-mobile-app",
|
|
144
|
+
timestamp: new Date().toISOString(),
|
|
145
|
+
};
|
|
146
|
+
```
|
|
34
147
|
|
|
35
|
-
|
|
36
|
-
const job = jobKey("abc-123"); // "cca:job:abc-123"
|
|
37
|
-
const idx = jobIndexKey("myns"); // "cca:jobs:myns"
|
|
148
|
+
## Key Reference
|
|
38
149
|
|
|
39
|
-
|
|
40
|
-
const chan = notifyChannel("myns"); // "cca:notify:myns"
|
|
41
|
-
const list = notifyListKey("myns"); // "cca:notify:myns" (same key, dual-purpose)
|
|
150
|
+
### cc-discord Keys
|
|
42
151
|
|
|
43
|
-
|
|
44
|
-
const payload: NotificationPayload = {
|
|
45
|
-
text: "Build finished ✓",
|
|
46
|
-
routing: ["discord"], // omit or leave empty for all transports
|
|
47
|
-
};
|
|
152
|
+
cc-discord owns all namespace-scoped Claude sessions. All keys live under `cca:discord:`.
|
|
48
153
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
154
|
+
| Builder | Pattern | Redis type | Owner | Description |
|
|
155
|
+
|---|---|---|---|---|
|
|
156
|
+
| `discordMetaInputKey(ns)` | `cca:discord:meta:{ns}:input` | LIST | cc-discord | Input queue for namespace session (RPUSH/RPOP). |
|
|
157
|
+
| `discordMetaStatusKey(ns)` | `cca:discord:meta:{ns}:status` | STRING (JSON) | cc-discord | Live session status (typing, tool, etc.), TTL 7d. |
|
|
158
|
+
| `discordChatOutgoing(ns)` | `cca:discord:chat:outgoing:{ns}` | CHANNEL | cc-discord | Outgoing messages to UI (pub/sub). |
|
|
159
|
+
| `discordChatLog(ns)` | `cca:discord:chat:log:{ns}` | LIST | cc-discord | Chat history, capped at 500, LIFO. |
|
|
160
|
+
| `discordChatIncoming(ns)` | `cca:discord:chat:incoming:{ns}` | CHANNEL | cc-discord | Incoming messages from UI/Discord (pub/sub). |
|
|
161
|
+
| `discordNotify(ns)` | `cca:discord:notify:{ns}` | CHANNEL + LIST | cc-discord | Job-completion notifications (PUBLISH + RPOP poll). |
|
|
52
162
|
|
|
53
|
-
|
|
54
|
-
TTL.JOB_SECONDS // 604800
|
|
55
|
-
CAP.CHAT_LOG // 500
|
|
56
|
-
```
|
|
163
|
+
### cc-tg Keys
|
|
57
164
|
|
|
58
|
-
|
|
165
|
+
cc-tg owns a single dedicated `money-brain` session with no namespace scoping.
|
|
59
166
|
|
|
60
|
-
|
|
167
|
+
| Builder | Pattern | Redis type | Owner | Description |
|
|
168
|
+
|---|---|---|---|---|
|
|
169
|
+
| `tgChatOutgoing()` | `cca:tg:chat:outgoing` | CHANNEL | cc-tg | Outgoing messages to UI (pub/sub). |
|
|
170
|
+
| `tgChatIncoming()` | `cca:tg:chat:incoming` | CHANNEL | cc-tg | Incoming messages from UI/Telegram (pub/sub). |
|
|
171
|
+
| `tgNotify()` | `cca:tg:notify` | CHANNEL + LIST | cc-tg | Job-completion notifications (PUBLISH + RPOP poll). |
|
|
61
172
|
|
|
62
|
-
|
|
63
|
-
|---|---|---|---|
|
|
64
|
-
| `wikiKey(repoSlug)` | `cca:wiki:{repoSlug}` | HASH | Per-repo wiki pages. Field = page name, value = markdown. |
|
|
65
|
-
| `wikiUpdatedKey(repoSlug)` | `cca:wiki:{repoSlug}:updated` | STRING | ISO timestamp of last wiki update. |
|
|
173
|
+
### cc-agent Keys (jobs only)
|
|
66
174
|
|
|
67
|
-
|
|
175
|
+
cc-agent is a pure job runner. It no longer manages meta-agent lifecycle.
|
|
68
176
|
|
|
69
177
|
| Builder | Pattern | Redis type | Description |
|
|
70
178
|
|---|---|---|---|
|
|
@@ -76,183 +184,80 @@ CAP.CHAT_LOG // 500
|
|
|
76
184
|
| `jobDoneChannel(id)` | `cca:job:done:{id}` | CHANNEL | Job completion pub/sub. |
|
|
77
185
|
| `jobDoneQueueKey(id)` | `cca:job:done:{id}:queue` | LIST | LPUSH/BLPOP queue for `wait_for_job`, TTL 7d. |
|
|
78
186
|
| `jobIndexKey(ns)` | `cca:jobs:{ns}` | SET | Job IDs per namespace. |
|
|
187
|
+
| `EVENT_STREAM` | `cca:event-stream` | STREAM | Job status events (XADD/XREADGROUP). |
|
|
188
|
+
| `COORDINATOR_GROUP` | `coordinator` | — | Consumer group name. |
|
|
79
189
|
|
|
80
|
-
###
|
|
81
|
-
|
|
82
|
-
| Constant | Value | Description |
|
|
83
|
-
|---|---|---|
|
|
84
|
-
| `EVENT_STREAM` | `cca:event-stream` | Redis Stream — job status events. |
|
|
85
|
-
| `COORDINATOR_GROUP` | `coordinator` | Consumer group name. |
|
|
86
|
-
|
|
87
|
-
### Notify / Chat
|
|
88
|
-
|
|
89
|
-
| Builder | Pattern | Description |
|
|
90
|
-
|---|---|---|
|
|
91
|
-
| `notifyChannel(ns)` | `cca:notify:{ns}` | CHANNEL — coordinator publishes job completion. |
|
|
92
|
-
| `notifyListKey(ns)` | `cca:notify:{ns}` | LIST — delivery queue (RPUSH/RPOP). Same key as channel (safe — Redis pub/sub and list namespaces are independent). |
|
|
93
|
-
| `notifyLogKey(ns)` | `cca:notify-log:{ns}` | LIST — persistent audit log, capped at `CAP.NOTIFY_LOG` (100). |
|
|
94
|
-
| `notifyPublishCommand(ns, payload)` | — | Returns a `redis-cli PUBLISH` shell command string. Useful for cron prompts. |
|
|
95
|
-
| `chatLogKey(ns)` | `cca:chat:log:{ns}` | LIST — chat history, capped at `CAP.CHAT_LOG` (500), LIFO. |
|
|
96
|
-
| `chatIncomingChannel(ns)` | `cca:chat:incoming:{ns}` | CHANNEL — UI → cc-tg. |
|
|
97
|
-
| `chatOutgoingChannel(ns)` | `cca:chat:outgoing:{ns}` | CHANNEL — cc-tg → UI. |
|
|
98
|
-
|
|
99
|
-
### Meta-Agent
|
|
100
|
-
|
|
101
|
-
| Builder | Pattern | Description |
|
|
102
|
-
|---|---|---|
|
|
103
|
-
| `metaKey(ns)` | `cca:meta:{ns}` | STRING (JSON) — MetaAgentInfo state, TTL 30d. |
|
|
104
|
-
| `metaInputKey(ns)` | `cca:meta:{ns}:input` | LIST — input queue (RPUSH/RPOP). |
|
|
105
|
-
| `metaAgentStatusKey(ns)` | `cca:meta-agent:status:{ns}` | STRING (JSON) — live status, TTL 7d. |
|
|
106
|
-
| `META_AGENTS_INDEX` | `cca:meta:agents:index` | SET — canonical registry. |
|
|
107
|
-
|
|
108
|
-
### Wiki
|
|
109
|
-
|
|
110
|
-
| Builder | Pattern | Description |
|
|
111
|
-
|---|---|---|
|
|
112
|
-
| `wikiKey(repoSlug)` | `cca:wiki:{repoSlug}` | HASH — wiki pages. Field = page name, value = markdown. |
|
|
113
|
-
| `wikiUpdatedKey(repoSlug)` | `cca:wiki:{repoSlug}:updated` | STRING — ISO timestamp of last update. |
|
|
114
|
-
|
|
115
|
-
### Profiles
|
|
116
|
-
|
|
117
|
-
| Builder | Pattern | Description |
|
|
118
|
-
|---|---|---|
|
|
119
|
-
| `profileKey(name)` | `cca:profile:{name}` | STRING (JSON) — saved Profile. |
|
|
120
|
-
| `PROFILES_INDEX` | `cca:profiles:index` | SET — profile names. |
|
|
121
|
-
|
|
122
|
-
### Crons
|
|
123
|
-
|
|
124
|
-
| Builder | Pattern | Description |
|
|
125
|
-
|---|---|---|
|
|
126
|
-
| `cronsKey(ns)` | `cca:crons:{ns}` | STRING (JSON array) — cron definitions. |
|
|
127
|
-
| `deletedCronsKey(ns)` | `cca:deleted-crons:{ns}` | SET — tombstone IDs, TTL 7d. |
|
|
128
|
-
|
|
129
|
-
### Learnings
|
|
130
|
-
|
|
131
|
-
| Builder | Pattern | Description |
|
|
132
|
-
|---|---|---|
|
|
133
|
-
| `learningsKey(ns)` | `cca:learnings:{ns}` | LIST — learnings (LPUSH, capped at `CAP.LEARNINGS` = 50), TTL 90d, LIFO. |
|
|
134
|
-
|
|
135
|
-
### Plans / Coordinator
|
|
136
|
-
|
|
137
|
-
| Builder | Pattern | Description |
|
|
138
|
-
|---|---|---|
|
|
139
|
-
| `planKey(id)` | `cca:plan:{id}` | STRING (JSON) — PlanRecord, TTL 30d. |
|
|
140
|
-
| `coordinatorPlanKey(jobId)` | `cca:coordinator:plan:{jobId}` | STRING — coordinator plan JSON. |
|
|
141
|
-
|
|
142
|
-
### Swarm
|
|
190
|
+
### Shared / Utility Keys
|
|
143
191
|
|
|
144
|
-
| Builder | Pattern | Description |
|
|
145
|
-
|
|
146
|
-
| `
|
|
147
|
-
| `
|
|
148
|
-
|
|
149
|
-
|
|
192
|
+
| Builder / Constant | Pattern | Redis type | Description |
|
|
193
|
+
|---|---|---|---|
|
|
194
|
+
| `notifyChannel(ns)` | `cca:notify:{ns}` | CHANNEL | Legacy coordinator notify (use service-scoped builders for new code). |
|
|
195
|
+
| `notifyListKey(ns)` | `cca:notify:{ns}` | LIST | Same key — delivery queue. |
|
|
196
|
+
| `notifyLogKey(ns)` | `cca:notify-log:{ns}` | LIST | Notification audit log, capped at 100, LIFO. |
|
|
197
|
+
| `notifyPublishCommand(ns, payload)` | — | — | Returns a `redis-cli PUBLISH` shell command string. Useful for cron prompts. |
|
|
198
|
+
| `planKey(id)` | `cca:plan:{id}` | STRING (JSON) | PlanRecord, TTL 30d. |
|
|
199
|
+
| `coordinatorPlanKey(jobId)` | `cca:coordinator:plan:{jobId}` | STRING | Coordinator plan JSON. |
|
|
200
|
+
| `profileKey(name)` | `cca:profile:{name}` | STRING (JSON) | Saved Profile. |
|
|
201
|
+
| `PROFILES_INDEX` | `cca:profiles:index` | SET | Profile names. |
|
|
202
|
+
| `cronsKey(ns)` | `cca:crons:{ns}` | STRING (JSON array) | Cron definitions. |
|
|
203
|
+
| `deletedCronsKey(ns)` | `cca:deleted-crons:{ns}` | SET | Tombstone IDs, TTL 7d. |
|
|
204
|
+
| `learningsKey(ns)` | `cca:learnings:{ns}` | LIST | Learnings (LPUSH, capped at 50), TTL 90d, LIFO. |
|
|
205
|
+
| `wikiKey(repoSlug)` | `cca:wiki:{repoSlug}` | HASH | Wiki pages. Field = page name, value = markdown. |
|
|
206
|
+
| `wikiUpdatedKey(repoSlug)` | `cca:wiki:{repoSlug}:updated` | STRING | ISO timestamp of last wiki update. |
|
|
207
|
+
| `swarmKey(id)` | `cca:swarm:{id}` | STRING (JSON) | SwarmRecord. |
|
|
208
|
+
| `SWARM_REQUESTS_KEY` | `cca:swarm:requests` | LIST | Swarm task request queue (LPUSH). |
|
|
209
|
+
| `CC_AGENT_VERSION_KEY` | `cca:meta:cc-agent:version` | STRING | Running cc-agent version. |
|
|
210
|
+
| `CC_TG_VERSION_KEY` | `cca:meta:cc-tg:version` | STRING | Running cc-tg version. |
|
|
211
|
+
| `TOKEN_INDEX_KEY` | `cca:token:index` | STRING | Token rotation index. |
|
|
212
|
+
| `VOICE_PENDING_KEY` | `voice:pending` | LIST | Transcription pending queue (cc-tg only). |
|
|
213
|
+
| `VOICE_FAILED_KEY` | `voice:failed` | LIST | Failure log, TTL 48h (cc-tg only). |
|
|
150
214
|
|
|
151
|
-
|
|
152
|
-
|---|---|---|
|
|
153
|
-
| `CC_AGENT_VERSION_KEY` | `cca:meta:cc-agent:version` | STRING — running cc-agent version. |
|
|
154
|
-
| `CC_TG_VERSION_KEY` | `cca:meta:cc-tg:version` | STRING — running cc-tg version. |
|
|
155
|
-
| `TOKEN_INDEX_KEY` | `cca:token:index` | STRING — token rotation index. |
|
|
215
|
+
### Service Ownership Constants
|
|
156
216
|
|
|
157
|
-
|
|
217
|
+
```typescript
|
|
218
|
+
CC_DISCORD_WORKSPACE_ROOT // "cc-discord-workspace"
|
|
219
|
+
// Usage: `${HOME}/${CC_DISCORD_WORKSPACE_ROOT}/{namespace}`
|
|
158
220
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
| `VOICE_FAILED_KEY` | `voice:failed` | LIST — failure log, TTL 48h. |
|
|
221
|
+
CC_TG_WORKSPACE // "money-brain"
|
|
222
|
+
// Usage: `${HOME}/${CC_TG_WORKSPACE}`
|
|
223
|
+
```
|
|
163
224
|
|
|
164
225
|
## Constants
|
|
165
226
|
|
|
166
227
|
```typescript
|
|
167
|
-
TTL.JOB_SECONDS
|
|
168
|
-
TTL.PLAN_SECONDS
|
|
169
|
-
TTL.LEARNINGS_SECONDS
|
|
170
|
-
TTL.VOICE_FAILED_SECONDS
|
|
171
|
-
|
|
172
|
-
CAP.NOTIFY_LOG
|
|
173
|
-
CAP.CHAT_LOG
|
|
174
|
-
CAP.LEARNINGS
|
|
175
|
-
CAP.EVENT_STREAM
|
|
176
|
-
|
|
177
|
-
TIMING.COORDINATOR_POLL_MS
|
|
178
|
-
TIMING.DEPENDENCY_TICK_MS
|
|
179
|
-
TIMING.INPUT_POLL_INTERVAL_MS
|
|
180
|
-
TIMING.META_AGENT_FLUSH_DELAY_MS
|
|
228
|
+
TTL.JOB_SECONDS // 604800 (7 days)
|
|
229
|
+
TTL.PLAN_SECONDS // 2592000 (30 days)
|
|
230
|
+
TTL.LEARNINGS_SECONDS // 7776000 (90 days)
|
|
231
|
+
TTL.VOICE_FAILED_SECONDS // 172800 (48 hours)
|
|
232
|
+
|
|
233
|
+
CAP.NOTIFY_LOG // 100
|
|
234
|
+
CAP.CHAT_LOG // 500
|
|
235
|
+
CAP.LEARNINGS // 50
|
|
236
|
+
CAP.EVENT_STREAM // 500
|
|
237
|
+
|
|
238
|
+
TIMING.COORDINATOR_POLL_MS // 2000
|
|
239
|
+
TIMING.DEPENDENCY_TICK_MS // 3000
|
|
240
|
+
TIMING.INPUT_POLL_INTERVAL_MS // 3000
|
|
241
|
+
TIMING.META_AGENT_FLUSH_DELAY_MS // 1500
|
|
181
242
|
```
|
|
182
243
|
|
|
183
244
|
## Notification Routing
|
|
184
245
|
|
|
185
|
-
|
|
186
|
-
receives the notification is determined by `spawning_namespace` on the spawn call:
|
|
187
|
-
|
|
188
|
-
```
|
|
189
|
-
coordinator: targetNamespace = job.spawningNamespace ?? coordinator.namespace
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
If `spawning_namespace` is absent the notification falls back to the coordinator's own
|
|
193
|
-
namespace (typically `"money-brain"`), which is the source of the routing bug described
|
|
194
|
-
below.
|
|
246
|
+
Each service subscribes to its own notify channel for job-completion notifications:
|
|
195
247
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
```
|
|
199
|
-
Discord #simorgh-mobile-app
|
|
200
|
-
→ cc-discord routeToMetaAgent("simorgh-mobile-app")
|
|
201
|
-
→ RPUSH cca:meta:simorgh-mobile-app:input
|
|
202
|
-
→ cc-agent meta-agent picks up message
|
|
203
|
-
→ spawn_agent { repoUrl, task } ← no spawning_namespace
|
|
204
|
-
→ Coordinator: targetNs = coordinator.namespace = "money-brain"
|
|
205
|
-
→ PUBLISH cca:notify:money-brain
|
|
206
|
-
→ cc-tg (subscribed to cca:notify:money-brain) → Telegram ✗
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
Two bugs combine to produce this:
|
|
210
|
-
|
|
211
|
-
| Bug | Repo | Description |
|
|
248
|
+
| Service | Notify key builder | Pattern |
|
|
212
249
|
|---|---|---|
|
|
213
|
-
|
|
|
214
|
-
|
|
|
250
|
+
| cc-discord | `discordNotify(ns)` | `cca:discord:notify:{ns}` |
|
|
251
|
+
| cc-tg | `tgNotify()` | `cca:tg:notify` |
|
|
215
252
|
|
|
216
|
-
|
|
253
|
+
Both use the same dual-purpose pattern: the coordinator PUBLISHes on the channel (pub/sub)
|
|
254
|
+
and also RPUSHes to the same key as a list (poll fallback). Redis pub/sub and list namespaces
|
|
255
|
+
are independent so this is safe.
|
|
217
256
|
|
|
218
|
-
|
|
219
|
-
Discord #simorgh-mobile-app
|
|
220
|
-
→ cc-discord routeToMetaAgent("simorgh-mobile-app")
|
|
221
|
-
→ RPUSH cca:meta:simorgh-mobile-app:input
|
|
222
|
-
→ cc-agent meta-agent picks up message
|
|
223
|
-
→ spawn_agent { repoUrl, task }
|
|
224
|
-
← cc-agent MCP injects spawning_namespace = "simorgh-mobile-app"
|
|
225
|
-
→ Coordinator: targetNs = "simorgh-mobile-app"
|
|
226
|
-
→ PUBLISH cca:notify:simorgh-mobile-app
|
|
227
|
-
→ cc-discord (subscribed to cca:notify:simorgh-mobile-app)
|
|
228
|
-
→ Discord #simorgh-mobile-app ✓
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
### Redis keys involved
|
|
232
|
-
|
|
233
|
-
| Step | Key / Channel | Builder |
|
|
234
|
-
|---|---|---|
|
|
235
|
-
| Input to meta-agent | `cca:meta:{ns}:input` | `metaInputKey(ns)` |
|
|
236
|
-
| Job completion pub/sub | `cca:notify:{ns}` | `notifyChannel(ns)` |
|
|
237
|
-
| Job completion list (5 s poll fallback) | `cca:notify:{ns}` | `notifyListKey(ns)` |
|
|
238
|
-
| Notification audit log | `cca:notify-log:{ns}` | `notifyLogKey(ns)` |
|
|
257
|
+
### Setting spawning_namespace on spawn calls
|
|
239
258
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
**`gonzih/cc-agent`** (Bug A): When the `spawn_agent` MCP tool is called from within a
|
|
243
|
-
running meta-agent context, the MCP handler should auto-inject
|
|
244
|
-
`spawning_namespace: metaAgentNamespace` if the caller has not already set it. The
|
|
245
|
-
meta-agent's namespace is known to cc-agent at the time of the call.
|
|
246
|
-
|
|
247
|
-
**`gonzih/cc-discord`** (Bug B): The notifier must subscribe to `cca:notify:{ns}` for
|
|
248
|
-
every namespace registered in `routedChannelIds`, and route incoming notifications to
|
|
249
|
-
the corresponding Discord channel — not just the single default
|
|
250
|
-
`DISCORD_NOTIFY_CHANNEL_ID`.
|
|
251
|
-
|
|
252
|
-
### Setting spawning_namespace on manual spawn calls
|
|
253
|
-
|
|
254
|
-
Any caller that spawns jobs and wants notifications routed back to itself must set
|
|
255
|
-
`spawning_namespace` on the `SpawnParams`:
|
|
259
|
+
Any caller spawning jobs via cc-agent must set `spawning_namespace` on `SpawnParams` so
|
|
260
|
+
the coordinator routes completion notifications to the right service:
|
|
256
261
|
|
|
257
262
|
```typescript
|
|
258
263
|
import type { SpawnParams } from "@gonzih/cc-wire";
|
|
@@ -260,15 +265,34 @@ import type { SpawnParams } from "@gonzih/cc-wire";
|
|
|
260
265
|
const params: SpawnParams = {
|
|
261
266
|
repoUrl: "https://github.com/org/repo",
|
|
262
267
|
task: "fix the build",
|
|
263
|
-
spawning_namespace: "simorgh-mobile-app", //
|
|
268
|
+
spawning_namespace: "simorgh-mobile-app", // cc-agent uses this to route the notify
|
|
264
269
|
};
|
|
265
270
|
```
|
|
266
271
|
|
|
272
|
+
## Migration from v0.1.x
|
|
273
|
+
|
|
274
|
+
If you were using the old cc-agent-owned meta-agent keys, replace them:
|
|
275
|
+
|
|
276
|
+
| Old (deprecated) | New | Owner |
|
|
277
|
+
|---|---|---|
|
|
278
|
+
| `metaInputKey(ns)` → `cca:meta:{ns}:input` | `discordMetaInputKey(ns)` → `cca:discord:meta:{ns}:input` | cc-discord |
|
|
279
|
+
| `metaAgentStatusKey(ns)` → `cca:meta-agent:status:{ns}` | `discordMetaStatusKey(ns)` → `cca:discord:meta:{ns}:status` | cc-discord |
|
|
280
|
+
| `META_AGENTS_INDEX` → `cca:meta:agents:index` | — (cc-discord maintains its own registry) | cc-discord |
|
|
281
|
+
| `chatLogKey(ns)` → `cca:chat:log:{ns}` | `discordChatLog(ns)` → `cca:discord:chat:log:{ns}` | cc-discord |
|
|
282
|
+
| `chatOutgoingChannel(ns)` → `cca:chat:outgoing:{ns}` | `discordChatOutgoing(ns)` → `cca:discord:chat:outgoing:{ns}` | cc-discord |
|
|
283
|
+
| `chatIncomingChannel(ns)` → `cca:chat:incoming:{ns}` | `discordChatIncoming(ns)` → `cca:discord:chat:incoming:{ns}` | cc-discord |
|
|
284
|
+
| `notifyChannel(ns)` (Discord use) | `discordNotify(ns)` | cc-discord |
|
|
285
|
+
| `notifyChannel(ns)` / `notifyListKey(ns)` (tg use) | `tgNotify()` | cc-tg |
|
|
286
|
+
|
|
287
|
+
The deprecated exports remain in v0.2.x for migration; they will be removed in v0.3.x.
|
|
288
|
+
|
|
267
289
|
## Development
|
|
268
290
|
|
|
269
291
|
```sh
|
|
270
|
-
npm run build
|
|
271
|
-
npm test
|
|
292
|
+
npm run build # compile ESM + CJS
|
|
293
|
+
npm test # run all tests (channels: node --test; runtime: vitest)
|
|
294
|
+
npm run test:channels # key builder tests only (node --test + tsx)
|
|
295
|
+
npm run test:runtime # runtime tests only (vitest + ioredis-mock)
|
|
272
296
|
```
|
|
273
297
|
|
|
274
298
|
## License
|
package/dist/cjs/channels.cjs
CHANGED
|
@@ -12,13 +12,29 @@
|
|
|
12
12
|
* All values match the exact strings used in the source repos as of 2026-05.
|
|
13
13
|
*/
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.
|
|
15
|
+
exports.swarmKey = exports.wikiUpdatedKey = exports.wikiKey = exports.deletedCronsKey = exports.cronsKey = exports.learningsKey = exports.tgNotify = exports.tgChatIncoming = exports.tgChatOutgoing = exports.discordNotify = exports.discordChatIncoming = exports.discordChatLog = exports.discordChatOutgoing = exports.discordMetaStatusKey = exports.discordMetaInputKey = exports.metaAgentStatusKey = exports.metaInputKey = exports.metaKey = exports.chatOutgoingChannel = exports.chatIncomingChannel = exports.chatLogKey = exports.notifyPublishCommand = exports.notifyLogKey = exports.notifyListKey = exports.notifyChannel = exports.profileKey = exports.planKey = exports.coordinatorPlanKey = exports.jobDoneQueueKey = exports.jobDoneChannel = exports.jobOutputLiveChannel = exports.jobInputKey = exports.jobSignalKey = exports.jobOutputKey = exports.jobKey = exports.jobIndexKey = exports.SWARM_REQUESTS_KEY = exports.VOICE_FAILED_KEY = exports.VOICE_PENDING_KEY = exports.CC_TG_VERSION_KEY = exports.JOB_INDEX_PREFIX = exports.JOB_INDEX_GLOB = exports.CC_AGENT_VERSION_KEY = exports.TOKEN_INDEX_KEY = exports.PROFILES_INDEX = exports.META_AGENTS_INDEX = exports.COORDINATOR_GROUP = exports.EVENT_STREAM = exports.CC_TG_WORKSPACE = exports.CC_DISCORD_WORKSPACE_ROOT = void 0;
|
|
16
|
+
exports.TIMING = exports.CAP = exports.TTL = void 0;
|
|
17
|
+
// ─── Service Ownership Constants ─────────────────────────────────────────────
|
|
18
|
+
/**
|
|
19
|
+
* Root directory (relative to HOME) where cc-discord checks out per-namespace
|
|
20
|
+
* workspace clones: `~/cc-discord-workspace/{namespace}`.
|
|
21
|
+
*/
|
|
22
|
+
exports.CC_DISCORD_WORKSPACE_ROOT = "cc-discord-workspace";
|
|
23
|
+
/**
|
|
24
|
+
* Directory name (relative to HOME) of the money-brain repo used by cc-tg
|
|
25
|
+
* for its dedicated session: `~/money-brain`.
|
|
26
|
+
*/
|
|
27
|
+
exports.CC_TG_WORKSPACE = "money-brain";
|
|
16
28
|
// ─── Static Keys ─────────────────────────────────────────────────────────────
|
|
17
29
|
/** Redis Stream written by cc-agent on every job status change. */
|
|
18
30
|
exports.EVENT_STREAM = "cca:event-stream";
|
|
19
31
|
/** Consumer group name used by the coordinator to read from EVENT_STREAM. */
|
|
20
32
|
exports.COORDINATOR_GROUP = "coordinator";
|
|
21
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* SET — canonical registry of meta-agent namespaces.
|
|
35
|
+
* @deprecated cc-discord now maintains its own namespace registry.
|
|
36
|
+
* This key is no longer written by the cc-suite; kept for migration only.
|
|
37
|
+
*/
|
|
22
38
|
exports.META_AGENTS_INDEX = "cca:meta:agents:index";
|
|
23
39
|
/** SET — index of saved profile names. */
|
|
24
40
|
exports.PROFILES_INDEX = "cca:profiles:index";
|
|
@@ -118,12 +134,63 @@ exports.chatOutgoingChannel = chatOutgoingChannel;
|
|
|
118
134
|
/** STRING (JSON) — MetaAgentInfo state, TTL 30 days. */
|
|
119
135
|
const metaKey = (namespace) => `cca:meta:${namespace}`;
|
|
120
136
|
exports.metaKey = metaKey;
|
|
121
|
-
/**
|
|
137
|
+
/**
|
|
138
|
+
* LIST — input queue for a meta-agent (RPUSH by cc-tg, RPOP by cc-agent).
|
|
139
|
+
* @deprecated Use `discordMetaInputKey(ns)` — cc-discord now owns namespace sessions directly.
|
|
140
|
+
*/
|
|
122
141
|
const metaInputKey = (namespace) => `cca:meta:${namespace}:input`;
|
|
123
142
|
exports.metaInputKey = metaInputKey;
|
|
124
|
-
/**
|
|
143
|
+
/**
|
|
144
|
+
* STRING (JSON) — live meta-agent status (typing, tool, etc.), TTL 7 days.
|
|
145
|
+
* @deprecated Use `discordMetaStatusKey(ns)` — cc-discord now owns namespace sessions directly.
|
|
146
|
+
*/
|
|
125
147
|
const metaAgentStatusKey = (namespace) => `cca:meta-agent:status:${namespace}`;
|
|
126
148
|
exports.metaAgentStatusKey = metaAgentStatusKey;
|
|
149
|
+
// ─── cc-discord Keys (dynamic) ───────────────────────────────────────────────
|
|
150
|
+
//
|
|
151
|
+
// cc-discord owns all namespace-scoped Claude sessions. It reads from the
|
|
152
|
+
// meta input queue, writes status, and publishes/subscribes to chat/notify
|
|
153
|
+
// channels — all under the "cca:discord:" prefix to avoid collisions with
|
|
154
|
+
// the old cc-agent-owned keys.
|
|
155
|
+
/** LIST — input queue for a cc-discord-managed namespace session (RPUSH/RPOP). */
|
|
156
|
+
const discordMetaInputKey = (namespace) => `cca:discord:meta:${namespace}:input`;
|
|
157
|
+
exports.discordMetaInputKey = discordMetaInputKey;
|
|
158
|
+
/** STRING (JSON) — live status for a cc-discord-managed namespace session, TTL 7 days. */
|
|
159
|
+
const discordMetaStatusKey = (namespace) => `cca:discord:meta:${namespace}:status`;
|
|
160
|
+
exports.discordMetaStatusKey = discordMetaStatusKey;
|
|
161
|
+
/** CHANNEL — cc-discord → UI outgoing chat messages (pub/sub). */
|
|
162
|
+
const discordChatOutgoing = (namespace) => `cca:discord:chat:outgoing:${namespace}`;
|
|
163
|
+
exports.discordChatOutgoing = discordChatOutgoing;
|
|
164
|
+
/** LIST — cc-discord chat history (LPUSH capped at 500, LIFO). */
|
|
165
|
+
const discordChatLog = (namespace) => `cca:discord:chat:log:${namespace}`;
|
|
166
|
+
exports.discordChatLog = discordChatLog;
|
|
167
|
+
/** CHANNEL — UI/Discord → cc-discord incoming chat messages (pub/sub). */
|
|
168
|
+
const discordChatIncoming = (namespace) => `cca:discord:chat:incoming:${namespace}`;
|
|
169
|
+
exports.discordChatIncoming = discordChatIncoming;
|
|
170
|
+
/**
|
|
171
|
+
* CHANNEL + LIST — job-completion notifications for a Discord namespace.
|
|
172
|
+
*
|
|
173
|
+
* Same dual-purpose pattern as `notifyChannel`/`notifyListKey`: PUBLISH by
|
|
174
|
+
* coordinator (pub/sub) and RPUSH/RPOP delivery queue; Redis pub/sub and list
|
|
175
|
+
* namespaces are independent so the dual use is safe.
|
|
176
|
+
*/
|
|
177
|
+
const discordNotify = (namespace) => `cca:discord:notify:${namespace}`;
|
|
178
|
+
exports.discordNotify = discordNotify;
|
|
179
|
+
// ─── cc-tg Keys (static) ─────────────────────────────────────────────────────
|
|
180
|
+
//
|
|
181
|
+
// cc-tg owns a single dedicated money-brain session with no namespace scoping.
|
|
182
|
+
/** CHANNEL — cc-tg → UI outgoing chat messages (pub/sub). */
|
|
183
|
+
const tgChatOutgoing = () => "cca:tg:chat:outgoing";
|
|
184
|
+
exports.tgChatOutgoing = tgChatOutgoing;
|
|
185
|
+
/** CHANNEL — UI/Telegram → cc-tg incoming chat messages (pub/sub). */
|
|
186
|
+
const tgChatIncoming = () => "cca:tg:chat:incoming";
|
|
187
|
+
exports.tgChatIncoming = tgChatIncoming;
|
|
188
|
+
/**
|
|
189
|
+
* CHANNEL + LIST — job-completion notifications for the cc-tg session.
|
|
190
|
+
* Same dual-purpose pattern (pub/sub + RPOP poll fallback).
|
|
191
|
+
*/
|
|
192
|
+
const tgNotify = () => "cca:tg:notify";
|
|
193
|
+
exports.tgNotify = tgNotify;
|
|
127
194
|
// ─── Learnings Keys (dynamic) ─────────────────────────────────────────────────
|
|
128
195
|
/** LIST — learnings for a namespace (LPUSH capped at 50, LIFO), TTL 90 days. */
|
|
129
196
|
const learningsKey = (namespace) => `cca:learnings:${namespace}`;
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -24,3 +24,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
25
|
__exportStar(require("./channels.cjs"), exports);
|
|
26
26
|
__exportStar(require("./types.cjs"), exports);
|
|
27
|
+
__exportStar(require("./runtime.cjs"), exports);
|