tycono 0.1.96-beta.57 → 0.1.96-beta.59
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/bin/tycono.ts +11 -4
- 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/bin/tycono.ts
CHANGED
|
@@ -221,15 +221,22 @@ async function startServerForTui(): Promise<void> {
|
|
|
221
221
|
const origStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
222
222
|
const origLog = (...args: unknown[]) => origStdoutWrite(args.join(' ') + '\n');
|
|
223
223
|
|
|
224
|
-
// Redirect console
|
|
225
|
-
// ⛔ Do NOT redirect process.
|
|
226
|
-
// stdout.write: Ink needs full control for rendering
|
|
227
|
-
// stderr.write: Node.js http client uses it internally — redirect breaks HTTP
|
|
224
|
+
// Redirect console + stdout.write to suppress server logs
|
|
225
|
+
// ⛔ Do NOT redirect process.stderr.write — breaks Node.js http client
|
|
228
226
|
console.log = (...a: unknown[]) => { logStream.write(a.join(' ') + '\n'); };
|
|
229
227
|
console.error = (...a: unknown[]) => { logStream.write(a.join(' ') + '\n'); };
|
|
230
228
|
console.warn = (...a: unknown[]) => { logStream.write(a.join(' ') + '\n'); };
|
|
231
229
|
console.info = (...a: unknown[]) => { logStream.write(a.join(' ') + '\n'); };
|
|
232
230
|
|
|
231
|
+
// Intercept stdout.write — allow Ink (ANSI), redirect server text to log
|
|
232
|
+
const isInkOutput = (s: string) => s.includes('\x1b[') || s.includes('\x1b(');
|
|
233
|
+
process.stdout.write = ((chunk: any, ...args: any[]) => {
|
|
234
|
+
const str = typeof chunk === 'string' ? chunk : chunk.toString();
|
|
235
|
+
if (isInkOutput(str)) return origStdoutWrite(chunk, ...args);
|
|
236
|
+
logStream.write(str);
|
|
237
|
+
return true;
|
|
238
|
+
}) as any;
|
|
239
|
+
|
|
233
240
|
const { createHttpServer } = await import('../src/api/src/create-server.js');
|
|
234
241
|
const server = createHttpServer();
|
|
235
242
|
|
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(() => {
|