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.
- package/CHANGELOG.md +97 -0
- package/agents/analyst.md +11 -11
- package/agents/critic.md +11 -11
- package/agents/executor.md +11 -11
- package/agents/explorer.md +11 -11
- package/agents/planner.md +11 -11
- package/agents/reviewer.md +11 -11
- package/agents/security-reviewer.md +11 -11
- package/agents/test-engineer.md +11 -11
- package/agents/verifier.md +11 -11
- package/agents/writer.md +11 -11
- package/docs/next-upgrade-roadmap.md +117 -42
- package/docs/refactor-tasks-phase3.md +394 -394
- package/docs/refactor-tasks-phase4.md +564 -564
- package/docs/refactor-tasks-phase5.md +402 -402
- package/docs/refactor-tasks-phase6.md +662 -662
- package/docs/research/AGENT-EXECUTION-ARCHITECTURE.md +261 -0
- package/docs/research/AGENT-LIFECYCLE-COMPARISON.md +111 -0
- package/docs/research/AUDIT_OH_MY_PI.md +261 -0
- package/docs/research/AUDIT_PI_CREW.md +457 -0
- package/docs/research/CAVEMAN-DEEP-RESEARCH.md +281 -0
- package/docs/research/COMPARISON_OH_MY_PI_VS_PI_CREW.md +264 -0
- package/docs/research/DEEP-RESEARCH-PI-POWERBAR.md +343 -0
- package/docs/research/DEEP_RESEARCH_SUBAGENT_ARCHITECTURE.md +480 -0
- package/docs/research/GAP_CLOSURE_IMPLEMENTATION_PLAN.md +354 -0
- package/docs/research/IMPLEMENTATION_PLAN.md +385 -0
- package/docs/research/LIVE-SESSION-PRODUCTION-READY-PLAN.md +502 -0
- package/docs/research/OH-MY-PI-DEEP-RESEARCH-v14.7.6.md +266 -0
- package/docs/research/REMAINING-GAPS-PLAN.md +363 -0
- package/docs/research/SESSION-SUMMARY-2026-05-08.md +146 -0
- package/docs/research/UI-RESPONSIVENESS-AUDIT.md +173 -0
- package/docs/research-awesome-agent-skills-distillation.md +100 -100
- package/docs/research-extension-examples.md +297 -297
- package/docs/research-extension-system.md +324 -324
- package/docs/research-oh-my-pi-distillation.md +56 -9
- package/docs/research-optimization-plan.md +548 -548
- package/docs/research-phase10-distillation.md +198 -198
- package/docs/research-phase11-distillation.md +201 -201
- package/docs/research-pi-coding-agent.md +357 -357
- package/docs/research-source-pi-crew-reference.md +174 -174
- package/docs/runtime-flow.md +148 -148
- package/docs/source-runtime-refactor-map.md +107 -107
- package/index.ts +6 -6
- package/package.json +99 -98
- package/schema.json +8 -0
- package/skills/async-worker-recovery/SKILL.md +42 -42
- package/skills/context-artifact-hygiene/SKILL.md +52 -52
- package/skills/delegation-patterns/SKILL.md +54 -54
- package/skills/mailbox-interactive/SKILL.md +40 -40
- package/skills/model-routing-context/SKILL.md +39 -39
- package/skills/multi-perspective-review/SKILL.md +58 -58
- package/skills/observability-reliability/SKILL.md +41 -41
- package/skills/orchestration/SKILL.md +157 -0
- package/skills/ownership-session-security/SKILL.md +41 -41
- package/skills/pi-extension-lifecycle/SKILL.md +39 -39
- package/skills/requirements-to-task-packet/SKILL.md +63 -63
- package/skills/resource-discovery-config/SKILL.md +41 -41
- package/skills/runtime-state-reader/SKILL.md +44 -44
- package/skills/secure-agent-orchestration-review/SKILL.md +45 -45
- package/skills/state-mutation-locking/SKILL.md +42 -42
- package/skills/systematic-debugging/SKILL.md +67 -67
- package/skills/ui-render-performance/SKILL.md +39 -39
- package/skills/verification-before-done/SKILL.md +57 -57
- package/skills/worktree-isolation/SKILL.md +39 -39
- package/src/agents/agent-config.ts +6 -0
- package/src/agents/agent-search.ts +98 -0
- package/src/agents/agent-serializer.ts +4 -0
- package/src/agents/discover-agents.ts +17 -4
- package/src/config/config.ts +24 -0
- package/src/config/defaults.ts +11 -0
- package/src/extension/autonomous-policy.ts +26 -33
- package/src/extension/cross-extension-rpc.ts +82 -82
- package/src/extension/help.ts +1 -0
- package/src/extension/management.ts +5 -0
- package/src/extension/register.ts +58 -13
- package/src/extension/registration/commands.ts +33 -1
- package/src/extension/registration/compaction-guard.ts +125 -125
- package/src/extension/registration/team-tool.ts +6 -4
- package/src/extension/run-bundle-schema.ts +89 -89
- package/src/extension/run-index.ts +24 -18
- package/src/extension/run-maintenance.ts +68 -62
- package/src/extension/team-tool/api.ts +23 -2
- package/src/extension/team-tool/cancel.ts +86 -11
- package/src/extension/team-tool/context.ts +3 -0
- package/src/extension/team-tool/handle-settings.ts +188 -188
- package/src/extension/team-tool/inspect.ts +41 -41
- package/src/extension/team-tool/intent-policy.ts +42 -0
- package/src/extension/team-tool/lifecycle-actions.ts +47 -18
- package/src/extension/team-tool/parallel-dispatch.ts +156 -0
- package/src/extension/team-tool/plan.ts +19 -19
- package/src/extension/team-tool/respond.ts +10 -2
- package/src/extension/team-tool/run.ts +3 -2
- package/src/extension/team-tool/status.ts +1 -1
- package/src/extension/team-tool-types.ts +1 -0
- package/src/extension/team-tool.ts +13 -3
- package/src/hooks/registry.ts +61 -0
- package/src/hooks/types.ts +41 -0
- package/src/i18n.ts +184 -184
- package/src/observability/exporters/otlp-exporter.ts +77 -77
- package/src/prompt/prompt-runtime.ts +72 -72
- package/src/runtime/agent-control.ts +108 -2
- package/src/runtime/agent-memory.ts +72 -72
- package/src/runtime/agent-observability.ts +114 -114
- package/src/runtime/async-marker.ts +26 -26
- package/src/runtime/async-runner.ts +3 -1
- package/src/runtime/attention-events.ts +28 -28
- package/src/runtime/background-runner.ts +19 -0
- package/src/runtime/cancellation-token.ts +89 -0
- package/src/runtime/cancellation.ts +61 -51
- package/src/runtime/capability-inventory.ts +116 -0
- package/src/runtime/child-pi.ts +2 -1
- package/src/runtime/code-summary.ts +247 -0
- package/src/runtime/completion-guard.ts +190 -190
- package/src/runtime/crash-recovery.ts +181 -0
- package/src/runtime/crew-agent-records.ts +35 -7
- package/src/runtime/crew-agent-runtime.ts +1 -0
- package/src/runtime/custom-tools/irc-tool.ts +201 -0
- package/src/runtime/custom-tools/submit-result-tool.ts +90 -0
- package/src/runtime/delivery-coordinator.ts +3 -1
- package/src/runtime/direct-run.ts +35 -35
- package/src/runtime/effectiveness.ts +81 -76
- package/src/runtime/event-stream-bridge.ts +90 -0
- package/src/runtime/foreground-control.ts +82 -82
- package/src/runtime/green-contract.ts +46 -46
- package/src/runtime/group-join.ts +106 -106
- package/src/runtime/heartbeat-gradient.ts +28 -28
- package/src/runtime/heartbeat-watcher.ts +124 -124
- package/src/runtime/live-agent-control.ts +88 -88
- package/src/runtime/live-agent-manager.ts +78 -2
- package/src/runtime/live-control-realtime.ts +36 -36
- package/src/runtime/live-extension-bridge.ts +150 -0
- package/src/runtime/live-irc.ts +92 -0
- package/src/runtime/live-session-health.ts +100 -0
- package/src/runtime/live-session-runtime.ts +297 -7
- package/src/runtime/mcp-proxy.ts +113 -0
- package/src/runtime/notebook-helpers.ts +90 -0
- package/src/runtime/orphan-sentinel.ts +7 -0
- package/src/runtime/output-validator.ts +187 -0
- package/src/runtime/parallel-research.ts +44 -44
- package/src/runtime/parallel-utils.ts +57 -0
- package/src/runtime/parent-guard.ts +80 -0
- package/src/runtime/pi-json-output.ts +111 -111
- package/src/runtime/policy-engine.ts +79 -79
- package/src/runtime/progress-event-coalescer.ts +43 -43
- package/src/runtime/prose-compressor.ts +164 -0
- package/src/runtime/recovery-recipes.ts +74 -74
- package/src/runtime/result-extractor.ts +121 -0
- package/src/runtime/role-permission.ts +39 -39
- package/src/runtime/runtime-resolver.ts +1 -4
- package/src/runtime/semaphore.ts +131 -0
- package/src/runtime/sensitive-paths.ts +92 -0
- package/src/runtime/session-resources.ts +25 -25
- package/src/runtime/session-snapshot.ts +59 -59
- package/src/runtime/session-usage.ts +79 -79
- package/src/runtime/sidechain-output.ts +29 -29
- package/src/runtime/stream-preview.ts +177 -0
- package/src/runtime/subagent-manager.ts +3 -2
- package/src/runtime/subprocess-tool-registry.ts +67 -0
- package/src/runtime/supervisor-contact.ts +59 -59
- package/src/runtime/task-display.ts +38 -38
- package/src/runtime/task-output-context.ts +59 -9
- package/src/runtime/task-runner/capabilities.ts +78 -78
- package/src/runtime/task-runner/live-executor.ts +2 -0
- package/src/runtime/task-runner/progress.ts +119 -119
- package/src/runtime/task-runner/prompt-builder.ts +70 -8
- package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
- package/src/runtime/task-runner/result-utils.ts +14 -14
- package/src/runtime/task-runner/run-projection.ts +104 -0
- package/src/runtime/task-runner/state-helpers.ts +22 -22
- package/src/runtime/task-runner.ts +75 -4
- package/src/runtime/team-runner.ts +60 -8
- package/src/runtime/worker-heartbeat.ts +21 -21
- package/src/runtime/worker-startup.ts +57 -57
- package/src/runtime/workspace-tree.ts +298 -0
- package/src/runtime/yield-handler.ts +189 -0
- package/src/schema/config-schema.ts +6 -0
- package/src/schema/team-tool-schema.ts +11 -1
- package/src/skills/discover-skills.ts +67 -0
- package/src/state/active-run-registry.ts +4 -2
- package/src/state/artifact-store.ts +4 -1
- package/src/state/atomic-write.ts +50 -1
- package/src/state/blob-store.ts +117 -0
- package/src/state/contracts.ts +1 -0
- package/src/state/event-log-rotation.ts +158 -0
- package/src/state/event-log.ts +52 -2
- package/src/state/mailbox.ts +87 -7
- package/src/state/state-store.ts +24 -4
- package/src/state/task-claims.ts +44 -44
- package/src/state/types.ts +20 -0
- package/src/state/usage.ts +29 -29
- package/src/subagents/async-entry.ts +1 -1
- package/src/subagents/index.ts +3 -3
- package/src/subagents/live/control.ts +1 -1
- package/src/subagents/live/manager.ts +1 -1
- package/src/subagents/live/realtime.ts +1 -1
- package/src/subagents/live/session-runtime.ts +1 -1
- package/src/subagents/manager.ts +1 -1
- package/src/subagents/spawn.ts +1 -1
- package/src/teams/team-serializer.ts +38 -38
- package/src/types/diff.d.ts +18 -18
- package/src/ui/agent-management-overlay.ts +144 -0
- package/src/ui/crew-footer.ts +101 -101
- package/src/ui/crew-select-list.ts +111 -111
- package/src/ui/crew-widget.ts +11 -2
- package/src/ui/dashboard-panes/cancellation-pane.ts +43 -0
- package/src/ui/dashboard-panes/capability-pane.ts +60 -0
- package/src/ui/dashboard-panes/mailbox-pane.ts +35 -11
- package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
- package/src/ui/dynamic-border.ts +25 -25
- package/src/ui/layout-primitives.ts +106 -106
- package/src/ui/live-run-sidebar.ts +4 -0
- package/src/ui/loaders.ts +158 -158
- package/src/ui/powerbar-publisher.ts +77 -15
- package/src/ui/render-coalescer.ts +51 -0
- package/src/ui/render-diff.ts +119 -119
- package/src/ui/render-scheduler.ts +143 -143
- package/src/ui/run-dashboard.ts +4 -0
- package/src/ui/run-event-bus.ts +209 -0
- package/src/ui/run-snapshot-cache.ts +68 -16
- package/src/ui/snapshot-types.ts +8 -0
- package/src/ui/spinner.ts +17 -17
- package/src/ui/status-colors.ts +58 -58
- package/src/ui/syntax-highlight.ts +116 -116
- package/src/ui/transcript-entries.ts +258 -0
- package/src/utils/atomic-write.ts +33 -33
- package/src/utils/completion-dedupe.ts +63 -63
- package/src/utils/frontmatter.ts +68 -68
- package/src/utils/git.ts +262 -262
- package/src/utils/ids.ts +17 -12
- package/src/utils/incremental-reader.ts +104 -0
- package/src/utils/names.ts +27 -27
- package/src/utils/redaction.ts +44 -44
- package/src/utils/safe-paths.ts +47 -47
- package/src/utils/scan-cache.ts +137 -0
- package/src/utils/sleep.ts +32 -32
- package/src/utils/sse-parser.ts +134 -0
- package/src/utils/task-name-generator.ts +337 -0
- package/src/utils/visual.ts +33 -2
- package/src/workflows/validate-workflow.ts +40 -40
- package/src/worktree/branch-freshness.ts +45 -45
- package/src/worktree/cleanup.ts +2 -1
- package/teams/default.team.md +12 -12
- package/teams/fast-fix.team.md +11 -11
- package/teams/implementation.team.md +18 -18
- package/teams/parallel-research.team.md +14 -14
- package/teams/research.team.md +11 -11
- package/teams/review.team.md +12 -12
- package/workflows/default.workflow.md +29 -29
- package/workflows/fast-fix.workflow.md +22 -22
- package/workflows/implementation.workflow.md +38 -38
- package/workflows/parallel-research.workflow.md +46 -46
- package/workflows/research.workflow.md +22 -22
- package/workflows/review.workflow.md +30 -30
package/src/ui/crew-footer.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/ui/crew-widget.ts
CHANGED
|
@@ -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")
|
|
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
|
-
|
|
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
|
-
|
|
8
|
-
`Mailbox pane: inbox unread=${mailbox.inboxUnread} · outbox pending=${mailbox.outboxPending} · attention=${mailbox.needsAttention}${approx}`,
|
|
9
|
-
|
|
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
|
+
}
|