pi-crew 0.1.46 → 0.1.49

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 (253) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/agents/analyst.md +11 -11
  3. package/agents/critic.md +11 -11
  4. package/agents/executor.md +11 -11
  5. package/agents/explorer.md +11 -11
  6. package/agents/planner.md +11 -11
  7. package/agents/reviewer.md +11 -11
  8. package/agents/security-reviewer.md +11 -11
  9. package/agents/test-engineer.md +11 -11
  10. package/agents/verifier.md +11 -11
  11. package/agents/writer.md +11 -11
  12. package/docs/next-upgrade-roadmap.md +117 -42
  13. package/docs/refactor-tasks-phase3.md +394 -394
  14. package/docs/refactor-tasks-phase4.md +564 -564
  15. package/docs/refactor-tasks-phase5.md +402 -402
  16. package/docs/refactor-tasks-phase6.md +662 -662
  17. package/docs/research/AGENT-EXECUTION-ARCHITECTURE.md +261 -0
  18. package/docs/research/AGENT-LIFECYCLE-COMPARISON.md +111 -0
  19. package/docs/research/AUDIT_OH_MY_PI.md +261 -0
  20. package/docs/research/AUDIT_PI_CREW.md +457 -0
  21. package/docs/research/CAVEMAN-DEEP-RESEARCH.md +281 -0
  22. package/docs/research/COMPARISON_OH_MY_PI_VS_PI_CREW.md +264 -0
  23. package/docs/research/DEEP-RESEARCH-PI-POWERBAR.md +343 -0
  24. package/docs/research/DEEP_RESEARCH_SUBAGENT_ARCHITECTURE.md +480 -0
  25. package/docs/research/GAP_CLOSURE_IMPLEMENTATION_PLAN.md +354 -0
  26. package/docs/research/IMPLEMENTATION_PLAN.md +385 -0
  27. package/docs/research/LIVE-SESSION-PRODUCTION-READY-PLAN.md +502 -0
  28. package/docs/research/OH-MY-PI-DEEP-RESEARCH-v14.7.6.md +266 -0
  29. package/docs/research/REMAINING-GAPS-PLAN.md +363 -0
  30. package/docs/research/SESSION-SUMMARY-2026-05-08.md +146 -0
  31. package/docs/research/UI-RESPONSIVENESS-AUDIT.md +173 -0
  32. package/docs/research-awesome-agent-skills-distillation.md +100 -100
  33. package/docs/research-extension-examples.md +297 -297
  34. package/docs/research-extension-system.md +324 -324
  35. package/docs/research-oh-my-pi-distillation.md +56 -9
  36. package/docs/research-optimization-plan.md +548 -548
  37. package/docs/research-phase10-distillation.md +198 -198
  38. package/docs/research-phase11-distillation.md +201 -201
  39. package/docs/research-pi-coding-agent.md +357 -357
  40. package/docs/research-source-pi-crew-reference.md +174 -174
  41. package/docs/runtime-flow.md +148 -148
  42. package/docs/source-runtime-refactor-map.md +107 -107
  43. package/index.ts +6 -6
  44. package/package.json +99 -98
  45. package/schema.json +8 -0
  46. package/skills/async-worker-recovery/SKILL.md +42 -42
  47. package/skills/context-artifact-hygiene/SKILL.md +52 -52
  48. package/skills/delegation-patterns/SKILL.md +54 -54
  49. package/skills/mailbox-interactive/SKILL.md +40 -40
  50. package/skills/model-routing-context/SKILL.md +39 -39
  51. package/skills/multi-perspective-review/SKILL.md +58 -58
  52. package/skills/observability-reliability/SKILL.md +41 -41
  53. package/skills/orchestration/SKILL.md +157 -0
  54. package/skills/ownership-session-security/SKILL.md +41 -41
  55. package/skills/pi-extension-lifecycle/SKILL.md +39 -39
  56. package/skills/requirements-to-task-packet/SKILL.md +63 -63
  57. package/skills/resource-discovery-config/SKILL.md +41 -41
  58. package/skills/runtime-state-reader/SKILL.md +44 -44
  59. package/skills/secure-agent-orchestration-review/SKILL.md +45 -45
  60. package/skills/state-mutation-locking/SKILL.md +42 -42
  61. package/skills/systematic-debugging/SKILL.md +67 -67
  62. package/skills/ui-render-performance/SKILL.md +39 -39
  63. package/skills/verification-before-done/SKILL.md +57 -57
  64. package/skills/worktree-isolation/SKILL.md +39 -39
  65. package/src/agents/agent-config.ts +6 -0
  66. package/src/agents/agent-search.ts +98 -0
  67. package/src/agents/agent-serializer.ts +4 -0
  68. package/src/agents/discover-agents.ts +17 -4
  69. package/src/config/config.ts +24 -0
  70. package/src/config/defaults.ts +11 -0
  71. package/src/extension/autonomous-policy.ts +26 -33
  72. package/src/extension/cross-extension-rpc.ts +82 -82
  73. package/src/extension/help.ts +1 -0
  74. package/src/extension/management.ts +5 -0
  75. package/src/extension/register.ts +58 -13
  76. package/src/extension/registration/commands.ts +33 -1
  77. package/src/extension/registration/compaction-guard.ts +125 -125
  78. package/src/extension/registration/team-tool.ts +6 -4
  79. package/src/extension/run-bundle-schema.ts +89 -89
  80. package/src/extension/run-index.ts +24 -18
  81. package/src/extension/run-maintenance.ts +68 -62
  82. package/src/extension/team-tool/api.ts +23 -2
  83. package/src/extension/team-tool/cancel.ts +86 -11
  84. package/src/extension/team-tool/context.ts +3 -0
  85. package/src/extension/team-tool/handle-settings.ts +188 -188
  86. package/src/extension/team-tool/inspect.ts +41 -41
  87. package/src/extension/team-tool/intent-policy.ts +42 -0
  88. package/src/extension/team-tool/lifecycle-actions.ts +47 -18
  89. package/src/extension/team-tool/parallel-dispatch.ts +156 -0
  90. package/src/extension/team-tool/plan.ts +19 -19
  91. package/src/extension/team-tool/respond.ts +10 -2
  92. package/src/extension/team-tool/run.ts +3 -2
  93. package/src/extension/team-tool/status.ts +1 -1
  94. package/src/extension/team-tool-types.ts +1 -0
  95. package/src/extension/team-tool.ts +13 -3
  96. package/src/hooks/registry.ts +61 -0
  97. package/src/hooks/types.ts +41 -0
  98. package/src/i18n.ts +184 -184
  99. package/src/observability/exporters/otlp-exporter.ts +77 -77
  100. package/src/prompt/prompt-runtime.ts +72 -72
  101. package/src/runtime/agent-control.ts +108 -2
  102. package/src/runtime/agent-memory.ts +72 -72
  103. package/src/runtime/agent-observability.ts +114 -114
  104. package/src/runtime/async-marker.ts +26 -26
  105. package/src/runtime/async-runner.ts +3 -1
  106. package/src/runtime/attention-events.ts +28 -28
  107. package/src/runtime/background-runner.ts +19 -0
  108. package/src/runtime/cancellation-token.ts +89 -0
  109. package/src/runtime/cancellation.ts +61 -51
  110. package/src/runtime/capability-inventory.ts +116 -0
  111. package/src/runtime/child-pi.ts +2 -1
  112. package/src/runtime/code-summary.ts +247 -0
  113. package/src/runtime/completion-guard.ts +190 -190
  114. package/src/runtime/crash-recovery.ts +181 -0
  115. package/src/runtime/crew-agent-records.ts +35 -7
  116. package/src/runtime/crew-agent-runtime.ts +1 -0
  117. package/src/runtime/custom-tools/irc-tool.ts +201 -0
  118. package/src/runtime/custom-tools/submit-result-tool.ts +90 -0
  119. package/src/runtime/delivery-coordinator.ts +3 -1
  120. package/src/runtime/direct-run.ts +35 -35
  121. package/src/runtime/effectiveness.ts +81 -76
  122. package/src/runtime/event-stream-bridge.ts +90 -0
  123. package/src/runtime/foreground-control.ts +82 -82
  124. package/src/runtime/green-contract.ts +46 -46
  125. package/src/runtime/group-join.ts +106 -106
  126. package/src/runtime/heartbeat-gradient.ts +28 -28
  127. package/src/runtime/heartbeat-watcher.ts +124 -124
  128. package/src/runtime/live-agent-control.ts +88 -88
  129. package/src/runtime/live-agent-manager.ts +78 -2
  130. package/src/runtime/live-control-realtime.ts +36 -36
  131. package/src/runtime/live-extension-bridge.ts +150 -0
  132. package/src/runtime/live-irc.ts +92 -0
  133. package/src/runtime/live-session-health.ts +100 -0
  134. package/src/runtime/live-session-runtime.ts +297 -7
  135. package/src/runtime/mcp-proxy.ts +113 -0
  136. package/src/runtime/notebook-helpers.ts +90 -0
  137. package/src/runtime/orphan-sentinel.ts +7 -0
  138. package/src/runtime/output-validator.ts +187 -0
  139. package/src/runtime/parallel-research.ts +44 -44
  140. package/src/runtime/parallel-utils.ts +57 -0
  141. package/src/runtime/parent-guard.ts +80 -0
  142. package/src/runtime/pi-json-output.ts +111 -111
  143. package/src/runtime/policy-engine.ts +79 -79
  144. package/src/runtime/progress-event-coalescer.ts +43 -43
  145. package/src/runtime/prose-compressor.ts +164 -0
  146. package/src/runtime/recovery-recipes.ts +74 -74
  147. package/src/runtime/result-extractor.ts +121 -0
  148. package/src/runtime/role-permission.ts +39 -39
  149. package/src/runtime/runtime-resolver.ts +1 -4
  150. package/src/runtime/semaphore.ts +131 -0
  151. package/src/runtime/sensitive-paths.ts +92 -0
  152. package/src/runtime/session-resources.ts +25 -25
  153. package/src/runtime/session-snapshot.ts +59 -59
  154. package/src/runtime/session-usage.ts +79 -79
  155. package/src/runtime/sidechain-output.ts +29 -29
  156. package/src/runtime/stream-preview.ts +177 -0
  157. package/src/runtime/subagent-manager.ts +3 -2
  158. package/src/runtime/subprocess-tool-registry.ts +67 -0
  159. package/src/runtime/supervisor-contact.ts +59 -59
  160. package/src/runtime/task-display.ts +38 -38
  161. package/src/runtime/task-output-context.ts +59 -9
  162. package/src/runtime/task-runner/capabilities.ts +78 -78
  163. package/src/runtime/task-runner/live-executor.ts +2 -0
  164. package/src/runtime/task-runner/progress.ts +119 -119
  165. package/src/runtime/task-runner/prompt-builder.ts +70 -8
  166. package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
  167. package/src/runtime/task-runner/result-utils.ts +14 -14
  168. package/src/runtime/task-runner/run-projection.ts +104 -0
  169. package/src/runtime/task-runner/state-helpers.ts +22 -22
  170. package/src/runtime/task-runner.ts +75 -4
  171. package/src/runtime/team-runner.ts +60 -8
  172. package/src/runtime/worker-heartbeat.ts +21 -21
  173. package/src/runtime/worker-startup.ts +57 -57
  174. package/src/runtime/workspace-tree.ts +298 -0
  175. package/src/runtime/yield-handler.ts +189 -0
  176. package/src/schema/config-schema.ts +6 -0
  177. package/src/schema/team-tool-schema.ts +11 -1
  178. package/src/skills/discover-skills.ts +67 -0
  179. package/src/state/active-run-registry.ts +4 -2
  180. package/src/state/artifact-store.ts +4 -1
  181. package/src/state/atomic-write.ts +50 -1
  182. package/src/state/blob-store.ts +117 -0
  183. package/src/state/contracts.ts +1 -0
  184. package/src/state/event-log-rotation.ts +158 -0
  185. package/src/state/event-log.ts +52 -2
  186. package/src/state/mailbox.ts +87 -7
  187. package/src/state/state-store.ts +24 -4
  188. package/src/state/task-claims.ts +44 -44
  189. package/src/state/types.ts +20 -0
  190. package/src/state/usage.ts +29 -29
  191. package/src/subagents/async-entry.ts +1 -1
  192. package/src/subagents/index.ts +3 -3
  193. package/src/subagents/live/control.ts +1 -1
  194. package/src/subagents/live/manager.ts +1 -1
  195. package/src/subagents/live/realtime.ts +1 -1
  196. package/src/subagents/live/session-runtime.ts +1 -1
  197. package/src/subagents/manager.ts +1 -1
  198. package/src/subagents/spawn.ts +1 -1
  199. package/src/teams/team-serializer.ts +38 -38
  200. package/src/types/diff.d.ts +18 -18
  201. package/src/ui/agent-management-overlay.ts +144 -0
  202. package/src/ui/crew-footer.ts +101 -101
  203. package/src/ui/crew-select-list.ts +111 -111
  204. package/src/ui/crew-widget.ts +11 -2
  205. package/src/ui/dashboard-panes/cancellation-pane.ts +43 -0
  206. package/src/ui/dashboard-panes/capability-pane.ts +60 -0
  207. package/src/ui/dashboard-panes/mailbox-pane.ts +35 -11
  208. package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
  209. package/src/ui/dynamic-border.ts +25 -25
  210. package/src/ui/layout-primitives.ts +106 -106
  211. package/src/ui/live-run-sidebar.ts +4 -0
  212. package/src/ui/loaders.ts +158 -158
  213. package/src/ui/powerbar-publisher.ts +77 -15
  214. package/src/ui/render-coalescer.ts +51 -0
  215. package/src/ui/render-diff.ts +119 -119
  216. package/src/ui/render-scheduler.ts +143 -143
  217. package/src/ui/run-dashboard.ts +4 -0
  218. package/src/ui/run-event-bus.ts +209 -0
  219. package/src/ui/run-snapshot-cache.ts +68 -16
  220. package/src/ui/snapshot-types.ts +8 -0
  221. package/src/ui/spinner.ts +17 -17
  222. package/src/ui/status-colors.ts +58 -58
  223. package/src/ui/syntax-highlight.ts +116 -116
  224. package/src/ui/transcript-entries.ts +258 -0
  225. package/src/utils/atomic-write.ts +33 -33
  226. package/src/utils/completion-dedupe.ts +63 -63
  227. package/src/utils/frontmatter.ts +68 -68
  228. package/src/utils/git.ts +262 -262
  229. package/src/utils/ids.ts +17 -12
  230. package/src/utils/incremental-reader.ts +104 -0
  231. package/src/utils/names.ts +27 -27
  232. package/src/utils/redaction.ts +44 -44
  233. package/src/utils/safe-paths.ts +47 -47
  234. package/src/utils/scan-cache.ts +137 -0
  235. package/src/utils/sleep.ts +32 -32
  236. package/src/utils/sse-parser.ts +134 -0
  237. package/src/utils/task-name-generator.ts +337 -0
  238. package/src/utils/visual.ts +33 -2
  239. package/src/workflows/validate-workflow.ts +40 -40
  240. package/src/worktree/branch-freshness.ts +45 -45
  241. package/src/worktree/cleanup.ts +2 -1
  242. package/teams/default.team.md +12 -12
  243. package/teams/fast-fix.team.md +11 -11
  244. package/teams/implementation.team.md +18 -18
  245. package/teams/parallel-research.team.md +14 -14
  246. package/teams/research.team.md +11 -11
  247. package/teams/review.team.md +12 -12
  248. package/workflows/default.workflow.md +29 -29
  249. package/workflows/fast-fix.workflow.md +22 -22
  250. package/workflows/implementation.workflow.md +38 -38
  251. package/workflows/parallel-research.workflow.md +46 -46
  252. package/workflows/research.workflow.md +22 -22
  253. package/workflows/review.workflow.md +30 -30
@@ -1,101 +1,101 @@
1
- import type { UsageState } from "../state/types.ts";
2
- import { pad, truncate } from "../utils/visual.ts";
3
- import type { RunStatus } from "./status-colors.ts";
4
- import type { CrewTheme } from "./theme-adapter.ts";
5
-
6
- export interface CrewFooterData {
7
- pwd: string;
8
- branch?: string;
9
- runId?: string;
10
- status?: RunStatus;
11
- usage?: UsageState;
12
- contextWindow?: number;
13
- contextPercent?: number;
14
- badges?: string[];
15
- }
16
-
17
- function formatCount(value: number | undefined): string {
18
- if (value === undefined || !Number.isFinite(value)) return "?";
19
- if (Math.abs(value) >= 1000) return `${(value / 1000).toFixed(Math.abs(value) >= 10_000 ? 0 : 1)}k`;
20
- return `${value}`;
21
- }
22
-
23
- function formatCost(value: number | undefined): string {
24
- return value === undefined || !Number.isFinite(value) ? "$0.0000" : `$${value.toFixed(4)}`;
25
- }
26
-
27
- function displayPwd(pwd: string): string {
28
- const home = process.env.HOME || process.env.USERPROFILE;
29
- if (home && pwd.startsWith(home)) return `~${pwd.slice(home.length) || "/"}`;
30
- return pwd || ".";
31
- }
32
-
33
- function contextText(data: CrewFooterData): string {
34
- const windowText = data.contextWindow && Number.isFinite(data.contextWindow) ? formatCount(data.contextWindow) : "window";
35
- const percent = data.contextPercent;
36
- if (percent === undefined || !Number.isFinite(percent)) return `?/${windowText}`;
37
- return `${percent.toFixed(1)}%/${windowText}`;
38
- }
39
-
40
- export class CrewFooter {
41
- private data: CrewFooterData;
42
- private readonly theme: CrewTheme;
43
- private cacheKey = "";
44
- private cacheWidth = 0;
45
- private cacheLines: string[] = [];
46
-
47
- constructor(data: CrewFooterData, theme: CrewTheme) {
48
- this.data = data;
49
- this.theme = theme;
50
- }
51
-
52
- setData(data: CrewFooterData): void {
53
- this.data = data;
54
- this.invalidate();
55
- }
56
-
57
- invalidate(): void {
58
- this.cacheKey = "";
59
- this.cacheLines = [];
60
- }
61
-
62
- render(width: number): string[] {
63
- const key = JSON.stringify(this.data);
64
- if (this.cacheKey === key && this.cacheWidth === width && this.cacheLines.length) return this.cacheLines;
65
- const lineWidth = Math.max(1, width);
66
- const firstParts = [
67
- displayPwd(this.data.pwd),
68
- this.data.branch ? `(${this.data.branch})` : undefined,
69
- this.data.runId,
70
- this.data.status,
71
- ].filter((part): part is string => Boolean(part));
72
- const usage = this.data.usage;
73
- const context = contextText(this.data);
74
- const contextPercent = this.data.contextPercent;
75
- const contextColor = contextPercent !== undefined && Number.isFinite(contextPercent)
76
- ? contextPercent > 90
77
- ? "error"
78
- : contextPercent > 70
79
- ? "warning"
80
- : undefined
81
- : undefined;
82
- const contextRendered = contextColor ? this.theme.fg(contextColor, context) : context;
83
- const usageLine = [
84
- `↑${formatCount(usage?.input)}`,
85
- `↓${formatCount(usage?.output)}`,
86
- `R ${formatCount(usage?.cacheRead)} cache`,
87
- `W ${formatCount(usage?.cacheWrite)} cache`,
88
- formatCost(usage?.cost),
89
- contextRendered,
90
- ].join(" • ");
91
- const badges = this.data.badges?.length ? this.data.badges.map((badge) => `[${badge}]`).join(" ") : "";
92
- this.cacheLines = [
93
- this.theme.fg("dim", pad(truncate(firstParts.join(" • "), lineWidth, "..."), lineWidth)),
94
- this.theme.fg("dim", pad(truncate(usageLine, lineWidth, "..."), lineWidth)),
95
- this.theme.fg("dim", pad(truncate(badges, lineWidth, "..."), lineWidth)),
96
- ];
97
- this.cacheKey = key;
98
- this.cacheWidth = width;
99
- return this.cacheLines;
100
- }
101
- }
1
+ import type { UsageState } from "../state/types.ts";
2
+ import { pad, truncate } from "../utils/visual.ts";
3
+ import type { RunStatus } from "./status-colors.ts";
4
+ import type { CrewTheme } from "./theme-adapter.ts";
5
+
6
+ export interface CrewFooterData {
7
+ pwd: string;
8
+ branch?: string;
9
+ runId?: string;
10
+ status?: RunStatus;
11
+ usage?: UsageState;
12
+ contextWindow?: number;
13
+ contextPercent?: number;
14
+ badges?: string[];
15
+ }
16
+
17
+ function formatCount(value: number | undefined): string {
18
+ if (value === undefined || !Number.isFinite(value)) return "?";
19
+ if (Math.abs(value) >= 1000) return `${(value / 1000).toFixed(Math.abs(value) >= 10_000 ? 0 : 1)}k`;
20
+ return `${value}`;
21
+ }
22
+
23
+ function formatCost(value: number | undefined): string {
24
+ return value === undefined || !Number.isFinite(value) ? "$0.0000" : `$${value.toFixed(4)}`;
25
+ }
26
+
27
+ function displayPwd(pwd: string): string {
28
+ const home = process.env.HOME || process.env.USERPROFILE;
29
+ if (home && pwd.startsWith(home)) return `~${pwd.slice(home.length) || "/"}`;
30
+ return pwd || ".";
31
+ }
32
+
33
+ function contextText(data: CrewFooterData): string {
34
+ const windowText = data.contextWindow && Number.isFinite(data.contextWindow) ? formatCount(data.contextWindow) : "window";
35
+ const percent = data.contextPercent;
36
+ if (percent === undefined || !Number.isFinite(percent)) return `?/${windowText}`;
37
+ return `${percent.toFixed(1)}%/${windowText}`;
38
+ }
39
+
40
+ export class CrewFooter {
41
+ private data: CrewFooterData;
42
+ private readonly theme: CrewTheme;
43
+ private cacheKey = "";
44
+ private cacheWidth = 0;
45
+ private cacheLines: string[] = [];
46
+
47
+ constructor(data: CrewFooterData, theme: CrewTheme) {
48
+ this.data = data;
49
+ this.theme = theme;
50
+ }
51
+
52
+ setData(data: CrewFooterData): void {
53
+ this.data = data;
54
+ this.invalidate();
55
+ }
56
+
57
+ invalidate(): void {
58
+ this.cacheKey = "";
59
+ this.cacheLines = [];
60
+ }
61
+
62
+ render(width: number): string[] {
63
+ const key = JSON.stringify(this.data);
64
+ if (this.cacheKey === key && this.cacheWidth === width && this.cacheLines.length) return this.cacheLines;
65
+ const lineWidth = Math.max(1, width);
66
+ const firstParts = [
67
+ displayPwd(this.data.pwd),
68
+ this.data.branch ? `(${this.data.branch})` : undefined,
69
+ this.data.runId,
70
+ this.data.status,
71
+ ].filter((part): part is string => Boolean(part));
72
+ const usage = this.data.usage;
73
+ const context = contextText(this.data);
74
+ const contextPercent = this.data.contextPercent;
75
+ const contextColor = contextPercent !== undefined && Number.isFinite(contextPercent)
76
+ ? contextPercent > 90
77
+ ? "error"
78
+ : contextPercent > 70
79
+ ? "warning"
80
+ : undefined
81
+ : undefined;
82
+ const contextRendered = contextColor ? this.theme.fg(contextColor, context) : context;
83
+ const usageLine = [
84
+ `↑${formatCount(usage?.input)}`,
85
+ `↓${formatCount(usage?.output)}`,
86
+ `R ${formatCount(usage?.cacheRead)} cache`,
87
+ `W ${formatCount(usage?.cacheWrite)} cache`,
88
+ formatCost(usage?.cost),
89
+ contextRendered,
90
+ ].join(" • ");
91
+ const badges = this.data.badges?.length ? this.data.badges.map((badge) => `[${badge}]`).join(" ") : "";
92
+ this.cacheLines = [
93
+ this.theme.fg("dim", pad(truncate(firstParts.join(" • "), lineWidth, "..."), lineWidth)),
94
+ this.theme.fg("dim", pad(truncate(usageLine, lineWidth, "..."), lineWidth)),
95
+ this.theme.fg("dim", pad(truncate(badges, lineWidth, "..."), lineWidth)),
96
+ ];
97
+ this.cacheKey = key;
98
+ this.cacheWidth = width;
99
+ return this.cacheLines;
100
+ }
101
+ }
@@ -1,111 +1,111 @@
1
- import { pad, truncate } from "../utils/visual.ts";
2
- import type { CrewTheme } from "./theme-adapter.ts";
3
-
4
- export interface CrewSelectItem<T = string> {
5
- value: T;
6
- label: string;
7
- description?: string;
8
- }
9
-
10
- export interface CrewSelectListOptions<T = string> {
11
- onSelect: (item: CrewSelectItem<T>) => void;
12
- onCancel: () => void;
13
- onPreview?: (item: CrewSelectItem<T>) => void;
14
- maxHeight?: number;
15
- }
16
-
17
- export class CrewSelectList<T = string> {
18
- private readonly items: CrewSelectItem<T>[];
19
- private readonly theme: CrewTheme;
20
- private readonly options: CrewSelectListOptions<T>;
21
- private selectedIndex = 0;
22
- private scrollOffset = 0;
23
-
24
- constructor(items: CrewSelectItem<T>[], theme: CrewTheme, options: CrewSelectListOptions<T>) {
25
- this.items = [...items];
26
- this.theme = theme;
27
- this.options = options;
28
- this.selectedIndex = this.items.length ? 0 : -1;
29
- }
30
-
31
- invalidate(): void {}
32
-
33
- getSelected(): CrewSelectItem<T> | undefined {
34
- return this.selectedIndex >= 0 ? this.items[this.selectedIndex] : undefined;
35
- }
36
-
37
- setSelectedIndex(index: number): void {
38
- if (!this.items.length) {
39
- this.selectedIndex = -1;
40
- this.scrollOffset = 0;
41
- return;
42
- }
43
- const next = Math.min(this.items.length - 1, Math.max(0, index));
44
- const changed = next !== this.selectedIndex;
45
- this.selectedIndex = next;
46
- this.ensureVisible();
47
- if (changed) {
48
- const selected = this.getSelected();
49
- if (selected) this.options.onPreview?.(selected);
50
- }
51
- }
52
-
53
- handleInput(data: string): void {
54
- if (data === "q" || data === "\u001b") {
55
- this.options.onCancel();
56
- return;
57
- }
58
- if (data === "j" || data === "\u001b[B") {
59
- this.setSelectedIndex(this.selectedIndex + 1);
60
- return;
61
- }
62
- if (data === "k" || data === "\u001b[A") {
63
- this.setSelectedIndex(this.selectedIndex - 1);
64
- return;
65
- }
66
- if (data === "\r" || data === "\n") {
67
- const selected = this.getSelected();
68
- if (selected) this.options.onSelect(selected);
69
- }
70
- }
71
-
72
- render(width: number): string[] {
73
- if (!this.items.length) return [this.theme.fg("muted", "(no items)")];
74
- const maxHeight = Math.max(1, Math.floor(this.options.maxHeight ?? this.items.length));
75
- this.ensureVisible();
76
- const hasTop = this.scrollOffset > 0;
77
- const availableWithoutBottom = Math.max(1, maxHeight - (hasTop ? 1 : 0));
78
- const hasBottom = this.scrollOffset + availableWithoutBottom < this.items.length;
79
- const slots = this.visibleItemSlots(maxHeight, hasTop, hasBottom);
80
- const visibleItems = this.items.slice(this.scrollOffset, this.scrollOffset + slots);
81
- const lines: string[] = [];
82
- if (hasTop) lines.push(this.theme.fg("muted", `↑ ${this.scrollOffset} more`));
83
- for (const [offset, item] of visibleItems.entries()) {
84
- const index = this.scrollOffset + offset;
85
- const prefix = index === this.selectedIndex ? " → " : " ";
86
- const suffix = item.description ? this.theme.fg("dim", ` — ${item.description}`) : "";
87
- const raw = `${prefix}${item.label}${suffix}`;
88
- const line = index === this.selectedIndex ? this.theme.inverse?.(raw) ?? raw : raw;
89
- lines.push(pad(truncate(line, width, "..."), Math.max(1, width)));
90
- }
91
- if (hasBottom) lines.push(this.theme.fg("muted", `↓ ${this.items.length - (this.scrollOffset + slots)} more`));
92
- return lines.slice(0, maxHeight);
93
- }
94
-
95
- private visibleItemSlots(maxHeight: number, hasTop: boolean, hasBottom: boolean): number {
96
- return Math.max(1, maxHeight - (hasTop ? 1 : 0) - (hasBottom ? 1 : 0));
97
- }
98
-
99
- private ensureVisible(): void {
100
- if (this.selectedIndex < 0) return;
101
- const maxHeight = Math.max(1, Math.floor(this.options.maxHeight ?? this.items.length));
102
- const reservedTop = this.scrollOffset > 0 ? 1 : 0;
103
- const visibleSlots = Math.max(1, maxHeight - reservedTop - 1);
104
- if (this.selectedIndex < this.scrollOffset) {
105
- this.scrollOffset = this.selectedIndex;
106
- } else if (this.selectedIndex >= this.scrollOffset + visibleSlots) {
107
- this.scrollOffset = Math.max(0, this.selectedIndex - visibleSlots + 1);
108
- }
109
- this.scrollOffset = Math.min(this.scrollOffset, Math.max(0, this.items.length - 1));
110
- }
111
- }
1
+ import { pad, truncate } from "../utils/visual.ts";
2
+ import type { CrewTheme } from "./theme-adapter.ts";
3
+
4
+ export interface CrewSelectItem<T = string> {
5
+ value: T;
6
+ label: string;
7
+ description?: string;
8
+ }
9
+
10
+ export interface CrewSelectListOptions<T = string> {
11
+ onSelect: (item: CrewSelectItem<T>) => void;
12
+ onCancel: () => void;
13
+ onPreview?: (item: CrewSelectItem<T>) => void;
14
+ maxHeight?: number;
15
+ }
16
+
17
+ export class CrewSelectList<T = string> {
18
+ private readonly items: CrewSelectItem<T>[];
19
+ private readonly theme: CrewTheme;
20
+ private readonly options: CrewSelectListOptions<T>;
21
+ private selectedIndex = 0;
22
+ private scrollOffset = 0;
23
+
24
+ constructor(items: CrewSelectItem<T>[], theme: CrewTheme, options: CrewSelectListOptions<T>) {
25
+ this.items = [...items];
26
+ this.theme = theme;
27
+ this.options = options;
28
+ this.selectedIndex = this.items.length ? 0 : -1;
29
+ }
30
+
31
+ invalidate(): void {}
32
+
33
+ getSelected(): CrewSelectItem<T> | undefined {
34
+ return this.selectedIndex >= 0 ? this.items[this.selectedIndex] : undefined;
35
+ }
36
+
37
+ setSelectedIndex(index: number): void {
38
+ if (!this.items.length) {
39
+ this.selectedIndex = -1;
40
+ this.scrollOffset = 0;
41
+ return;
42
+ }
43
+ const next = Math.min(this.items.length - 1, Math.max(0, index));
44
+ const changed = next !== this.selectedIndex;
45
+ this.selectedIndex = next;
46
+ this.ensureVisible();
47
+ if (changed) {
48
+ const selected = this.getSelected();
49
+ if (selected) this.options.onPreview?.(selected);
50
+ }
51
+ }
52
+
53
+ handleInput(data: string): void {
54
+ if (data === "q" || data === "\u001b") {
55
+ this.options.onCancel();
56
+ return;
57
+ }
58
+ if (data === "j" || data === "\u001b[B") {
59
+ this.setSelectedIndex(this.selectedIndex + 1);
60
+ return;
61
+ }
62
+ if (data === "k" || data === "\u001b[A") {
63
+ this.setSelectedIndex(this.selectedIndex - 1);
64
+ return;
65
+ }
66
+ if (data === "\r" || data === "\n") {
67
+ const selected = this.getSelected();
68
+ if (selected) this.options.onSelect(selected);
69
+ }
70
+ }
71
+
72
+ render(width: number): string[] {
73
+ if (!this.items.length) return [this.theme.fg("muted", "(no items)")];
74
+ const maxHeight = Math.max(1, Math.floor(this.options.maxHeight ?? this.items.length));
75
+ this.ensureVisible();
76
+ const hasTop = this.scrollOffset > 0;
77
+ const availableWithoutBottom = Math.max(1, maxHeight - (hasTop ? 1 : 0));
78
+ const hasBottom = this.scrollOffset + availableWithoutBottom < this.items.length;
79
+ const slots = this.visibleItemSlots(maxHeight, hasTop, hasBottom);
80
+ const visibleItems = this.items.slice(this.scrollOffset, this.scrollOffset + slots);
81
+ const lines: string[] = [];
82
+ if (hasTop) lines.push(this.theme.fg("muted", `↑ ${this.scrollOffset} more`));
83
+ for (const [offset, item] of visibleItems.entries()) {
84
+ const index = this.scrollOffset + offset;
85
+ const prefix = index === this.selectedIndex ? " → " : " ";
86
+ const suffix = item.description ? this.theme.fg("dim", ` — ${item.description}`) : "";
87
+ const raw = `${prefix}${item.label}${suffix}`;
88
+ const line = index === this.selectedIndex ? this.theme.inverse?.(raw) ?? raw : raw;
89
+ lines.push(pad(truncate(line, width, "..."), Math.max(1, width)));
90
+ }
91
+ if (hasBottom) lines.push(this.theme.fg("muted", `↓ ${this.items.length - (this.scrollOffset + slots)} more`));
92
+ return lines.slice(0, maxHeight);
93
+ }
94
+
95
+ private visibleItemSlots(maxHeight: number, hasTop: boolean, hasBottom: boolean): number {
96
+ return Math.max(1, maxHeight - (hasTop ? 1 : 0) - (hasBottom ? 1 : 0));
97
+ }
98
+
99
+ private ensureVisible(): void {
100
+ if (this.selectedIndex < 0) return;
101
+ const maxHeight = Math.max(1, Math.floor(this.options.maxHeight ?? this.items.length));
102
+ const reservedTop = this.scrollOffset > 0 ? 1 : 0;
103
+ const visibleSlots = Math.max(1, maxHeight - reservedTop - 1);
104
+ if (this.selectedIndex < this.scrollOffset) {
105
+ this.scrollOffset = this.selectedIndex;
106
+ } else if (this.selectedIndex >= this.scrollOffset + visibleSlots) {
107
+ this.scrollOffset = Math.max(0, this.selectedIndex - visibleSlots + 1);
108
+ }
109
+ this.scrollOffset = Math.min(this.scrollOffset, Math.max(0, this.items.length - 1));
110
+ }
111
+ }
@@ -13,6 +13,7 @@ import { asCrewTheme, subscribeThemeChange } from "./theme-adapter.ts";
13
13
  import { Box, Text } from "./layout-primitives.ts";
14
14
  import { requestRender, setExtensionWidget } from "./pi-ui-compat.ts";
15
15
  import type { RunSnapshotCache, RunUiSnapshot } from "./snapshot-types.ts";
16
+ import { runEventBus } from "./run-event-bus.ts";
16
17
  import { DEFAULT_UI } from "../config/defaults.ts";
17
18
 
18
19
  const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
@@ -78,7 +79,11 @@ function agentActivity(agent: CrewAgentRecord): string {
78
79
  if (recent) return recent.replace(/\s+/g, " ").trim();
79
80
  if (agent.progress?.activityState === "needs_attention") return "needs attention";
80
81
  if (agent.status === "queued") return "queued";
81
- if (agent.status === "running") return "thinking…";
82
+ if (agent.status === "running") {
83
+ const age = agent.startedAt ? Date.now() - new Date(agent.startedAt).getTime() : Infinity;
84
+ if (age < 5000 && !agent.progress?.currentTool) return "spawning…";
85
+ return "thinking…";
86
+ }
82
87
  if (agent.status === "failed") return agent.error ?? "failed";
83
88
  return "done";
84
89
  }
@@ -219,6 +224,7 @@ class CrewWidgetComponent implements WidgetComponent {
219
224
  private cachedBaseLines: string[] = [];
220
225
  private cachedTheme: CrewTheme;
221
226
  private readonly unsubscribeTheme: () => void;
227
+ private readonly unsubscribeEventBus: () => void;
222
228
 
223
229
  constructor(model: CrewWidgetModel, themeLike: unknown) {
224
230
  this.model = model;
@@ -226,6 +232,7 @@ class CrewWidgetComponent implements WidgetComponent {
226
232
  this.cachedTheme = this.theme;
227
233
  this.cacheSignature = "";
228
234
  this.unsubscribeTheme = subscribeThemeChange(themeLike, () => this.invalidate());
235
+ this.unsubscribeEventBus = runEventBus.onAny(() => this.invalidate());
229
236
  }
230
237
 
231
238
  private buildSignature(runs: WidgetRun[]): string {
@@ -250,6 +257,7 @@ class CrewWidgetComponent implements WidgetComponent {
250
257
 
251
258
  dispose(): void {
252
259
  this.unsubscribeTheme();
260
+ this.unsubscribeEventBus();
253
261
  }
254
262
 
255
263
  render(width: number): string[] {
@@ -274,7 +282,8 @@ class CrewWidgetComponent implements WidgetComponent {
274
282
  // Update only spinner and command icon on header line to avoid full re-color for every frame.
275
283
  const updatedHeader = `${runningGlyph}${this.cachedBaseLines[0]?.slice(1) ?? ""}`;
276
284
  this.cachedLines[0] = truncate(colorWidgetLine(updatedHeader, 0, this.theme), width);
277
- return this.cachedLines;
285
+ // Safety: ensure all lines fit within terminal width (handles emoji/CJK width mismatch)
286
+ return this.cachedLines.map((line) => truncate(line, width));
278
287
  }
279
288
  }
280
289
 
@@ -0,0 +1,43 @@
1
+ import type { TeamRunManifest, TeamTaskState } from "../../state/types.ts";
2
+
3
+ export interface CancellationPaneOptions {
4
+ maxReasons?: number;
5
+ }
6
+
7
+ export function renderCancellationPane(manifest: TeamRunManifest, tasks: TeamTaskState[], opts: CancellationPaneOptions = {}): string[] {
8
+ const maxReasons = opts.maxReasons ?? 5;
9
+ if (manifest.status !== "cancelled" && manifest.status !== "blocked") {
10
+ const cancellingTasks = tasks.filter((t) => t.status === "cancelled");
11
+ if (cancellingTasks.length === 0) return ["Cancellation pane: no active cancellations"];
12
+ }
13
+
14
+ const lines: string[] = ["Cancellation pane"];
15
+
16
+ if (manifest.status === "cancelled") {
17
+ lines.push(` Run status: cancelled`);
18
+ } else if (manifest.status === "blocked") {
19
+ lines.push(` Run status: blocked`);
20
+ }
21
+
22
+ const cancelledTasks = tasks.filter((t) => t.status === "cancelled");
23
+ if (cancelledTasks.length > 0) {
24
+ lines.push(` Cancelled tasks (${cancelledTasks.length}):`);
25
+ for (const task of cancelledTasks.slice(0, maxReasons)) {
26
+ const reason = task.error ?? "unknown";
27
+ lines.push(` ✗ ${task.id}: ${reason}`);
28
+ }
29
+ if (cancelledTasks.length > maxReasons) {
30
+ lines.push(` ... and ${cancelledTasks.length - maxReasons} more`);
31
+ }
32
+ }
33
+
34
+ if (manifest.policyDecisions?.length) {
35
+ const decisions = manifest.policyDecisions.slice(0, maxReasons);
36
+ lines.push(` Policy decisions (${manifest.policyDecisions.length}):`);
37
+ for (const d of decisions) {
38
+ lines.push(` ${d.action}: ${d.message}`);
39
+ }
40
+ }
41
+
42
+ return lines;
43
+ }
@@ -0,0 +1,60 @@
1
+ import { buildCapabilityInventory } from "../../runtime/capability-inventory.ts";
2
+ import type { PiTeamsConfig } from "../../config/config.ts";
3
+ import type { CapabilityItem } from "../../runtime/capability-inventory.ts";
4
+
5
+ export interface CapabilityPaneOptions {
6
+ config?: PiTeamsConfig;
7
+ filter?: string;
8
+ }
9
+
10
+ function kindIcon(kind: string): string {
11
+ switch (kind) {
12
+ case "team": return "👥";
13
+ case "workflow": return "📋";
14
+ case "agent": return "🤖";
15
+ case "skill": return "🔧";
16
+ case "tool": return "🛠";
17
+ case "runtime": return "⚙";
18
+ default: return "•";
19
+ }
20
+ }
21
+
22
+ function stateLabel(state: string): string {
23
+ switch (state) {
24
+ case "active": return "";
25
+ case "disabled": return " [DISABLED]";
26
+ case "shadowed": return " [SHADOWED]";
27
+ case "missing": return " [MISSING]";
28
+ default: return "";
29
+ }
30
+ }
31
+
32
+ export function renderCapabilityPane(cwd: string, opts: CapabilityPaneOptions = {}): string[] {
33
+ const inventory = buildCapabilityInventory(cwd, opts.config);
34
+ const filtered = opts.filter
35
+ ? inventory.filter((item) => item.kind.includes(opts.filter!.toLowerCase()) || item.name.toLowerCase().includes(opts.filter!.toLowerCase()) || item.id.toLowerCase().includes(opts.filter!.toLowerCase()))
36
+ : inventory;
37
+
38
+ if (filtered.length === 0) return ["Capability pane: no items found"];
39
+
40
+ const byKind = new Map<string, CapabilityItem[]>();
41
+ for (const item of filtered) {
42
+ const group = byKind.get(item.kind) ?? [];
43
+ group.push(item);
44
+ byKind.set(item.kind, group);
45
+ }
46
+
47
+ const lines = [`Capability pane: ${filtered.length} item(s) (filter: ${opts.filter ?? "none"})`];
48
+ for (const [kind, items] of byKind) {
49
+ lines.push(` ${kindIcon(kind)} ${kind} (${items.length}):`);
50
+ for (const item of items.slice(0, 10)) {
51
+ const icon = item.state === "active" ? "✓" : "✗";
52
+ lines.push(` ${icon} ${item.name}${stateLabel(item.state)} [${item.source}]`);
53
+ }
54
+ if (items.length > 10) lines.push(` ... and ${items.length - 10} more`);
55
+ }
56
+
57
+ const disabled = filtered.filter((i) => i.state === "disabled").length;
58
+ if (disabled > 0) lines.push(` Disabled: ${disabled}`);
59
+ return lines;
60
+ }
@@ -1,11 +1,35 @@
1
- import type { RunUiSnapshot } from "../snapshot-types.ts";
2
-
3
- export function renderMailboxPane(snapshot: RunUiSnapshot | undefined): string[] {
4
- if (!snapshot) return ["Mailbox pane: snapshot unavailable"];
5
- const mailbox = snapshot.mailbox;
6
- const approx = mailbox.approximate ? " · approximate (tail)" : "";
7
- return [
8
- `Mailbox pane: inbox unread=${mailbox.inboxUnread} · outbox pending=${mailbox.outboxPending} · attention=${mailbox.needsAttention}${approx}`,
9
- mailbox.needsAttention > 0 ? "Needs attention: press Enter for detail · A ack · N nudge · C compose · X ack all." : "No mailbox items need attention. Press Enter for detail or C compose.",
10
- ];
11
- }
1
+ import type { RunUiSnapshot } from "../snapshot-types.ts";
2
+
3
+ export function renderMailboxPane(snapshot: RunUiSnapshot | undefined): string[] {
4
+ if (!snapshot) return ["Mailbox pane: snapshot unavailable"];
5
+ const mailbox = snapshot.mailbox;
6
+ const approx = mailbox.approximate ? " · approximate (tail)" : "";
7
+ const lines: string[] = [
8
+ `Mailbox pane: inbox unread=${mailbox.inboxUnread} · outbox pending=${mailbox.outboxPending} · attention=${mailbox.needsAttention}${approx}`,
9
+ ];
10
+ // Kind-separated breakdown
11
+ const kindParts: string[] = [];
12
+ const steer = mailbox.steerUnread ?? 0;
13
+ const followUp = mailbox.followUpUnread ?? 0;
14
+ const response = mailbox.responseUnread ?? 0;
15
+ const message = mailbox.messageUnread ?? 0;
16
+ if (steer > 0) kindParts.push(`steer=${steer}`);
17
+ if (followUp > 0) kindParts.push(`follow-up=${followUp}`);
18
+ if (response > 0) kindParts.push(`response=${response}`);
19
+ if (message > 0) kindParts.push(`message=${message}`);
20
+ if (kindParts.length > 0) {
21
+ lines.push(` Breakdown: ${kindParts.join(" · ")}`);
22
+ if (steer > 0) {
23
+ lines.push(" ⚠ Urgent: steering messages require immediate attention.");
24
+ }
25
+ if (followUp > 0) {
26
+ lines.push(` 📋 ${followUp} follow-up(s) pending review.`);
27
+ }
28
+ }
29
+ if (mailbox.needsAttention > 0) {
30
+ lines.push("Needs attention: press Enter for detail · A ack · N nudge · C compose · X ack all.");
31
+ } else {
32
+ lines.push("No mailbox items need attention. Press Enter for detail or C compose.");
33
+ }
34
+ return lines;
35
+ }