tycono 0.1.94-beta.0 → 0.1.94-beta.10
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 +3 -2
- package/src/api/src/create-server.ts +2 -1
- package/src/api/src/engine/agent-loop.ts +149 -142
- package/src/api/src/engine/context-assembler.ts +147 -66
- package/src/api/src/engine/org-tree.ts +1 -1
- package/src/api/src/engine/role-lifecycle.ts +1 -1
- package/src/api/src/engine/runners/claude-cli.ts +2 -3
- package/src/api/src/engine/tools/definitions.ts +7 -4
- package/src/api/src/engine/tools/executor.ts +37 -3
- package/src/api/src/routes/execute.ts +171 -148
- package/src/api/src/routes/operations.ts +1 -1
- package/src/api/src/routes/sessions.ts +2 -0
- package/src/api/src/services/company-config.ts +3 -0
- package/src/api/src/services/digest-engine.ts +2 -2
- package/src/api/src/services/execution-manager.ts +82 -4
- package/src/api/src/services/scaffold.ts +24 -6
- package/src/api/src/services/session-store.ts +20 -2
- package/src/api/src/services/supervisor-heartbeat.ts +461 -0
- package/src/api/src/services/wave-multiplexer.ts +11 -3
- package/src/api/src/services/wave-tracker.ts +175 -0
- package/src/shared/types.ts +3 -2
- package/src/web/dist/assets/index-B8yxzPmd.css +1 -0
- package/src/web/dist/assets/index-C9U34tT1.js +138 -0
- package/src/web/dist/assets/index-DuB5baFp.js +1 -0
- package/src/web/dist/assets/preview-app-QWV7zpb0.js +1 -0
- package/src/web/dist/index.html +2 -2
- package/src/web/dist/tyconoforge.js +1 -0
- package/templates/teams/agency.json +31 -7
- package/templates/teams/research.json +32 -8
- package/templates/teams/startup.json +31 -7
- package/src/web/dist/assets/index-BMTSnwP3.js +0 -1
- package/src/web/dist/assets/index-BnhRwHgb.css +0 -1
- package/src/web/dist/assets/index-Cs1ZyjC9.js +0 -116
- package/src/web/dist/assets/preview-app-DgScvta-.js +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tycono",
|
|
3
|
-
"version": "0.1.94-beta.
|
|
3
|
+
"version": "0.1.94-beta.10",
|
|
4
4
|
"description": "Build an AI company. Watch them work.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"dev:web": "npm run dev --prefix src/web",
|
|
24
24
|
"build": "npm run build:web && npm run build:forge",
|
|
25
25
|
"build:web": "npm run build --prefix src/web",
|
|
26
|
-
"build:forge": "
|
|
26
|
+
"build:forge": "cp node_modules/tyconoforge/dist/tyconoforge.js src/web/dist/tyconoforge.js",
|
|
27
27
|
"typecheck": "npm run typecheck:api && npm run typecheck:web",
|
|
28
28
|
"typecheck:api": "cd src/api && npx tsc --noEmit",
|
|
29
29
|
"typecheck:web": "cd src/web && npx tsc --noEmit",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"gray-matter": "^4.0.3",
|
|
39
39
|
"marked": "^15.0.6",
|
|
40
40
|
"tsx": "^4.19.3",
|
|
41
|
+
"tyconoforge": "^0.1.0-beta.0",
|
|
41
42
|
"yaml": "^2.7.0"
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|
|
@@ -128,7 +128,8 @@ export function createHttpServer(): http.Server {
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
// SSE 엔드포인트: Express 우회하여 raw HTTP로 처리
|
|
131
|
-
|
|
131
|
+
// BUG-008: /api/waves/:waveId/directive and /api/waves/:waveId/question POST도 포함
|
|
132
|
+
if ((url.startsWith('/api/exec/') || url.startsWith('/api/jobs') || url.startsWith('/api/waves/') || url === '/api/waves/save' || url === '/api/setup/import-knowledge') && method === 'POST') {
|
|
132
133
|
setExecCors(req, res);
|
|
133
134
|
if (url === '/api/setup/import-knowledge') {
|
|
134
135
|
handleImportKnowledge(req, res);
|
|
@@ -172,7 +172,12 @@ export async function runAgentLoop(config: AgentConfig): Promise<AgentResult> {
|
|
|
172
172
|
const hasBash = !readOnly && !!config.codeRoot;
|
|
173
173
|
const node = orgTree.nodes.get(roleId);
|
|
174
174
|
const heartbeatEnabled = node?.heartbeat?.enabled === true && subordinates.length > 0;
|
|
175
|
-
|
|
175
|
+
// Peers = other roles with the same reportsTo (same parent in org tree)
|
|
176
|
+
const parentId = node?.reportsTo;
|
|
177
|
+
const hasPeers = parentId
|
|
178
|
+
? (getSubordinates(orgTree, parentId).filter(id => id !== roleId).length > 0)
|
|
179
|
+
: false;
|
|
180
|
+
const tools = getToolsForRole(subordinates.length > 0, readOnly, hasBash, heartbeatEnabled, hasPeers);
|
|
176
181
|
|
|
177
182
|
// 3. Set up tool executor
|
|
178
183
|
const toolExecOptions: ToolExecutorOptions = {
|
|
@@ -550,163 +555,63 @@ export async function runAgentLoop(config: AgentConfig): Promise<AgentResult> {
|
|
|
550
555
|
}
|
|
551
556
|
}
|
|
552
557
|
|
|
553
|
-
//
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
['write', 'edit', 'bash'].includes(tc.name.toLowerCase()),
|
|
558
|
-
);
|
|
558
|
+
// Detect file changes once — used by Phase B and Post-K
|
|
559
|
+
const hasFileChanges = allToolCalls.some((tc) =>
|
|
560
|
+
['write', 'edit', 'bash'].includes(tc.name.toLowerCase()),
|
|
561
|
+
);
|
|
559
562
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
if (turns < maxTurns) {
|
|
572
|
-
turns++;
|
|
573
|
-
const verifyResponse = await llm.chat(context.systemPrompt, messages, tools, abortSignal);
|
|
574
|
-
totalInput += verifyResponse.usage.inputTokens;
|
|
575
|
-
totalOutput += verifyResponse.usage.outputTokens;
|
|
576
|
-
config.tokenLedger?.record({
|
|
577
|
-
ts: new Date().toISOString(),
|
|
578
|
-
sessionId: config.sessionId,
|
|
579
|
-
roleId,
|
|
580
|
-
model: config.model ?? 'unknown',
|
|
581
|
-
inputTokens: verifyResponse.usage.inputTokens,
|
|
582
|
-
outputTokens: verifyResponse.usage.outputTokens,
|
|
583
|
-
});
|
|
563
|
+
// Phase B: Member Self-Verification — type checking + visual verification
|
|
564
|
+
// Any non-C-Level role that made file changes gets verification (no hardcoded role IDs)
|
|
565
|
+
if (!isCLevel && hasFileChanges) {
|
|
566
|
+
const verifyPrompt = [
|
|
567
|
+
'[AUTO-VERIFICATION] 작업이 완료되었습니다. 아래 검증을 수행하세요:',
|
|
568
|
+
'1. `cd src/api && npx tsc --noEmit` — 타입 에러 확인',
|
|
569
|
+
'2. `cd src/web && npx tsc --noEmit` — 프론트엔드 타입 에러 확인',
|
|
570
|
+
'3. UI/CSS 변경이 있었다면 Playwright MCP로 스크린샷을 촬영하여 시각 검증',
|
|
571
|
+
'검증 결과를 간단히 보고하세요.',
|
|
572
|
+
].join('\n');
|
|
584
573
|
|
|
585
|
-
|
|
586
|
-
for (const block of verifyResponse.content) {
|
|
587
|
-
if (block.type === 'text' && block.text) {
|
|
588
|
-
outputParts.push(block.text);
|
|
589
|
-
onText?.(block.text);
|
|
590
|
-
}
|
|
591
|
-
}
|
|
574
|
+
messages.push({ role: 'user', content: verifyPrompt });
|
|
592
575
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
}
|
|
607
|
-
messages.push({
|
|
608
|
-
role: 'user',
|
|
609
|
-
content: verifyResults.map((r) => ({
|
|
610
|
-
type: 'tool_result' as const,
|
|
611
|
-
tool_use_id: r.tool_use_id,
|
|
612
|
-
content: r.content,
|
|
613
|
-
is_error: r.is_error,
|
|
614
|
-
})) as unknown as MessageContent[],
|
|
615
|
-
});
|
|
576
|
+
if (turns < maxTurns) {
|
|
577
|
+
turns++;
|
|
578
|
+
const verifyResponse = await llm.chat(context.systemPrompt, messages, tools, abortSignal);
|
|
579
|
+
totalInput += verifyResponse.usage.inputTokens;
|
|
580
|
+
totalOutput += verifyResponse.usage.outputTokens;
|
|
581
|
+
config.tokenLedger?.record({
|
|
582
|
+
ts: new Date().toISOString(),
|
|
583
|
+
sessionId: config.sessionId,
|
|
584
|
+
roleId,
|
|
585
|
+
model: config.model ?? 'unknown',
|
|
586
|
+
inputTokens: verifyResponse.usage.inputTokens,
|
|
587
|
+
outputTokens: verifyResponse.usage.outputTokens,
|
|
588
|
+
});
|
|
616
589
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
config.tokenLedger?.record({
|
|
623
|
-
ts: new Date().toISOString(),
|
|
624
|
-
sessionId: config.sessionId,
|
|
625
|
-
roleId,
|
|
626
|
-
model: config.model ?? 'unknown',
|
|
627
|
-
inputTokens: summaryResponse.usage.inputTokens,
|
|
628
|
-
outputTokens: summaryResponse.usage.outputTokens,
|
|
629
|
-
});
|
|
630
|
-
for (const block of summaryResponse.content) {
|
|
631
|
-
if (block.type === 'text' && block.text) {
|
|
632
|
-
outputParts.push(block.text);
|
|
633
|
-
onText?.(block.text);
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
}
|
|
590
|
+
messages.push({ role: 'assistant', content: verifyResponse.content });
|
|
591
|
+
for (const block of verifyResponse.content) {
|
|
592
|
+
if (block.type === 'text' && block.text) {
|
|
593
|
+
outputParts.push(block.text);
|
|
594
|
+
onText?.(block.text);
|
|
637
595
|
}
|
|
638
|
-
|
|
639
|
-
onTurnComplete?.(turns);
|
|
640
596
|
}
|
|
641
597
|
|
|
642
|
-
//
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
'[POST-KNOWLEDGING] 구현이 완료되었습니다. The Loop 마무리를 수행하세요:',
|
|
646
|
-
'',
|
|
647
|
-
'## ④ Knowledge 업데이트 (The Loop Step 4)',
|
|
648
|
-
'다음 중 해당하는 항목을 수행하세요:',
|
|
649
|
-
'- 본인 journal 업데이트 (`roles/' + roleId + '/journal/YYYY-MM-DD.md` — 오늘 날짜 파일)',
|
|
650
|
-
'- 구현 중 새로 발견한 패턴/아키텍처 결정이 있다면 관련 문서 업데이트',
|
|
651
|
-
' (예: architecture/web-app-ia.md, architecture/session-worktree-isolation.md 등)',
|
|
652
|
-
'- 중요한 기술 결정은 operations/decisions/ 또는 architecture/ 반영',
|
|
653
|
-
'',
|
|
654
|
-
'## ⑤ Task 상태 갱신 (The Loop Step 5)',
|
|
655
|
-
'- `projects/tycono-platform/tasks.md` (또는 관련 tasks 파일)에서 완료한 태스크 상태를 DONE으로 변경',
|
|
656
|
-
'- 다음 작업이 있다면 식별하여 메모',
|
|
657
|
-
'',
|
|
658
|
-
'⛔ **필수**: ④와 ⑤를 모두 수행해야 The Loop이 완료됩니다.',
|
|
659
|
-
'이제 ④⑤를 수행하세요 (Read, Edit 도구 사용).',
|
|
660
|
-
].join('\n');
|
|
661
|
-
|
|
662
|
-
messages.push({ role: 'user', content: postKPrompt });
|
|
663
|
-
|
|
664
|
-
// Run Post-K loop (최대 2턴 — C-Level의 3턴보다 짧게)
|
|
665
|
-
const maxPostKRounds = 2;
|
|
666
|
-
for (let round = 0; round < maxPostKRounds && turns < maxTurns; round++) {
|
|
667
|
-
if (abortSignal?.aborted) break;
|
|
668
|
-
turns++;
|
|
669
|
-
|
|
670
|
-
const postKResponse = await llm.chat(context.systemPrompt, messages, tools, abortSignal);
|
|
671
|
-
totalInput += postKResponse.usage.inputTokens;
|
|
672
|
-
totalOutput += postKResponse.usage.outputTokens;
|
|
673
|
-
config.tokenLedger?.record({
|
|
674
|
-
ts: new Date().toISOString(),
|
|
675
|
-
sessionId: config.sessionId,
|
|
676
|
-
roleId,
|
|
677
|
-
model: config.model ?? 'unknown',
|
|
678
|
-
inputTokens: postKResponse.usage.inputTokens,
|
|
679
|
-
outputTokens: postKResponse.usage.outputTokens,
|
|
680
|
-
});
|
|
681
|
-
|
|
682
|
-
messages.push({ role: 'assistant', content: postKResponse.content });
|
|
683
|
-
for (const block of postKResponse.content) {
|
|
684
|
-
if (block.type === 'text' && block.text) {
|
|
685
|
-
outputParts.push(block.text);
|
|
686
|
-
onText?.(block.text);
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
// If no tool calls, Post-K is done
|
|
691
|
-
if (postKResponse.stopReason !== 'tool_use') break;
|
|
692
|
-
|
|
693
|
-
// Execute Post-K tool calls
|
|
694
|
-
const postKToolCalls = postKResponse.content.filter(
|
|
598
|
+
// Execute verification tool calls if needed
|
|
599
|
+
if (verifyResponse.stopReason === 'tool_use') {
|
|
600
|
+
const verifyToolCalls = verifyResponse.content.filter(
|
|
695
601
|
(b): b is MessageContent & { type: 'tool_use' } => b.type === 'tool_use',
|
|
696
602
|
);
|
|
697
|
-
const
|
|
698
|
-
for (const tc of
|
|
603
|
+
const verifyResults: ToolResult[] = [];
|
|
604
|
+
for (const tc of verifyToolCalls) {
|
|
699
605
|
allToolCalls.push({ name: tc.name, input: tc.input });
|
|
700
606
|
const result = await executeTool(
|
|
701
607
|
{ id: tc.id, name: tc.name, input: tc.input },
|
|
702
608
|
toolExecOptions,
|
|
703
609
|
);
|
|
704
|
-
|
|
610
|
+
verifyResults.push(result);
|
|
705
611
|
}
|
|
706
|
-
|
|
707
612
|
messages.push({
|
|
708
613
|
role: 'user',
|
|
709
|
-
content:
|
|
614
|
+
content: verifyResults.map((r) => ({
|
|
710
615
|
type: 'tool_result' as const,
|
|
711
616
|
tool_use_id: r.tool_use_id,
|
|
712
617
|
content: r.content,
|
|
@@ -714,8 +619,110 @@ export async function runAgentLoop(config: AgentConfig): Promise<AgentResult> {
|
|
|
714
619
|
})) as unknown as MessageContent[],
|
|
715
620
|
});
|
|
716
621
|
|
|
717
|
-
|
|
622
|
+
if (turns < maxTurns) {
|
|
623
|
+
turns++;
|
|
624
|
+
const summaryResponse = await llm.chat(context.systemPrompt, messages, tools, abortSignal);
|
|
625
|
+
totalInput += summaryResponse.usage.inputTokens;
|
|
626
|
+
totalOutput += summaryResponse.usage.outputTokens;
|
|
627
|
+
config.tokenLedger?.record({
|
|
628
|
+
ts: new Date().toISOString(),
|
|
629
|
+
sessionId: config.sessionId,
|
|
630
|
+
roleId,
|
|
631
|
+
model: config.model ?? 'unknown',
|
|
632
|
+
inputTokens: summaryResponse.usage.inputTokens,
|
|
633
|
+
outputTokens: summaryResponse.usage.outputTokens,
|
|
634
|
+
});
|
|
635
|
+
for (const block of summaryResponse.content) {
|
|
636
|
+
if (block.type === 'text' && block.text) {
|
|
637
|
+
outputParts.push(block.text);
|
|
638
|
+
onText?.(block.text);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
onTurnComplete?.(turns);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// ── Post-K: ④⑤ Knowledge/Task update (KP-008) ──
|
|
649
|
+
// ALL roles (C-Level and members) update journal/tasks after significant work.
|
|
650
|
+
// Runs for: members who made file changes, C-Level who dispatched work.
|
|
651
|
+
if (hasFileChanges || dispatches.length > 0) {
|
|
652
|
+
const postKPrompt = [
|
|
653
|
+
'[POST-KNOWLEDGING] 작업이 완료되었습니다. The Loop 마무리를 수행하세요:',
|
|
654
|
+
'',
|
|
655
|
+
'## ④ Knowledge 업데이트 (The Loop Step 4)',
|
|
656
|
+
'다음 중 해당하는 항목을 수행하세요:',
|
|
657
|
+
'- 본인 journal 업데이트 (`roles/' + roleId + '/journal/YYYY-MM-DD.md` — 오늘 날짜 파일)',
|
|
658
|
+
'- 구현 중 새로 발견한 패턴/아키텍처 결정이 있다면 관련 문서 업데이트',
|
|
659
|
+
' (예: architecture/web-app-ia.md, architecture/session-worktree-isolation.md 등)',
|
|
660
|
+
'- 중요한 기술 결정은 operations/decisions/ 또는 architecture/ 반영',
|
|
661
|
+
'',
|
|
662
|
+
'## ⑤ Task 상태 갱신 (The Loop Step 5)',
|
|
663
|
+
'- `projects/tycono-platform/tasks.md` (또는 관련 tasks 파일)에서 완료한 태스크 상태를 DONE으로 변경',
|
|
664
|
+
'- 다음 작업이 있다면 식별하여 메모',
|
|
665
|
+
'',
|
|
666
|
+
'⛔ **필수**: ④와 ⑤를 모두 수행해야 The Loop이 완료됩니다.',
|
|
667
|
+
'이제 ④⑤를 수행하세요 (Read, Edit 도구 사용).',
|
|
668
|
+
].join('\n');
|
|
669
|
+
|
|
670
|
+
messages.push({ role: 'user', content: postKPrompt });
|
|
671
|
+
|
|
672
|
+
// Run Post-K loop (최대 2턴)
|
|
673
|
+
const maxPostKRounds = 2;
|
|
674
|
+
for (let round = 0; round < maxPostKRounds && turns < maxTurns; round++) {
|
|
675
|
+
if (abortSignal?.aborted) break;
|
|
676
|
+
turns++;
|
|
677
|
+
|
|
678
|
+
const postKResponse = await llm.chat(context.systemPrompt, messages, tools, abortSignal);
|
|
679
|
+
totalInput += postKResponse.usage.inputTokens;
|
|
680
|
+
totalOutput += postKResponse.usage.outputTokens;
|
|
681
|
+
config.tokenLedger?.record({
|
|
682
|
+
ts: new Date().toISOString(),
|
|
683
|
+
sessionId: config.sessionId,
|
|
684
|
+
roleId,
|
|
685
|
+
model: config.model ?? 'unknown',
|
|
686
|
+
inputTokens: postKResponse.usage.inputTokens,
|
|
687
|
+
outputTokens: postKResponse.usage.outputTokens,
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
messages.push({ role: 'assistant', content: postKResponse.content });
|
|
691
|
+
for (const block of postKResponse.content) {
|
|
692
|
+
if (block.type === 'text' && block.text) {
|
|
693
|
+
outputParts.push(block.text);
|
|
694
|
+
onText?.(block.text);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// If no tool calls, Post-K is done
|
|
699
|
+
if (postKResponse.stopReason !== 'tool_use') break;
|
|
700
|
+
|
|
701
|
+
// Execute Post-K tool calls
|
|
702
|
+
const postKToolCalls = postKResponse.content.filter(
|
|
703
|
+
(b): b is MessageContent & { type: 'tool_use' } => b.type === 'tool_use',
|
|
704
|
+
);
|
|
705
|
+
const postKResults: ToolResult[] = [];
|
|
706
|
+
for (const tc of postKToolCalls) {
|
|
707
|
+
allToolCalls.push({ name: tc.name, input: tc.input });
|
|
708
|
+
const result = await executeTool(
|
|
709
|
+
{ id: tc.id, name: tc.name, input: tc.input },
|
|
710
|
+
toolExecOptions,
|
|
711
|
+
);
|
|
712
|
+
postKResults.push(result);
|
|
718
713
|
}
|
|
714
|
+
|
|
715
|
+
messages.push({
|
|
716
|
+
role: 'user',
|
|
717
|
+
content: postKResults.map((r) => ({
|
|
718
|
+
type: 'tool_result' as const,
|
|
719
|
+
tool_use_id: r.tool_use_id,
|
|
720
|
+
content: r.content,
|
|
721
|
+
is_error: r.is_error,
|
|
722
|
+
})) as unknown as MessageContent[],
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
onTurnComplete?.(turns);
|
|
719
726
|
}
|
|
720
727
|
}
|
|
721
728
|
}
|