tycono 0.1.96-beta.3 → 0.1.96-beta.30
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 +106 -35
- package/bin/tycono.ts +23 -1
- package/package.json +1 -1
- package/src/api/src/routes/active-sessions.ts +3 -0
- package/src/api/src/routes/execute.ts +6 -12
- package/src/api/src/services/supervisor-heartbeat.ts +19 -0
- package/src/tui/api.ts +43 -6
- package/src/tui/app.tsx +444 -164
- package/src/tui/components/CommandMode.tsx +320 -0
- package/src/tui/components/OrgTree.tsx +17 -18
- package/src/tui/components/PanelMode.tsx +230 -0
- package/src/tui/components/SetupWizard.tsx +10 -3
- package/src/tui/components/StatusBar.tsx +44 -25
- package/src/tui/components/StreamView.tsx +171 -0
- package/src/tui/hooks/useApi.ts +28 -7
- package/src/tui/hooks/useCommand.ts +186 -0
- package/src/tui/hooks/useSSE.ts +130 -27
- package/src/tui/store.ts +12 -0
- package/src/tui/components/CommandInput.tsx +0 -32
- package/src/tui/components/HelpOverlay.tsx +0 -51
- package/src/tui/components/SessionList.tsx +0 -74
- package/src/tui/components/StreamPanel.tsx +0 -182
- package/src/tui/components/WaveDialog.tsx +0 -56
- package/src/tui/hooks/useKeyboard.ts +0 -62
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src=".github/assets/
|
|
2
|
+
<img src=".github/assets/wave-org-propagation.png" alt="Tycono — CEO dispatches through org hierarchy in real time" width="720" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<h1 align="center">tycono</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<strong>
|
|
9
|
-
<sub>
|
|
8
|
+
<strong>Cursor gives you one AI developer. Tycono gives you an AI team.</strong><br>
|
|
9
|
+
<sub>Give one order. Watch your AI team plan, build, and learn together.</sub>
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
@@ -25,26 +25,76 @@
|
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
Cursor, Lovable, Bolt — they all give you **one AI agent**. It helps, but you still drive everything.
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
**tycono** gives you an **AI team**. A CTO reviews architecture. Engineers write code. A PM breaks down tasks. QA catches bugs. You just give the order and watch them work.
|
|
31
|
+
|
|
32
|
+
One command. Your AI team is running.
|
|
31
33
|
|
|
32
34
|
```bash
|
|
33
35
|
npx tycono
|
|
34
36
|
```
|
|
35
37
|
|
|
38
|
+
## Core Pillars
|
|
39
|
+
|
|
40
|
+
### 1. CEO Supervisor — Org-chart orchestration
|
|
41
|
+
|
|
42
|
+
You give one order. The system dispatches through a real hierarchy.
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
dispatch → watch → relay → quality gate → re-dispatch (if needed)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
CEO delegates to C-levels, C-levels dispatch to their teams. Authority is enforced — engineers can't make CEO decisions, PMs can't merge code. The org chart isn't decoration, it's the execution engine.
|
|
49
|
+
|
|
50
|
+
<p align="center">
|
|
51
|
+
<img src=".github/assets/wave-org-propagation.png" alt="Wave Center — org propagation with real-time status" width="640" />
|
|
52
|
+
</p>
|
|
53
|
+
|
|
54
|
+
### 2. Observability — See everything, intervene anytime
|
|
55
|
+
|
|
56
|
+
Your AI team isn't a black box. Watch every agent work in real time, inject directives mid-execution, and drill down to any level.
|
|
57
|
+
|
|
58
|
+
- **Wave Center** — Org-tree dispatch with real-time streaming
|
|
59
|
+
- **Activity Stream** — Every event logged (dispatches, tool calls, decisions)
|
|
60
|
+
- **CEO Directive** — Change direction while agents are running
|
|
61
|
+
- **Cost Tracking** — Per-role, per-model token breakdown
|
|
62
|
+
|
|
63
|
+
### 3. Isolation Infrastructure — Agents don't collide
|
|
64
|
+
|
|
65
|
+
Multiple agents working simultaneously without stepping on each other.
|
|
66
|
+
|
|
67
|
+
| Resource | Isolation | Status |
|
|
68
|
+
|----------|-----------|--------|
|
|
69
|
+
| **Code** | Git worktree per session | Designed |
|
|
70
|
+
| **Ports** | Dynamic port registry | ✅ Live |
|
|
71
|
+
| **Browser** | Separate daemon per session | ✅ Live |
|
|
72
|
+
| **Knowledge** | Shared reads, scoped writes | ✅ Live |
|
|
73
|
+
|
|
74
|
+
### 4. AKB (Pre-K / Post-K) — Knowledge that compounds
|
|
75
|
+
|
|
76
|
+
Every AI tool today: `Plan → Execute → Done`. Knowledge resets. Tycono adds what the industry doesn't have:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
Pre-K: Read existing knowledge → Plan grounded in what the company knows
|
|
80
|
+
Execute: Do the work
|
|
81
|
+
Post-K: Extract insights → Cross-link → Register in knowledge graph
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Session 50 is dramatically smarter than session 1. Your company learns.
|
|
85
|
+
|
|
36
86
|
## Why Tycono?
|
|
37
87
|
|
|
38
|
-
|
|
88
|
+
Same goal as Cursor, Lovable, Bolt — **get AI to do your work**. Different method.
|
|
39
89
|
|
|
40
|
-
| |
|
|
90
|
+
| | Cursor / Lovable / Bolt | Tycono |
|
|
41
91
|
|---|---|---|
|
|
42
|
-
| **
|
|
43
|
-
| **
|
|
44
|
-
| **
|
|
45
|
-
| **
|
|
46
|
-
| **Scale** | 1
|
|
47
|
-
| **Visibility** |
|
|
92
|
+
| **Agents** | 1 AI helps you | **AI team works for you** |
|
|
93
|
+
| **Your role** | Keep directing | **Give one order, watch** |
|
|
94
|
+
| **Knowledge** | Resets every session | **Compounds forever** |
|
|
95
|
+
| **Quality** | You review everything | **QA agent catches bugs** |
|
|
96
|
+
| **Scale** | 1 task at a time | **Parallel across roles** |
|
|
97
|
+
| **Visibility** | Editor / chat | **Real-time org tree** |
|
|
48
98
|
|
|
49
99
|
## Company-as-Code
|
|
50
100
|
|
|
@@ -80,33 +130,46 @@ A setup wizard guides you through:
|
|
|
80
130
|
- Node.js >= 18
|
|
81
131
|
- [Anthropic API key](https://console.anthropic.com/) or Claude Max subscription
|
|
82
132
|
|
|
83
|
-
##
|
|
133
|
+
## Interfaces
|
|
84
134
|
|
|
85
|
-
###
|
|
135
|
+
### Web Dashboard — Visual management
|
|
86
136
|
|
|
87
|
-
|
|
137
|
+
A browser-based dashboard for visual management. Org tree, Wave dispatch, Knowledge graph, Activity stream.
|
|
88
138
|
|
|
89
139
|
<p align="center">
|
|
90
|
-
<img src=".github/assets/hero-office.png" alt="
|
|
140
|
+
<img src=".github/assets/hero-office.png" alt="Web Dashboard" width="640" />
|
|
91
141
|
</p>
|
|
92
142
|
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
-
|
|
96
|
-
-
|
|
143
|
+
- **Wave Center** — selective org-tree dispatch with target checkboxes
|
|
144
|
+
- **Chats** — 1:1 conversations with any role, persistent sessions
|
|
145
|
+
- **Knowledge Base** — graph/tree/list views, cross-linked documents
|
|
146
|
+
- **Decisions** — CEO strategic decision log with full context
|
|
97
147
|
|
|
98
|
-
###
|
|
148
|
+
### TUI — Terminal-native operations *(coming soon)*
|
|
99
149
|
|
|
100
|
-
|
|
150
|
+
For developers who live in the terminal. A k9s/lazygit-style multi-panel TUI built with [Ink](https://github.com/vadimdemedes/ink).
|
|
101
151
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
152
|
+
```
|
|
153
|
+
┌──────────────────────────────────────────────────┐
|
|
154
|
+
│ TYCONO v0.2 │ Wave #37 running │ 3 active │$2.1│
|
|
155
|
+
├──────────────┬───────────────────────────────────┤
|
|
156
|
+
│ [Org Tree] │ [Real-time Stream] │
|
|
157
|
+
│ CEO │ CTO: "Reviewing architecture..." │
|
|
158
|
+
│ ├ CTO ● │ → dispatch → Engineer │
|
|
159
|
+
│ │ ├ ENG ○ │ CBO: "Market analysis done" ✓ │
|
|
160
|
+
│ │ └ QA ○ │ │
|
|
161
|
+
│ └ CBO ● │ │
|
|
162
|
+
├──────────────┴───────────────────────────────────┤
|
|
163
|
+
│ > wave "Write the Q1 strategy report" │
|
|
164
|
+
└──────────────────────────────────────────────────┘
|
|
165
|
+
```
|
|
105
166
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
167
|
+
```bash
|
|
168
|
+
npx tycono --tui # Terminal mode (coming soon)
|
|
169
|
+
npx tycono # Web dashboard (current)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Same API server, same engine — just a different frontend. Use what fits your workflow.
|
|
110
173
|
|
|
111
174
|
## Key Features
|
|
112
175
|
|
|
@@ -130,10 +193,6 @@ Every task produces knowledge. Cross-linked Markdown documents that grow with ev
|
|
|
130
193
|
|
|
131
194
|
Each role has scoped authority defined in `role.yaml`. Engineers can't make CEO decisions. PMs can't merge code. The org chart isn't decoration — it's enforcement.
|
|
132
195
|
|
|
133
|
-
### Level System
|
|
134
|
-
|
|
135
|
-
Roles gain XP from completed work. Level up unlocks accessories and reflects experience. Your CTO at Lv.14 has seen things your new intern hasn't.
|
|
136
|
-
|
|
137
196
|
### Local-First, BYOK
|
|
138
197
|
|
|
139
198
|
Everything runs on your machine. Your data never leaves. Bring your own Anthropic API key — no middleman, no telemetry, no tracking.
|
|
@@ -181,7 +240,8 @@ your-company/
|
|
|
181
240
|
## CLI Usage
|
|
182
241
|
|
|
183
242
|
```bash
|
|
184
|
-
npx tycono # Start server +
|
|
243
|
+
npx tycono # Start server + web dashboard
|
|
244
|
+
npx tycono --tui # Terminal UI (coming soon)
|
|
185
245
|
npx tycono --help # Show help
|
|
186
246
|
npx tycono --version # Show version
|
|
187
247
|
```
|
|
@@ -194,6 +254,17 @@ npx tycono --version # Show version
|
|
|
194
254
|
| `PORT` | Server port | auto-detect |
|
|
195
255
|
| `COMPANY_ROOT` | Company directory | current directory |
|
|
196
256
|
|
|
257
|
+
## Roadmap
|
|
258
|
+
|
|
259
|
+
- [x] Web dashboard (Office + Pro views)
|
|
260
|
+
- [x] CEO Wave dispatch with org-tree targeting
|
|
261
|
+
- [x] AKB — Pre-K / Post-K knowledge loop
|
|
262
|
+
- [x] Port Registry for multi-agent isolation
|
|
263
|
+
- [ ] **TUI mode** — terminal-native multi-panel interface *(in progress)*
|
|
264
|
+
- [ ] Git worktree isolation per agent session
|
|
265
|
+
- [ ] **Desktop app** (.dmg / .exe) — background execution, notifications, no API key setup needed
|
|
266
|
+
- [ ] Multi-LLM support (OpenAI, local models)
|
|
267
|
+
|
|
197
268
|
## Built with Tycono
|
|
198
269
|
|
|
199
270
|
This isn't a demo. Tycono's own landing page, documentation, and knowledge base were built by AI agents running inside Tycono. The PM wrote the PRD. The CTO reviewed architecture. The Designer created UX specs. The Engineer implemented every section.
|
package/bin/tycono.ts
CHANGED
|
@@ -213,6 +213,27 @@ async function startServerForTui(): Promise<void> {
|
|
|
213
213
|
const port = process.env.PORT ? Number(process.env.PORT) : await findFreePort();
|
|
214
214
|
process.env.PORT = String(port);
|
|
215
215
|
|
|
216
|
+
// Suppress ALL server output BEFORE creating server — hijack process streams
|
|
217
|
+
const logFile = path.resolve(process.env.COMPANY_ROOT || process.cwd(), '.tycono', 'server.log');
|
|
218
|
+
try { fs.mkdirSync(path.dirname(logFile), { recursive: true }); } catch {}
|
|
219
|
+
const logFd = fs.openSync(logFile, 'a');
|
|
220
|
+
const logStream = fs.createWriteStream(logFile, { fd: logFd });
|
|
221
|
+
const origStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
222
|
+
const origStderrWrite = process.stderr.write.bind(process.stderr);
|
|
223
|
+
// Intercept all stdout/stderr — only allow Ink's output (ANSI escape sequences)
|
|
224
|
+
const isInkOutput = (s: string) => s.includes('\x1b[') || s.includes('\x1b(');
|
|
225
|
+
process.stdout.write = ((chunk: any, ...args: any[]) => {
|
|
226
|
+
const str = typeof chunk === 'string' ? chunk : chunk.toString();
|
|
227
|
+
if (isInkOutput(str)) return origStdoutWrite(chunk, ...args);
|
|
228
|
+
logStream.write(str);
|
|
229
|
+
return true;
|
|
230
|
+
}) as any;
|
|
231
|
+
process.stderr.write = ((chunk: any, ...args: any[]) => {
|
|
232
|
+
logStream.write(typeof chunk === 'string' ? chunk : chunk.toString());
|
|
233
|
+
return true;
|
|
234
|
+
}) as any;
|
|
235
|
+
const origLog = (...args: unknown[]) => origStdoutWrite(args.join(' ') + '\n');
|
|
236
|
+
|
|
216
237
|
const { createHttpServer } = await import('../src/api/src/create-server.js');
|
|
217
238
|
const server = createHttpServer();
|
|
218
239
|
|
|
@@ -222,7 +243,8 @@ async function startServerForTui(): Promise<void> {
|
|
|
222
243
|
server.listen(port, host, () => resolve());
|
|
223
244
|
});
|
|
224
245
|
|
|
225
|
-
|
|
246
|
+
origLog(` API server started on port ${port}`);
|
|
247
|
+
origLog(` Logs: ${logFile}`);
|
|
226
248
|
|
|
227
249
|
// Graceful shutdown
|
|
228
250
|
const shutdown = () => {
|
package/package.json
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { Router } from 'express';
|
|
7
7
|
import { portRegistry } from '../services/port-registry.js';
|
|
8
8
|
import { executionManager } from '../services/execution-manager.js';
|
|
9
|
+
import { getSession } from '../services/session-store.js';
|
|
9
10
|
|
|
10
11
|
export const activeSessionsRouter = Router();
|
|
11
12
|
|
|
@@ -17,8 +18,10 @@ activeSessionsRouter.get('/', (_req, res) => {
|
|
|
17
18
|
|
|
18
19
|
const enriched = sessions.map(s => {
|
|
19
20
|
const exec = executionManager.getActiveExecution(s.sessionId);
|
|
21
|
+
const session = getSession(s.sessionId);
|
|
20
22
|
return {
|
|
21
23
|
...s,
|
|
24
|
+
waveId: session?.waveId ?? null,
|
|
22
25
|
messageStatus: exec?.status ?? null,
|
|
23
26
|
roleName: exec?.roleId ?? s.roleId,
|
|
24
27
|
alive: s.pid ? isAlive(s.pid) : null,
|
|
@@ -212,10 +212,8 @@ function handleStartJob(body: Record<string, unknown>, res: ServerResponse): voi
|
|
|
212
212
|
const attachments = body.attachments as ImageAttachment[] | undefined;
|
|
213
213
|
|
|
214
214
|
if (type === 'wave') {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
215
|
+
// directive가 없으면 idle 상태로 시작 (empty wave)
|
|
216
|
+
const actualDirective = directive || '';
|
|
219
217
|
|
|
220
218
|
const targetRoles = body.targetRoles as string[] | undefined;
|
|
221
219
|
const continuous = body.continuous === true;
|
|
@@ -224,7 +222,7 @@ function handleStartJob(body: Record<string, unknown>, res: ServerResponse): voi
|
|
|
224
222
|
{
|
|
225
223
|
const state = supervisorHeartbeat.start(
|
|
226
224
|
`wave-${Date.now()}`,
|
|
227
|
-
|
|
225
|
+
actualDirective,
|
|
228
226
|
targetRoles && targetRoles.length > 0 ? targetRoles : undefined,
|
|
229
227
|
continuous,
|
|
230
228
|
);
|
|
@@ -238,7 +236,7 @@ function handleStartJob(body: Record<string, unknown>, res: ServerResponse): voi
|
|
|
238
236
|
waveId: state.waveId,
|
|
239
237
|
supervisorSessionId: state.supervisorSessionId,
|
|
240
238
|
mode: 'supervisor',
|
|
241
|
-
directive,
|
|
239
|
+
directive: actualDirective,
|
|
242
240
|
});
|
|
243
241
|
return;
|
|
244
242
|
}
|
|
@@ -497,12 +495,8 @@ function handleWaveStream(waveId: string, url: string, res: ServerResponse, req:
|
|
|
497
495
|
sessionIds = waveMultiplexer.getWaveSessionIds(waveId);
|
|
498
496
|
}
|
|
499
497
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
res.end(JSON.stringify({ error: `No sessions found for wave: ${waveId}` }));
|
|
503
|
-
return;
|
|
504
|
-
}
|
|
505
|
-
|
|
498
|
+
// Don't 404 on empty waves — keep SSE alive, sessions will appear later
|
|
499
|
+
// (e.g. idle wave waiting for first directive, or supervisor restarting)
|
|
506
500
|
const client = waveMultiplexer.attach(waveId, res as any, fromWaveSeq);
|
|
507
501
|
|
|
508
502
|
req.on('close', () => {
|
|
@@ -88,6 +88,14 @@ class SupervisorHeartbeat {
|
|
|
88
88
|
};
|
|
89
89
|
|
|
90
90
|
this.supervisors.set(waveId, state);
|
|
91
|
+
|
|
92
|
+
// Empty directive → idle wave (don't spawn supervisor yet)
|
|
93
|
+
if (!directive) {
|
|
94
|
+
state.status = 'stopped';
|
|
95
|
+
console.log(`[Supervisor] Idle wave created: ${waveId} (no directive)`);
|
|
96
|
+
return state;
|
|
97
|
+
}
|
|
98
|
+
|
|
91
99
|
this.spawnSupervisor(state);
|
|
92
100
|
return state;
|
|
93
101
|
}
|
|
@@ -133,6 +141,17 @@ class SupervisorHeartbeat {
|
|
|
133
141
|
|
|
134
142
|
state.pendingDirectives.push(directive);
|
|
135
143
|
console.log(`[Supervisor] Directive queued for wave ${waveId}: ${text.slice(0, 80)}`);
|
|
144
|
+
|
|
145
|
+
// If supervisor is stopped (agent finished or idle wave), wake it up
|
|
146
|
+
if (state.status === 'stopped') {
|
|
147
|
+
// Update the wave's directive if it was empty (idle wave first message)
|
|
148
|
+
if (!state.directive) {
|
|
149
|
+
state.directive = text;
|
|
150
|
+
}
|
|
151
|
+
state.crashCount = 0;
|
|
152
|
+
this.scheduleRestart(state, 0);
|
|
153
|
+
}
|
|
154
|
+
|
|
136
155
|
return directive;
|
|
137
156
|
}
|
|
138
157
|
|
package/src/tui/api.ts
CHANGED
|
@@ -16,7 +16,7 @@ export function getBaseUrl(): string {
|
|
|
16
16
|
|
|
17
17
|
/* ─── HTTP helpers ─── */
|
|
18
18
|
|
|
19
|
-
async function fetchJson<T>(path: string, options?: { method?: string; body?: unknown }): Promise<T> {
|
|
19
|
+
export async function fetchJson<T>(path: string, options?: { method?: string; body?: unknown }): Promise<T> {
|
|
20
20
|
const url = `${BASE_URL}${path}`;
|
|
21
21
|
const method = options?.method ?? 'GET';
|
|
22
22
|
const bodyStr = options?.body ? JSON.stringify(options.body) : undefined;
|
|
@@ -83,6 +83,7 @@ export interface SessionInfo {
|
|
|
83
83
|
status: string;
|
|
84
84
|
source: string;
|
|
85
85
|
waveId?: string;
|
|
86
|
+
parentSessionId?: string;
|
|
86
87
|
createdAt: string;
|
|
87
88
|
}
|
|
88
89
|
|
|
@@ -129,7 +130,7 @@ export async function fetchExecStatus(): Promise<ExecStatus> {
|
|
|
129
130
|
return fetchJson<ExecStatus>('/api/exec/status');
|
|
130
131
|
}
|
|
131
132
|
|
|
132
|
-
export async function dispatchWave(directive
|
|
133
|
+
export async function dispatchWave(directive?: string, options?: {
|
|
133
134
|
targetRoles?: string[];
|
|
134
135
|
continuous?: boolean;
|
|
135
136
|
}): Promise<WaveResponse> {
|
|
@@ -137,7 +138,7 @@ export async function dispatchWave(directive: string, options?: {
|
|
|
137
138
|
method: 'POST',
|
|
138
139
|
body: {
|
|
139
140
|
type: 'wave',
|
|
140
|
-
directive,
|
|
141
|
+
directive: directive ?? '',
|
|
141
142
|
targetRoles: options?.targetRoles,
|
|
142
143
|
continuous: options?.continuous ?? false,
|
|
143
144
|
},
|
|
@@ -155,13 +156,46 @@ export async function fetchActiveWaves(): Promise<{ waves: Array<{ waveId: strin
|
|
|
155
156
|
return fetchJson('/api/waves/active');
|
|
156
157
|
}
|
|
157
158
|
|
|
159
|
+
/* ─── Active Sessions (port/worktree visibility) ─── */
|
|
160
|
+
|
|
161
|
+
export interface ActiveSessionInfo {
|
|
162
|
+
sessionId: string;
|
|
163
|
+
roleId: string;
|
|
164
|
+
task: string;
|
|
165
|
+
ports: { api: number; vite: number; hmr?: number };
|
|
166
|
+
worktreePath?: string;
|
|
167
|
+
pid?: number;
|
|
168
|
+
startedAt: string;
|
|
169
|
+
status: 'active' | 'idle' | 'dead';
|
|
170
|
+
waveId?: string | null;
|
|
171
|
+
messageStatus?: string | null;
|
|
172
|
+
alive?: boolean | null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface ActiveSessionsResponse {
|
|
176
|
+
sessions: ActiveSessionInfo[];
|
|
177
|
+
summary: { active: number; totalPorts: number };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function fetchActiveSessions(): Promise<ActiveSessionsResponse> {
|
|
181
|
+
return fetchJson<ActiveSessionsResponse>('/api/active-sessions');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export async function killSession(sessionId: string): Promise<{ ok: boolean }> {
|
|
185
|
+
return fetchJson<{ ok: boolean }>(`/api/active-sessions/${sessionId}`, { method: 'DELETE' });
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export async function cleanupSessions(): Promise<{ cleaned: number; remaining: number }> {
|
|
189
|
+
return fetchJson<{ cleaned: number; remaining: number }>('/api/active-sessions/cleanup', { method: 'POST' });
|
|
190
|
+
}
|
|
191
|
+
|
|
158
192
|
/* ─── Setup API calls ─── */
|
|
159
193
|
|
|
160
194
|
export interface TeamTemplate {
|
|
161
195
|
id: string;
|
|
162
196
|
name: string;
|
|
163
197
|
description: string;
|
|
164
|
-
roles: string
|
|
198
|
+
roles: Array<string | { id: string; name: string; level?: string }>;
|
|
165
199
|
}
|
|
166
200
|
|
|
167
201
|
export interface ScaffoldResult {
|
|
@@ -232,9 +266,12 @@ export function subscribeToWaveStream(
|
|
|
232
266
|
}
|
|
233
267
|
}
|
|
234
268
|
|
|
235
|
-
if (eventType === 'activity' && data) {
|
|
269
|
+
if ((eventType === 'activity' || eventType === 'wave:event') && data) {
|
|
236
270
|
try {
|
|
237
|
-
|
|
271
|
+
const parsed = JSON.parse(data);
|
|
272
|
+
// wave:event wraps the actual event in .event field
|
|
273
|
+
const evt = parsed.event ?? parsed;
|
|
274
|
+
onEvent(evt as SSEEvent);
|
|
238
275
|
} catch { /* ignore parse errors */ }
|
|
239
276
|
} else if (eventType === 'stream:end' && data) {
|
|
240
277
|
try {
|