pi-crew 0.9.12 → 0.9.14

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.
@@ -114,6 +114,12 @@ export const TeamToolParams = Type.Object({
114
114
  goal: Type.Optional(
115
115
  Type.String({ description: "High-level objective for a team run." }),
116
116
  ),
117
+ chain: Type.Optional(
118
+ Type.String({
119
+ description:
120
+ 'Chain expression: "step1 -> step2 -> step3". Runs each step as a sequential team run, passing handoff context forward via the goal text. Supports inline goals ("...") and @team references. e.g. chain=\'"Research X" -> "Analyze" -> "Write report"\'.',
121
+ }),
122
+ ),
117
123
  task: Type.Optional(
118
124
  Type.String({
119
125
  description: "Concrete task text for direct role/agent execution.",
@@ -370,6 +376,9 @@ export interface TeamToolParamsValue {
370
376
  role?: string;
371
377
  agent?: string;
372
378
  goal?: string;
379
+ /** Chain expression: "step1 -> step2 -> step3". Runs each step as a sequential
380
+ * team run with handoff context passed forward via the goal. */
381
+ chain?: string;
373
382
  task?: string;
374
383
  singleAgent?: boolean;
375
384
  runId?: string;
@@ -201,6 +201,9 @@ export interface TeamRunManifest {
201
201
  args?: unknown;
202
202
  summary?: string;
203
203
  policyDecisions?: PolicyDecision[];
204
+ /** #2 (assessment): goal-achievement verdict — kills the silent false-green. */
205
+ goalAchieved?: boolean | "unknown";
206
+ goalAchievementNote?: string;
204
207
  }
205
208
 
206
209
  export interface UsageState {
@@ -42,7 +42,7 @@ export function getRenderWidth(width?: number): number {
42
42
  if (Number.isFinite(stdoutCols) && stdoutCols! > 0) return Math.floor(stdoutCols!);
43
43
  return DEFAULT_WIDGET_WIDTH;
44
44
  }
45
- export { notificationBadge } from "./widget-formatters.ts";
45
+ export { notificationBadge, NOTIFICATION_BADGE_CAP } from "./widget-formatters.ts";
46
46
 
47
47
  // ── Constants ─────────────────────────────────────────────────────────
48
48
 
@@ -144,9 +144,22 @@ export function agentStats(agent: CrewAgentRecord, liveHandle?: LiveAgentHandle)
144
144
 
145
145
  // ── Notification badge ────────────────────────────────────────────────
146
146
 
147
+ // Bug 021: the bell glyph 🔔 was misread as "queued messages" — users saw
148
+ // `🔔227` and concluded there were 227 pending items, when the value is a
149
+ // CUMULATIVE warning/error/critical count with zero actual queue behind it.
150
+ // Fix: relabel to an explicit "alerts" segment (no bell) and cap the display
151
+ // at 99+ (standard badge practice). The cumulative count stays accurate
152
+ // internally (widgetState.notificationCount) and remains fully logged in
153
+ // .crew/state/notifications/YYYY-MM-DD.jsonl — this bounds presentation only.
154
+ // Deeper fixes (decay window, owner-scope, auto-reset on all-runs-terminal,
155
+ // full deprecation) are product decisions documented in
156
+ // docs/bugs/bug-021-notification-badge-counter-misleading.md.
157
+ export const NOTIFICATION_BADGE_CAP = 99;
158
+
147
159
  export function notificationBadge(count: number | undefined, env: NodeJS.ProcessEnv = process.env): string {
148
160
  if (!count || count <= 0) return "";
149
161
  const term = `${env.TERM ?? ""} ${env.WT_SESSION ?? ""} ${env.TERM_PROGRAM ?? ""}`.toLowerCase();
150
162
  const supportsEmoji = !term.includes("dumb") && env.NO_COLOR !== "1";
151
- return supportsEmoji ? ` 🔔${count}` : ` [!${count}]`;
163
+ const label = count > NOTIFICATION_BADGE_CAP ? `${NOTIFICATION_BADGE_CAP}+ alerts` : `${count} alerts`;
164
+ return supportsEmoji ? ` · ${label}` : ` [${label}]`;
152
165
  }
@@ -15,6 +15,7 @@ import { computeLiveDurationMs } from "../live-duration.ts";
15
15
  import { getTaskUsage } from "../../runtime/usage-tracker.ts";
16
16
  import { agentActivity, agentStats, elapsed, formatTokensCompact, notificationBadge } from "./widget-formatters.ts";
17
17
  import { activeWidgetRuns, shortRunLabel } from "./widget-model.ts";
18
+ import { isFinishedRunStatus } from "../../runtime/process-status.ts";
18
19
  import type { WidgetRun } from "./widget-types.ts";
19
20
 
20
21
  const MAX_AGENTS_DISPLAY = 3;
@@ -69,6 +70,7 @@ export function buildWidgetLines(cwd: string, frame = 0, maxLines = 8, providedR
69
70
  });
70
71
  const completed = agents.filter((a) => a.status === "completed").length;
71
72
  const runGlyph = iconForStatus(run.status, { runningGlyph });
73
+ const isTerminal = isFinishedRunStatus(run.status);
72
74
  // Run progress line. v1–v3 flickered on snapshot.tasks state, v4 was
73
75
  // too minimal (`0/1 agents` only), v5 duplicated the worker activity
74
76
  // line (tools/tokens/duration already shown one row below). v6 (this)
@@ -81,10 +83,19 @@ export function buildWidgetLines(cwd: string, frame = 0, maxLines = 8, providedR
81
83
  // for a healthy run), and `run.createdAt` is immutable. The format
82
84
  // shape `"X/Y agents · Ns"` is therefore truly invariant: same number
83
85
  // of `·`-separated fields, same field meanings, every render tick.
86
+ //
87
+ // Bug 022 (timer-fix + label): for TERMINAL runs (failed/cancelled/
88
+ // completed) the elapsed counter previously kept ticking up forever
89
+ // from createdAt (a failed run showed `2028s` and climbing, read as
90
+ // "still running"). Now it FREEZES at updatedAt (when the run
91
+ // reached its terminal status). The status label is also surfaced
92
+ // explicitly so the row cannot be misread as an active run.
84
93
  const agentCountText = `${completed}/${agents.length} agents`;
85
- const runElapsedMs = Math.max(0, Date.now() - new Date(run.createdAt).getTime());
94
+ const runEndMs = isTerminal ? new Date(run.updatedAt).getTime() : Date.now();
95
+ const runElapsedMs = Math.max(0, Number.isFinite(runEndMs) ? runEndMs - new Date(run.createdAt).getTime() : 0);
86
96
  const runElapsedText = `${Math.floor(runElapsedMs / 1000)}s`;
87
- const progressPart = `${agentCountText} · ${runElapsedText}`;
97
+ const statusLabel = isTerminal ? ` · ${run.status}` : "";
98
+ const progressPart = `${agentCountText} · ${runElapsedText}${statusLabel}`;
88
99
  lines.push(truncate(`├─ ${runGlyph} ${shortRunLabel(run)} · ${progressPart} · ${run.runId.slice(-8)}`, width));
89
100
 
90
101
  const liveForRun = listLiveAgents().filter((a) => a.runId === run.runId);
@@ -19,6 +19,24 @@ export const PEM_PRIVATE_KEY_PATTERN = /-----BEGIN [A-Z ]+PRIVATE KEY-----[\s\S]
19
19
  // Full mitigation ladder: (1) redaction here + at artifact-write; (2) Phase 1.5
20
20
  // sanitized-env verification; (3) sandbox (deferred).
21
21
 
22
+ // Exclusion list: LLM usage-count key names that contain "token" but are
23
+ // observable metrics, NOT credentials. These must NEVER be redacted so that
24
+ // token-usage observability in events.jsonl is preserved. The isSecretKey()
25
+ // keyword-scan matches '_token' in 'prompt_tokens' (underscore + "token"),
26
+ // falsely classifying usage counts as secrets. See performance/quality
27
+ // assessment fix #5.
28
+ const TOKEN_COUNT_KEYS = new Set([
29
+ "prompt_tokens",
30
+ "completion_tokens",
31
+ "total_tokens",
32
+ "cached_tokens",
33
+ "reasoning_tokens",
34
+ "cached_read_tokens",
35
+ "cached_write_tokens",
36
+ "input_tokens",
37
+ "output_tokens",
38
+ ]);
39
+
22
40
  // JWT — three base64url segments separated by dots, distinctive "eyJ" headers.
23
41
  // Linear: single + on [A-Za-z0-9_-] per segment, no nesting.
24
42
  export const JWT_PATTERN = /(?<![A-Za-z0-9_-])eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g;
@@ -43,6 +61,8 @@ export const STRIPE_KEY_PATTERN = /(?<![A-Za-z0-9_])sk_live_[0-9a-zA-Z]{24}(?![0
43
61
  // a more complex regex, catastrophic backtracking (ReDoS) could result.
44
62
  // Any modifications must preserve O(n) complexity where n = keyName.length.
45
63
  export function isSecretKey(keyName: string): boolean {
64
+ // Fast path: known token-count keys are never secrets.
65
+ if (TOKEN_COUNT_KEYS.has(keyName.toLowerCase())) return false;
46
66
  // Fast path: common secret key names (safe anchored regex, no backtracking)
47
67
  const lower = keyName.toLowerCase();
48
68
  if (/^(token|apikey|api_key|password|secret|credential|authorization|privatekey|private_key)$/.test(lower)) {