pi-crew 0.2.12 → 0.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-crew",
3
- "version": "0.2.12",
3
+ "version": "0.2.13",
4
4
  "description": "Pi extension for coordinated AI teams, workflows, worktrees, and async task orchestration",
5
5
  "author": "baphuongna",
6
6
  "license": "MIT",
@@ -204,7 +204,7 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
204
204
  pi.registerCommand("team-run", {
205
205
  description: "Manually start a pi-crew run (agent may also use the team tool autonomously)",
206
206
  handler: async (args: string, ctx: ExtensionCommandContext) => {
207
- const result = await handleTeamTool(parseRunArgs(args), { ...teamCommandContext(ctx), metricRegistry: deps.getMetricRegistry?.(), startForegroundRun: (runner, runId) => deps.startForegroundRun(ctx as ExtensionContext, runner, runId), abortForegroundRun: deps.abortForegroundRun, onRunStarted: (runId) => deps.openLiveSidebar(ctx as ExtensionContext, runId) });
207
+ const result = await handleTeamTool(parseRunArgs(args), { ...teamCommandContext(ctx), metricRegistry: deps.getMetricRegistry?.(), startForegroundRun: (runner, runId) => deps.startForegroundRun(ctx as ExtensionContext, runner, runId), abortForegroundRun: deps.abortForegroundRun, onRunStarted: undefined });
208
208
  await notifyCommandResult(ctx, commandText(result));
209
209
  },
210
210
  });
@@ -152,7 +152,13 @@ function agentStats(agent: CrewAgentRecord, liveHandle?: LiveAgentHandle): strin
152
152
  const ctxPct = stats?.contextUsage?.percent;
153
153
  if (ctxPct != null) parts.push(`${Math.round(ctxPct)}% ctx`);
154
154
  } catch { /* ignore */ }
155
- const ms = (act.completedAtMs ?? Date.now()) - act.startedAtMs;
155
+ const completedMs = act.completedAtMs || 0;
156
+ const startedMs = act.startedAtMs || 0;
157
+ // Validate: startedAtMs should be within reasonable bounds (not seconds, not far future)
158
+ const nowMs = Date.now();
159
+ const isValidStarted = startedMs > 0 && startedMs < nowMs + 60000 && startedMs > nowMs - 3155692600000;
160
+ const isValidCompleted = completedMs === 0 || (completedMs > 0 && completedMs < nowMs + 60000);
161
+ const ms = (isValidCompleted ? completedMs : nowMs) - (isValidStarted ? startedMs : nowMs);
156
162
  parts.push(`${(ms / 1000).toFixed(1)}s`);
157
163
  } else {
158
164
  if (agent.toolUses) parts.push(`${agent.toolUses} tools`);
@@ -423,7 +429,13 @@ export function updateCrewWidget(
423
429
  if (!ctx.hasUI) return;
424
430
  state.frame += 1;
425
431
  const maxLines = config?.widgetMaxLines ?? MAX_LINES_DEFAULT;
426
- const workspaceId = ctx.sessionManager?.getSessionId?.();
432
+ // Get workspaceId from sessionManager, fallback to ownerSessionId from active runs
433
+ let workspaceId = ctx.sessionManager?.getSessionId?.();
434
+ if (!workspaceId && manifestCache) {
435
+ const runs = manifestCache.list(20);
436
+ const active = runs.find((r) => r.status === "running" || r.status === "queued");
437
+ if (active?.ownerSessionId) workspaceId = active.ownerSessionId;
438
+ }
427
439
  const runs = activeWidgetRuns(ctx.cwd, manifestCache, snapshotCache, preloadedManifests, workspaceId);
428
440
  const lines = buildCrewWidgetLines(ctx.cwd, state.frame, maxLines, runs, state.notificationCount ?? 0);
429
441
  const placement = config?.widgetPlacement ?? DEFAULT_UI.widgetPlacement;
@@ -63,9 +63,17 @@ export class LiveConversationOverlay {
63
63
 
64
64
  private static readonly SUMMARY_PREFIX = "\u200B"; // zero-width space as summary sentinel
65
65
 
66
+ private safeElapsedMs(act: typeof this.handle.activity): number {
67
+ const completedMs = act.completedAtMs || 0;
68
+ const startedMs = act.startedAtMs || 0;
69
+ const nowMs = Date.now();
70
+ const isValidStarted = startedMs > 0 && startedMs < nowMs + 60000 && startedMs > nowMs - 3155692600000;
71
+ const isValidCompleted = completedMs === 0 || (completedMs > 0 && completedMs < nowMs + 60000);
72
+ return (isValidCompleted ? completedMs : nowMs) - (isValidStarted ? startedMs : nowMs);
73
+ }
66
74
  private refreshSummary(): void {
67
75
  const act = this.handle.activity;
68
- const summary = `${LiveConversationOverlay.SUMMARY_PREFIX}[${act.turnCount} turns · ${act.toolUses} tools · ${((act.completedAtMs ?? Date.now()) - act.startedAtMs) / 1000}s]`;
76
+ const summary = `${LiveConversationOverlay.SUMMARY_PREFIX}[${act.turnCount} turns · ${act.toolUses} tools · ${(this.safeElapsedMs(act) / 1000).toFixed(1)}s]`;
69
77
  const lastLine = this.cachedLines[this.cachedLines.length - 1];
70
78
  if (lastLine?.startsWith(LiveConversationOverlay.SUMMARY_PREFIX)) {
71
79
  this.cachedLines[this.cachedLines.length - 1] = summary;
@@ -100,7 +108,7 @@ export class LiveConversationOverlay {
100
108
  : iconForStatus(this.handle.status);
101
109
  const name = this.handle.agent ?? this.handle.taskId;
102
110
  const act = this.handle.activity;
103
- const elapsed = `${((act.completedAtMs ?? Date.now()) - act.startedAtMs) / 1000}s`;
111
+ const elapsed = `${(this.safeElapsedMs(act) / 1000).toFixed(1)}s`;
104
112
  const headerParts: string[] = [];
105
113
  if (act.maxTurns != null) headerParts.push(`turn ${act.turnCount}/${act.maxTurns}`);
106
114
  else if (act.turnCount > 0) headerParts.push(`turn ${act.turnCount}`);
@@ -65,6 +65,8 @@ export class LiveRunSidebar {
65
65
  private cachedLines: string[] = [];
66
66
  private cachedWidth = 0;
67
67
  private cachedSignature = "";
68
+ private autoCloseTimeout?: NodeJS.Timeout;
69
+ private hasAutoClosed = false;
68
70
 
69
71
  constructor(input: { cwd: string; runId: string; done: Done; theme?: unknown; config?: CrewUiConfig; snapshotCache?: RunSnapshotCache }) {
70
72
  this.cwd = input.cwd;
@@ -167,6 +169,24 @@ export class LiveRunSidebar {
167
169
  lines.push(border("├", "─", "┤", w));
168
170
  for (const entry of formatTaskGraphLines(tasks).slice(0, 6)) lines.push(line(entry, w));
169
171
  lines.push(line("q close · /team-dashboard details", w), border("╰", "─", "╯", w));
172
+ // Auto-close logic: if run is terminal and no active agents, close after delay
173
+ const isTerminal = ["completed", "failed", "cancelled", "blocked"].includes(run.status);
174
+ const hasActiveAgents = agents.some((a) => a.status === "running");
175
+ if (isTerminal && !hasActiveAgents && !this.hasAutoClosed) {
176
+ const autoCloseMs = (this.config?.autoCloseDashboardMs ?? 3000);
177
+ if (autoCloseMs > 0) {
178
+ this.autoCloseTimeout = setTimeout(() => {
179
+ this.hasAutoClosed = true;
180
+ this.done(undefined);
181
+ }, autoCloseMs);
182
+ lines.push(line(`auto-close in ${Math.round(autoCloseMs / 1000)}s…`, w));
183
+ }
184
+ }
185
+ // Clear timeout if conditions change
186
+ else if (this.autoCloseTimeout) {
187
+ clearTimeout(this.autoCloseTimeout);
188
+ this.autoCloseTimeout = undefined;
189
+ }
170
190
  this.cachedLines = renderLines(lines.map((entry) => this.colorLine(entry)), w);
171
191
  this.cachedSignature = signature;
172
192
  this.cachedWidth = w;