tycono 0.1.96-beta.56 → 0.1.96-beta.58
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 +1 -1
- package/src/tui/api.ts +6 -0
- package/src/tui/app.tsx +1 -1
- package/src/tui/components/PanelMode.tsx +41 -12
- package/src/tui/components/SetupWizard.tsx +22 -12
package/package.json
CHANGED
package/src/tui/api.ts
CHANGED
|
@@ -208,6 +208,12 @@ export async function fetchKnowledgeDocs(): Promise<KnowledgeDoc[]> {
|
|
|
208
208
|
return fetchJson<KnowledgeDoc[]>('/api/knowledge');
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
/** Scan COMPANY_ROOT for all .md files (not API-dependent) */
|
|
212
|
+
export async function fetchCompanyRoot(): Promise<string> {
|
|
213
|
+
const health = await fetchJson<{ companyRoot: string }>('/api/health');
|
|
214
|
+
return health.companyRoot;
|
|
215
|
+
}
|
|
216
|
+
|
|
211
217
|
/* ─── Setup API calls ─── */
|
|
212
218
|
|
|
213
219
|
export interface TeamTemplate {
|
package/src/tui/app.tsx
CHANGED
|
@@ -563,7 +563,7 @@ export const App: React.FC = () => {
|
|
|
563
563
|
waveId={focusedWaveId}
|
|
564
564
|
activeSessions={api.activeSessions}
|
|
565
565
|
allSessions={api.sessions}
|
|
566
|
-
|
|
566
|
+
companyRoot={process.env.COMPANY_ROOT || process.cwd()}
|
|
567
567
|
waves={waves}
|
|
568
568
|
focusedWaveId={focusedWaveId}
|
|
569
569
|
onMove={(dir) => {
|
|
@@ -19,7 +19,8 @@ import { execSync } from 'node:child_process';
|
|
|
19
19
|
import { OrgTree } from './OrgTree';
|
|
20
20
|
import { StreamView } from './StreamView';
|
|
21
21
|
import type { OrgNode } from '../store';
|
|
22
|
-
import
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
import type { SSEEvent, ActiveSessionInfo, SessionInfo } from '../api';
|
|
23
24
|
import type { WaveInfo } from '../hooks/useCommand';
|
|
24
25
|
|
|
25
26
|
type RightTab = 'stream' | 'docs' | 'info';
|
|
@@ -35,7 +36,7 @@ interface PanelModeProps {
|
|
|
35
36
|
waveId: string | null;
|
|
36
37
|
activeSessions: ActiveSessionInfo[];
|
|
37
38
|
allSessions: SessionInfo[];
|
|
38
|
-
|
|
39
|
+
companyRoot: string;
|
|
39
40
|
waves: WaveInfo[];
|
|
40
41
|
focusedWaveId: string | null;
|
|
41
42
|
onMove: (direction: 'up' | 'down') => void;
|
|
@@ -78,6 +79,31 @@ function elapsed(startedAt: string): string {
|
|
|
78
79
|
return `${Math.floor(ms / 3600_000)}h`;
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
/** Scan COMPANY_ROOT for .md files (cached) */
|
|
83
|
+
let mdFileCache: { root: string; files: string[] } | null = null;
|
|
84
|
+
function scanMdFiles(companyRoot: string): string[] {
|
|
85
|
+
if (mdFileCache && mdFileCache.root === companyRoot) return mdFileCache.files;
|
|
86
|
+
const results: string[] = [];
|
|
87
|
+
const skip = new Set(['.git', 'node_modules', '.tycono', '.worktrees', 'dist', '.claude']);
|
|
88
|
+
function walk(dir: string, depth: number) {
|
|
89
|
+
if (depth > 3) return; // Don't go too deep
|
|
90
|
+
try {
|
|
91
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
92
|
+
if (skip.has(entry.name)) continue;
|
|
93
|
+
const full = path.join(dir, entry.name);
|
|
94
|
+
if (entry.isDirectory()) {
|
|
95
|
+
walk(full, depth + 1);
|
|
96
|
+
} else if (entry.name.endsWith('.md')) {
|
|
97
|
+
results.push(full);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} catch { /* permission error etc */ }
|
|
101
|
+
}
|
|
102
|
+
walk(companyRoot, 0);
|
|
103
|
+
mdFileCache = { root: companyRoot, files: results };
|
|
104
|
+
return results;
|
|
105
|
+
}
|
|
106
|
+
|
|
81
107
|
/** Extract files created/modified in this wave from SSE events */
|
|
82
108
|
function extractWaveFiles(events: SSEEvent[]): string[] {
|
|
83
109
|
const files = new Set<string>();
|
|
@@ -115,7 +141,7 @@ function readFilePreview(filePath: string, maxLines: number): string[] {
|
|
|
115
141
|
|
|
116
142
|
export const PanelMode: React.FC<PanelModeProps> = ({
|
|
117
143
|
tree, flatRoles, events, selectedRoleIndex, selectedRoleId,
|
|
118
|
-
streamStatus, waveId, activeSessions, allSessions,
|
|
144
|
+
streamStatus, waveId, activeSessions, allSessions, companyRoot, waves,
|
|
119
145
|
focusedWaveId, onMove, onSelect, onEscape, onFocusWave,
|
|
120
146
|
}) => {
|
|
121
147
|
const [termHeight, setTermHeight] = useState(process.stdout.rows || 30);
|
|
@@ -154,27 +180,30 @@ export const PanelMode: React.FC<PanelModeProps> = ({
|
|
|
154
180
|
return new Set(extractWaveFiles(events));
|
|
155
181
|
}, [rightTab === 'docs' || rightTab === 'info' ? events.length : 0, rightTab]);
|
|
156
182
|
|
|
157
|
-
// Build docs list
|
|
183
|
+
// Build docs list from filesystem scan + wave files
|
|
158
184
|
const docsList = useMemo(() => {
|
|
159
185
|
if (rightTab !== 'docs') return [];
|
|
160
186
|
|
|
161
187
|
interface DocEntry { path: string; title: string; isWave: boolean; }
|
|
162
188
|
const entries: DocEntry[] = [];
|
|
163
189
|
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const
|
|
190
|
+
// Scan all .md files from COMPANY_ROOT
|
|
191
|
+
const allMdFiles = companyRoot ? scanMdFiles(companyRoot) : [];
|
|
192
|
+
|
|
193
|
+
for (const filePath of allMdFiles) {
|
|
194
|
+
const rel = filePath.replace(companyRoot + '/', '');
|
|
195
|
+
const isWave = waveFileSet.has(filePath);
|
|
196
|
+
const isKb = rel.startsWith('knowledge/');
|
|
197
|
+
const isProject = rel.startsWith('projects/');
|
|
169
198
|
|
|
170
199
|
if (docsFilter === 'wave' && !isWave) continue;
|
|
171
200
|
if (docsFilter === 'kb' && !isKb) continue;
|
|
172
201
|
if (docsFilter === 'projects' && !isProject) continue;
|
|
173
202
|
|
|
174
|
-
entries.push({ path:
|
|
203
|
+
entries.push({ path: filePath, title: rel, isWave });
|
|
175
204
|
}
|
|
176
205
|
|
|
177
|
-
// Wave-only files not in
|
|
206
|
+
// Wave-only files not already in list (e.g. code files written by agents)
|
|
178
207
|
for (const f of waveFileSet) {
|
|
179
208
|
if (!entries.some(e => e.path === f)) {
|
|
180
209
|
if (docsFilter === 'kb' || docsFilter === 'projects') continue;
|
|
@@ -190,7 +219,7 @@ export const PanelMode: React.FC<PanelModeProps> = ({
|
|
|
190
219
|
});
|
|
191
220
|
|
|
192
221
|
return entries;
|
|
193
|
-
}, [rightTab, docsFilter,
|
|
222
|
+
}, [rightTab, docsFilter, companyRoot, waveFileSet]);
|
|
194
223
|
|
|
195
224
|
const selectedDoc = docsList[docsIndex] ?? null;
|
|
196
225
|
const filePreview = useMemo(() => {
|
|
@@ -35,19 +35,29 @@ export const SetupWizard: React.FC<SetupWizardProps> = ({ onComplete }) => {
|
|
|
35
35
|
const [resultRoles, setResultRoles] = useState(0);
|
|
36
36
|
const [errorMsg, setErrorMsg] = useState('');
|
|
37
37
|
|
|
38
|
-
// Load team templates
|
|
39
|
-
//
|
|
40
|
-
// and http response callbacks don't fire → timeout
|
|
38
|
+
// Load team templates with retry — same-process server+client
|
|
39
|
+
// can have event loop contention causing ECONNRESET or timeout
|
|
41
40
|
useEffect(() => {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
41
|
+
let cancelled = false;
|
|
42
|
+
const load = async (attempt = 0): Promise<void> => {
|
|
43
|
+
if (cancelled) return;
|
|
44
|
+
// Delay to let Ink's render cycle settle
|
|
45
|
+
await new Promise(r => setTimeout(r, attempt === 0 ? 1000 : 2000));
|
|
46
|
+
if (cancelled) return;
|
|
47
|
+
try {
|
|
48
|
+
const teams = await fetchSetupTeams();
|
|
49
|
+
if (!cancelled) setTeams(teams);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
if (cancelled) return;
|
|
52
|
+
if (attempt < 3) {
|
|
53
|
+
return load(attempt + 1);
|
|
54
|
+
}
|
|
55
|
+
setErrorMsg(`Failed to load team templates: ${err instanceof Error ? err.message : 'unknown'}`);
|
|
56
|
+
setStep('error');
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
load();
|
|
60
|
+
return () => { cancelled = true; };
|
|
51
61
|
}, []);
|
|
52
62
|
|
|
53
63
|
// Esc to cancel (only during input steps)
|