tycono 0.3.22-beta.1 → 0.3.22-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.22-beta.1",
3
+ "version": "0.3.22-beta.3",
4
4
  "description": "Build an AI company. Watch them work.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,6 +13,7 @@
13
13
  */
14
14
  import { executionManager, type Execution } from './execution-manager.js';
15
15
  import { createSession, getSession, listSessions, addMessage, type Message } from './session-store.js';
16
+ import Anthropic from '@anthropic-ai/sdk';
16
17
  import { buildOrgTree, getSubordinates } from '../engine/org-tree.js';
17
18
  import fs from 'node:fs';
18
19
  import path from 'node:path';
@@ -226,14 +227,15 @@ class SupervisorHeartbeat {
226
227
  }
227
228
  state.crashCount = 0;
228
229
 
229
- // Dual Mode: Conversation vs Dispatch (code-level enforcement)
230
- // If directive looks like a question/status check spawn conversation mode
231
- // If directive looks like a task → spawn full supervisor with dispatch tools
232
- if (this.isConversationDirective(text)) {
233
- this.spawnConversation(state, text);
234
- } else {
235
- this.scheduleRestart(state, 0);
236
- }
230
+ // Dual Mode: Conversation vs Dispatch
231
+ // AI classifies the directive (Haiku), falls back to regex
232
+ this.classifyDirective(text).then(isConversation => {
233
+ if (isConversation) {
234
+ this.spawnConversation(state, text);
235
+ } else {
236
+ this.scheduleRestart(state, 0);
237
+ }
238
+ });
237
239
  }
238
240
 
239
241
  return directive;
@@ -321,39 +323,71 @@ class SupervisorHeartbeat {
321
323
  /* ─── Internal: Dual Mode ─────────────────── */
322
324
 
323
325
  /**
324
- * Heuristic: is this directive a question/status check (conversation)
326
+ * Classify: is this directive a question/status check (conversation)
325
327
  * or a work task (needs dispatch)?
326
328
  *
327
- * Conversation signals: question marks, status keywords, short length
328
- * Dispatch signals: imperative verbs (만들어, 수정해, 구현해), long directives
329
+ * Uses Haiku LLM for classification when ANTHROPIC_API_KEY is available.
330
+ * Falls back to regex heuristic when no API key.
329
331
  */
330
- private isConversationDirective(text: string): boolean {
332
+ private async classifyDirective(text: string): Promise<boolean> {
333
+ // Try AI classification first (fast, accurate, language-agnostic)
334
+ if (process.env.ANTHROPIC_API_KEY) {
335
+ try {
336
+ const result = await this.classifyWithHaiku(text);
337
+ console.log(`[Supervisor] AI classified "${text.slice(0, 40)}" as ${result ? 'conversation' : 'task'}`);
338
+ return result;
339
+ } catch (err) {
340
+ console.warn(`[Supervisor] AI classification failed, falling back to regex:`, err);
341
+ }
342
+ }
343
+
344
+ // Fallback: regex heuristic
345
+ return this.isConversationDirectiveFallback(text);
346
+ }
347
+
348
+ private async classifyWithHaiku(text: string): Promise<boolean> {
349
+ const client = new Anthropic();
350
+ const response = await client.messages.create({
351
+ model: 'claude-haiku-4-5-20251001',
352
+ max_tokens: 1,
353
+ system: `You classify user messages to a CEO AI assistant.
354
+ Reply with exactly one character:
355
+ - "Q" if the message is a question, status check, or casual conversation (no action needed)
356
+ - "T" if the message is a task, work instruction, delegation request, or any directive that requires action (creating, building, fixing, assigning work to team members, etc.)
357
+
358
+ Examples:
359
+ "뭐 했어?" → Q
360
+ "CBO한테 일 줘" → T
361
+ "시장 조사해" → T
362
+ "현재 상태 알려줘" → Q
363
+ "게임 만들어 CTO에게 시켜" → T
364
+ "how's it going?" → Q
365
+ "deploy the app" → T`,
366
+ messages: [{ role: 'user', content: text }],
367
+ });
368
+ const reply = (response.content[0] as { type: 'text'; text: string }).text.trim();
369
+ return reply === 'Q';
370
+ }
371
+
372
+ private isConversationDirectiveFallback(text: string): boolean {
331
373
  const t = text.trim();
332
374
 
333
375
  // Short messages with question marks → conversation
334
376
  if (t.includes('?') && t.length < 100) return true;
335
377
 
336
- // Task patterns FIRST — action verbs override question patterns
378
+ // Task patterns — action verbs override
337
379
  const taskPatterns = [
338
380
  /만들어/, /구현해/, /개발해/, /수정해/, /변경해/, /리팩토링/,
339
381
  /설계해/, /작성해/, /배포해/, /테스트해/, /고쳐/, /해줘/, /해봐/,
340
382
  /진행시켜/, /진행해/, /시작해/, /실행해/, /돌려/,
341
383
  /시켜/, /맡겨/, /일\s*줘/, /지시해/, /분석해/, /조사해/, /검토해/,
342
- /에게\s*(시|맡|줘)/, /한테\s*(시|맡|줘)/, // "CBO에게 시켜", "CTO한테 맡겨"
384
+ /에게\s*(시|맡|줘)/, /한테\s*(시|맡|줘)/,
343
385
  /build/i, /create/i, /implement/i, /develop/i, /fix/i, /deploy/i, /refactor/i,
344
386
  /proceed/i, /start/i, /execute/i, /run/i, /do it/i, /go ahead/i,
345
387
  /assign/i, /dispatch/i, /delegate/i, /tell.*to/i, /ask.*to/i,
346
388
  ];
347
389
  if (taskPatterns.some(p => p.test(t))) return false;
348
390
 
349
- // Korean question patterns
350
- const questionPatterns = [
351
- /확인해/, /알려줘/, /보여줘/, /어때/, /뭐야/, /뭐지/, /뭘까/,
352
- /상태/, /상황/, /현재/, /어디/, /얼마/,
353
- /what/i, /how.*going/i, /status/i, /check/i, /show/i, /tell/i,
354
- ];
355
- if (taskPatterns.some(p => p.test(t))) return false;
356
-
357
391
  // Default: short → conversation, long → dispatch
358
392
  return t.length < 60;
359
393
  }
@@ -520,6 +554,12 @@ Do NOT dispatch anyone. Do NOT create new files. Just answer concisely.`;
520
554
  /* ─── Internal: Spawn / Restart ────────────── */
521
555
 
522
556
  private spawnSupervisor(state: SupervisorState): void {
557
+ // Use latest pending directive as the active task (not the wave's initial directive)
558
+ const undelivered = state.pendingDirectives.filter(d => !d.delivered);
559
+ if (undelivered.length > 0) {
560
+ state.directive = undelivered[undelivered.length - 1].text;
561
+ }
562
+
523
563
  const orgTree = buildOrgTree(COMPANY_ROOT, state.preset);
524
564
  let cLevelRoles = getSubordinates(orgTree, 'ceo');
525
565