codingbuddy 4.3.0 → 4.5.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 (105) hide show
  1. package/dist/src/agent/agent.service.d.ts +2 -0
  2. package/dist/src/agent/agent.service.js +45 -0
  3. package/dist/src/agent/agent.service.js.map +1 -1
  4. package/dist/src/agent/agent.types.d.ts +13 -0
  5. package/dist/src/agent/agent.types.js.map +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/config/config.service.d.ts +7 -1
  9. package/dist/src/config/config.service.js +13 -1
  10. package/dist/src/config/config.service.js.map +1 -1
  11. package/dist/src/keyword/keyword.module.js +2 -0
  12. package/dist/src/keyword/keyword.module.js.map +1 -1
  13. package/dist/src/keyword/keyword.service.d.ts +3 -0
  14. package/dist/src/keyword/keyword.service.js +20 -1
  15. package/dist/src/keyword/keyword.service.js.map +1 -1
  16. package/dist/src/keyword/keyword.types.d.ts +2 -0
  17. package/dist/src/keyword/taskmaestro-detector.d.ts +1 -0
  18. package/dist/src/keyword/taskmaestro-detector.js +16 -0
  19. package/dist/src/keyword/taskmaestro-detector.js.map +1 -0
  20. package/dist/src/mcp/handlers/agent.handler.js +7 -0
  21. package/dist/src/mcp/handlers/agent.handler.js.map +1 -1
  22. package/dist/src/mcp/handlers/mode.handler.js +11 -0
  23. package/dist/src/mcp/handlers/mode.handler.js.map +1 -1
  24. package/dist/src/mcp/handlers/skill.handler.js +9 -2
  25. package/dist/src/mcp/handlers/skill.handler.js.map +1 -1
  26. package/dist/src/mcp/mcp.service.d.ts +1 -0
  27. package/dist/src/mcp/mcp.service.js +17 -0
  28. package/dist/src/mcp/mcp.service.js.map +1 -1
  29. package/dist/src/mcp/sse-auth.guard.js +4 -6
  30. package/dist/src/mcp/sse-auth.guard.js.map +1 -1
  31. package/dist/src/model/model.constants.d.ts +11 -0
  32. package/dist/src/model/model.constants.js +12 -1
  33. package/dist/src/model/model.constants.js.map +1 -1
  34. package/dist/src/model/model.resolver.d.ts +1 -1
  35. package/dist/src/model/model.resolver.js +15 -4
  36. package/dist/src/model/model.resolver.js.map +1 -1
  37. package/dist/src/shared/client-type.d.ts +6 -0
  38. package/dist/src/shared/client-type.js +21 -0
  39. package/dist/src/shared/client-type.js.map +1 -0
  40. package/dist/src/shared/version.d.ts +1 -1
  41. package/dist/src/shared/version.js +1 -1
  42. package/dist/src/skill/i18n/keywords.js +1466 -15
  43. package/dist/src/skill/i18n/keywords.js.map +1 -1
  44. package/dist/src/tui/components/FlowMap.d.ts +3 -1
  45. package/dist/src/tui/components/FlowMap.js +30 -3
  46. package/dist/src/tui/components/FlowMap.js.map +1 -1
  47. package/dist/src/tui/components/FlowMap.spec.js +88 -0
  48. package/dist/src/tui/components/FlowMap.spec.js.map +1 -1
  49. package/dist/src/tui/components/FocusedAgentPanel.d.ts +3 -1
  50. package/dist/src/tui/components/FocusedAgentPanel.js +8 -3
  51. package/dist/src/tui/components/FocusedAgentPanel.js.map +1 -1
  52. package/dist/src/tui/components/FocusedAgentPanel.spec.js +76 -0
  53. package/dist/src/tui/components/FocusedAgentPanel.spec.js.map +1 -1
  54. package/dist/src/tui/components/HeaderBar.d.ts +3 -1
  55. package/dist/src/tui/components/HeaderBar.js +12 -6
  56. package/dist/src/tui/components/HeaderBar.js.map +1 -1
  57. package/dist/src/tui/components/HeaderBar.spec.js +31 -13
  58. package/dist/src/tui/components/HeaderBar.spec.js.map +1 -1
  59. package/dist/src/tui/components/StageHealthBar.d.ts +5 -1
  60. package/dist/src/tui/components/StageHealthBar.js +6 -1
  61. package/dist/src/tui/components/StageHealthBar.js.map +1 -1
  62. package/dist/src/tui/components/StageHealthBar.spec.js +22 -0
  63. package/dist/src/tui/components/StageHealthBar.spec.js.map +1 -1
  64. package/dist/src/tui/components/focused-agent.pure.d.ts +1 -0
  65. package/dist/src/tui/components/focused-agent.pure.js +11 -0
  66. package/dist/src/tui/components/focused-agent.pure.js.map +1 -1
  67. package/dist/src/tui/components/index.d.ts +3 -1
  68. package/dist/src/tui/components/index.js +10 -1
  69. package/dist/src/tui/components/index.js.map +1 -1
  70. package/dist/src/tui/components/live.pure.d.ts +9 -0
  71. package/dist/src/tui/components/live.pure.js +70 -0
  72. package/dist/src/tui/components/live.pure.js.map +1 -0
  73. package/dist/src/tui/dashboard-app.js +9 -6
  74. package/dist/src/tui/dashboard-app.js.map +1 -1
  75. package/dist/src/tui/dashboard-app.spec.js +14 -0
  76. package/dist/src/tui/dashboard-app.spec.js.map +1 -1
  77. package/dist/src/tui/dashboard-types.d.ts +6 -0
  78. package/dist/src/tui/dashboard-types.js.map +1 -1
  79. package/dist/src/tui/eventbus-ui.integration.spec.js +3 -0
  80. package/dist/src/tui/eventbus-ui.integration.spec.js.map +1 -1
  81. package/dist/src/tui/hooks/index.d.ts +1 -1
  82. package/dist/src/tui/hooks/index.js +3 -3
  83. package/dist/src/tui/hooks/index.js.map +1 -1
  84. package/dist/src/tui/hooks/use-dashboard-state.js +16 -0
  85. package/dist/src/tui/hooks/use-dashboard-state.js.map +1 -1
  86. package/dist/src/tui/hooks/use-tick.d.ts +1 -0
  87. package/dist/src/tui/hooks/use-tick.js +15 -0
  88. package/dist/src/tui/hooks/use-tick.js.map +1 -0
  89. package/dist/src/tui/hooks/use-tick.spec.js +70 -0
  90. package/dist/src/tui/hooks/use-tick.spec.js.map +1 -0
  91. package/dist/src/tui/index.d.ts +1 -1
  92. package/dist/src/tui/index.js +2 -2
  93. package/dist/src/tui/index.js.map +1 -1
  94. package/dist/src/tui-bundle.mjs +191 -52
  95. package/dist/tsconfig.build.tsbuildinfo +1 -1
  96. package/package.json +5 -4
  97. package/dist/src/tui/components/header-bar.pure.d.ts +0 -1
  98. package/dist/src/tui/components/header-bar.pure.js +0 -9
  99. package/dist/src/tui/components/header-bar.pure.js.map +0 -1
  100. package/dist/src/tui/hooks/use-clock.d.ts +0 -1
  101. package/dist/src/tui/hooks/use-clock.js +0 -16
  102. package/dist/src/tui/hooks/use-clock.js.map +0 -1
  103. package/dist/src/tui/hooks/use-clock.spec.js +0 -43
  104. package/dist/src/tui/hooks/use-clock.spec.js.map +0 -1
  105. /package/dist/src/tui/hooks/{use-clock.spec.d.ts → use-tick.spec.d.ts} +0 -0
@@ -1431,8 +1431,21 @@ function useTerminalSize() {
1431
1431
  return { ...size, layoutMode: getLayoutMode(size.columns) };
1432
1432
  }
1433
1433
 
1434
+ // src/tui/hooks/use-tick.ts
1435
+ import { useState as useState2, useEffect as useEffect2 } from "react";
1436
+ function useTick(intervalMs = 1e3) {
1437
+ const [tick, setTick] = useState2(0);
1438
+ useEffect2(() => {
1439
+ const id = setInterval(() => {
1440
+ setTick((prev) => prev + 1);
1441
+ }, intervalMs);
1442
+ return () => clearInterval(id);
1443
+ }, [intervalMs]);
1444
+ return tick;
1445
+ }
1446
+
1434
1447
  // src/tui/hooks/use-dashboard-state.ts
1435
- import { useReducer, useEffect as useEffect2 } from "react";
1448
+ import { useReducer, useEffect as useEffect3 } from "react";
1436
1449
 
1437
1450
  // src/tui/events/event-bus.ts
1438
1451
  var import_eventemitter2 = __toESM(require_eventemitter2());
@@ -3489,6 +3502,7 @@ ${file.content}
3489
3502
  var ConfigService = class {
3490
3503
  constructor() {
3491
3504
  this.logger = new Logger6(ConfigService.name);
3505
+ this.projectRootSource = "auto_detect";
3492
3506
  this.projectConfig = null;
3493
3507
  this.isLoaded = false;
3494
3508
  this.projectRoot = this.resolveProjectRoot();
@@ -3506,6 +3520,7 @@ var ConfigService = class {
3506
3520
  const stat2 = statSync(normalizedPath);
3507
3521
  if (stat2.isDirectory()) {
3508
3522
  this.logger.log(`Using project root from CODINGBUDDY_PROJECT_ROOT: ${normalizedPath}`);
3523
+ this.projectRootSource = "env";
3509
3524
  return normalizedPath;
3510
3525
  }
3511
3526
  this.logger.warn(
@@ -3543,7 +3558,7 @@ var ConfigService = class {
3543
3558
  * @param root - Path to the project root directory
3544
3559
  * @throws Error if path does not exist, is not a directory, or contains invalid characters
3545
3560
  */
3546
- async setProjectRootAndReload(root) {
3561
+ async setProjectRootAndReload(root, source = "roots_list") {
3547
3562
  if (root.includes("\0")) {
3548
3563
  throw new Error("Path contains null bytes (possible null byte injection)");
3549
3564
  }
@@ -3569,6 +3584,7 @@ var ConfigService = class {
3569
3584
  }
3570
3585
  clearProjectRootCache();
3571
3586
  this.projectRoot = resolvedPath;
3587
+ this.projectRootSource = source;
3572
3588
  this.isLoaded = false;
3573
3589
  this.projectConfig = null;
3574
3590
  await this.loadProjectConfig();
@@ -3580,6 +3596,18 @@ var ConfigService = class {
3580
3596
  getProjectRoot() {
3581
3597
  return this.projectRoot;
3582
3598
  }
3599
+ /**
3600
+ * Get how the project root was resolved
3601
+ */
3602
+ getProjectRootSource() {
3603
+ return this.projectRootSource;
3604
+ }
3605
+ getClientName() {
3606
+ return this.clientName;
3607
+ }
3608
+ setClientName(name) {
3609
+ this.clientName = name;
3610
+ }
3583
3611
  /**
3584
3612
  * Load all project configuration
3585
3613
  */
@@ -3959,6 +3987,7 @@ function createInitialDashboardState() {
3959
3987
  tasks: [],
3960
3988
  eventLog: [],
3961
3989
  toolCalls: [],
3990
+ activityHistory: [],
3962
3991
  objectives: [],
3963
3992
  activeSkills: [],
3964
3993
  toolInvokeCount: 0,
@@ -4060,7 +4089,8 @@ function dashboardReducer(state, action) {
4060
4089
  const entry = {
4061
4090
  timestamp: new Date(action.payload.timestamp).toTimeString().slice(0, 8),
4062
4091
  message: `${action.payload.toolName}${action.payload.agentId ? ` [${action.payload.agentId}]` : ""}`,
4063
- level: "info"
4092
+ level: "info",
4093
+ rawTimestamp: action.payload.timestamp
4064
4094
  };
4065
4095
  const base = state.eventLog.length >= EVENT_LOG_MAX ? state.eventLog.slice(1) : state.eventLog;
4066
4096
  const invokedAgentId = action.payload.agentId;
@@ -4088,11 +4118,24 @@ function dashboardReducer(state, action) {
4088
4118
  status: "completed"
4089
4119
  };
4090
4120
  const toolCallsBase = state.toolCalls.length >= TOOL_CALLS_MAX ? state.toolCalls.slice(1) : state.toolCalls;
4121
+ const sec = Math.floor(action.payload.timestamp / 1e3);
4122
+ const history = state.activityHistory;
4123
+ const last = history.length > 0 ? history[history.length - 1] : null;
4124
+ let activityHistory;
4125
+ if (last && last.timestamp === sec) {
4126
+ activityHistory = [
4127
+ ...history.slice(0, -1),
4128
+ { timestamp: sec, toolCalls: last.toolCalls + 1 }
4129
+ ];
4130
+ } else {
4131
+ activityHistory = [...history.slice(-59), { timestamp: sec, toolCalls: 1 }];
4132
+ }
4091
4133
  return {
4092
4134
  ...state,
4093
4135
  agents,
4094
4136
  eventLog: [...base, entry],
4095
4137
  toolCalls: [...toolCallsBase, toolCall],
4138
+ activityHistory,
4096
4139
  toolInvokeCount: state.toolInvokeCount + 1
4097
4140
  };
4098
4141
  }
@@ -4166,7 +4209,7 @@ function dashboardReducer(state, action) {
4166
4209
  }
4167
4210
  function useDashboardState(eventBus) {
4168
4211
  const [state, dispatch] = useReducer(dashboardReducer, void 0, createInitialDashboardState);
4169
- useEffect2(() => {
4212
+ useEffect3(() => {
4170
4213
  if (!eventBus) return;
4171
4214
  const onActivated = (p) => dispatch({ type: "AGENT_ACTIVATED", payload: p });
4172
4215
  const onDeactivated = (p) => dispatch({ type: "AGENT_DEACTIVATED", payload: p });
@@ -4210,7 +4253,7 @@ function useDashboardState(eventBus) {
4210
4253
  eventBus.off(TUI_EVENTS.CONTEXT_UPDATED, onContextUpdated);
4211
4254
  };
4212
4255
  }, [eventBus]);
4213
- useEffect2(() => {
4256
+ useEffect3(() => {
4214
4257
  const cleanupInterval = setInterval(() => {
4215
4258
  dispatch({ type: "CLEANUP_STALE_AGENTS", payload: { now: Date.now(), ttlMs: 3e4 } });
4216
4259
  }, 1e4);
@@ -4333,6 +4376,59 @@ function getAgentAvatar(agentName) {
4333
4376
  return "\u{1F916}";
4334
4377
  }
4335
4378
 
4379
+ // src/tui/components/live.pure.ts
4380
+ function formatElapsed(startedAt, now) {
4381
+ const totalSec = Math.max(0, Math.floor((now - startedAt) / 1e3));
4382
+ const min = Math.floor(totalSec / 60);
4383
+ const sec = totalSec % 60;
4384
+ return min > 0 ? `${min}m ${sec}s` : `${sec}s`;
4385
+ }
4386
+ function formatRelativeTime(timestamp, now) {
4387
+ const diffSec = Math.max(0, Math.floor((now - timestamp) / 1e3));
4388
+ if (diffSec <= 2) return "just now";
4389
+ if (diffSec < 60) return `${diffSec}s ago`;
4390
+ const diffMin = Math.floor(diffSec / 60);
4391
+ if (diffMin < 60) return `${diffMin}m ago`;
4392
+ const diffHr = Math.floor(diffMin / 60);
4393
+ return `${diffHr}h ago`;
4394
+ }
4395
+ var SPINNER_FRAMES = "\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827";
4396
+ function spinnerFrame(tick) {
4397
+ return SPINNER_FRAMES[tick % 8];
4398
+ }
4399
+ function pulseIcon(tick) {
4400
+ return tick % 2 === 0 ? "\u25CF" : "\u25C9";
4401
+ }
4402
+ var SPARK_CHARS = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
4403
+ function renderSparkline(samples, width) {
4404
+ if (samples.length === 0) return "";
4405
+ const visible = samples.slice(-width);
4406
+ const min = Math.min(...visible);
4407
+ const max = Math.max(...visible);
4408
+ const range = max - min;
4409
+ return visible.map((v) => {
4410
+ if (range === 0) return SPARK_CHARS[0];
4411
+ const idx = Math.round((v - min) / range * 7);
4412
+ return SPARK_CHARS[idx];
4413
+ }).join("");
4414
+ }
4415
+ function computeThroughput(samples) {
4416
+ if (samples.length <= 1) return "0.0/min";
4417
+ const first = samples[0];
4418
+ const last = samples[samples.length - 1];
4419
+ const durationMin = (last.timestamp - first.timestamp) / 60;
4420
+ if (durationMin <= 0) return "0.0/min";
4421
+ const totalCalls = samples.reduce((sum, s) => sum + s.toolCalls, 0);
4422
+ return `${(totalCalls / durationMin).toFixed(1)}/min`;
4423
+ }
4424
+ function formatTimeWithSeconds(now) {
4425
+ const d = new Date(now);
4426
+ const h = String(d.getHours()).padStart(2, "0");
4427
+ const m = String(d.getMinutes()).padStart(2, "0");
4428
+ const s = String(d.getSeconds()).padStart(2, "0");
4429
+ return `${h}:${m}:${s}`;
4430
+ }
4431
+
4336
4432
  // src/tui/components/HeaderBar.tsx
4337
4433
  var PROCESS_MODES = ["PLAN", "ACT", "EVAL"];
4338
4434
  function ModeFlow({ currentMode }) {
@@ -4343,17 +4439,23 @@ function ModeFlow({ currentMode }) {
4343
4439
  return /* @__PURE__ */ React.createElement(React.Fragment, { key: mode }, i > 0 && /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " \u2192 "), isActive ? /* @__PURE__ */ React.createElement(Text, { color, bold: true }, "[", mode, "]") : /* @__PURE__ */ React.createElement(Text, { dimColor: true }, mode));
4344
4440
  }));
4345
4441
  }
4346
- function StateIndicator({ globalState }) {
4347
- const icon = GLOBAL_STATE_ICONS[globalState];
4442
+ function StateIndicator({
4443
+ globalState,
4444
+ tick
4445
+ }) {
4446
+ const isRunning = globalState === "RUNNING";
4447
+ const icon = isRunning && tick !== void 0 ? spinnerFrame(tick) : GLOBAL_STATE_ICONS[globalState];
4348
4448
  const color = GLOBAL_STATE_COLORS[globalState];
4349
- return /* @__PURE__ */ React.createElement(Text, { color, bold: globalState === "RUNNING" || globalState === "ERROR" }, icon, " ", globalState);
4449
+ return /* @__PURE__ */ React.createElement(Text, { color, bold: isRunning || globalState === "ERROR" }, icon, " ", globalState);
4350
4450
  }
4351
4451
  function HeaderBar({
4352
4452
  workspace,
4353
4453
  currentMode,
4354
4454
  globalState,
4355
4455
  layoutMode,
4356
- width
4456
+ width,
4457
+ tick,
4458
+ now
4357
4459
  }) {
4358
4460
  if (layoutMode === "narrow") {
4359
4461
  return /* @__PURE__ */ React.createElement(
@@ -4369,7 +4471,8 @@ function HeaderBar({
4369
4471
  /* @__PURE__ */ React.createElement(Box, { flexGrow: 1 }),
4370
4472
  /* @__PURE__ */ React.createElement(ModeFlow, { currentMode }),
4371
4473
  /* @__PURE__ */ React.createElement(Box, { flexGrow: 1 }),
4372
- /* @__PURE__ */ React.createElement(StateIndicator, { globalState })
4474
+ /* @__PURE__ */ React.createElement(StateIndicator, { globalState, tick }),
4475
+ now !== void 0 && /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " ", formatTimeWithSeconds(now))
4373
4476
  );
4374
4477
  }
4375
4478
  return /* @__PURE__ */ React.createElement(
@@ -4381,7 +4484,7 @@ function HeaderBar({
4381
4484
  overflowX: "hidden",
4382
4485
  flexDirection: "row"
4383
4486
  },
4384
- /* @__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 })),
4487
+ /* @__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, tick }), now !== void 0 && /* @__PURE__ */ React.createElement(Text, { dimColor: true }, formatTimeWithSeconds(now))),
4385
4488
  /* @__PURE__ */ React.createElement(Box, { flexGrow: 1 }),
4386
4489
  /* @__PURE__ */ React.createElement(Box, { flexShrink: 1, overflowX: "hidden" }, /* @__PURE__ */ React.createElement(Text, { dimColor: true, wrap: "truncate" }, workspace))
4387
4490
  );
@@ -4900,10 +5003,18 @@ function FlowMap({
4900
5003
  layoutMode,
4901
5004
  width,
4902
5005
  height,
4903
- activeStage = null
5006
+ activeStage = null,
5007
+ tick,
5008
+ now
4904
5009
  }) {
4905
5010
  const contentWidth = Math.max(1, width - 2);
4906
5011
  const contentHeight = Math.max(1, height - 3);
5012
+ const hasRunningAgents = useMemo(
5013
+ () => [...agents.values()].some((a) => a.status === "running"),
5014
+ [agents]
5015
+ );
5016
+ const liveTick = hasRunningAgents ? tick ?? 0 : 0;
5017
+ const liveNow = hasRunningAgents ? now : void 0;
4907
5018
  const compactContent = useMemo(() => {
4908
5019
  if (layoutMode !== "narrow") return null;
4909
5020
  return renderFlowMapCompact(agents);
@@ -4913,6 +5024,20 @@ function FlowMap({
4913
5024
  const buf = layoutMode === "wide" ? renderFlowMap(agents, edges, contentWidth, contentHeight, activeStage) : renderFlowMapSimplified(agents, contentWidth, contentHeight);
4914
5025
  return buf.toLinesDirect();
4915
5026
  }, [agents, edges, contentWidth, contentHeight, layoutMode, activeStage]);
5027
+ const liveOverlays = useMemo(() => {
5028
+ if (liveNow === void 0) return null;
5029
+ const overlays = [];
5030
+ for (const [id, agent] of agents) {
5031
+ if (agent.status === "running" && agent.startedAt != null) {
5032
+ overlays.push({
5033
+ id,
5034
+ icon: pulseIcon(liveTick),
5035
+ elapsed: formatElapsed(agent.startedAt, liveNow)
5036
+ });
5037
+ }
5038
+ }
5039
+ return overlays.length > 0 ? overlays : null;
5040
+ }, [agents, liveTick, liveNow]);
4916
5041
  if (layoutMode === "narrow") {
4917
5042
  return /* @__PURE__ */ React2.createElement(
4918
5043
  Box2,
@@ -4924,7 +5049,8 @@ function FlowMap({
4924
5049
  height
4925
5050
  },
4926
5051
  /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "cyan" }, "FLOW MAP"),
4927
- /* @__PURE__ */ React2.createElement(Text2, null, compactContent)
5052
+ /* @__PURE__ */ React2.createElement(Text2, null, compactContent),
5053
+ liveOverlays?.map((o) => /* @__PURE__ */ React2.createElement(Text2, { key: o.id, color: "yellow" }, o.icon, " ", o.elapsed))
4928
5054
  );
4929
5055
  }
4930
5056
  if (!lines) {
@@ -4950,7 +5076,8 @@ function FlowMap({
4950
5076
  height
4951
5077
  },
4952
5078
  /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "cyan" }, "FLOW MAP"),
4953
- lines.map((row, y) => /* @__PURE__ */ React2.createElement(ColorRow, { key: y, cells: row }))
5079
+ lines.map((row, y) => /* @__PURE__ */ React2.createElement(ColorRow, { key: y, cells: row })),
5080
+ liveOverlays?.map((o) => /* @__PURE__ */ React2.createElement(Text2, { key: o.id, color: "yellow" }, o.icon, " ", o.elapsed))
4954
5081
  );
4955
5082
  }
4956
5083
 
@@ -4970,6 +5097,13 @@ function formatLogTail(events, maxLines = 10) {
4970
5097
  const tail = events.slice(-maxLines);
4971
5098
  return tail.map((e) => `${e.timestamp} ${e.message}`).join("\n");
4972
5099
  }
5100
+ function formatLogTailRelative(events, now, maxLines = 10) {
5101
+ const tail = events.slice(-maxLines);
5102
+ return tail.map((e) => {
5103
+ const ts = e.rawTimestamp != null ? formatRelativeTime(e.rawTimestamp, now) : e.timestamp;
5104
+ return `${ts} ${e.message}`;
5105
+ }).join("\n");
5106
+ }
4973
5107
  function formatSectionDivider(title, width = 50) {
4974
5108
  const prefix = "\u2500\u2500\u2500 ";
4975
5109
  const suffix = " ";
@@ -5056,6 +5190,8 @@ function FocusedAgentPanel({
5056
5190
  eventLog,
5057
5191
  contextDecisions = [],
5058
5192
  contextNotes = [],
5193
+ tick,
5194
+ now,
5059
5195
  width,
5060
5196
  height
5061
5197
  }) {
@@ -5078,7 +5214,10 @@ function FocusedAgentPanel({
5078
5214
  const statusLabel = agent.status.toUpperCase();
5079
5215
  const progressBar = formatEnhancedProgressBar(agent.progress);
5080
5216
  const objective = formatObjective(objectives);
5081
- const logs = formatLogTail(eventLog);
5217
+ const logs = now != null ? formatLogTailRelative(eventLog, now) : formatLogTail(eventLog);
5218
+ const isRunning = agent.status === "running";
5219
+ const showSpinner = isRunning && tick != null;
5220
+ const showElapsed = isRunning && now != null && agent.startedAt != null;
5082
5221
  return /* @__PURE__ */ React4.createElement(
5083
5222
  Box4,
5084
5223
  {
@@ -5088,7 +5227,7 @@ function FocusedAgentPanel({
5088
5227
  width,
5089
5228
  height
5090
5229
  },
5091
- /* @__PURE__ */ React4.createElement(Box4, { gap: 2 }, /* @__PURE__ */ React4.createElement(Text4, null, avatar), /* @__PURE__ */ React4.createElement(Text4, { bold: true }, agent.name), /* @__PURE__ */ React4.createElement(Text4, { color: statusColor, bold: true }, icon), /* @__PURE__ */ React4.createElement(Text4, { color: statusColor }, statusLabel), /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, agent.stage)),
5230
+ /* @__PURE__ */ React4.createElement(Box4, { gap: 2 }, /* @__PURE__ */ React4.createElement(Text4, null, avatar), /* @__PURE__ */ React4.createElement(Text4, { bold: true }, agent.name), showSpinner ? /* @__PURE__ */ React4.createElement(Text4, { color: "cyan" }, spinnerFrame(tick)) : /* @__PURE__ */ React4.createElement(Text4, { color: statusColor, bold: true }, icon), /* @__PURE__ */ React4.createElement(Text4, { color: statusColor }, statusLabel), showElapsed && /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, formatElapsed(agent.startedAt, now)), /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, agent.stage)),
5092
5231
  /* @__PURE__ */ React4.createElement(Box4, { gap: 1 }, /* @__PURE__ */ React4.createElement(Text4, { color: "magenta" }, progressBar.bar), /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, progressBar.label)),
5093
5232
  /* @__PURE__ */ React4.createElement(SectionDivider, { title: "Objective" }),
5094
5233
  objective ? /* @__PURE__ */ React4.createElement(Text4, null, objective) : /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "No objectives"),
@@ -5248,8 +5387,12 @@ function StageHealthBar({
5248
5387
  toolCount,
5249
5388
  agentCount,
5250
5389
  skillCount,
5251
- width
5390
+ width,
5391
+ activityHistory,
5392
+ tick: _tick,
5393
+ now: _now
5252
5394
  }) {
5395
+ const hasActivity = activityHistory && activityHistory.length > 0;
5253
5396
  return /* @__PURE__ */ React6.createElement(
5254
5397
  Box6,
5255
5398
  {
@@ -5258,7 +5401,10 @@ function StageHealthBar({
5258
5401
  width,
5259
5402
  flexDirection: "column"
5260
5403
  },
5261
- /* @__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))))
5404
+ /* @__PURE__ */ React6.createElement(Box6, null, hasActivity && /* @__PURE__ */ React6.createElement(Box6, { gap: 1, marginRight: 1 }, /* @__PURE__ */ React6.createElement(Text6, { color: "cyan" }, renderSparkline(
5405
+ activityHistory.map((s) => s.toolCalls),
5406
+ 10
5407
+ )), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, computeThroughput(activityHistory))), /* @__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))))
5262
5408
  );
5263
5409
  }
5264
5410
 
@@ -5556,6 +5702,8 @@ function DashboardApp({
5556
5702
  workspace: workspaceProp
5557
5703
  }) {
5558
5704
  const { columns, rows, layoutMode } = useTerminalSize();
5705
+ const tick = useTick(1e3);
5706
+ const now = useMemo3(() => Date.now(), [tick]);
5559
5707
  const internalState = useDashboardState(externalState ? void 0 : eventBus);
5560
5708
  const state = externalState ?? internalState;
5561
5709
  const focusedAgent = state.focusedAgentId ? state.agents.get(state.focusedAgentId) ?? null : null;
@@ -5572,7 +5720,9 @@ function DashboardApp({
5572
5720
  currentMode: state.currentMode,
5573
5721
  globalState: state.globalState,
5574
5722
  layoutMode,
5575
- width: grid.header.width
5723
+ width: grid.header.width,
5724
+ tick,
5725
+ now
5576
5726
  }
5577
5727
  ), layoutMode === "narrow" ? /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column" }, grid.checklistPanel.height > 0 && /* @__PURE__ */ React8.createElement(
5578
5728
  ChecklistPanel,
@@ -5593,7 +5743,9 @@ function DashboardApp({
5593
5743
  contextDecisions: state.contextDecisions,
5594
5744
  contextNotes: state.contextNotes,
5595
5745
  width: grid.focusedAgent.width,
5596
- height: grid.focusedAgent.height
5746
+ height: grid.focusedAgent.height,
5747
+ tick,
5748
+ now
5597
5749
  }
5598
5750
  ), /* @__PURE__ */ React8.createElement(
5599
5751
  FlowMap,
@@ -5603,7 +5755,9 @@ function DashboardApp({
5603
5755
  layoutMode,
5604
5756
  width: grid.flowMap.width,
5605
5757
  height: grid.flowMap.height,
5606
- activeStage: state.currentMode
5758
+ activeStage: state.currentMode,
5759
+ tick,
5760
+ now
5607
5761
  }
5608
5762
  )) : /* @__PURE__ */ React8.createElement(
5609
5763
  Box8,
@@ -5620,7 +5774,9 @@ function DashboardApp({
5620
5774
  layoutMode,
5621
5775
  width: grid.flowMap.width,
5622
5776
  height: grid.flowMap.height,
5623
- activeStage: state.currentMode
5777
+ activeStage: state.currentMode,
5778
+ tick,
5779
+ now
5624
5780
  }
5625
5781
  ), /* @__PURE__ */ React8.createElement(
5626
5782
  ActivityVisualizer,
@@ -5654,7 +5810,9 @@ function DashboardApp({
5654
5810
  contextDecisions: state.contextDecisions,
5655
5811
  contextNotes: state.contextNotes,
5656
5812
  width: grid.focusedAgent.width,
5657
- height: grid.focusedAgent.height
5813
+ height: grid.focusedAgent.height,
5814
+ tick,
5815
+ now
5658
5816
  }
5659
5817
  ))
5660
5818
  ), /* @__PURE__ */ React8.createElement(
@@ -5665,7 +5823,10 @@ function DashboardApp({
5665
5823
  toolCount: state.toolInvokeCount,
5666
5824
  agentCount: state.agentActivateCount,
5667
5825
  skillCount: state.skillInvokeCount,
5668
- width: grid.stageHealth.width
5826
+ width: grid.stageHealth.width,
5827
+ activityHistory: state.activityHistory,
5828
+ tick,
5829
+ now
5669
5830
  }
5670
5831
  ));
5671
5832
  }
@@ -5676,7 +5837,7 @@ import { Box as Box10, useInput } from "ink";
5676
5837
  import * as path8 from "path";
5677
5838
 
5678
5839
  // src/tui/hooks/use-multi-session-state.ts
5679
- import { useState as useState2, useCallback, useEffect as useEffect3, useRef } from "react";
5840
+ import { useState as useState3, useCallback, useEffect as useEffect4, useRef } from "react";
5680
5841
  function toSessionState(managed) {
5681
5842
  return {
5682
5843
  pid: managed.instance.pid,
@@ -5696,10 +5857,10 @@ function getPidArray(sessions) {
5696
5857
  return [...sessions.keys()];
5697
5858
  }
5698
5859
  function useMultiSessionState(manager) {
5699
- const [sessions, setSessions] = useState2(
5860
+ const [sessions, setSessions] = useState3(
5700
5861
  () => buildInitialSessions(manager)
5701
5862
  );
5702
- const [activeSessionPid, setActiveSessionPid] = useState2(() => {
5863
+ const [activeSessionPid, setActiveSessionPid] = useState3(() => {
5703
5864
  const initial = manager.getSessions();
5704
5865
  return initial.length > 0 ? initial[0].instance.pid : null;
5705
5866
  });
@@ -5774,7 +5935,7 @@ function useMultiSessionState(manager) {
5774
5935
  eventCleanupRef.current.delete(pid);
5775
5936
  }
5776
5937
  }, []);
5777
- useEffect3(() => {
5938
+ useEffect4(() => {
5778
5939
  for (const managed of manager.getSessions()) {
5779
5940
  subscribeSession(managed.instance.pid, managed.eventBus);
5780
5941
  }
@@ -5978,28 +6139,6 @@ function createDefaultAgentState(params) {
5978
6139
  return state;
5979
6140
  }
5980
6141
 
5981
- // src/tui/hooks/use-clock.ts
5982
- import { useState as useState3, useEffect as useEffect4 } from "react";
5983
-
5984
- // src/tui/components/header-bar.pure.ts
5985
- function formatTime(date) {
5986
- const hours = String(date.getHours()).padStart(2, "0");
5987
- const minutes = String(date.getMinutes()).padStart(2, "0");
5988
- return `${hours}:${minutes}`;
5989
- }
5990
-
5991
- // src/tui/hooks/use-clock.ts
5992
- function useClock() {
5993
- const [time, setTime] = useState3(() => formatTime(/* @__PURE__ */ new Date()));
5994
- useEffect4(() => {
5995
- const interval = setInterval(() => {
5996
- setTime(formatTime(/* @__PURE__ */ new Date()));
5997
- }, 1e3);
5998
- return () => clearInterval(interval);
5999
- }, []);
6000
- return time;
6001
- }
6002
-
6003
6142
  // src/tui/index.tsx
6004
6143
  function startTui(options) {
6005
6144
  const renderOptions = options.stdout ? { stdout: options.stdout } : {};
@@ -6020,9 +6159,9 @@ export {
6020
6159
  getLayoutMode,
6021
6160
  renderMultiSession,
6022
6161
  startTui,
6023
- useClock,
6024
6162
  useDashboardState,
6025
- useTerminalSize
6163
+ useTerminalSize,
6164
+ useTick
6026
6165
  };
6027
6166
  /*! Bundled license information:
6028
6167