tycono-server 0.1.0-beta.1 → 0.1.0-beta.3
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/package.json
CHANGED
|
@@ -101,7 +101,8 @@ export function createHttpServer(): http.Server {
|
|
|
101
101
|
const app = createExpressApp();
|
|
102
102
|
|
|
103
103
|
const server = http.createServer((req, res) => {
|
|
104
|
-
const
|
|
104
|
+
const rawUrl = req.url ?? '';
|
|
105
|
+
const url = rawUrl.split('?')[0]; // Strip query string for route matching
|
|
105
106
|
const method = req.method ?? '';
|
|
106
107
|
|
|
107
108
|
// GET /api/waves/active — restore active waves after refresh
|
|
@@ -150,8 +151,8 @@ export function createHttpServer(): http.Server {
|
|
|
150
151
|
return;
|
|
151
152
|
}
|
|
152
153
|
|
|
153
|
-
// Non-SSE exec/jobs endpoints (GET, DELETE)
|
|
154
|
-
if ((url.startsWith('/api/exec/') || url.startsWith('/api/jobs')) && (method === 'GET' || method === 'DELETE')) {
|
|
154
|
+
// Non-SSE exec/jobs/waves endpoints (GET, DELETE)
|
|
155
|
+
if ((url.startsWith('/api/exec/') || url.startsWith('/api/jobs') || url.startsWith('/api/waves/')) && (method === 'GET' || method === 'DELETE')) {
|
|
155
156
|
setExecCors(req, res);
|
|
156
157
|
handleExecRequest(req, res);
|
|
157
158
|
return;
|
|
@@ -126,6 +126,32 @@ export function handleExecRequest(req: IncomingMessage, res: ServerResponse): vo
|
|
|
126
126
|
return;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
// ── GET /api/waves/:waveId — Single wave status ──
|
|
130
|
+
const waveDetailMatch = url.match(/^\/api\/waves\/([^/]+)$/);
|
|
131
|
+
if (method === 'GET' && waveDetailMatch) {
|
|
132
|
+
const waveId = waveDetailMatch[1];
|
|
133
|
+
// Try active waves first
|
|
134
|
+
const activeWaves = waveMultiplexer.getActiveWaves();
|
|
135
|
+
const active = activeWaves.find((w: { waveId: string }) => w.waveId === waveId);
|
|
136
|
+
if (active) {
|
|
137
|
+
jsonResponse(res, 200, active);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
// Fallback: read wave file from disk
|
|
141
|
+
const wavePath = path.join(COMPANY_ROOT, '.tycono', 'waves', `${waveId}.json`);
|
|
142
|
+
if (fs.existsSync(wavePath)) {
|
|
143
|
+
try {
|
|
144
|
+
const waveData = JSON.parse(fs.readFileSync(wavePath, 'utf-8'));
|
|
145
|
+
jsonResponse(res, 200, waveData);
|
|
146
|
+
} catch {
|
|
147
|
+
jsonResponse(res, 500, { error: 'Failed to read wave file' });
|
|
148
|
+
}
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
jsonResponse(res, 404, { error: 'Wave not found' });
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
129
155
|
// ── Legacy /api/exec/* routes ──
|
|
130
156
|
const sessionMatch = url.match(/\/api\/exec\/session\/([^/]+)\/message$/);
|
|
131
157
|
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import fs from 'node:fs';
|
|
11
11
|
import path from 'node:path';
|
|
12
|
+
import { execSync } from 'node:child_process';
|
|
12
13
|
import YAML from 'yaml';
|
|
13
14
|
import type { PresetDefinition, LoadedPreset, PresetSummary } from '../../../shared/types.js';
|
|
14
15
|
|
|
@@ -123,6 +124,19 @@ export function loadPresets(companyRoot: string): LoadedPreset[] {
|
|
|
123
124
|
}
|
|
124
125
|
}
|
|
125
126
|
|
|
127
|
+
// 3. Bundled presets (shipped with tycono-server, fallback if not in user's project)
|
|
128
|
+
const bundledPresetsDir = path.resolve(__dirname, '../../../../presets');
|
|
129
|
+
if (fs.existsSync(bundledPresetsDir)) {
|
|
130
|
+
const loadedIds = new Set(presets.map(p => p.definition.id));
|
|
131
|
+
const entries = fs.readdirSync(bundledPresetsDir, { withFileTypes: true });
|
|
132
|
+
for (const entry of entries) {
|
|
133
|
+
if (!entry.isDirectory()) continue;
|
|
134
|
+
if (loadedIds.has(entry.name)) continue; // user's preset takes priority
|
|
135
|
+
const preset = loadPresetFromDir(path.join(bundledPresetsDir, entry.name));
|
|
136
|
+
if (preset) presets.push(preset);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
126
140
|
return presets;
|
|
127
141
|
}
|
|
128
142
|
|
|
@@ -142,8 +156,58 @@ export function getPresetSummaries(companyRoot: string): PresetSummary[] {
|
|
|
142
156
|
|
|
143
157
|
/**
|
|
144
158
|
* Find a specific preset by ID.
|
|
159
|
+
* Falls back to remote download from tycono.ai if not found locally.
|
|
145
160
|
*/
|
|
146
161
|
export function getPresetById(companyRoot: string, presetId: string): LoadedPreset | null {
|
|
147
162
|
const presets = loadPresets(companyRoot);
|
|
148
|
-
|
|
163
|
+
const local = presets.find(p => p.definition.id === presetId);
|
|
164
|
+
if (local) return local;
|
|
165
|
+
|
|
166
|
+
// Try downloading from tycono.ai preset registry
|
|
167
|
+
const downloaded = downloadPreset(companyRoot, presetId);
|
|
168
|
+
return downloaded;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Download a preset from the remote registry (tycono.ai).
|
|
173
|
+
* Saves to knowledge/presets/{id}/ for future use.
|
|
174
|
+
*/
|
|
175
|
+
function downloadPreset(companyRoot: string, presetId: string): LoadedPreset | null {
|
|
176
|
+
const REGISTRY_URL = process.env.TYCONO_PRESET_REGISTRY || 'https://tycono.ai/api/presets';
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
// Synchronous HTTP request (preset download is a blocking init step)
|
|
180
|
+
const response = execSync(
|
|
181
|
+
`curl -s --max-time 10 "${REGISTRY_URL}/${presetId}/download"`,
|
|
182
|
+
{ encoding: 'utf-8' },
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
const data = JSON.parse(response);
|
|
186
|
+
if (!data.preset || !data.files) return null;
|
|
187
|
+
|
|
188
|
+
// Save to local presets directory
|
|
189
|
+
const targetDir = path.join(companyRoot, PRESETS_DIR, presetId);
|
|
190
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
191
|
+
|
|
192
|
+
// Write preset.yaml
|
|
193
|
+
fs.writeFileSync(
|
|
194
|
+
path.join(targetDir, 'preset.yaml'),
|
|
195
|
+
YAML.stringify(data.preset),
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
// Write knowledge files
|
|
199
|
+
if (data.files && typeof data.files === 'object') {
|
|
200
|
+
for (const [filePath, content] of Object.entries(data.files)) {
|
|
201
|
+
const fullPath = path.join(targetDir, filePath);
|
|
202
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
203
|
+
fs.writeFileSync(fullPath, content as string);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
console.log(`[Preset] Downloaded "${presetId}" from ${REGISTRY_URL}`);
|
|
208
|
+
return loadPresetFromDir(targetDir);
|
|
209
|
+
} catch (err) {
|
|
210
|
+
console.warn(`[Preset] Failed to download "${presetId}": ${(err as Error).message}`);
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
149
213
|
}
|
|
@@ -300,14 +300,14 @@ class WaveMultiplexer {
|
|
|
300
300
|
getActiveWaves(): Array<{
|
|
301
301
|
id: string;
|
|
302
302
|
directive: string;
|
|
303
|
-
dispatches: Array<{ sessionId: string; roleId: string; roleName: string }>;
|
|
303
|
+
dispatches: Array<{ sessionId: string; roleId: string; roleName: string; status: string }>;
|
|
304
304
|
startedAt: number;
|
|
305
305
|
sessionIds: string[];
|
|
306
306
|
}> {
|
|
307
307
|
const result: Array<{
|
|
308
308
|
id: string;
|
|
309
309
|
directive: string;
|
|
310
|
-
dispatches: Array<{ sessionId: string; roleId: string; roleName: string }>;
|
|
310
|
+
dispatches: Array<{ sessionId: string; roleId: string; roleName: string; status: string }>;
|
|
311
311
|
startedAt: number;
|
|
312
312
|
sessionIds: string[];
|
|
313
313
|
}> = [];
|
|
@@ -316,12 +316,13 @@ class WaveMultiplexer {
|
|
|
316
316
|
const hasActive = Array.from(sessions.values()).some(e => e.status === 'running' || e.status === 'awaiting_input');
|
|
317
317
|
if (!hasActive) continue;
|
|
318
318
|
|
|
319
|
+
// Include ALL sessions (not just root) so plugin can show full team status
|
|
319
320
|
const rootSessions = Array.from(sessions.values())
|
|
320
|
-
.filter(e => !e.parentSessionId || !sessions.has(e.parentSessionId))
|
|
321
321
|
.map(e => ({
|
|
322
322
|
sessionId: e.sessionId,
|
|
323
323
|
roleId: e.roleId,
|
|
324
324
|
roleName: e.roleId.toUpperCase(),
|
|
325
|
+
status: e.status,
|
|
325
326
|
}));
|
|
326
327
|
|
|
327
328
|
const firstExec = rootSessions.length > 0
|