codingbuddy 4.2.0 → 4.4.0

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.
Files changed (150) hide show
  1. package/dist/src/cli/cli.d.ts +2 -2
  2. package/dist/src/cli/cli.js +4 -2
  3. package/dist/src/cli/cli.js.map +1 -1
  4. package/dist/src/cli/cli.types.d.ts +3 -0
  5. package/dist/src/cli/init/prompts/agent-prompt.d.ts +1 -1
  6. package/dist/src/cli/init/prompts/model-prompt.js +48 -6
  7. package/dist/src/cli/init/prompts/model-prompt.js.map +1 -1
  8. package/dist/src/cli/restart-tui.d.ts +9 -0
  9. package/dist/src/cli/restart-tui.js +30 -0
  10. package/dist/src/cli/restart-tui.js.map +1 -0
  11. package/dist/src/cli/run-tui.d.ts +3 -1
  12. package/dist/src/cli/run-tui.js +13 -1
  13. package/dist/src/cli/run-tui.js.map +1 -1
  14. package/dist/src/config/config.service.d.ts +7 -1
  15. package/dist/src/config/config.service.js +13 -1
  16. package/dist/src/config/config.service.js.map +1 -1
  17. package/dist/src/keyword/keyword.module.js +2 -0
  18. package/dist/src/keyword/keyword.module.js.map +1 -1
  19. package/dist/src/keyword/keyword.service.d.ts +3 -0
  20. package/dist/src/keyword/keyword.service.js +13 -1
  21. package/dist/src/keyword/keyword.service.js.map +1 -1
  22. package/dist/src/keyword/keyword.types.d.ts +3 -3
  23. package/dist/src/keyword/keyword.types.js +31 -1
  24. package/dist/src/keyword/keyword.types.js.map +1 -1
  25. package/dist/src/keyword/patterns/backend.patterns.js +10 -0
  26. package/dist/src/keyword/patterns/backend.patterns.js.map +1 -1
  27. package/dist/src/keyword/patterns/context.patterns.js +1 -1
  28. package/dist/src/keyword/patterns/data-science.patterns.d.ts +2 -0
  29. package/dist/src/keyword/patterns/data-science.patterns.js +71 -0
  30. package/dist/src/keyword/patterns/data-science.patterns.js.map +1 -0
  31. package/dist/src/keyword/patterns/index.d.ts +3 -0
  32. package/dist/src/keyword/patterns/index.js +7 -1
  33. package/dist/src/keyword/patterns/index.js.map +1 -1
  34. package/dist/src/keyword/patterns/intent-pattern-checks.js +24 -0
  35. package/dist/src/keyword/patterns/intent-pattern-checks.js.map +1 -1
  36. package/dist/src/keyword/patterns/security.patterns.d.ts +2 -0
  37. package/dist/src/keyword/patterns/security.patterns.js +106 -0
  38. package/dist/src/keyword/patterns/security.patterns.js.map +1 -0
  39. package/dist/src/keyword/patterns/systems.patterns.d.ts +2 -0
  40. package/dist/src/keyword/patterns/systems.patterns.js +101 -0
  41. package/dist/src/keyword/patterns/systems.patterns.js.map +1 -0
  42. package/dist/src/keyword/patterns/test.patterns.d.ts +2 -0
  43. package/dist/src/keyword/patterns/test.patterns.js +71 -0
  44. package/dist/src/keyword/patterns/test.patterns.js.map +1 -0
  45. package/dist/src/keyword/strategies/__tests__/strategy-test.utils.d.ts +1 -1
  46. package/dist/src/keyword/strategies/__tests__/strategy-test.utils.js +4 -0
  47. package/dist/src/keyword/strategies/__tests__/strategy-test.utils.js.map +1 -1
  48. package/dist/src/keyword/strategies/act-agent.strategy.js +2 -2
  49. package/dist/src/keyword/strategies/act-agent.strategy.js.map +1 -1
  50. package/dist/src/mcp/handlers/index.d.ts +1 -0
  51. package/dist/src/mcp/handlers/index.js +3 -1
  52. package/dist/src/mcp/handlers/index.js.map +1 -1
  53. package/dist/src/mcp/handlers/mode.handler.js +26 -1
  54. package/dist/src/mcp/handlers/mode.handler.js.map +1 -1
  55. package/dist/src/mcp/handlers/skill.handler.js +9 -2
  56. package/dist/src/mcp/handlers/skill.handler.js.map +1 -1
  57. package/dist/src/mcp/handlers/tui.handler.d.ts +8 -0
  58. package/dist/src/mcp/handlers/tui.handler.js +50 -0
  59. package/dist/src/mcp/handlers/tui.handler.js.map +1 -0
  60. package/dist/src/mcp/mcp.module.js +1 -0
  61. package/dist/src/mcp/mcp.module.js.map +1 -1
  62. package/dist/src/mcp/mcp.service.d.ts +1 -0
  63. package/dist/src/mcp/mcp.service.js +17 -0
  64. package/dist/src/mcp/mcp.service.js.map +1 -1
  65. package/dist/src/model/model.constants.d.ts +11 -0
  66. package/dist/src/model/model.constants.js +12 -1
  67. package/dist/src/model/model.constants.js.map +1 -1
  68. package/dist/src/model/model.resolver.d.ts +1 -1
  69. package/dist/src/model/model.resolver.js +15 -4
  70. package/dist/src/model/model.resolver.js.map +1 -1
  71. package/dist/src/shared/client-type.d.ts +6 -0
  72. package/dist/src/shared/client-type.js +21 -0
  73. package/dist/src/shared/client-type.js.map +1 -0
  74. package/dist/src/shared/version.d.ts +1 -0
  75. package/dist/src/shared/version.js +5 -0
  76. package/dist/src/shared/version.js.map +1 -0
  77. package/dist/src/shared/version.utils.js +2 -14
  78. package/dist/src/shared/version.utils.js.map +1 -1
  79. package/dist/src/skill/i18n/keywords.js +1466 -15
  80. package/dist/src/skill/i18n/keywords.js.map +1 -1
  81. package/dist/src/tui/__perf__/rendering-performance.spec.js +4 -4
  82. package/dist/src/tui/__perf__/rendering-performance.spec.js.map +1 -1
  83. package/dist/src/tui/components/ActivityVisualizer.d.ts +7 -3
  84. package/dist/src/tui/components/ActivityVisualizer.js +8 -9
  85. package/dist/src/tui/components/ActivityVisualizer.js.map +1 -1
  86. package/dist/src/tui/components/ActivityVisualizer.spec.js +73 -27
  87. package/dist/src/tui/components/ActivityVisualizer.spec.js.map +1 -1
  88. package/dist/src/tui/components/ChecklistPanel.d.ts +10 -0
  89. package/dist/src/tui/components/ChecklistPanel.js +19 -0
  90. package/dist/src/tui/components/ChecklistPanel.js.map +1 -0
  91. package/dist/src/tui/components/ChecklistPanel.spec.d.ts +1 -0
  92. package/dist/src/tui/components/ChecklistPanel.spec.js +45 -0
  93. package/dist/src/tui/components/ChecklistPanel.spec.js.map +1 -0
  94. package/dist/src/tui/components/FocusedAgentPanel.d.ts +2 -8
  95. package/dist/src/tui/components/FocusedAgentPanel.js +1 -14
  96. package/dist/src/tui/components/FocusedAgentPanel.js.map +1 -1
  97. package/dist/src/tui/components/FocusedAgentPanel.spec.js +20 -51
  98. package/dist/src/tui/components/FocusedAgentPanel.spec.js.map +1 -1
  99. package/dist/src/tui/components/HeaderBar.d.ts +1 -2
  100. package/dist/src/tui/components/HeaderBar.js +3 -7
  101. package/dist/src/tui/components/HeaderBar.js.map +1 -1
  102. package/dist/src/tui/components/HeaderBar.spec.js +13 -13
  103. package/dist/src/tui/components/HeaderBar.spec.js.map +1 -1
  104. package/dist/src/tui/components/StageHealthBar.d.ts +3 -1
  105. package/dist/src/tui/components/StageHealthBar.js +10 -4
  106. package/dist/src/tui/components/StageHealthBar.js.map +1 -1
  107. package/dist/src/tui/components/StageHealthBar.spec.js +21 -7
  108. package/dist/src/tui/components/StageHealthBar.spec.js.map +1 -1
  109. package/dist/src/tui/components/activity-visualizer.pure.d.ts +3 -8
  110. package/dist/src/tui/components/activity-visualizer.pure.js +113 -50
  111. package/dist/src/tui/components/activity-visualizer.pure.js.map +1 -1
  112. package/dist/src/tui/components/checklist-panel.pure.d.ts +2 -0
  113. package/dist/src/tui/components/checklist-panel.pure.js +18 -0
  114. package/dist/src/tui/components/checklist-panel.pure.js.map +1 -0
  115. package/dist/src/tui/components/flow-map.pure.d.ts +2 -2
  116. package/dist/src/tui/components/flow-map.pure.js +120 -29
  117. package/dist/src/tui/components/flow-map.pure.js.map +1 -1
  118. package/dist/src/tui/components/focused-agent.pure.d.ts +0 -14
  119. package/dist/src/tui/components/focused-agent.pure.js +0 -45
  120. package/dist/src/tui/components/focused-agent.pure.js.map +1 -1
  121. package/dist/src/tui/components/grid-layout.pure.js +26 -4
  122. package/dist/src/tui/components/grid-layout.pure.js.map +1 -1
  123. package/dist/src/tui/components/index.d.ts +4 -2
  124. package/dist/src/tui/components/index.js +7 -7
  125. package/dist/src/tui/components/index.js.map +1 -1
  126. package/dist/src/tui/components/stage-health.pure.d.ts +1 -0
  127. package/dist/src/tui/components/stage-health.pure.js +4 -0
  128. package/dist/src/tui/components/stage-health.pure.js.map +1 -1
  129. package/dist/src/tui/dashboard-app.d.ts +2 -1
  130. package/dist/src/tui/dashboard-app.js +12 -15
  131. package/dist/src/tui/dashboard-app.js.map +1 -1
  132. package/dist/src/tui/dashboard-app.spec.js +11 -3
  133. package/dist/src/tui/dashboard-app.spec.js.map +1 -1
  134. package/dist/src/tui/dashboard-types.d.ts +6 -0
  135. package/dist/src/tui/dashboard-types.js +1 -0
  136. package/dist/src/tui/dashboard-types.js.map +1 -1
  137. package/dist/src/tui/eventbus-ui.integration.spec.js +1 -0
  138. package/dist/src/tui/eventbus-ui.integration.spec.js.map +1 -1
  139. package/dist/src/tui/events/parse-agent.js +0 -26
  140. package/dist/src/tui/events/parse-agent.js.map +1 -1
  141. package/dist/src/tui/events/response-event-extractor.js +33 -7
  142. package/dist/src/tui/events/response-event-extractor.js.map +1 -1
  143. package/dist/src/tui/hooks/use-dashboard-state.d.ts +6 -0
  144. package/dist/src/tui/hooks/use-dashboard-state.js +68 -18
  145. package/dist/src/tui/hooks/use-dashboard-state.js.map +1 -1
  146. package/dist/src/tui/multi-session-app.js +6 -1
  147. package/dist/src/tui/multi-session-app.js.map +1 -1
  148. package/dist/src/tui-bundle.mjs +631 -334
  149. package/dist/tsconfig.build.tsbuildinfo +1 -1
  150. package/package.json +3 -3
@@ -1371,12 +1371,12 @@ var require_eventemitter2 = __commonJS({
1371
1371
  });
1372
1372
 
1373
1373
  // src/tui/index.tsx
1374
- import React10 from "react";
1374
+ import React11 from "react";
1375
1375
  import { render } from "ink";
1376
1376
 
1377
1377
  // src/tui/dashboard-app.tsx
1378
- import React7, { useMemo as useMemo3 } from "react";
1379
- import { Box as Box7 } from "ink";
1378
+ import React8, { useMemo as useMemo3 } from "react";
1379
+ import { Box as Box8 } from "ink";
1380
1380
 
1381
1381
  // src/tui/hooks/use-terminal-size.ts
1382
1382
  import { useState, useEffect } from "react";
@@ -1400,6 +1400,7 @@ function createDefaultDashboardNode(params) {
1400
1400
  status: "idle",
1401
1401
  isPrimary: false,
1402
1402
  progress: 0,
1403
+ isParallel: false,
1403
1404
  ...params
1404
1405
  };
1405
1406
  }
@@ -1492,23 +1493,6 @@ var MODE_KEYWORD_TO_AGENT = {
1492
1493
  EVAL: "eval-mode",
1493
1494
  AUTO: "auto-mode"
1494
1495
  };
1495
- var TOOL_AGENT_MAP = {
1496
- search_rules: "query",
1497
- get_agent_details: "query",
1498
- get_project_config: "config",
1499
- set_project_root: "config",
1500
- suggest_config_updates: "config",
1501
- recommend_skills: "skill",
1502
- get_skill: "skill",
1503
- list_skills: "skill",
1504
- analyze_task: "analysis",
1505
- generate_checklist: "checklist",
1506
- read_context: "context",
1507
- update_context: "context",
1508
- cleanup_context: "context",
1509
- get_code_conventions: "conventions",
1510
- dispatch_agents: "orchestrator"
1511
- };
1512
1496
  function parseAgentFromToolName(toolName, args) {
1513
1497
  if (toolName === "get_agent_system_prompt") {
1514
1498
  return parseGetAgentSystemPrompt(args);
@@ -1519,15 +1503,6 @@ function parseAgentFromToolName(toolName, args) {
1519
1503
  if (toolName === "prepare_parallel_agents") {
1520
1504
  return parseParallelAgents(args);
1521
1505
  }
1522
- const role = TOOL_AGENT_MAP[toolName];
1523
- if (role) {
1524
- return {
1525
- agentId: toolName,
1526
- name: toolName,
1527
- role,
1528
- isPrimary: false
1529
- };
1530
- }
1531
1506
  return null;
1532
1507
  }
1533
1508
  function parseGetAgentSystemPrompt(args) {
@@ -1664,13 +1639,24 @@ function extractFromParseMode(json) {
1664
1639
  if (Array.isArray(par.specialists) && par.specialists.length > 0) {
1665
1640
  const specialists = par.specialists.filter((s) => typeof s === "string");
1666
1641
  if (specialists.length > 0) {
1642
+ const parallelMode = typeof json.mode === "string" && VALID_MODES.has(json.mode) ? json.mode : "PLAN";
1667
1643
  events.push({
1668
1644
  event: TUI_EVENTS.PARALLEL_STARTED,
1669
- payload: {
1670
- specialists,
1671
- mode: typeof json.mode === "string" && VALID_MODES.has(json.mode) ? json.mode : "PLAN"
1672
- }
1645
+ payload: { specialists, mode: parallelMode }
1673
1646
  });
1647
+ if (delegateName) {
1648
+ for (const name of specialists) {
1649
+ events.push({
1650
+ event: TUI_EVENTS.AGENT_RELATIONSHIP,
1651
+ payload: {
1652
+ from: `primary:${delegateName}`,
1653
+ to: `specialist:${name}`,
1654
+ label: "parallel",
1655
+ type: "delegation"
1656
+ }
1657
+ });
1658
+ }
1659
+ }
1674
1660
  }
1675
1661
  }
1676
1662
  }
@@ -1694,12 +1680,27 @@ function extractFromPrepareParallelAgents(json) {
1694
1680
  }).filter((name) => name !== null);
1695
1681
  if (specialists.length === 0) return [];
1696
1682
  const mode = typeof json.mode === "string" && VALID_MODES.has(json.mode) ? json.mode : "PLAN";
1697
- return [
1683
+ const primaryAgentId = typeof json.primaryAgentId === "string" ? json.primaryAgentId : null;
1684
+ const events = [
1698
1685
  {
1699
1686
  event: TUI_EVENTS.PARALLEL_STARTED,
1700
1687
  payload: { specialists, mode }
1701
1688
  }
1702
1689
  ];
1690
+ if (primaryAgentId) {
1691
+ for (const name of specialists) {
1692
+ events.push({
1693
+ event: TUI_EVENTS.AGENT_RELATIONSHIP,
1694
+ payload: {
1695
+ from: primaryAgentId,
1696
+ to: `specialist:${name}`,
1697
+ label: "parallel",
1698
+ type: "delegation"
1699
+ }
1700
+ });
1701
+ }
1702
+ }
1703
+ return events;
1703
1704
  }
1704
1705
  function extractFromDispatchAgents(json) {
1705
1706
  const events = [];
@@ -2086,12 +2087,24 @@ var ACT_PRIMARY_AGENTS = [
2086
2087
  // IaC, Kubernetes, multi-cloud specialist - high priority for infra tasks
2087
2088
  "data-engineer",
2088
2089
  // Database/schema specialist - high priority for data tasks
2090
+ "data-scientist",
2091
+ // EDA, statistical analysis, ML modeling, visualization, Jupyter notebooks
2092
+ "ai-ml-engineer",
2093
+ // ML frameworks, LLM integration, embeddings, fine-tuning
2089
2094
  "mobile-developer",
2090
2095
  // Mobile app specialist - detected by project files
2091
2096
  "frontend-developer",
2092
2097
  "backend-developer",
2093
2098
  "devops-engineer",
2094
- "agent-architect"
2099
+ "agent-architect",
2100
+ "test-engineer",
2101
+ // TDD, unit/integration/e2e test specialist
2102
+ "security-engineer",
2103
+ // Security features implementation & vulnerability remediation (intent priority: 5th)
2104
+ "systems-developer",
2105
+ // Rust, C, C++, FFI, WASM, embedded, low-level optimization (intent priority: 6th)
2106
+ "software-engineer"
2107
+ // fallback default — no intent patterns, language-agnostic generalist
2095
2108
  ];
2096
2109
  var EVAL_PRIMARY_AGENT = "code-reviewer";
2097
2110
  var ALL_PRIMARY_AGENTS = [
@@ -3476,6 +3489,7 @@ ${file.content}
3476
3489
  var ConfigService = class {
3477
3490
  constructor() {
3478
3491
  this.logger = new Logger6(ConfigService.name);
3492
+ this.projectRootSource = "auto_detect";
3479
3493
  this.projectConfig = null;
3480
3494
  this.isLoaded = false;
3481
3495
  this.projectRoot = this.resolveProjectRoot();
@@ -3493,6 +3507,7 @@ var ConfigService = class {
3493
3507
  const stat2 = statSync(normalizedPath);
3494
3508
  if (stat2.isDirectory()) {
3495
3509
  this.logger.log(`Using project root from CODINGBUDDY_PROJECT_ROOT: ${normalizedPath}`);
3510
+ this.projectRootSource = "env";
3496
3511
  return normalizedPath;
3497
3512
  }
3498
3513
  this.logger.warn(
@@ -3530,7 +3545,7 @@ var ConfigService = class {
3530
3545
  * @param root - Path to the project root directory
3531
3546
  * @throws Error if path does not exist, is not a directory, or contains invalid characters
3532
3547
  */
3533
- async setProjectRootAndReload(root) {
3548
+ async setProjectRootAndReload(root, source = "roots_list") {
3534
3549
  if (root.includes("\0")) {
3535
3550
  throw new Error("Path contains null bytes (possible null byte injection)");
3536
3551
  }
@@ -3556,6 +3571,7 @@ var ConfigService = class {
3556
3571
  }
3557
3572
  clearProjectRootCache();
3558
3573
  this.projectRoot = resolvedPath;
3574
+ this.projectRootSource = source;
3559
3575
  this.isLoaded = false;
3560
3576
  this.projectConfig = null;
3561
3577
  await this.loadProjectConfig();
@@ -3567,6 +3583,18 @@ var ConfigService = class {
3567
3583
  getProjectRoot() {
3568
3584
  return this.projectRoot;
3569
3585
  }
3586
+ /**
3587
+ * Get how the project root was resolved
3588
+ */
3589
+ getProjectRootSource() {
3590
+ return this.projectRootSource;
3591
+ }
3592
+ getClientName() {
3593
+ return this.clientName;
3594
+ }
3595
+ setClientName(name) {
3596
+ this.clientName = name;
3597
+ }
3570
3598
  /**
3571
3599
  * Load all project configuration
3572
3600
  */
@@ -3933,6 +3961,7 @@ function selectFocusedAgent(agents, currentFocusId) {
3933
3961
  var EVENT_LOG_MAX = 100;
3934
3962
  var EDGE_MAX = 200;
3935
3963
  var TOOL_CALLS_MAX = 200;
3964
+ var EXPECTED_AGENT_DURATION_MS = 12e4;
3936
3965
  function createInitialDashboardState() {
3937
3966
  return {
3938
3967
  workspace: process.cwd(),
@@ -3948,6 +3977,8 @@ function createInitialDashboardState() {
3948
3977
  objectives: [],
3949
3978
  activeSkills: [],
3950
3979
  toolInvokeCount: 0,
3980
+ agentActivateCount: 0,
3981
+ skillInvokeCount: 0,
3951
3982
  outputStats: { files: 0, commits: 0 },
3952
3983
  contextDecisions: [],
3953
3984
  contextNotes: [],
@@ -3969,11 +4000,20 @@ function dashboardReducer(state, action) {
3969
4000
  stage: state.currentMode ?? "PLAN",
3970
4001
  status: "running",
3971
4002
  isPrimary,
3972
- progress: 0
4003
+ progress: 0,
4004
+ isParallel: false,
4005
+ // AGENT_ACTIVATED는 항상 단일 에이전트
4006
+ startedAt: Date.now()
3973
4007
  });
3974
4008
  const globalState = "RUNNING";
3975
4009
  const focusedAgentId = selectFocusedAgent(agents, state.focusedAgentId);
3976
- return { ...state, agents, globalState, focusedAgentId };
4010
+ return {
4011
+ ...state,
4012
+ agents,
4013
+ globalState,
4014
+ focusedAgentId,
4015
+ agentActivateCount: state.agentActivateCount + 1
4016
+ };
3977
4017
  }
3978
4018
  case "AGENT_DEACTIVATED": {
3979
4019
  const { agentId, reason } = action.payload;
@@ -3983,7 +4023,8 @@ function dashboardReducer(state, action) {
3983
4023
  agents.set(agentId, {
3984
4024
  ...existing,
3985
4025
  status: reason === "error" ? "error" : "done",
3986
- progress: reason === "error" ? existing.progress : 100
4026
+ progress: reason === "error" ? existing.progress : 100,
4027
+ completedAt: Date.now()
3987
4028
  });
3988
4029
  }
3989
4030
  let anyRunning = false;
@@ -3998,24 +4039,19 @@ function dashboardReducer(state, action) {
3998
4039
  return { ...state, agents, globalState, focusedAgentId };
3999
4040
  }
4000
4041
  case "MODE_CHANGED": {
4001
- const newMode = action.payload.to;
4002
- let agents = state.agents;
4003
- let needsClone = false;
4004
- for (const a of state.agents.values()) {
4005
- if (a.status === "running") {
4006
- needsClone = true;
4007
- break;
4042
+ const { from, to: newMode } = action.payload;
4043
+ const agents = cloneAgents(state.agents);
4044
+ for (const [id, a] of agents) {
4045
+ if (from !== null && a.stage === from && (a.status === "done" || a.status === "idle" && a.isParallel)) {
4046
+ agents.delete(id);
4047
+ continue;
4008
4048
  }
4009
- }
4010
- if (needsClone) {
4011
- agents = cloneAgents(state.agents);
4012
- for (const [id, a] of agents) {
4013
- if (a.status === "running") {
4014
- agents.set(id, { ...a, stage: newMode });
4015
- }
4049
+ if (a.status === "running") {
4050
+ agents.set(id, { ...a, stage: newMode });
4016
4051
  }
4017
4052
  }
4018
- return { ...state, currentMode: newMode, agents, activeSkills: [] };
4053
+ const focusedAgentId = selectFocusedAgent(agents, state.focusedAgentId);
4054
+ return { ...state, currentMode: newMode, agents, activeSkills: [], focusedAgentId };
4019
4055
  }
4020
4056
  case "AGENT_RELATIONSHIP": {
4021
4057
  const edge = {
@@ -4049,10 +4085,15 @@ function dashboardReducer(state, action) {
4049
4085
  targetAgent = state.agents.get(state.focusedAgentId) ?? null;
4050
4086
  }
4051
4087
  if (targetAgent && targetAgent.status === "running") {
4088
+ const now = Date.now();
4089
+ const elapsed = targetAgent.startedAt !== void 0 ? now - targetAgent.startedAt : 0;
4090
+ const timeBased = Math.min(90, elapsed / EXPECTED_AGENT_DURATION_MS * 100);
4091
+ const toolBased = Math.min(95, targetAgent.progress + 3);
4092
+ const newProgress = Math.round(Math.max(timeBased, toolBased));
4052
4093
  agents = cloneAgents(state.agents);
4053
4094
  agents.set(targetAgent.id, {
4054
4095
  ...targetAgent,
4055
- progress: Math.min(95, targetAgent.progress + 5)
4096
+ progress: newProgress
4056
4097
  });
4057
4098
  }
4058
4099
  const toolCall = {
@@ -4075,7 +4116,11 @@ function dashboardReducer(state, action) {
4075
4116
  case "SKILL_RECOMMENDED": {
4076
4117
  const { skillName } = action.payload;
4077
4118
  if (state.activeSkills.includes(skillName)) return state;
4078
- return { ...state, activeSkills: [...state.activeSkills, skillName] };
4119
+ return {
4120
+ ...state,
4121
+ activeSkills: [...state.activeSkills, skillName],
4122
+ skillInvokeCount: state.skillInvokeCount + 1
4123
+ };
4079
4124
  }
4080
4125
  case "PARALLEL_STARTED": {
4081
4126
  const { specialists, mode } = action.payload;
@@ -4089,7 +4134,10 @@ function dashboardReducer(state, action) {
4089
4134
  stage: mode,
4090
4135
  status: "idle",
4091
4136
  isPrimary: false,
4092
- progress: 0
4137
+ progress: 0,
4138
+ isParallel: true,
4139
+ // parallel dispatch로 등록된 에이전트
4140
+ startedAt: Date.now()
4093
4141
  });
4094
4142
  }
4095
4143
  return { ...state, agents };
@@ -4106,6 +4154,21 @@ function dashboardReducer(state, action) {
4106
4154
  }
4107
4155
  case "SESSION_RESET":
4108
4156
  return createInitialDashboardState();
4157
+ case "CLEANUP_STALE_AGENTS": {
4158
+ const { now, ttlMs } = action.payload;
4159
+ let changed = false;
4160
+ const agents = cloneAgents(state.agents);
4161
+ for (const [id, a] of agents) {
4162
+ if (a.status === "running") continue;
4163
+ if (a.status === "done" && a.completedAt !== void 0 && now - a.completedAt > ttlMs || a.status === "error" && a.completedAt !== void 0 && now - a.completedAt > ttlMs * 2 || a.status === "idle" && a.isParallel && a.startedAt !== void 0 && now - a.startedAt > ttlMs) {
4164
+ agents.delete(id);
4165
+ changed = true;
4166
+ }
4167
+ }
4168
+ if (!changed) return state;
4169
+ const focusedAgentId = selectFocusedAgent(agents, state.focusedAgentId);
4170
+ return { ...state, agents, focusedAgentId };
4171
+ }
4109
4172
  // Reserved: no emitter currently produces PARALLEL_COMPLETED. Subscription kept for forward compatibility.
4110
4173
  case "PARALLEL_COMPLETED":
4111
4174
  case "AGENTS_LOADED":
@@ -4162,6 +4225,12 @@ function useDashboardState(eventBus) {
4162
4225
  eventBus.off(TUI_EVENTS.CONTEXT_UPDATED, onContextUpdated);
4163
4226
  };
4164
4227
  }, [eventBus]);
4228
+ useEffect2(() => {
4229
+ const cleanupInterval = setInterval(() => {
4230
+ dispatch({ type: "CLEANUP_STALE_AGENTS", payload: { now: Date.now(), ttlMs: 3e4 } });
4231
+ }, 1e4);
4232
+ return () => clearInterval(cleanupInterval);
4233
+ }, []);
4165
4234
  return state;
4166
4235
  }
4167
4236
 
@@ -4296,7 +4365,6 @@ function StateIndicator({ globalState }) {
4296
4365
  }
4297
4366
  function HeaderBar({
4298
4367
  workspace,
4299
- sessionId,
4300
4368
  currentMode,
4301
4369
  globalState,
4302
4370
  layoutMode,
@@ -4319,7 +4387,6 @@ function HeaderBar({
4319
4387
  /* @__PURE__ */ React.createElement(StateIndicator, { globalState })
4320
4388
  );
4321
4389
  }
4322
- const sessDisplay = sessionId.length > 8 ? sessionId.slice(0, 8) : sessionId;
4323
4390
  return /* @__PURE__ */ React.createElement(
4324
4391
  Box,
4325
4392
  {
@@ -4329,9 +4396,9 @@ function HeaderBar({
4329
4396
  overflowX: "hidden",
4330
4397
  flexDirection: "row"
4331
4398
  },
4332
- /* @__PURE__ */ React.createElement(Box, { gap: 2, flexShrink: 0 }, /* @__PURE__ */ React.createElement(Text, { color: "cyan", bold: true }, "\u27E8\u27E9 CODINGBUDDY AGENT DASHBOARD"), /* @__PURE__ */ React.createElement(ModeFlow, { currentMode }), /* @__PURE__ */ React.createElement(StateIndicator, { globalState })),
4399
+ /* @__PURE__ */ React.createElement(Box, { gap: 2, flexShrink: 1, minWidth: 0 }, /* @__PURE__ */ React.createElement(Text, { color: "cyan", bold: true }, "\u27E8\u27E9 CODINGBUDDY AGENT DASHBOARD"), /* @__PURE__ */ React.createElement(ModeFlow, { currentMode }), /* @__PURE__ */ React.createElement(StateIndicator, { globalState })),
4333
4400
  /* @__PURE__ */ React.createElement(Box, { flexGrow: 1 }),
4334
- /* @__PURE__ */ React.createElement(Box, { flexShrink: 1, overflowX: "hidden" }, /* @__PURE__ */ React.createElement(Text, { dimColor: true, wrap: "truncate" }, workspace, " sess:", sessDisplay))
4401
+ /* @__PURE__ */ React.createElement(Box, { flexShrink: 1, overflowX: "hidden" }, /* @__PURE__ */ React.createElement(Text, { dimColor: true, wrap: "truncate" }, workspace))
4335
4402
  );
4336
4403
  }
4337
4404
 
@@ -4491,9 +4558,16 @@ function computeLabelPosition(path9, label) {
4491
4558
  // src/tui/components/flow-map.pure.ts
4492
4559
  var NODE_WIDTH = 17;
4493
4560
  var NODE_HEIGHT = 4;
4561
+ var PARALLEL_STYLES = {
4562
+ parallel: { fg: "cyan" }
4563
+ // 파랑 — 병렬 실행
4564
+ };
4565
+ var TREE_CONNECTOR_STYLE = { fg: "cyan", dim: true };
4494
4566
  var STAGE_ORDER = ["PLAN", "ACT", "EVAL", "AUTO"];
4495
- var STAGE_SLOT_WIDTH = 8;
4567
+ var STAGE_SLOT_WIDTH = 18;
4496
4568
  var PIPELINE_RIGHT_MARGIN = 10;
4569
+ var DIMMED_STYLE = { dim: true };
4570
+ var WAITING_STYLE = { fg: "gray", dim: true };
4497
4571
  function groupByStage(agents) {
4498
4572
  const byStage = /* @__PURE__ */ new Map();
4499
4573
  for (const agent of agents.values()) {
@@ -4503,9 +4577,15 @@ function groupByStage(agents) {
4503
4577
  }
4504
4578
  return byStage;
4505
4579
  }
4580
+ function isBoxAgent(agent) {
4581
+ return agent.isPrimary || agent.isParallel;
4582
+ }
4506
4583
  function sortAgents(agents) {
4507
4584
  return [...agents].sort((a, b) => {
4508
4585
  if (a.isPrimary !== b.isPrimary) return a.isPrimary ? -1 : 1;
4586
+ if (!a.isPrimary && !b.isPrimary) {
4587
+ if (a.isParallel !== b.isParallel) return a.isParallel ? -1 : 1;
4588
+ }
4509
4589
  return a.name.localeCompare(b.name);
4510
4590
  });
4511
4591
  }
@@ -4539,14 +4619,18 @@ function layoutAgentNodes(agents, columns) {
4539
4619
  if (!col) continue;
4540
4620
  const sorted = sortAgents(stageAgents);
4541
4621
  const startY = 3;
4542
- sorted.forEach((agent, idx) => {
4622
+ let currentY = startY;
4623
+ for (const agent of sorted) {
4624
+ const boxAgent = isBoxAgent(agent);
4625
+ const height = boxAgent ? NODE_HEIGHT : 1;
4543
4626
  positions.set(agent.id, {
4544
4627
  x: col.startX + Math.floor((col.width - NODE_WIDTH) / 2),
4545
- y: startY + idx * (NODE_HEIGHT + 1),
4628
+ y: currentY,
4546
4629
  width: NODE_WIDTH,
4547
- height: NODE_HEIGHT
4630
+ height
4548
4631
  });
4549
- });
4632
+ currentY += boxAgent ? height + 1 : height;
4633
+ }
4550
4634
  }
4551
4635
  return positions;
4552
4636
  }
@@ -4554,8 +4638,27 @@ function getNodeTextStyle(status) {
4554
4638
  if (status === "running") return { fg: "white", bold: true };
4555
4639
  return { dim: true };
4556
4640
  }
4557
- function drawAgentNode(buf, x, y, boxW, agent) {
4558
- const borderStyle = getStatusStyle(agent.status);
4641
+ function drawTreeConnector(buf, x, y, agent, isLast, dimmed = false) {
4642
+ const connector = isLast ? "\u2514\u2500" : "\u251C\u2500";
4643
+ const icon = STATUS_ICONS[agent.status];
4644
+ const maxNameLen = Math.max(1, NODE_WIDTH - connector.length - icon.length - 3);
4645
+ const nameStr = agent.name.length > maxNameLen ? agent.name.slice(0, maxNameLen - 3) + "..." : agent.name;
4646
+ buf.writeText(x, y, connector, dimmed ? DIMMED_STYLE : TREE_CONNECTOR_STYLE);
4647
+ buf.writeText(
4648
+ x + connector.length + 1,
4649
+ y,
4650
+ icon,
4651
+ dimmed ? DIMMED_STYLE : STATUS_STYLES[agent.status]
4652
+ );
4653
+ buf.writeText(
4654
+ x + connector.length + 1 + icon.length + 1,
4655
+ y,
4656
+ nameStr,
4657
+ dimmed ? DIMMED_STYLE : getNodeTextStyle(agent.status)
4658
+ );
4659
+ }
4660
+ function drawAgentNode(buf, x, y, boxW, agent, dimmed = false) {
4661
+ const borderStyle = dimmed ? DIMMED_STYLE : getStatusStyle(agent.status);
4559
4662
  if (agent.isPrimary) {
4560
4663
  buf.drawDoubleBox(x, y, boxW, NODE_HEIGHT, borderStyle);
4561
4664
  } else {
@@ -4564,15 +4667,26 @@ function drawAgentNode(buf, x, y, boxW, agent) {
4564
4667
  const icon = STATUS_ICONS[agent.status];
4565
4668
  const maxNameLen = Math.max(1, boxW - 5);
4566
4669
  const nameStr = agent.name.length > maxNameLen ? agent.name.slice(0, maxNameLen - 3) + "..." : agent.name;
4567
- buf.writeText(x + 2, y + 1, nameStr, getNodeTextStyle(agent.status));
4568
- buf.writeText(x + 2 + nameStr.length + 1, y + 1, icon, STATUS_STYLES[agent.status]);
4569
- const barWidth = boxW - 4;
4570
- const bar = buildProgressBar(agent.progress, barWidth);
4571
- const filledStr = PROGRESS_BAR_CHARS.filled.repeat(bar.filled);
4572
- const emptyStr = PROGRESS_BAR_CHARS.empty.repeat(bar.empty);
4573
- buf.writeText(x + 2, y + 2, filledStr, PROGRESS_BAR_STYLES.filled);
4574
- if (bar.empty > 0) {
4575
- buf.writeText(x + 2 + bar.filled, y + 2, emptyStr, PROGRESS_BAR_STYLES.empty);
4670
+ const textStyle = dimmed ? DIMMED_STYLE : getNodeTextStyle(agent.status);
4671
+ const iconStyle = dimmed ? DIMMED_STYLE : STATUS_STYLES[agent.status];
4672
+ buf.writeText(x + 2, y + 1, nameStr, textStyle);
4673
+ buf.writeText(x + 2 + nameStr.length + 1, y + 1, icon, iconStyle);
4674
+ if (agent.isPrimary) {
4675
+ const barWidth = Math.max(1, boxW - 8);
4676
+ const bar = buildProgressBar(agent.progress, barWidth);
4677
+ const filledStr = PROGRESS_BAR_CHARS.filled.repeat(bar.filled);
4678
+ const emptyStr = PROGRESS_BAR_CHARS.empty.repeat(bar.empty);
4679
+ const filledStyle = dimmed ? DIMMED_STYLE : PROGRESS_BAR_STYLES.filled;
4680
+ const emptyStyle = dimmed ? DIMMED_STYLE : PROGRESS_BAR_STYLES.empty;
4681
+ buf.writeText(x + 2, y + 2, filledStr, filledStyle);
4682
+ if (bar.empty > 0) {
4683
+ buf.writeText(x + 2 + bar.filled, y + 2, emptyStr, emptyStyle);
4684
+ }
4685
+ buf.writeText(x + 2 + barWidth + 1, y + 2, bar.label, dimmed ? DIMMED_STYLE : WAITING_STYLE);
4686
+ } else if (agent.status === "idle") {
4687
+ buf.writeText(x + 2, y + 2, "\u231B waiting...", dimmed ? DIMMED_STYLE : WAITING_STYLE);
4688
+ } else if (agent.isParallel) {
4689
+ buf.writeText(x + 2, y + 2, "\u2AF8 parallel", dimmed ? DIMMED_STYLE : PARALLEL_STYLES.parallel);
4576
4690
  }
4577
4691
  }
4578
4692
  function buildProgressBar(value, barWidth) {
@@ -4607,7 +4721,17 @@ function countByStatus(agents) {
4607
4721
  }
4608
4722
  return counts;
4609
4723
  }
4610
- function renderPipelineHeader(buf, width, activeStage, hasAutoAgents = false) {
4724
+ function countStageRunningDone(agents, stage) {
4725
+ let running = 0;
4726
+ let done = 0;
4727
+ for (const agent of agents.values()) {
4728
+ if (agent.stage !== stage) continue;
4729
+ if (agent.status === "running") running++;
4730
+ else if (agent.status === "done") done++;
4731
+ }
4732
+ return { running, done };
4733
+ }
4734
+ function renderPipelineHeader(buf, width, activeStage, hasAutoAgents = false, agents) {
4611
4735
  const stages = hasAutoAgents ? STAGE_ORDER : STAGE_ORDER.filter((s) => s !== "AUTO");
4612
4736
  let x = 1;
4613
4737
  for (let i = 0; i < stages.length; i++) {
@@ -4618,6 +4742,17 @@ function renderPipelineHeader(buf, width, activeStage, hasAutoAgents = false) {
4618
4742
  x += 2;
4619
4743
  buf.writeText(x, 0, stage, stageStyle);
4620
4744
  x += stage.length;
4745
+ if (agents && agents.size > 0) {
4746
+ const { running, done } = countStageRunningDone(agents, stage);
4747
+ if (running > 0 || done > 0) {
4748
+ const parts = [];
4749
+ if (running > 0) parts.push(`${running}\u2191`);
4750
+ if (done > 0) parts.push(`${done}\u2713`);
4751
+ const statsStr = ` (${parts.join(" ")})`;
4752
+ buf.writeText(x, 0, statsStr, { fg: "gray", dim: !isActive });
4753
+ x += statsStr.length;
4754
+ }
4755
+ }
4621
4756
  if (i < stages.length - 1) {
4622
4757
  x += 1;
4623
4758
  const connLen = Math.max(
@@ -4644,20 +4779,36 @@ function renderFlowMap(agents, edges, width, height, activeStage = null) {
4644
4779
  const buf = new ColorBuffer(width, height);
4645
4780
  const columns = layoutStageColumns(width, hasAutoAgents);
4646
4781
  const positions = layoutAgentNodes(agents, columns);
4647
- renderPipelineHeader(buf, width, activeStage, hasAutoAgents);
4782
+ renderPipelineHeader(buf, width, activeStage, hasAutoAgents, agents);
4783
+ const inactiveIds = activeStage !== null ? new Set([...agents.values()].filter((a) => a.stage !== activeStage).map((a) => a.id)) : /* @__PURE__ */ new Set();
4648
4784
  for (const [id, pos] of positions) {
4649
4785
  const agent = agents.get(id);
4650
4786
  if (!agent) continue;
4651
- if (agent.status === "running" && agent.isPrimary) {
4787
+ if (agent.status === "running" && agent.isPrimary && !inactiveIds.has(id)) {
4652
4788
  drawGlow(buf, pos.x, pos.y, pos.width, pos.height, GLOW_STYLE);
4653
4789
  }
4654
4790
  }
4655
- for (const [id, pos] of positions) {
4656
- const agent = agents.get(id);
4657
- if (!agent) continue;
4658
- drawAgentNode(buf, pos.x, pos.y, pos.width, agent);
4791
+ const byStage = groupByStage(agents);
4792
+ for (const [, stageAgents] of byStage) {
4793
+ const sorted = sortAgents(stageAgents);
4794
+ const connectorAgents = sorted.filter((a) => !isBoxAgent(a));
4795
+ const connectorLastIdx = connectorAgents.length - 1;
4796
+ const connectorIndexMap = new Map(connectorAgents.map((a, i) => [a.id, i]));
4797
+ for (const agent of sorted) {
4798
+ const pos = positions.get(agent.id);
4799
+ if (!pos) continue;
4800
+ if (isBoxAgent(agent)) {
4801
+ drawAgentNode(buf, pos.x, pos.y, pos.width, agent, inactiveIds.has(agent.id));
4802
+ } else {
4803
+ const isLast = connectorIndexMap.get(agent.id) === connectorLastIdx;
4804
+ drawTreeConnector(buf, pos.x, pos.y, agent, isLast, inactiveIds.has(agent.id));
4805
+ }
4806
+ }
4659
4807
  }
4660
4808
  for (const edge of edges) {
4809
+ const fromAgent = agents.get(edge.from);
4810
+ const toAgent = agents.get(edge.to);
4811
+ if (fromAgent && toAgent && fromAgent.stage === toAgent.stage) continue;
4661
4812
  const fromPos = positions.get(edge.from);
4662
4813
  const toPos = positions.get(edge.to);
4663
4814
  if (!fromPos || !toPos) continue;
@@ -4670,13 +4821,20 @@ function renderFlowMap(agents, edges, width, height, activeStage = null) {
4670
4821
  y: toPos.y + Math.floor(toPos.height / 2)
4671
4822
  };
4672
4823
  const path9 = computeEdgePath(fromPoint, toPoint);
4824
+ const edgeDimmed = inactiveIds.has(edge.from) && inactiveIds.has(edge.to);
4673
4825
  for (const seg of path9) {
4674
4826
  const isArrow = seg.char === "\u25B8" || seg.char === "\u25C2" || seg.char === "\u25BE" || seg.char === "\u25B4";
4675
- buf.setChar(seg.x, seg.y, seg.char, isArrow ? EDGE_STYLES.arrow : EDGE_STYLES.path);
4827
+ const segStyle = edgeDimmed ? DIMMED_STYLE : isArrow ? EDGE_STYLES.arrow : EDGE_STYLES.path;
4828
+ buf.setChar(seg.x, seg.y, seg.char, segStyle);
4676
4829
  }
4677
4830
  const labelPos = computeLabelPosition(path9, edge.label);
4678
4831
  if (labelPos) {
4679
- buf.writeText(labelPos.x, labelPos.y - 1, edge.label, EDGE_STYLES.label);
4832
+ buf.writeText(
4833
+ labelPos.x,
4834
+ labelPos.y - 1,
4835
+ edge.label,
4836
+ edgeDimmed ? DIMMED_STYLE : EDGE_STYLES.label
4837
+ );
4680
4838
  }
4681
4839
  }
4682
4840
  const counts = countByStatus(agents);
@@ -4695,20 +4853,22 @@ function renderFlowMap(agents, edges, width, height, activeStage = null) {
4695
4853
  }
4696
4854
  return buf;
4697
4855
  }
4698
- function renderFlowMapSimplified(agents, width, height) {
4856
+ function renderFlowMapSimplified(agents, width, height, activeStage = null) {
4699
4857
  const buf = new ColorBuffer(width, height);
4700
4858
  const byStage = groupByStage(agents);
4701
4859
  let y = 0;
4702
4860
  for (const stage of STAGE_ORDER) {
4703
4861
  const stageAgents = byStage.get(stage);
4704
4862
  if (!stageAgents || stageAgents.length === 0) continue;
4705
- buf.writeText(1, y, stage, STAGE_LABEL_STYLES[stage]);
4863
+ const isInactiveStage = activeStage !== null && stage !== activeStage;
4864
+ const stageLabelStyle = isInactiveStage ? { fg: STAGE_LABEL_STYLES[stage].fg, dim: true } : STAGE_LABEL_STYLES[stage];
4865
+ buf.writeText(1, y, stage, stageLabelStyle);
4706
4866
  y++;
4707
4867
  const sorted = sortAgents(stageAgents);
4708
4868
  for (const agent of sorted) {
4709
4869
  if (y >= height - 1) break;
4710
4870
  const boxW = Math.min(width - 2, NODE_WIDTH);
4711
- drawAgentNode(buf, 1, y, boxW, agent);
4871
+ drawAgentNode(buf, 1, y, boxW, agent, isInactiveStage);
4712
4872
  y += NODE_HEIGHT + 1;
4713
4873
  }
4714
4874
  }
@@ -4821,15 +4981,6 @@ function formatObjective(objectives, maxLines = 3) {
4821
4981
  }
4822
4982
  return lines.join("\n");
4823
4983
  }
4824
- function formatToolIO(tools, inputs, outputs) {
4825
- const toolLine = `Tools: ${tools.join(" / ") || "none"}`;
4826
- const inLine = `IN : ${inputs.join(", ") || "none"}`;
4827
- const outParts = Object.entries(outputs).filter(([, v]) => v !== void 0 && v > 0).map(([k, v]) => `${k}(${v})`);
4828
- const outLine = `OUT: ${outParts.join(" / ") || "none"}`;
4829
- return `${toolLine}
4830
- ${inLine}
4831
- ${outLine}`;
4832
- }
4833
4984
  function formatLogTail(events, maxLines = 10) {
4834
4985
  const tail = events.slice(-maxLines);
4835
4986
  return tail.map((e) => `${e.timestamp} ${e.message}`).join("\n");
@@ -4849,20 +5000,6 @@ function formatEnhancedProgressBar(progress, width = 30) {
4849
5000
  label: `${clamped}%`
4850
5001
  };
4851
5002
  }
4852
- var SPARK_CHARS = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
4853
- function formatActivitySparkline(toolCalls, bucketCount = 20, windowMs = 6e4, now) {
4854
- const currentTime = now ?? Date.now();
4855
- const bucketSize = windowMs / bucketCount;
4856
- const buckets = new Array(bucketCount).fill(0);
4857
- for (const call of toolCalls) {
4858
- const age = currentTime - call.timestamp;
4859
- if (age < 0 || age >= windowMs) continue;
4860
- const idx = Math.min(bucketCount - 1, Math.floor(age / bucketSize));
4861
- buckets[bucketCount - 1 - idx]++;
4862
- }
4863
- const max = Math.max(1, ...buckets);
4864
- return buckets.map((b) => SPARK_CHARS[Math.round(b / max * (SPARK_CHARS.length - 1))]).join("");
4865
- }
4866
5003
  function formatEnhancedChecklist(tasks, maxItems = 6) {
4867
5004
  const visible = tasks.slice(0, maxItems);
4868
5005
  const lines = visible.map((t) => t.completed ? ` \u2714 ${t.subject}` : ` \u25FB ${t.subject}`);
@@ -4931,12 +5068,7 @@ function FocusedAgentPanel({
4931
5068
  agent,
4932
5069
  objectives,
4933
5070
  activeSkills,
4934
- tasks,
4935
- tools,
4936
- inputs,
4937
- outputs,
4938
5071
  eventLog,
4939
- toolCalls,
4940
5072
  contextDecisions = [],
4941
5073
  contextNotes = [],
4942
5074
  width,
@@ -4961,11 +5093,7 @@ function FocusedAgentPanel({
4961
5093
  const statusLabel = agent.status.toUpperCase();
4962
5094
  const progressBar = formatEnhancedProgressBar(agent.progress);
4963
5095
  const objective = formatObjective(objectives);
4964
- const checklist = formatEnhancedChecklist(tasks);
4965
- const toolIO = formatToolIO(tools, inputs, outputs);
4966
5096
  const logs = formatLogTail(eventLog);
4967
- const agentToolCalls = toolCalls.filter((tc) => tc.agentId === agent.id);
4968
- const sparkline = formatActivitySparkline(agentToolCalls);
4969
5097
  return /* @__PURE__ */ React4.createElement(
4970
5098
  Box4,
4971
5099
  {
@@ -4981,15 +5109,6 @@ function FocusedAgentPanel({
4981
5109
  objective ? /* @__PURE__ */ React4.createElement(Text4, null, objective) : /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "No objectives"),
4982
5110
  /* @__PURE__ */ React4.createElement(SectionDivider, { title: "Skills" }),
4983
5111
  activeSkills.length > 0 ? activeSkills.map((s, i) => /* @__PURE__ */ React4.createElement(Text4, { key: i }, " ", s)) : /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "No skills"),
4984
- /* @__PURE__ */ React4.createElement(SectionDivider, { title: "Checklist" }),
4985
- checklist ? checklist.split("\n").map((line, i) => {
4986
- const isCompleted = line.includes("\u2714");
4987
- return /* @__PURE__ */ React4.createElement(Text4, { key: i, color: isCompleted ? "green" : void 0 }, line);
4988
- }) : /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "No tasks"),
4989
- /* @__PURE__ */ React4.createElement(SectionDivider, { title: "Activity" }),
4990
- /* @__PURE__ */ React4.createElement(Text4, null, sparkline),
4991
- /* @__PURE__ */ React4.createElement(SectionDivider, { title: "Tools / IO" }),
4992
- /* @__PURE__ */ React4.createElement(Text4, null, toolIO),
4993
5112
  /* @__PURE__ */ React4.createElement(SectionDivider, { title: "Context" }),
4994
5113
  contextDecisions.length > 0 || contextNotes.length > 0 ? /* @__PURE__ */ React4.createElement(ContextSection, { decisions: contextDecisions, notes: contextNotes, width }) : /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "No context"),
4995
5114
  /* @__PURE__ */ React4.createElement(SectionDivider, { title: "Event Log" }),
@@ -4997,9 +5116,115 @@ function FocusedAgentPanel({
4997
5116
  );
4998
5117
  }
4999
5118
 
5000
- // src/tui/components/StageHealthBar.tsx
5119
+ // src/tui/components/ChecklistPanel.tsx
5001
5120
  import React5 from "react";
5002
5121
  import { Box as Box5, Text as Text5 } from "ink";
5122
+
5123
+ // src/tui/components/checklist-panel.pure.ts
5124
+ function resolveChecklistTasks(tasks, contextDecisions, contextNotes) {
5125
+ if (tasks.length > 0) {
5126
+ return tasks;
5127
+ }
5128
+ if (contextDecisions.length > 0) {
5129
+ return [
5130
+ { id: "fallback-decision", subject: `Review: ${contextDecisions[0]}`, completed: false }
5131
+ ];
5132
+ }
5133
+ if (contextNotes.length > 0) {
5134
+ return [{ id: "fallback-note", subject: `Check: ${contextNotes[0]}`, completed: false }];
5135
+ }
5136
+ return [{ id: "fallback-default", subject: "Review current task objectives", completed: false }];
5137
+ }
5138
+
5139
+ // src/tui/components/ChecklistPanel.tsx
5140
+ function ChecklistPanel({
5141
+ tasks,
5142
+ contextDecisions = [],
5143
+ contextNotes = [],
5144
+ width,
5145
+ height
5146
+ }) {
5147
+ const resolvedTasks = resolveChecklistTasks(tasks, contextDecisions, contextNotes);
5148
+ const checklist = formatEnhancedChecklist(resolvedTasks);
5149
+ return /* @__PURE__ */ React5.createElement(
5150
+ Box5,
5151
+ {
5152
+ borderStyle: "single",
5153
+ borderColor: BORDER_COLORS.panel,
5154
+ flexDirection: "column",
5155
+ width,
5156
+ height
5157
+ },
5158
+ /* @__PURE__ */ React5.createElement(Text5, { bold: true, dimColor: true }, "\u2500\u2500\u2500 Checklist"),
5159
+ checklist.split("\n").map((line, i) => {
5160
+ const isCompleted = line.includes("\u2714");
5161
+ return /* @__PURE__ */ React5.createElement(Text5, { key: i, color: isCompleted ? "green" : void 0 }, line);
5162
+ })
5163
+ );
5164
+ }
5165
+
5166
+ // src/tui/components/StageHealthBar.tsx
5167
+ import React6 from "react";
5168
+ import { Box as Box6, Text as Text6 } from "ink";
5169
+
5170
+ // src/tui/components/stage-health.pure.ts
5171
+ function formatCount(n) {
5172
+ return n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(n);
5173
+ }
5174
+ function computeStageHealth(agents) {
5175
+ const health = {
5176
+ PLAN: createEmptyStageStats(),
5177
+ ACT: createEmptyStageStats(),
5178
+ EVAL: createEmptyStageStats(),
5179
+ AUTO: createEmptyStageStats()
5180
+ };
5181
+ for (const agent of agents.values()) {
5182
+ const stage = health[agent.stage];
5183
+ if (!stage) continue;
5184
+ switch (agent.status) {
5185
+ case "running":
5186
+ stage.running++;
5187
+ break;
5188
+ case "blocked":
5189
+ stage.blocked++;
5190
+ break;
5191
+ case "idle":
5192
+ stage.waiting++;
5193
+ break;
5194
+ case "done":
5195
+ stage.done++;
5196
+ break;
5197
+ case "error":
5198
+ stage.error++;
5199
+ break;
5200
+ }
5201
+ }
5202
+ return health;
5203
+ }
5204
+ function detectBottlenecks(eventLog) {
5205
+ const bottlenecks = /* @__PURE__ */ new Set();
5206
+ for (const entry of eventLog) {
5207
+ if (entry.level === "error") {
5208
+ const testMatch = entry.message.match(/(?:test_run|tests?):\s*FAIL\s*\((\d+)\)/i);
5209
+ if (testMatch) {
5210
+ bottlenecks.add(`tests failing(${testMatch[1]})`);
5211
+ continue;
5212
+ }
5213
+ const lintMatch = entry.message.match(/lint\(([^)]+)\)/i);
5214
+ if (lintMatch) {
5215
+ bottlenecks.add(`lint(${lintMatch[1]})`);
5216
+ continue;
5217
+ }
5218
+ const envMatch = entry.message.match(/missing\s+env\(([^)]+)\)/i);
5219
+ if (envMatch) {
5220
+ bottlenecks.add(`missing env(${envMatch[1]})`);
5221
+ }
5222
+ }
5223
+ }
5224
+ return [...bottlenecks];
5225
+ }
5226
+
5227
+ // src/tui/components/StageHealthBar.tsx
5003
5228
  function StageStatDisplay({
5004
5229
  mode,
5005
5230
  stats
@@ -5008,52 +5233,53 @@ function StageStatDisplay({
5008
5233
  const parts = [];
5009
5234
  if (stats.running > 0)
5010
5235
  parts.push(
5011
- /* @__PURE__ */ React5.createElement(Text5, { key: "r", color: "green" }, " ", "\u25CF ", stats.running, " running")
5236
+ /* @__PURE__ */ React6.createElement(Text6, { key: "r", color: "green" }, " ", "\u25CF ", stats.running, " running")
5012
5237
  );
5013
5238
  if (stats.blocked > 0)
5014
5239
  parts.push(
5015
- /* @__PURE__ */ React5.createElement(Text5, { key: "b", color: "yellow" }, " ", "\u23F8 ", stats.blocked, " blocked")
5240
+ /* @__PURE__ */ React6.createElement(Text6, { key: "b", color: "yellow" }, " ", "\u23F8 ", stats.blocked, " blocked")
5016
5241
  );
5017
5242
  if (stats.waiting > 0)
5018
5243
  parts.push(
5019
- /* @__PURE__ */ React5.createElement(Text5, { key: "w", dimColor: true }, " ", "\u25CB ", stats.waiting, " waiting")
5244
+ /* @__PURE__ */ React6.createElement(Text6, { key: "w", dimColor: true }, " ", "\u25CB ", stats.waiting, " waiting")
5020
5245
  );
5021
5246
  if (stats.done > 0)
5022
5247
  parts.push(
5023
- /* @__PURE__ */ React5.createElement(Text5, { key: "d", color: "green" }, " ", "\u2713 ", stats.done)
5248
+ /* @__PURE__ */ React6.createElement(Text6, { key: "d", color: "green" }, " ", "\u2713 ", stats.done)
5024
5249
  );
5025
5250
  if (stats.error > 0)
5026
5251
  parts.push(
5027
- /* @__PURE__ */ React5.createElement(Text5, { key: "e", color: "red", bold: true }, " ", "! ", stats.error, " err")
5252
+ /* @__PURE__ */ React6.createElement(Text6, { key: "e", color: "red", bold: true }, " ", "! ", stats.error, " err")
5028
5253
  );
5029
5254
  if (parts.length === 0)
5030
5255
  parts.push(
5031
- /* @__PURE__ */ React5.createElement(Text5, { key: "idle", dimColor: true }, " ", "idle")
5256
+ /* @__PURE__ */ React6.createElement(Text6, { key: "idle", dimColor: true }, " ", "idle")
5032
5257
  );
5033
- return /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { color, bold: true }, mode, ":"), parts);
5258
+ return /* @__PURE__ */ React6.createElement(Box6, null, /* @__PURE__ */ React6.createElement(Text6, { color, bold: true }, mode, ":"), parts);
5034
5259
  }
5035
5260
  function StageHealthBar({
5036
5261
  stageHealth,
5037
5262
  bottlenecks,
5038
5263
  toolCount,
5264
+ agentCount,
5265
+ skillCount,
5039
5266
  width
5040
5267
  }) {
5041
- const countStr = toolCount >= 1e3 ? `${Math.round(toolCount / 1e3)}k` : String(toolCount);
5042
- return /* @__PURE__ */ React5.createElement(
5043
- Box5,
5268
+ return /* @__PURE__ */ React6.createElement(
5269
+ Box6,
5044
5270
  {
5045
5271
  borderStyle: "double",
5046
5272
  borderColor: BORDER_COLORS.panel,
5047
5273
  width,
5048
5274
  flexDirection: "column"
5049
5275
  },
5050
- /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Box5, { gap: 2 }, ["PLAN", "ACT", "EVAL"].map((mode) => /* @__PURE__ */ React5.createElement(StageStatDisplay, { key: mode, mode, stats: stageHealth[mode] }))), /* @__PURE__ */ React5.createElement(Box5, { flexGrow: 1 }), /* @__PURE__ */ React5.createElement(Box5, { gap: 2 }, bottlenecks.length > 0 && /* @__PURE__ */ React5.createElement(Text5, { color: "red", bold: true }, "\u26A1 Bottlenecks: ", bottlenecks.join(" / ")), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "tools: ", countStr)))
5276
+ /* @__PURE__ */ React6.createElement(Box6, null, /* @__PURE__ */ React6.createElement(Box6, { gap: 2 }, ["PLAN", "ACT", "EVAL"].map((mode) => /* @__PURE__ */ React6.createElement(StageStatDisplay, { key: mode, mode, stats: stageHealth[mode] }))), /* @__PURE__ */ React6.createElement(Box6, { flexGrow: 1 }), /* @__PURE__ */ React6.createElement(Box6, { gap: 2 }, bottlenecks.length > 0 && /* @__PURE__ */ React6.createElement(Text6, { color: "red", bold: true }, "\u26A1 Bottlenecks: ", bottlenecks.join(" / ")), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "\u{1F916} ", formatCount(agentCount)), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "\u2699 ", formatCount(skillCount)), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "\u{1F527} ", formatCount(toolCount))))
5051
5277
  );
5052
5278
  }
5053
5279
 
5054
5280
  // src/tui/components/ActivityVisualizer.tsx
5055
- import React6, { useMemo as useMemo2 } from "react";
5056
- import { Box as Box6, Text as Text6 } from "ink";
5281
+ import React7, { useMemo as useMemo2 } from "react";
5282
+ import { Box as Box7, Text as Text7 } from "ink";
5057
5283
 
5058
5284
  // src/tui/utils/display-width.ts
5059
5285
  function isWide(code) {
@@ -5087,180 +5313,202 @@ function truncateToDisplayWidth(str, maxWidth) {
5087
5313
  }
5088
5314
  return str.slice(0, codeUnitIndex);
5089
5315
  }
5090
- function padEndDisplayWidth(str, targetWidth) {
5091
- const currentWidth = estimateDisplayWidth(str);
5092
- if (currentWidth >= targetWidth) return str;
5093
- return str + " ".repeat(targetWidth - currentWidth);
5094
- }
5095
5316
 
5096
5317
  // src/tui/components/activity-visualizer.pure.ts
5097
- function aggregateForBarChart(calls, maxTools = 10) {
5098
- if (calls.length === 0) return [];
5099
- const toolCounts = /* @__PURE__ */ new Map();
5100
- for (const call of calls) {
5101
- toolCounts.set(call.toolName, (toolCounts.get(call.toolName) ?? 0) + 1);
5102
- }
5103
- return [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, maxTools).map(([tool, count]) => ({ tool, count }));
5104
- }
5105
- var TOOL_NAME_WIDTH = 12;
5106
- var BAR_FIXED_OVERHEAD = TOOL_NAME_WIDTH + 2 + 2 + 4;
5107
- function renderBarChart(data, width, height) {
5108
- if (data.length === 0 || height <= 0 || width <= 0) return [];
5109
- const lines = [truncateToDisplayWidth("\u{1F4CA} Activity", width)];
5110
- const barWidth = Math.max(1, width - BAR_FIXED_OVERHEAD);
5111
- const maxCount = data[0]?.count || 1;
5112
- for (const item of data) {
5318
+ var ACTIVITY_STATUS_ICONS = Object.freeze({
5319
+ running: "\u25CF",
5320
+ idle: "\u25CB",
5321
+ blocked: "\u23F8",
5322
+ error: "!",
5323
+ done: "\u25CB"
5324
+ });
5325
+ function renderSubtree(nodeId, agents, childrenOf, visited, prefix, isLast, lines, height, width) {
5326
+ if (lines.length >= height) return;
5327
+ if (visited.has(nodeId)) return;
5328
+ visited.add(nodeId);
5329
+ const node = agents.get(nodeId);
5330
+ if (!node) return;
5331
+ const connector = isLast ? "\u2514" : "\u251C";
5332
+ const icon = ACTIVITY_STATUS_ICONS[node.status] ?? "?";
5333
+ lines.push(truncateToDisplayWidth(`${prefix}${connector} ${icon} ${node.name}`, width));
5334
+ const childIds = (childrenOf.get(nodeId) ?? []).filter((id) => agents.has(id));
5335
+ const nextPrefix = prefix + (isLast ? " " : "\u2502 ");
5336
+ for (let i = 0; i < childIds.length; i++) {
5113
5337
  if (lines.length >= height) break;
5114
- const name = padEndDisplayWidth(
5115
- truncateToDisplayWidth(item.tool, TOOL_NAME_WIDTH),
5116
- TOOL_NAME_WIDTH
5338
+ renderSubtree(
5339
+ childIds[i],
5340
+ agents,
5341
+ childrenOf,
5342
+ visited,
5343
+ nextPrefix,
5344
+ i === childIds.length - 1,
5345
+ lines,
5346
+ height,
5347
+ width
5117
5348
  );
5118
- const filled = Math.round(item.count / maxCount * barWidth);
5119
- const empty = barWidth - filled;
5120
- const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
5121
- const countStr = String(item.count).padStart(4);
5122
- lines.push(truncateToDisplayWidth(`${name} [${bar}] ${countStr}`, width));
5349
+ }
5350
+ }
5351
+ function renderAgentTree(agents, edges, activeSkills, width, height) {
5352
+ if (height <= 0 || width <= 0) return [];
5353
+ const lines = [truncateToDisplayWidth("\u{1F4CA} Activity", width)];
5354
+ if (height <= 1) return lines;
5355
+ if (agents.size === 0) {
5356
+ lines.push(truncateToDisplayWidth(" No agents", width));
5357
+ return lines.slice(0, height);
5358
+ }
5359
+ const childrenOf = /* @__PURE__ */ new Map();
5360
+ for (const edge of edges) {
5361
+ if (!childrenOf.has(edge.from)) childrenOf.set(edge.from, []);
5362
+ childrenOf.get(edge.from).push(edge.to);
5363
+ }
5364
+ const root = [...agents.values()].find((a) => a.isPrimary) ?? [...agents.values()][0];
5365
+ const rootIcon = ACTIVITY_STATUS_ICONS[root.status] ?? "?";
5366
+ lines.push(truncateToDisplayWidth(`${rootIcon} ${root.name}`, width));
5367
+ const visited = /* @__PURE__ */ new Set([root.id]);
5368
+ const rootChildIds = (childrenOf.get(root.id) ?? []).filter((id) => agents.has(id));
5369
+ const totalChildren = rootChildIds.length + activeSkills.length;
5370
+ for (let i = 0; i < rootChildIds.length; i++) {
5371
+ if (lines.length >= height) break;
5372
+ const isLast = i === totalChildren - 1;
5373
+ renderSubtree(rootChildIds[i], agents, childrenOf, visited, " ", isLast, lines, height, width);
5374
+ }
5375
+ for (let i = 0; i < activeSkills.length; i++) {
5376
+ if (lines.length >= height) break;
5377
+ const isLast = rootChildIds.length + i === totalChildren - 1;
5378
+ const connector = isLast ? "\u2514" : "\u251C";
5379
+ lines.push(truncateToDisplayWidth(` ${connector} \u25C9 ${activeSkills[i]} (skill)`, width));
5123
5380
  }
5124
5381
  return lines.slice(0, height);
5125
5382
  }
5126
- var LIVE_AGENT_NAME_WIDTH = 15;
5127
- var LIVE_HOT_WINDOW_MS = 5e3;
5128
- var LIVE_RECENT_WINDOW_MS = 3e4;
5129
- var LIVE_HOT_SEC = LIVE_HOT_WINDOW_MS / 1e3;
5130
- var LIVE_RECENT_SEC = LIVE_RECENT_WINDOW_MS / 1e3;
5131
- function renderLiveContext(calls, currentMode, width, height, now) {
5132
- const currentTime = now ?? Date.now();
5383
+ function wordWrap(text, width) {
5384
+ if (width <= 0) return [];
5385
+ const words = text.split(" ");
5386
+ const result = [];
5387
+ let current = "";
5388
+ let currentWidth = 0;
5389
+ for (const word of words) {
5390
+ const wordWidth = estimateDisplayWidth(word);
5391
+ if (current.length === 0) {
5392
+ current = wordWidth <= width ? word : truncateToDisplayWidth(word, width);
5393
+ currentWidth = estimateDisplayWidth(current);
5394
+ } else if (currentWidth + 1 + wordWidth <= width) {
5395
+ current += " " + word;
5396
+ currentWidth += 1 + wordWidth;
5397
+ } else {
5398
+ result.push(current);
5399
+ current = wordWidth <= width ? word : truncateToDisplayWidth(word, width);
5400
+ currentWidth = estimateDisplayWidth(current);
5401
+ }
5402
+ }
5403
+ if (current.length > 0) result.push(current);
5404
+ return result;
5405
+ }
5406
+ function renderAgentStatusCard(focusedAgent, currentMode, objectives, activeSkills, width, height) {
5133
5407
  if (height <= 0 || width <= 0) return [];
5134
5408
  const lines = [truncateToDisplayWidth("\u{1F4AC} Live", width)];
5135
- if (currentMode) {
5136
- lines.push(truncateToDisplayWidth(`\u27EB Mode: ${currentMode}`, width));
5137
- }
5138
- const maxItems = height - lines.length;
5139
- const seen = /* @__PURE__ */ new Set();
5140
- for (let i = calls.length - 1; i >= 0 && seen.size < maxItems; i--) {
5141
- const call = calls[i];
5142
- if (call.status === "error") continue;
5143
- if (seen.has(call.toolName)) continue;
5144
- seen.add(call.toolName);
5145
- const ageSec = Math.floor((currentTime - call.timestamp) / 1e3);
5146
- const ageIndicator = ageSec < LIVE_HOT_SEC ? "\u25C9" : ageSec < LIVE_RECENT_SEC ? "\u25CB" : "\xB7";
5147
- const agent = call.agentId !== "unknown" ? truncateToDisplayWidth(call.agentId, LIVE_AGENT_NAME_WIDTH) + " " : "";
5148
- lines.push(truncateToDisplayWidth(`${ageIndicator} ${agent}${call.toolName}`, width));
5409
+ if (height <= 1) return lines;
5410
+ lines.push(truncateToDisplayWidth(`\u27EB Mode: ${currentMode ?? "\u2014"}`, width));
5411
+ if (!focusedAgent) {
5412
+ lines.push(truncateToDisplayWidth(" No agent", width));
5413
+ return lines.slice(0, height);
5414
+ }
5415
+ lines.push(truncateToDisplayWidth(`\u{1F916} ${focusedAgent.name}`, width));
5416
+ const statusIcon = ACTIVITY_STATUS_ICONS[focusedAgent.status] ?? "?";
5417
+ lines.push(truncateToDisplayWidth(` ${statusIcon} ${focusedAgent.status}`, width));
5418
+ const separator = truncateToDisplayWidth("\u2500".repeat(width), width);
5419
+ if (lines.length < height) lines.push(separator);
5420
+ let objectivesAdded = false;
5421
+ if (objectives.length > 0 && lines.length < height) {
5422
+ const wrapped = wordWrap(objectives[0], width);
5423
+ for (const wline of wrapped) {
5424
+ if (lines.length >= height) break;
5425
+ lines.push(wline);
5426
+ objectivesAdded = true;
5427
+ }
5428
+ }
5429
+ if (objectivesAdded && lines.length < height) lines.push(separator);
5430
+ for (const skill of activeSkills) {
5431
+ if (lines.length >= height) break;
5432
+ lines.push(truncateToDisplayWidth(`\u2699 ${skill}`, width));
5149
5433
  }
5150
5434
  return lines.slice(0, height);
5151
5435
  }
5152
5436
 
5153
5437
  // src/tui/components/ActivityVisualizer.tsx
5154
5438
  function ActivityVisualizer({
5155
- toolCalls,
5156
5439
  currentMode,
5440
+ focusedAgent,
5441
+ agents,
5442
+ edges,
5443
+ activeSkills,
5444
+ objectives,
5157
5445
  width,
5158
5446
  height
5159
5447
  }) {
5160
- if (width <= 0 || height <= 0) {
5161
- return /* @__PURE__ */ React6.createElement(Box6, null);
5162
- }
5163
- const barChartWidth = Math.floor(width * 0.6);
5164
- const livePanelWidth = width - barChartWidth;
5448
+ const treeWidth = Math.floor(width * 0.6);
5449
+ const cardWidth = width - treeWidth;
5165
5450
  const contentHeight = Math.max(1, height - 2);
5166
- const barChartData = useMemo2(() => aggregateForBarChart(toolCalls), [toolCalls]);
5167
- const barChartLines = useMemo2(
5168
- () => renderBarChart(barChartData, Math.max(1, barChartWidth - 2), contentHeight),
5169
- [barChartData, barChartWidth, contentHeight]
5451
+ const treeLines = useMemo2(
5452
+ () => renderAgentTree(agents, edges, activeSkills, Math.max(1, treeWidth - 2), contentHeight),
5453
+ [agents, edges, activeSkills, treeWidth, contentHeight]
5170
5454
  );
5171
- const liveLines = useMemo2(
5172
- () => renderLiveContext(toolCalls, currentMode, Math.max(1, livePanelWidth - 2), contentHeight),
5173
- [toolCalls, currentMode, livePanelWidth, contentHeight]
5455
+ const cardLines = useMemo2(
5456
+ () => renderAgentStatusCard(
5457
+ focusedAgent,
5458
+ currentMode,
5459
+ objectives,
5460
+ activeSkills,
5461
+ Math.max(1, cardWidth - 2),
5462
+ contentHeight
5463
+ ),
5464
+ [focusedAgent, currentMode, objectives, activeSkills, cardWidth, contentHeight]
5174
5465
  );
5175
- return /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "row", width, height }, /* @__PURE__ */ React6.createElement(
5176
- Box6,
5466
+ if (width <= 0 || height <= 0) {
5467
+ return /* @__PURE__ */ React7.createElement(Box7, null);
5468
+ }
5469
+ return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "row", width, height }, /* @__PURE__ */ React7.createElement(
5470
+ Box7,
5177
5471
  {
5178
5472
  borderStyle: "single",
5179
5473
  borderColor: BORDER_COLORS.panel,
5180
5474
  flexDirection: "column",
5181
- width: barChartWidth,
5475
+ width: treeWidth,
5182
5476
  height
5183
5477
  },
5184
- barChartLines.map((line, i) => /* @__PURE__ */ React6.createElement(Text6, { key: i }, line))
5185
- ), /* @__PURE__ */ React6.createElement(
5186
- Box6,
5478
+ treeLines.map((line, i) => /* @__PURE__ */ React7.createElement(Text7, { key: i }, line))
5479
+ ), /* @__PURE__ */ React7.createElement(
5480
+ Box7,
5187
5481
  {
5188
5482
  borderStyle: "single",
5189
5483
  borderColor: BORDER_COLORS.panel,
5190
5484
  flexDirection: "column",
5191
- width: livePanelWidth,
5485
+ width: cardWidth,
5192
5486
  height
5193
5487
  },
5194
- liveLines.map((line, i) => /* @__PURE__ */ React6.createElement(Text6, { key: i }, line))
5488
+ cardLines.map((line, i) => /* @__PURE__ */ React7.createElement(Text7, { key: i }, line))
5195
5489
  ));
5196
5490
  }
5197
5491
 
5198
- // src/tui/components/stage-health.pure.ts
5199
- function computeStageHealth(agents) {
5200
- const health = {
5201
- PLAN: createEmptyStageStats(),
5202
- ACT: createEmptyStageStats(),
5203
- EVAL: createEmptyStageStats(),
5204
- AUTO: createEmptyStageStats()
5205
- };
5206
- for (const agent of agents.values()) {
5207
- const stage = health[agent.stage];
5208
- if (!stage) continue;
5209
- switch (agent.status) {
5210
- case "running":
5211
- stage.running++;
5212
- break;
5213
- case "blocked":
5214
- stage.blocked++;
5215
- break;
5216
- case "idle":
5217
- stage.waiting++;
5218
- break;
5219
- case "done":
5220
- stage.done++;
5221
- break;
5222
- case "error":
5223
- stage.error++;
5224
- break;
5225
- }
5226
- }
5227
- return health;
5228
- }
5229
- function detectBottlenecks(eventLog) {
5230
- const bottlenecks = /* @__PURE__ */ new Set();
5231
- for (const entry of eventLog) {
5232
- if (entry.level === "error") {
5233
- const testMatch = entry.message.match(/(?:test_run|tests?):\s*FAIL\s*\((\d+)\)/i);
5234
- if (testMatch) {
5235
- bottlenecks.add(`tests failing(${testMatch[1]})`);
5236
- continue;
5237
- }
5238
- const lintMatch = entry.message.match(/lint\(([^)]+)\)/i);
5239
- if (lintMatch) {
5240
- bottlenecks.add(`lint(${lintMatch[1]})`);
5241
- continue;
5242
- }
5243
- const envMatch = entry.message.match(/missing\s+env\(([^)]+)\)/i);
5244
- if (envMatch) {
5245
- bottlenecks.add(`missing env(${envMatch[1]})`);
5246
- }
5247
- }
5248
- }
5249
- return [...bottlenecks];
5250
- }
5251
-
5252
5492
  // src/tui/components/grid-layout.pure.ts
5253
5493
  var HEADER_HEIGHT = 3;
5254
5494
  var STAGE_HEALTH_HEIGHT = 3;
5255
5495
  var NARROW_FLOW_MAP_HEIGHT = 5;
5256
5496
  var MIN_ROWS = HEADER_HEIGHT + STAGE_HEALTH_HEIGHT + 2;
5257
5497
  var MIN_COLUMNS = 20;
5498
+ var CHECKLIST_HEIGHT_MIN = 6;
5499
+ var CHECKLIST_HEIGHT_MAX = 10;
5258
5500
  var FOCUSED_AGENT_WIDTH = {
5259
5501
  wide: 63,
5260
5502
  // was 70 → -10% (FlowMap/Activity gains +7 cols)
5261
5503
  medium: 58
5262
5504
  // was 64 → -9.4% (FlowMap/Activity gains +6 cols)
5263
5505
  };
5506
+ function computeChecklistHeight(mainHeight) {
5507
+ return Math.max(
5508
+ CHECKLIST_HEIGHT_MIN,
5509
+ Math.min(CHECKLIST_HEIGHT_MAX, Math.ceil(mainHeight * 0.3))
5510
+ );
5511
+ }
5264
5512
  function computeGridLayout(columns, rows, layoutMode) {
5265
5513
  const cols = Math.max(MIN_COLUMNS, columns);
5266
5514
  const r = Math.max(MIN_ROWS, rows);
@@ -5275,11 +5523,20 @@ function computeGridLayout(columns, rows, layoutMode) {
5275
5523
  const mainHeight = r - HEADER_HEIGHT - STAGE_HEALTH_HEIGHT;
5276
5524
  if (layoutMode === "narrow") {
5277
5525
  const flowMapHeight2 = Math.min(NARROW_FLOW_MAP_HEIGHT, mainHeight - 1);
5278
- const focusedHeight = mainHeight - flowMapHeight2;
5526
+ const availableForChecklist = mainHeight - flowMapHeight2;
5527
+ const rawChecklistHeight = computeChecklistHeight(mainHeight);
5528
+ const checklistHeight2 = Math.min(rawChecklistHeight, Math.max(0, availableForChecklist - 1));
5529
+ const focusedHeight = availableForChecklist - checklistHeight2;
5279
5530
  return {
5280
5531
  header,
5281
- focusedAgent: { x: 0, y: mainY, width: cols, height: focusedHeight },
5282
- flowMap: { x: 0, y: mainY + focusedHeight, width: cols, height: flowMapHeight2 },
5532
+ checklistPanel: { x: 0, y: mainY, width: cols, height: checklistHeight2 },
5533
+ focusedAgent: { x: 0, y: mainY + checklistHeight2, width: cols, height: focusedHeight },
5534
+ flowMap: {
5535
+ x: 0,
5536
+ y: mainY + checklistHeight2 + focusedHeight,
5537
+ width: cols,
5538
+ height: flowMapHeight2
5539
+ },
5283
5540
  monitorPanel: { x: 0, y: 0, width: 0, height: 0 },
5284
5541
  stageHealth,
5285
5542
  total: { width: cols, height: r }
@@ -5289,119 +5546,148 @@ function computeGridLayout(columns, rows, layoutMode) {
5289
5546
  const flowMapWidth = cols - focusedWidth;
5290
5547
  const flowMapHeight = Math.ceil(mainHeight / 2);
5291
5548
  const monitorHeight = mainHeight - flowMapHeight;
5549
+ const checklistHeight = computeChecklistHeight(mainHeight);
5550
+ const focusedAgentHeight = mainHeight - checklistHeight;
5292
5551
  return {
5293
5552
  header,
5553
+ checklistPanel: { x: flowMapWidth, y: mainY, width: focusedWidth, height: checklistHeight },
5294
5554
  flowMap: { x: 0, y: mainY, width: flowMapWidth, height: flowMapHeight },
5295
5555
  monitorPanel: { x: 0, y: mainY + flowMapHeight, width: flowMapWidth, height: monitorHeight },
5296
- focusedAgent: { x: flowMapWidth, y: mainY, width: focusedWidth, height: mainHeight },
5556
+ focusedAgent: {
5557
+ x: flowMapWidth,
5558
+ y: mainY + checklistHeight,
5559
+ width: focusedWidth,
5560
+ height: focusedAgentHeight
5561
+ },
5297
5562
  stageHealth,
5298
5563
  total: { width: cols, height: r }
5299
5564
  };
5300
5565
  }
5301
5566
 
5302
5567
  // src/tui/dashboard-app.tsx
5303
- function DashboardApp({ eventBus, externalState }) {
5568
+ function DashboardApp({
5569
+ eventBus,
5570
+ externalState,
5571
+ workspace: workspaceProp
5572
+ }) {
5304
5573
  const { columns, rows, layoutMode } = useTerminalSize();
5305
5574
  const internalState = useDashboardState(externalState ? void 0 : eventBus);
5306
5575
  const state = externalState ?? internalState;
5307
5576
  const focusedAgent = state.focusedAgentId ? state.agents.get(state.focusedAgentId) ?? null : null;
5308
5577
  const stageHealth = useMemo3(() => computeStageHealth(state.agents), [state.agents]);
5309
- const tools = useMemo3(() => {
5310
- const seen = /* @__PURE__ */ new Set();
5311
- for (const e of state.eventLog) {
5312
- seen.add(e.message.split(" [")[0]);
5313
- }
5314
- return [...seen];
5315
- }, [state.eventLog]);
5316
5578
  const bottlenecks = useMemo3(() => detectBottlenecks(state.eventLog), [state.eventLog]);
5317
5579
  const grid = useMemo3(
5318
5580
  () => computeGridLayout(columns, rows, layoutMode),
5319
5581
  [columns, rows, layoutMode]
5320
5582
  );
5321
- return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", width: grid.total.width, height: grid.total.height }, /* @__PURE__ */ React7.createElement(
5583
+ return /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", width: grid.total.width, height: grid.total.height }, /* @__PURE__ */ React8.createElement(
5322
5584
  HeaderBar,
5323
5585
  {
5324
- workspace: state.workspace,
5325
- sessionId: state.sessionId,
5586
+ workspace: workspaceProp ?? state.workspace,
5326
5587
  currentMode: state.currentMode,
5327
5588
  globalState: state.globalState,
5328
5589
  layoutMode,
5329
5590
  width: grid.header.width
5330
5591
  }
5331
- ), layoutMode === "narrow" ? /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column" }, /* @__PURE__ */ React7.createElement(
5592
+ ), layoutMode === "narrow" ? /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column" }, grid.checklistPanel.height > 0 && /* @__PURE__ */ React8.createElement(
5593
+ ChecklistPanel,
5594
+ {
5595
+ tasks: state.tasks,
5596
+ contextDecisions: state.contextDecisions,
5597
+ contextNotes: state.contextNotes,
5598
+ width: grid.checklistPanel.width,
5599
+ height: grid.checklistPanel.height
5600
+ }
5601
+ ), /* @__PURE__ */ React8.createElement(
5332
5602
  FocusedAgentPanel,
5333
5603
  {
5334
5604
  agent: focusedAgent,
5335
5605
  objectives: state.objectives,
5336
5606
  activeSkills: state.activeSkills,
5337
- tasks: state.tasks,
5338
- tools,
5339
- inputs: tools,
5340
- outputs: state.outputStats,
5341
5607
  eventLog: state.eventLog,
5342
- toolCalls: state.toolCalls,
5343
5608
  contextDecisions: state.contextDecisions,
5344
5609
  contextNotes: state.contextNotes,
5345
5610
  width: grid.focusedAgent.width,
5346
5611
  height: grid.focusedAgent.height
5347
5612
  }
5348
- ), /* @__PURE__ */ React7.createElement(
5349
- FlowMap,
5350
- {
5351
- agents: state.agents,
5352
- edges: state.edges,
5353
- layoutMode,
5354
- width: grid.flowMap.width,
5355
- height: grid.flowMap.height
5356
- }
5357
- )) : /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "row", width: grid.total.width, height: grid.focusedAgent.height }, /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", width: grid.flowMap.width }, /* @__PURE__ */ React7.createElement(
5613
+ ), /* @__PURE__ */ React8.createElement(
5358
5614
  FlowMap,
5359
5615
  {
5360
5616
  agents: state.agents,
5361
5617
  edges: state.edges,
5362
5618
  layoutMode,
5363
5619
  width: grid.flowMap.width,
5364
- height: grid.flowMap.height
5620
+ height: grid.flowMap.height,
5621
+ activeStage: state.currentMode
5365
5622
  }
5366
- ), /* @__PURE__ */ React7.createElement(
5367
- ActivityVisualizer,
5623
+ )) : /* @__PURE__ */ React8.createElement(
5624
+ Box8,
5368
5625
  {
5369
- toolCalls: state.toolCalls,
5370
- currentMode: state.currentMode,
5371
- width: grid.monitorPanel.width,
5372
- height: grid.monitorPanel.height
5373
- }
5374
- )), /* @__PURE__ */ React7.createElement(
5375
- FocusedAgentPanel,
5376
- {
5377
- agent: focusedAgent,
5378
- objectives: state.objectives,
5379
- activeSkills: state.activeSkills,
5380
- tasks: state.tasks,
5381
- tools,
5382
- inputs: tools,
5383
- outputs: state.outputStats,
5384
- eventLog: state.eventLog,
5385
- toolCalls: state.toolCalls,
5386
- contextDecisions: state.contextDecisions,
5387
- contextNotes: state.contextNotes,
5388
- width: grid.focusedAgent.width,
5389
- height: grid.focusedAgent.height
5390
- }
5391
- )), /* @__PURE__ */ React7.createElement(
5626
+ flexDirection: "row",
5627
+ width: grid.total.width,
5628
+ height: grid.checklistPanel.height + grid.focusedAgent.height
5629
+ },
5630
+ /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", width: grid.flowMap.width }, /* @__PURE__ */ React8.createElement(
5631
+ FlowMap,
5632
+ {
5633
+ agents: state.agents,
5634
+ edges: state.edges,
5635
+ layoutMode,
5636
+ width: grid.flowMap.width,
5637
+ height: grid.flowMap.height,
5638
+ activeStage: state.currentMode
5639
+ }
5640
+ ), /* @__PURE__ */ React8.createElement(
5641
+ ActivityVisualizer,
5642
+ {
5643
+ currentMode: state.currentMode,
5644
+ focusedAgent,
5645
+ agents: state.agents,
5646
+ edges: state.edges,
5647
+ activeSkills: state.activeSkills,
5648
+ objectives: state.objectives,
5649
+ width: grid.monitorPanel.width,
5650
+ height: grid.monitorPanel.height
5651
+ }
5652
+ )),
5653
+ /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", width: grid.checklistPanel.width }, /* @__PURE__ */ React8.createElement(
5654
+ ChecklistPanel,
5655
+ {
5656
+ tasks: state.tasks,
5657
+ contextDecisions: state.contextDecisions,
5658
+ contextNotes: state.contextNotes,
5659
+ width: grid.checklistPanel.width,
5660
+ height: grid.checklistPanel.height
5661
+ }
5662
+ ), /* @__PURE__ */ React8.createElement(
5663
+ FocusedAgentPanel,
5664
+ {
5665
+ agent: focusedAgent,
5666
+ objectives: state.objectives,
5667
+ activeSkills: state.activeSkills,
5668
+ eventLog: state.eventLog,
5669
+ contextDecisions: state.contextDecisions,
5670
+ contextNotes: state.contextNotes,
5671
+ width: grid.focusedAgent.width,
5672
+ height: grid.focusedAgent.height
5673
+ }
5674
+ ))
5675
+ ), /* @__PURE__ */ React8.createElement(
5392
5676
  StageHealthBar,
5393
5677
  {
5394
5678
  stageHealth,
5395
5679
  bottlenecks,
5396
5680
  toolCount: state.toolInvokeCount,
5681
+ agentCount: state.agentActivateCount,
5682
+ skillCount: state.skillInvokeCount,
5397
5683
  width: grid.stageHealth.width
5398
5684
  }
5399
5685
  ));
5400
5686
  }
5401
5687
 
5402
5688
  // src/tui/multi-session-app.tsx
5403
- import React9, { useMemo as useMemo4, useCallback as useCallback2 } from "react";
5404
- import { Box as Box9, useInput } from "ink";
5689
+ import React10, { useMemo as useMemo4, useCallback as useCallback2 } from "react";
5690
+ import { Box as Box10, useInput } from "ink";
5405
5691
  import * as path8 from "path";
5406
5692
 
5407
5693
  // src/tui/hooks/use-multi-session-state.ts
@@ -5557,8 +5843,8 @@ function useMultiSessionState(manager) {
5557
5843
  }
5558
5844
 
5559
5845
  // src/tui/components/SessionTabBar.tsx
5560
- import React8 from "react";
5561
- import { Box as Box8, Text as Text7 } from "ink";
5846
+ import React9 from "react";
5847
+ import { Box as Box9, Text as Text8 } from "ink";
5562
5848
 
5563
5849
  // src/tui/components/session-tab-bar.pure.ts
5564
5850
  var STATUS_ICONS2 = {
@@ -5612,7 +5898,7 @@ function SessionTabBar({
5612
5898
  if (formatted === "") {
5613
5899
  return null;
5614
5900
  }
5615
- return /* @__PURE__ */ React8.createElement(Box8, { width, height: 1 }, /* @__PURE__ */ React8.createElement(Text7, null, formatted));
5901
+ return /* @__PURE__ */ React9.createElement(Box9, { width, height: 1 }, /* @__PURE__ */ React9.createElement(Text8, null, formatted));
5616
5902
  }
5617
5903
 
5618
5904
  // src/tui/multi-session-app.tsx
@@ -5640,6 +5926,10 @@ function MultiSessionApp({ manager }) {
5640
5926
  const found = managedSessions.find((s) => s.instance.pid === activeSessionPid);
5641
5927
  return found?.eventBus;
5642
5928
  }, [manager, activeSessionPid]);
5929
+ const activeProjectRoot = useMemo4(() => {
5930
+ if (activeSessionPid === null) return void 0;
5931
+ return sessions.get(activeSessionPid)?.projectRoot;
5932
+ }, [sessions, activeSessionPid]);
5643
5933
  const handleInput = useCallback2(
5644
5934
  (input, key) => {
5645
5935
  if (key.rightArrow) {
@@ -5658,7 +5948,14 @@ function MultiSessionApp({ manager }) {
5658
5948
  [switchNext, switchPrev, switchByIndex]
5659
5949
  );
5660
5950
  useInput(handleInput);
5661
- return /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column" }, /* @__PURE__ */ React9.createElement(SessionTabBar, { sessions: tabs, width: columns, layoutMode }), /* @__PURE__ */ React9.createElement(DashboardApp, { key: activeSessionPid ?? "none", eventBus: activeEventBus }));
5951
+ return /* @__PURE__ */ React10.createElement(Box10, { flexDirection: "column" }, /* @__PURE__ */ React10.createElement(SessionTabBar, { sessions: tabs, width: columns, layoutMode }), /* @__PURE__ */ React10.createElement(
5952
+ DashboardApp,
5953
+ {
5954
+ key: activeSessionPid ?? "none",
5955
+ eventBus: activeEventBus,
5956
+ workspace: activeProjectRoot
5957
+ }
5958
+ ));
5662
5959
  }
5663
5960
 
5664
5961
  // src/tui/types.ts
@@ -5721,10 +6018,10 @@ function useClock() {
5721
6018
  // src/tui/index.tsx
5722
6019
  function startTui(options) {
5723
6020
  const renderOptions = options.stdout ? { stdout: options.stdout } : {};
5724
- return render(/* @__PURE__ */ React10.createElement(DashboardApp, { eventBus: options.eventBus }), renderOptions);
6021
+ return render(/* @__PURE__ */ React11.createElement(DashboardApp, { eventBus: options.eventBus }), renderOptions);
5725
6022
  }
5726
6023
  function renderMultiSession(options) {
5727
- return render(/* @__PURE__ */ React10.createElement(MultiSessionApp, { manager: options.manager }));
6024
+ return render(/* @__PURE__ */ React11.createElement(MultiSessionApp, { manager: options.manager }));
5728
6025
  }
5729
6026
  export {
5730
6027
  AGENT_STATUSES,