tycono 0.3.14-beta.1 → 0.3.14-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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tycono",
3
- "version": "0.3.14-beta.1",
3
+ "version": "0.3.14-beta.3",
4
4
  "description": "Build an AI company. Watch them work.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,16 +1,21 @@
1
1
  /**
2
2
  * presets.ts — Preset API routes
3
3
  *
4
- * GET /api/presets — list all preset summaries
5
- * GET /api/presets/:id — get full preset detail
4
+ * GET /api/presets — list all preset summaries
5
+ * GET /api/presets/:id — get full preset detail
6
+ * POST /api/presets/install — install preset from data
7
+ * DELETE /api/presets/:id — remove installed preset
6
8
  */
7
9
  import { Router } from 'express';
10
+ import fs from 'node:fs';
11
+ import path from 'node:path';
12
+ import YAML from 'yaml';
8
13
  import { COMPANY_ROOT } from '../services/file-reader.js';
9
- import { getPresetSummaries, getPresetById } from '../services/preset-loader.js';
14
+ import { getPresetSummaries, getPresetById, loadPresets } from '../services/preset-loader.js';
10
15
 
11
16
  export const presetsRouter = Router();
12
17
 
13
- /** GET /api/presets — list preset summaries for TUI */
18
+ /** GET /api/presets — list preset summaries */
14
19
  presetsRouter.get('/', (_req, res) => {
15
20
  try {
16
21
  const summaries = getPresetSummaries(COMPANY_ROOT);
@@ -33,3 +38,86 @@ presetsRouter.get('/:id', (req, res) => {
33
38
  res.status(500).json({ error: 'Failed to load preset' });
34
39
  }
35
40
  });
41
+
42
+ /** POST /api/presets/install — install a preset from provided data */
43
+ presetsRouter.post('/install', (req, res) => {
44
+ try {
45
+ const { id, preset } = req.body as { id: string; preset: Record<string, unknown> };
46
+ if (!id || !preset) {
47
+ res.status(400).json({ error: 'id and preset are required' });
48
+ return;
49
+ }
50
+
51
+ // Validate preset has required fields
52
+ if (!preset.name || !preset.roles || !Array.isArray(preset.roles)) {
53
+ res.status(400).json({ error: 'preset must have name and roles array' });
54
+ return;
55
+ }
56
+
57
+ // Check for conflict with existing preset
58
+ const existing = getPresetById(COMPANY_ROOT, id);
59
+ if (existing && !existing.isDefault) {
60
+ res.status(409).json({ error: `Preset already installed: ${id}` });
61
+ return;
62
+ }
63
+
64
+ // Create preset directory and write preset.yaml
65
+ const presetDir = path.join(COMPANY_ROOT, 'company', 'presets', id);
66
+ fs.mkdirSync(presetDir, { recursive: true });
67
+
68
+ // Write preset.yaml
69
+ const yamlContent = YAML.stringify(preset);
70
+ fs.writeFileSync(path.join(presetDir, 'preset.yaml'), yamlContent);
71
+
72
+ // Create subdirectories for roles/knowledge/skills
73
+ fs.mkdirSync(path.join(presetDir, 'roles'), { recursive: true });
74
+ fs.mkdirSync(path.join(presetDir, 'knowledge'), { recursive: true });
75
+ fs.mkdirSync(path.join(presetDir, 'skills'), { recursive: true });
76
+
77
+ // Write knowledge docs if provided
78
+ const knowledge = req.body.knowledge as Array<{ filename: string; content: string }> | undefined;
79
+ if (knowledge) {
80
+ for (const doc of knowledge) {
81
+ fs.writeFileSync(path.join(presetDir, 'knowledge', doc.filename), doc.content);
82
+ }
83
+ }
84
+
85
+ // Write role yamls if provided
86
+ const roleDefinitions = req.body.roleDefinitions as Array<{ id: string; yaml: string }> | undefined;
87
+ if (roleDefinitions) {
88
+ for (const role of roleDefinitions) {
89
+ const roleDir = path.join(presetDir, 'roles', role.id);
90
+ fs.mkdirSync(roleDir, { recursive: true });
91
+ fs.writeFileSync(path.join(roleDir, 'role.yaml'), role.yaml);
92
+ }
93
+ }
94
+
95
+ res.json({ ok: true, id, path: `company/presets/${id}` });
96
+ } catch (err) {
97
+ res.status(500).json({ error: `Install failed: ${err instanceof Error ? err.message : 'unknown'}` });
98
+ }
99
+ });
100
+
101
+ /** DELETE /api/presets/:id — remove installed preset */
102
+ presetsRouter.delete('/:id', (req, res) => {
103
+ try {
104
+ const { id } = req.params;
105
+ if (id === 'default' || id === '_default') {
106
+ res.status(400).json({ error: 'Cannot remove default preset' });
107
+ return;
108
+ }
109
+
110
+ const presetDir = path.join(COMPANY_ROOT, 'company', 'presets', id);
111
+ if (!fs.existsSync(presetDir)) {
112
+ res.status(404).json({ error: `Preset not found: ${id}` });
113
+ return;
114
+ }
115
+
116
+ // Remove preset directory recursively
117
+ fs.rmSync(presetDir, { recursive: true, force: true });
118
+
119
+ res.json({ ok: true, id });
120
+ } catch (err) {
121
+ res.status(500).json({ error: `Remove failed: ${err instanceof Error ? err.message : 'unknown'}` });
122
+ }
123
+ });
@@ -724,8 +724,8 @@ Your job: monitor progress, course-correct if needed, wait for completion, then
724
724
  console.log(`[ExecMgr] Supervision recovery: ${deadExecution.roleId} died with ${runningChildren.length} running children. Restarting.`);
725
725
 
726
726
  // Propagate waveId from the dead session
727
- const deadSession = getSession(deadExecution.sessionId);
728
- const waveId = deadSession?.waveId;
727
+ const deadSes = getSession(deadExecution.sessionId);
728
+ const waveId = deadSes?.waveId;
729
729
 
730
730
  // Create new session for recovery
731
731
  const newSession = createSession(deadExecution.roleId, {
@@ -172,13 +172,15 @@ class SupervisorHeartbeat {
172
172
  const waveSessions = listSessions().filter(s => s.waveId === waveId);
173
173
  const ceoSession = waveSessions.find(s => s.roleId === 'ceo') ?? null;
174
174
 
175
- // Read original directive from wave artifact file
175
+ // Read original directive + preset from wave artifact file
176
176
  let originalDirective = '';
177
+ let originalPreset: string | undefined;
177
178
  try {
178
179
  const waveFile = path.join(COMPANY_ROOT, 'operations', 'waves', `${waveId}.json`);
179
180
  if (fs.existsSync(waveFile)) {
180
181
  const waveData = JSON.parse(fs.readFileSync(waveFile, 'utf-8'));
181
182
  originalDirective = waveData.directive ?? '';
183
+ originalPreset = waveData.preset;
182
184
  }
183
185
  } catch { /* ignore */ }
184
186
 
@@ -188,6 +190,7 @@ class SupervisorHeartbeat {
188
190
  waveId,
189
191
  directive: originalDirective || text,
190
192
  continuous: false,
193
+ preset: originalPreset,
191
194
  supervisorSessionId: ceoSession?.id ?? null,
192
195
  executionId: null,
193
196
  status: 'stopped',
package/src/tui/app.tsx CHANGED
@@ -668,6 +668,15 @@ export const App: React.FC = () => {
668
668
  // Command Mode: scrollable terminal (no fullscreen)
669
669
  // Panel Mode: fullscreen (intentional — like vim for inspection)
670
670
  if (mode === 'panel') {
671
+ // OOM debug: minimal Panel Mode to isolate if yoga layout is the cause
672
+ if (process.env.PANEL_MINIMAL) {
673
+ return (
674
+ <Box flexDirection="column">
675
+ <Text color="cyan">Panel Mode (minimal debug)</Text>
676
+ <Text color="gray">Events: {sse.events.length} | Roles: {flatRoleIds.length} | Press Esc to go back</Text>
677
+ </Box>
678
+ );
679
+ }
671
680
  return (
672
681
  <Box flexDirection="column" height={termHeight}>
673
682
  <Box flexGrow={1} flexDirection="column">