tycono 0.1.42 → 0.1.43
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/api/src/engine/agent-loop.ts +170 -65
- package/src/api/src/engine/context-assembler.ts +66 -10
- package/src/api/src/routes/knowledge.ts +3 -3
- package/src/api/src/services/git-save.ts +139 -38
- package/src/web/dist/assets/{index-BSryh72N.js → index-XDE-Ld_X.js} +28 -28
- package/src/web/dist/assets/{preview-app-Bl6BvDdj.js → preview-app-DtCHQ2CL.js} +1 -1
- package/src/web/dist/index.html +1 -1
package/package.json
CHANGED
|
@@ -245,96 +245,201 @@ export async function runAgentLoop(config: AgentConfig): Promise<AgentResult> {
|
|
|
245
245
|
onTurnComplete?.(turns);
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
-
// ──
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
'
|
|
261
|
-
'
|
|
248
|
+
// ── Post-execution phases (depth 0 only) ──
|
|
249
|
+
if (!readOnly && depth === 0 && turns > 0) {
|
|
250
|
+
const node = orgTree.nodes.get(roleId);
|
|
251
|
+
const isCLevel = node?.level === 'c-level';
|
|
252
|
+
|
|
253
|
+
// Phase A: C-Level Supervision Loop — review dispatches, update knowledge, dispatch next
|
|
254
|
+
if (isCLevel && dispatches.length > 0) {
|
|
255
|
+
const dispatchSummary = dispatches.map((d, i) =>
|
|
256
|
+
`${i + 1}. **${d.roleId}**: "${d.task.slice(0, 80)}"\n Result: ${d.result.slice(0, 300)}`,
|
|
257
|
+
).join('\n\n');
|
|
258
|
+
|
|
259
|
+
const supervisionPrompt = [
|
|
260
|
+
'[SUPERVISION LOOP] Your subordinates have completed their tasks. Follow the C-Level Protocol:',
|
|
261
|
+
'',
|
|
262
|
+
'## Subordinate Results',
|
|
263
|
+
dispatchSummary,
|
|
264
|
+
'',
|
|
265
|
+
'## Required Actions (do ALL of these):',
|
|
266
|
+
'',
|
|
267
|
+
'### 1. Review',
|
|
268
|
+
'Does each result meet the acceptance criteria? If not, re-dispatch with specific feedback.',
|
|
269
|
+
'',
|
|
270
|
+
'### 2. Knowledge Update (The Loop Step ④)',
|
|
271
|
+
'Record any new decisions, findings, or analysis in appropriate AKB documents:',
|
|
272
|
+
'- Update your journal (`roles/' + roleId + '/journal/`)',
|
|
273
|
+
'- Update relevant project docs if needed',
|
|
274
|
+
'- Update knowledge/ if there are reusable insights',
|
|
275
|
+
'',
|
|
276
|
+
'### 3. Task Update (The Loop Step ⑤)',
|
|
277
|
+
'Update task status in the relevant tasks.md or project documents.',
|
|
278
|
+
'Mark completed items as DONE. Identify the NEXT task to dispatch.',
|
|
279
|
+
'',
|
|
280
|
+
'### 4. Next Dispatch',
|
|
281
|
+
'If there are remaining tasks (e.g., QA after Engineer, or the next task in the backlog):',
|
|
282
|
+
'- Dispatch the next task to the appropriate subordinate',
|
|
283
|
+
'- If all work is done, synthesize a final report for your superior',
|
|
284
|
+
'',
|
|
285
|
+
'Execute these actions now using your tools (Read, Edit, Bash, dispatch).',
|
|
262
286
|
].join('\n');
|
|
263
287
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
288
|
+
// Run supervision loop (up to 3 additional rounds of tool use)
|
|
289
|
+
messages.push({ role: 'user', content: supervisionPrompt });
|
|
290
|
+
const maxSupervisionRounds = 3;
|
|
291
|
+
for (let round = 0; round < maxSupervisionRounds && turns < maxTurns; round++) {
|
|
292
|
+
if (abortSignal?.aborted) break;
|
|
268
293
|
turns++;
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
294
|
+
|
|
295
|
+
const supResponse = await llm.chat(context.systemPrompt, messages, tools, abortSignal);
|
|
296
|
+
totalInput += supResponse.usage.inputTokens;
|
|
297
|
+
totalOutput += supResponse.usage.outputTokens;
|
|
272
298
|
config.tokenLedger?.record({
|
|
273
299
|
ts: new Date().toISOString(),
|
|
274
300
|
jobId: config.jobId ?? 'unknown',
|
|
275
301
|
roleId,
|
|
276
302
|
model: config.model ?? 'unknown',
|
|
277
|
-
inputTokens:
|
|
278
|
-
outputTokens:
|
|
303
|
+
inputTokens: supResponse.usage.inputTokens,
|
|
304
|
+
outputTokens: supResponse.usage.outputTokens,
|
|
279
305
|
});
|
|
280
306
|
|
|
281
|
-
messages.push({ role: 'assistant', content:
|
|
282
|
-
|
|
283
|
-
for (const block of verifyResponse.content) {
|
|
307
|
+
messages.push({ role: 'assistant', content: supResponse.content });
|
|
308
|
+
for (const block of supResponse.content) {
|
|
284
309
|
if (block.type === 'text' && block.text) {
|
|
285
310
|
outputParts.push(block.text);
|
|
286
311
|
onText?.(block.text);
|
|
287
312
|
}
|
|
288
313
|
}
|
|
289
314
|
|
|
290
|
-
// If
|
|
291
|
-
if (
|
|
292
|
-
|
|
293
|
-
|
|
315
|
+
// If no tool calls, supervision is done
|
|
316
|
+
if (supResponse.stopReason !== 'tool_use') break;
|
|
317
|
+
|
|
318
|
+
// Execute tool calls
|
|
319
|
+
const supToolCalls = supResponse.content.filter(
|
|
320
|
+
(b): b is MessageContent & { type: 'tool_use' } => b.type === 'tool_use',
|
|
321
|
+
);
|
|
322
|
+
const supResults: ToolResult[] = [];
|
|
323
|
+
for (const tc of supToolCalls) {
|
|
324
|
+
allToolCalls.push({ name: tc.name, input: tc.input });
|
|
325
|
+
const result = await executeTool(
|
|
326
|
+
{ id: tc.id, name: tc.name, input: tc.input },
|
|
327
|
+
toolExecOptions,
|
|
294
328
|
);
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
329
|
+
supResults.push(result);
|
|
330
|
+
|
|
331
|
+
// Track additional dispatches from supervision
|
|
332
|
+
if (tc.name === 'dispatch' && !result.is_error) {
|
|
333
|
+
dispatches.push({
|
|
334
|
+
roleId: String(tc.input.roleId),
|
|
335
|
+
task: String(tc.input.task),
|
|
336
|
+
result: result.content,
|
|
337
|
+
});
|
|
303
338
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
messages.push({
|
|
342
|
+
role: 'user',
|
|
343
|
+
content: supResults.map((r) => ({
|
|
344
|
+
type: 'tool_result' as const,
|
|
345
|
+
tool_use_id: r.tool_use_id,
|
|
346
|
+
content: r.content,
|
|
347
|
+
is_error: r.is_error,
|
|
348
|
+
})) as unknown as MessageContent[],
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
onTurnComplete?.(turns);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Phase B: Engineer/CTO Verification — type checking + visual verification
|
|
356
|
+
const verifiableRoles = ['engineer', 'cto'];
|
|
357
|
+
if (verifiableRoles.includes(roleId)) {
|
|
358
|
+
const hasFileChanges = allToolCalls.some((tc) =>
|
|
359
|
+
['write', 'edit', 'bash'].includes(tc.name.toLowerCase()),
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
if (hasFileChanges) {
|
|
363
|
+
const verifyPrompt = [
|
|
364
|
+
'[AUTO-VERIFICATION] 작업이 완료되었습니다. 아래 검증을 수행하세요:',
|
|
365
|
+
'1. `cd src/api && npx tsc --noEmit` — 타입 에러 확인',
|
|
366
|
+
'2. `cd src/web && npx tsc --noEmit` — 프론트엔드 타입 에러 확인',
|
|
367
|
+
'3. UI/CSS 변경이 있었다면 Playwright MCP로 스크린샷을 촬영하여 시각 검증',
|
|
368
|
+
'검증 결과를 간단히 보고하세요.',
|
|
369
|
+
].join('\n');
|
|
370
|
+
|
|
371
|
+
messages.push({ role: 'user', content: verifyPrompt });
|
|
372
|
+
|
|
373
|
+
if (turns < maxTurns) {
|
|
374
|
+
turns++;
|
|
375
|
+
const verifyResponse = await llm.chat(context.systemPrompt, messages, tools, abortSignal);
|
|
376
|
+
totalInput += verifyResponse.usage.inputTokens;
|
|
377
|
+
totalOutput += verifyResponse.usage.outputTokens;
|
|
378
|
+
config.tokenLedger?.record({
|
|
379
|
+
ts: new Date().toISOString(),
|
|
380
|
+
jobId: config.jobId ?? 'unknown',
|
|
381
|
+
roleId,
|
|
382
|
+
model: config.model ?? 'unknown',
|
|
383
|
+
inputTokens: verifyResponse.usage.inputTokens,
|
|
384
|
+
outputTokens: verifyResponse.usage.outputTokens,
|
|
313
385
|
});
|
|
314
386
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
387
|
+
messages.push({ role: 'assistant', content: verifyResponse.content });
|
|
388
|
+
for (const block of verifyResponse.content) {
|
|
389
|
+
if (block.type === 'text' && block.text) {
|
|
390
|
+
outputParts.push(block.text);
|
|
391
|
+
onText?.(block.text);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Execute verification tool calls if needed
|
|
396
|
+
if (verifyResponse.stopReason === 'tool_use') {
|
|
397
|
+
const verifyToolCalls = verifyResponse.content.filter(
|
|
398
|
+
(b): b is MessageContent & { type: 'tool_use' } => b.type === 'tool_use',
|
|
399
|
+
);
|
|
400
|
+
const verifyResults: ToolResult[] = [];
|
|
401
|
+
for (const tc of verifyToolCalls) {
|
|
402
|
+
allToolCalls.push({ name: tc.name, input: tc.input });
|
|
403
|
+
const result = await executeTool(
|
|
404
|
+
{ id: tc.id, name: tc.name, input: tc.input },
|
|
405
|
+
toolExecOptions,
|
|
406
|
+
);
|
|
407
|
+
verifyResults.push(result);
|
|
408
|
+
}
|
|
409
|
+
messages.push({
|
|
410
|
+
role: 'user',
|
|
411
|
+
content: verifyResults.map((r) => ({
|
|
412
|
+
type: 'tool_result' as const,
|
|
413
|
+
tool_use_id: r.tool_use_id,
|
|
414
|
+
content: r.content,
|
|
415
|
+
is_error: r.is_error,
|
|
416
|
+
})) as unknown as MessageContent[],
|
|
327
417
|
});
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
418
|
+
|
|
419
|
+
if (turns < maxTurns) {
|
|
420
|
+
turns++;
|
|
421
|
+
const summaryResponse = await llm.chat(context.systemPrompt, messages, tools, abortSignal);
|
|
422
|
+
totalInput += summaryResponse.usage.inputTokens;
|
|
423
|
+
totalOutput += summaryResponse.usage.outputTokens;
|
|
424
|
+
config.tokenLedger?.record({
|
|
425
|
+
ts: new Date().toISOString(),
|
|
426
|
+
jobId: config.jobId ?? 'unknown',
|
|
427
|
+
roleId,
|
|
428
|
+
model: config.model ?? 'unknown',
|
|
429
|
+
inputTokens: summaryResponse.usage.inputTokens,
|
|
430
|
+
outputTokens: summaryResponse.usage.outputTokens,
|
|
431
|
+
});
|
|
432
|
+
for (const block of summaryResponse.content) {
|
|
433
|
+
if (block.type === 'text' && block.text) {
|
|
434
|
+
outputParts.push(block.text);
|
|
435
|
+
onText?.(block.text);
|
|
436
|
+
}
|
|
332
437
|
}
|
|
333
438
|
}
|
|
334
439
|
}
|
|
335
|
-
}
|
|
336
440
|
|
|
337
|
-
|
|
441
|
+
onTurnComplete?.(turns);
|
|
442
|
+
}
|
|
338
443
|
}
|
|
339
444
|
}
|
|
340
445
|
}
|
|
@@ -363,6 +363,9 @@ function loadCeoDecisions(companyRoot: string): string | null {
|
|
|
363
363
|
}
|
|
364
364
|
|
|
365
365
|
function buildDispatchSection(orgTree: OrgTree, roleId: string, subordinates: string[], teamStatus?: TeamStatus): string {
|
|
366
|
+
const node = orgTree.nodes.get(roleId);
|
|
367
|
+
const isCLevel = node?.level === 'c-level';
|
|
368
|
+
|
|
366
369
|
const subInfo = subordinates.map((id) => {
|
|
367
370
|
const sub = orgTree.nodes.get(id);
|
|
368
371
|
const base = sub ? `- **${sub.name}** (\`${id}\`): ${sub.persona.split('\n')[0]}` : `- ${id}`;
|
|
@@ -376,9 +379,7 @@ function buildDispatchSection(orgTree: OrgTree, roleId: string, subordinates: st
|
|
|
376
379
|
|
|
377
380
|
const exampleSubId = subordinates[0] ?? 'engineer';
|
|
378
381
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
You can assign tasks to your direct reports. They will execute independently and return results.
|
|
382
|
+
let section = `# Dispatch (Team Management)
|
|
382
383
|
|
|
383
384
|
## Available Team Members
|
|
384
385
|
${subInfo}
|
|
@@ -401,21 +402,76 @@ The command will:
|
|
|
401
402
|
If the subordinate takes longer than 100s, you'll get a job ID. Check the result with:
|
|
402
403
|
\`\`\`bash
|
|
403
404
|
python3 "$DISPATCH_CMD" --check <jobId>
|
|
404
|
-
|
|
405
|
+
\`\`\``;
|
|
405
406
|
|
|
406
|
-
|
|
407
|
+
// C-level roles get mandatory delegation rules
|
|
408
|
+
if (isCLevel) {
|
|
409
|
+
section += `
|
|
407
410
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
+
## C-Level Delegation Protocol (MANDATORY)
|
|
412
|
+
|
|
413
|
+
⛔ **You are a MANAGER. You do NOT write code, tests, or implementation yourself.**
|
|
414
|
+
⛔ **Your job is to PLAN, DELEGATE, REVIEW, and UPDATE KNOWLEDGE.**
|
|
415
|
+
|
|
416
|
+
### Core Rule: Always Delegate Down
|
|
417
|
+
|
|
418
|
+
When you receive a directive:
|
|
419
|
+
1. **Analyze** — Break it into sub-tasks appropriate for each subordinate
|
|
420
|
+
2. **Dispatch** — Assign tasks to subordinates with clear acceptance criteria
|
|
421
|
+
3. **Monitor** — Wait for results, review quality
|
|
422
|
+
4. **Follow up** — If output doesn't meet criteria, dispatch back with feedback
|
|
423
|
+
5. **Report** — Synthesize results and report to your superior
|
|
424
|
+
|
|
425
|
+
### What You Do vs What Subordinates Do
|
|
426
|
+
|
|
427
|
+
| YOU (C-Level) | SUBORDINATES (Members) |
|
|
428
|
+
|---------------|----------------------|
|
|
429
|
+
| Plan & decompose tasks | Implement code/design/tests |
|
|
430
|
+
| Dispatch with clear specs | Execute and return results |
|
|
431
|
+
| Review output quality | Fix issues when told |
|
|
432
|
+
| Update knowledge & tasks | Update their own journals |
|
|
433
|
+
| Report to superior | Report to you |
|
|
411
434
|
|
|
412
|
-
|
|
413
|
-
|
|
435
|
+
### The Supervision Loop (CRITICAL)
|
|
436
|
+
|
|
437
|
+
After EVERY dispatch, follow this loop:
|
|
438
|
+
|
|
439
|
+
\`\`\`
|
|
440
|
+
DISPATCH → WAIT → REVIEW → DECIDE
|
|
441
|
+
├── PASS → Knowledge Update → Task Update → Next Dispatch
|
|
442
|
+
└── FAIL → Re-dispatch with feedback
|
|
414
443
|
\`\`\`
|
|
415
444
|
|
|
445
|
+
1. **Review**: Does the output meet acceptance criteria?
|
|
446
|
+
2. **Knowledge Update**: Record decisions, findings, analysis in AKB (journals, knowledge/)
|
|
447
|
+
3. **Task Update**: Update task status in tasks.md or project docs
|
|
448
|
+
4. **Next Dispatch**: Identify and dispatch the next task
|
|
449
|
+
|
|
450
|
+
### Dispatch Quality Requirements
|
|
451
|
+
|
|
452
|
+
Every dispatch MUST include:
|
|
453
|
+
- **Context**: What documents/files to read first (CLAUDE.md + relevant Hub + SKILL.md)
|
|
454
|
+
- **Task**: Specific deliverable with acceptance criteria
|
|
455
|
+
- **Constraints**: File paths, standards, what NOT to do
|
|
456
|
+
- **AKB instruction**: "⛔ AKB Rule: Read CLAUDE.md before starting work."
|
|
457
|
+
|
|
458
|
+
### Anti-Patterns (NEVER do these)
|
|
459
|
+
|
|
460
|
+
- ❌ Writing code yourself instead of dispatching to engineer
|
|
461
|
+
- ❌ Dispatching without acceptance criteria
|
|
462
|
+
- ❌ Accepting output without reviewing it
|
|
463
|
+
- ❌ Forgetting to update knowledge/tasks after work completes
|
|
464
|
+
- ❌ Doing only 1 dispatch when you should chain multiple (Engineer → QA)
|
|
465
|
+
- ❌ Reporting to superior without synthesizing subordinate outputs`;
|
|
466
|
+
} else {
|
|
467
|
+
section += `
|
|
468
|
+
|
|
416
469
|
## Rules
|
|
417
470
|
- Only dispatch to your direct reports listed above
|
|
418
471
|
- Include clear task description, acceptance criteria, and relevant file paths
|
|
419
472
|
- The dispatched agent will work independently and return results to you
|
|
420
473
|
- After receiving results, synthesize and report back`;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return section;
|
|
421
477
|
}
|
|
@@ -277,10 +277,10 @@ knowledgeRouter.get('/{*path}', (req: Request, res: Response, next: NextFunction
|
|
|
277
277
|
return;
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
-
const absPath = path.
|
|
280
|
+
const absPath = path.resolve(companyRoot(), docId);
|
|
281
281
|
|
|
282
|
-
// Security: ensure path stays within
|
|
283
|
-
if (!absPath.startsWith(
|
|
282
|
+
// Security: ensure path stays within companyRoot
|
|
283
|
+
if (!absPath.startsWith(companyRoot() + path.sep) && absPath !== companyRoot()) {
|
|
284
284
|
res.status(403).json({ error: 'Forbidden' });
|
|
285
285
|
return;
|
|
286
286
|
}
|