tycono 0.1.96-beta.32 → 0.1.96-beta.34

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.1.96-beta.32",
3
+ "version": "0.1.96-beta.34",
4
4
  "description": "Build an AI company. Watch them work.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -149,7 +149,15 @@ class SupervisorHeartbeat {
149
149
  state.directive = text;
150
150
  }
151
151
  state.crashCount = 0;
152
- this.scheduleRestart(state, 0);
152
+
153
+ // Dual Mode: Conversation vs Dispatch (code-level enforcement)
154
+ // If directive looks like a question/status check → spawn conversation mode
155
+ // If directive looks like a task → spawn full supervisor with dispatch tools
156
+ if (this.isConversationDirective(text)) {
157
+ this.spawnConversation(state, text);
158
+ } else {
159
+ this.scheduleRestart(state, 0);
160
+ }
153
161
  }
154
162
 
155
163
  return directive;
@@ -234,6 +242,120 @@ class SupervisorHeartbeat {
234
242
  .filter(s => s.status === 'running' || s.status === 'starting' || s.status === 'restarting');
235
243
  }
236
244
 
245
+ /* ─── Internal: Dual Mode ─────────────────── */
246
+
247
+ /**
248
+ * Heuristic: is this directive a question/status check (conversation)
249
+ * or a work task (needs dispatch)?
250
+ *
251
+ * Conversation signals: question marks, status keywords, short length
252
+ * Dispatch signals: imperative verbs (만들어, 수정해, 구현해), long directives
253
+ */
254
+ private isConversationDirective(text: string): boolean {
255
+ const t = text.trim();
256
+
257
+ // Short messages with question marks → conversation
258
+ if (t.includes('?') && t.length < 100) return true;
259
+
260
+ // Korean question patterns
261
+ const questionPatterns = [
262
+ /확인해/, /알려줘/, /보여줘/, /어때/, /뭐야/, /뭐지/, /뭘까/,
263
+ /상태/, /상황/, /진행/, /현재/, /어디/, /얼마/,
264
+ /what/i, /how.*going/i, /status/i, /check/i, /show/i, /tell/i,
265
+ ];
266
+ if (questionPatterns.some(p => p.test(t))) return true;
267
+
268
+ // Long directives with action verbs → dispatch
269
+ const taskPatterns = [
270
+ /만들어/, /구현해/, /개발해/, /수정해/, /변경해/, /리팩토링/,
271
+ /설계해/, /작성해/, /배포해/, /테스트해/, /고쳐/,
272
+ /build/i, /create/i, /implement/i, /develop/i, /fix/i, /deploy/i, /refactor/i,
273
+ ];
274
+ if (taskPatterns.some(p => p.test(t))) return false;
275
+
276
+ // Default: short → conversation, long → dispatch
277
+ return t.length < 60;
278
+ }
279
+
280
+ /**
281
+ * Spawn a lightweight conversation session (no dispatch tools).
282
+ * CEO reads files and answers directly.
283
+ */
284
+ private spawnConversation(state: SupervisorState, directive: string): void {
285
+ // Build conversation context: previous directives + last execution summary
286
+ const deliveredDirectives = state.pendingDirectives.filter(d => d.delivered);
287
+ const directiveHistory = deliveredDirectives.length > 0
288
+ ? deliveredDirectives.map(d => `- CEO: "${d.text}"`).join('\n')
289
+ : '';
290
+
291
+ // Extract last execution's output from activity stream (what "just happened")
292
+ let lastExecutionSummary = '';
293
+ if (state.supervisorSessionId) {
294
+ try {
295
+ const events = ActivityStream.readAll(state.supervisorSessionId);
296
+ // Get last text outputs (the supervisor's final response)
297
+ const textEvents = events.filter(e => e.type === 'text' && e.roleId === 'ceo');
298
+ const toolEvents = events.filter(e => e.type === 'tool:start' && e.roleId === 'ceo');
299
+
300
+ // Summarize: what tools were used + final text
301
+ const toolSummary = toolEvents.slice(-10).map(e => {
302
+ const name = (e.data.name as string) ?? '';
303
+ const inp = e.data.input as Record<string, unknown> | undefined;
304
+ const detail = inp?.file_path ?? inp?.command ?? '';
305
+ return ` → ${name} ${String(detail).slice(0, 60)}`;
306
+ }).join('\n');
307
+
308
+ const lastText = textEvents.slice(-5).map(e => String(e.data.text ?? '')).join('').slice(-500);
309
+
310
+ if (toolSummary || lastText) {
311
+ lastExecutionSummary = `\n[Previous execution in this wave]\nTools used:\n${toolSummary}\n\nLast response:\n${lastText.slice(0, 500)}\n`;
312
+ }
313
+ } catch { /* ignore */ }
314
+ }
315
+
316
+ const context = [directiveHistory, lastExecutionSummary].filter(Boolean).join('\n');
317
+
318
+ const task = `${context ? context + '\n' : ''}[CEO Question] ${directive}
319
+
320
+ You are the CEO's AI assistant. The above shows what happened previously in this wave.
321
+ Answer the CEO's question based on context. Be specific — reference files, results, and actions from the previous execution.
322
+ Do NOT dispatch anyone. Do NOT create new files. Just answer concisely.`;
323
+
324
+ // Reuse session
325
+ let sessionId = state.supervisorSessionId;
326
+ if (!sessionId || !getSession(sessionId)) {
327
+ const session = createSession('ceo', {
328
+ mode: 'do',
329
+ source: 'wave',
330
+ waveId: state.waveId,
331
+ });
332
+ sessionId = session.id;
333
+ state.supervisorSessionId = sessionId;
334
+ }
335
+
336
+ state.status = 'running';
337
+
338
+ try {
339
+ const exec = executionManager.startExecution({
340
+ type: 'assign', // assign = no supervisor tools (dispatch/watch/amend)
341
+ roleId: 'ceo',
342
+ task,
343
+ sourceRole: 'ceo',
344
+ readOnly: true, // readOnly = no code changes, conversation only
345
+ sessionId,
346
+ });
347
+
348
+ state.executionId = exec.id;
349
+ this.watchExecution(state, exec);
350
+
351
+ console.log(`[Supervisor] Conversation mode for wave ${state.waveId} | directive: ${directive.slice(0, 60)}`);
352
+ } catch (err) {
353
+ console.error(`[Supervisor] Conversation spawn failed:`, err);
354
+ // Fallback to full supervisor
355
+ this.scheduleRestart(state, 0);
356
+ }
357
+ }
358
+
237
359
  /* ─── Internal: Spawn / Restart ────────────── */
238
360
 
239
361
  private spawnSupervisor(state: SupervisorState): void {