pi-crew 0.9.68 → 0.10.2
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 +222 -0
- package/NOTICE.md +21 -0
- package/README.md +44 -2
- package/agents/analyst.md +1 -1
- package/agents/cold-verifier.md +3 -1
- package/agents/critic.md +1 -1
- package/agents/executor.md +1 -1
- package/agents/explorer.md +1 -1
- package/agents/planner.md +1 -1
- package/agents/reviewer.md +1 -1
- package/agents/security-reviewer.md +1 -1
- package/agents/test-engineer.md +1 -1
- package/agents/verifier.md +1 -1
- package/agents/writer.md +1 -1
- package/dist/index.mjs +68113 -60774
- package/docs/README.md +2 -0
- package/docs/actions-reference.md +31 -0
- package/docs/commands-reference.md +17 -6
- package/docs/resource-formats.md +13 -0
- package/package.json +4 -2
- package/schema.json +503 -91
- package/scripts/resource-sampler.mjs +36 -2
- package/skills/requirements-to-task-packet/SKILL.md +26 -0
- package/skills/widget-rendering/SKILL.md +7 -7
- package/src/agents/agent-config.ts +2 -1
- package/src/agents/discover-agents.ts +23 -14
- package/src/config/config-merge.ts +183 -0
- package/src/config/config-validation.ts +687 -0
- package/src/config/config.ts +22 -864
- package/src/config/defaults.ts +43 -2
- package/src/config/drift-detector.ts +1 -1
- package/src/config/env-vars.ts +691 -0
- package/src/config/role-tools.ts +11 -9
- package/src/config/sanitize-project-config.ts +172 -0
- package/src/config/types.ts +49 -1
- package/src/extension/async-notifier.ts +25 -2
- package/src/extension/crew-cleanup.ts +13 -0
- package/src/extension/crew-vibes/config.ts +2 -1
- package/src/extension/crew-vibes/footer.ts +19 -0
- package/src/extension/crew-vibes/index.ts +11 -1
- package/src/extension/plan-orchestrate.ts +132 -0
- package/src/extension/register.ts +8 -0
- package/src/extension/registration/command-registration.ts +1 -0
- package/src/extension/registration/commands/dashboard.ts +158 -0
- package/src/extension/registration/commands/index.ts +35 -0
- package/src/extension/registration/commands/manage.ts +303 -0
- package/src/extension/registration/commands/run.ts +228 -0
- package/src/extension/registration/commands/shared.ts +639 -0
- package/src/extension/registration/commands/status.ts +60 -0
- package/src/extension/registration/commands.ts +13 -1224
- package/src/extension/registration/foreground-run-controller.ts +10 -2
- package/src/extension/registration/lifecycle-handlers.ts +178 -17
- package/src/extension/registration/runtime-cleanup.ts +23 -5
- package/src/extension/registration/subagent-tools.ts +218 -9
- package/src/extension/registration/team-tool.ts +5 -1
- package/src/extension/registration/ui.ts +5 -4
- package/src/extension/rpc-hmac.ts +5 -3
- package/src/extension/team-tool/api/heartbeat.ts +47 -10
- package/src/extension/team-tool/api/plan-approval.ts +9 -0
- package/src/extension/team-tool/api/task-claims.ts +109 -40
- package/src/extension/team-tool/cancel.ts +84 -50
- package/src/extension/team-tool/dispatch/index.ts +1 -0
- package/src/extension/team-tool/dispatch/run.ts +4 -1
- package/src/extension/team-tool/doctor.ts +103 -1
- package/src/extension/team-tool/orchestrate.ts +66 -1
- package/src/extension/team-tool/plans.ts +192 -0
- package/src/extension/team-tool/respond.ts +197 -65
- package/src/extension/team-tool/run-deadline.ts +35 -3
- package/src/extension/team-tool/run-intent.ts +63 -0
- package/src/extension/team-tool/run.ts +74 -20
- package/src/extension/team-tool/status.ts +84 -26
- package/src/extension/team-tool.ts +11 -2
- package/src/hooks/registry.ts +1 -6
- package/src/i18n.ts +9 -0
- package/src/prompt/prompt-runtime.ts +521 -2
- package/src/prompt/worker-events-channel.ts +173 -0
- package/src/runtime/README.md +8 -8
- package/src/runtime/async-runner.ts +7 -3
- package/src/runtime/background-runner.ts +42 -14
- package/src/runtime/broker/broker-issuer.ts +9 -2
- package/src/runtime/broker/crew-broker-tokens.ts +43 -6
- package/src/runtime/broker/crew-broker.ts +838 -10
- package/src/runtime/broker/wait-status-cache.ts +157 -0
- package/src/runtime/budget-enforcement.ts +281 -0
- package/src/runtime/child-pi/child-pi-constants.ts +8 -0
- package/src/runtime/child-pi/child-pi-spawn.ts +60 -14
- package/src/runtime/child-pi/child-pi-streams.ts +21 -1
- package/src/runtime/child-pi/child-pi-timers.ts +324 -0
- package/src/runtime/child-pi/child-pi.ts +97 -201
- package/src/runtime/child-pi/mock-fixtures.ts +16 -2
- package/src/runtime/crew-agent-records.ts +259 -14
- package/src/runtime/delegate-spawn.ts +148 -0
- package/src/runtime/detached-run-results.ts +90 -0
- package/src/runtime/deterministic-ast.ts +2 -1
- package/src/runtime/dispatch-batch.ts +945 -0
- package/src/runtime/finalize-run.ts +557 -0
- package/src/runtime/goal-workflow/adaptive-plan.ts +116 -15
- package/src/runtime/goal-workflow/dynamic-workflow-runner.ts +8 -3
- package/src/runtime/goal-workflow/goal-state-store.ts +1 -1
- package/src/runtime/group-join.ts +11 -125
- package/src/runtime/live-session/live-session-runtime.ts +26 -1
- package/src/runtime/merge-gate.ts +32 -10
- package/src/runtime/merge-loop.ts +130 -0
- package/src/runtime/model/model-budget-summary.ts +53 -0
- package/src/runtime/model/model-fallback.ts +36 -2
- package/src/runtime/model/pi-args.ts +10 -0
- package/src/runtime/model/provider-extensions.ts +10 -0
- package/src/runtime/orphan-worker-registry.ts +1 -1
- package/src/runtime/output/output-validator.ts +45 -0
- package/src/runtime/parent-guard.ts +3 -1
- package/src/runtime/peer-dep.ts +2 -1
- package/src/runtime/per-write-validator.ts +0 -5
- package/src/runtime/pi-spawn.ts +61 -15
- package/src/runtime/plan-approval.ts +125 -0
- package/src/runtime/plan-replan.ts +151 -0
- package/src/runtime/process-status.ts +16 -1
- package/src/runtime/recovery/checkpoint.ts +0 -18
- package/src/runtime/recovery/crash-recovery.ts +111 -46
- package/src/runtime/run-tracker.ts +77 -10
- package/src/runtime/scheduler-context.ts +98 -0
- package/src/runtime/scheduling/coalesce-tasks.ts +5 -0
- package/src/runtime/scheduling/global-worker-cap.ts +2 -1
- package/src/runtime/scheduling/nested-slots.ts +70 -0
- package/src/runtime/scheduling/run-coalesced-task-group.ts +64 -13
- package/src/runtime/scheduling/task-graph-scheduler.ts +0 -10
- package/src/runtime/settings-store.ts +219 -0
- package/src/runtime/spawn-policy.ts +217 -0
- package/src/runtime/stale-reconciler.ts +87 -6
- package/src/runtime/subagent-manager.ts +25 -1
- package/src/runtime/task-output-context.ts +230 -9
- package/src/runtime/task-packet.ts +23 -1
- package/src/runtime/task-runner/child-executor.ts +106 -7
- package/src/runtime/task-runner/post-execution.ts +125 -1
- package/src/runtime/task-runner/pre-execution.ts +39 -1
- package/src/runtime/task-runner/prompt-builder.ts +51 -1
- package/src/runtime/task-runner/retrieval-orchestrator.ts +72 -18
- package/src/runtime/task-runner/spec-evidence.ts +403 -0
- package/src/runtime/task-runner/state-helpers.ts +26 -24
- package/src/runtime/task-runner.ts +11 -0
- package/src/runtime/team-runner.ts +132 -1673
- package/src/runtime/verification/spec-sandbox.ts +255 -0
- package/src/runtime/verification/verification-gates.ts +3 -2
- package/src/runtime/verification/verification-worktree.ts +2 -1
- package/src/runtime/workflow-phase-advance.ts +100 -0
- package/src/runtime/workspace-tree.ts +9 -0
- package/src/schema/config-schema.ts +66 -26
- package/src/schema/sensitive-config-paths.ts +64 -0
- package/src/schema/team-tool-schema.ts +13 -3
- package/src/state/README.md +4 -10
- package/src/state/atomic-write.ts +20 -3
- package/src/state/contracts.ts +38 -0
- package/src/state/coordination/mailbox.ts +12 -2
- package/src/state/event-log/cursor.ts +223 -0
- package/src/state/event-log/event-log-rotation.ts +12 -4
- package/src/state/event-log/event-log.ts +152 -369
- package/src/state/event-log/sequence-cache.ts +373 -0
- package/src/state/event-log/worker-atomic-writer.ts +2 -1
- package/src/state/stores/active-run-registry.ts +3 -2
- package/src/state/stores/manifest-io.ts +237 -0
- package/src/state/stores/ownership-map.ts +162 -0
- package/src/state/stores/plan-store.ts +241 -0
- package/src/state/stores/run-cache.ts +0 -90
- package/src/state/stores/spec-store.ts +189 -0
- package/src/state/stores/state-store.ts +139 -232
- package/src/state/types.ts +199 -0
- package/src/ui/dashboard-panes/plan-pane.ts +136 -0
- package/src/ui/dashboard-panes/progress-pane.ts +6 -0
- package/src/ui/dashboard-panes/transcript-pane.ts +31 -0
- package/src/ui/dock-footer.ts +49 -0
- package/src/ui/heartbeat-aggregator.ts +9 -1
- package/src/ui/inline-panel/agent-pane.ts +375 -0
- package/src/ui/inline-panel/agent-transcript.ts +338 -0
- package/src/ui/inline-panel/agent-view-overlay.ts +225 -0
- package/src/ui/inline-panel/crew-editor.ts +192 -0
- package/src/ui/inline-panel/index.ts +290 -0
- package/src/ui/inline-panel/panel-rows.ts +37 -0
- package/src/ui/inline-panel/panel-selection.ts +157 -0
- package/src/ui/inline-panel/panel-store.ts +111 -0
- package/src/ui/inline-panel/view-session-store.ts +36 -0
- package/src/ui/keybinding-map.ts +54 -13
- package/src/ui/pi-ui-compat.ts +9 -0
- package/src/ui/powerbar-publisher.ts +52 -1
- package/src/ui/run-dashboard.ts +31 -5
- package/src/ui/run-snapshot-cache.ts +57 -30
- package/src/ui/snapshot-types.ts +6 -1
- package/src/ui/widget/index.ts +176 -22
- package/src/ui/widget/task-list.ts +198 -0
- package/src/ui/widget/widget-formatters.ts +240 -4
- package/src/ui/widget/widget-renderer.ts +243 -38
- package/src/ui/widget/widget-types.ts +11 -0
- package/src/utils/child-process-shield.ts +106 -0
- package/src/utils/file-coalescer.ts +0 -4
- package/src/utils/fs-errno.ts +66 -0
- package/src/utils/fs-watch.ts +1 -1
- package/src/utils/internal-error.ts +3 -1
- package/src/utils/paths.ts +11 -3
- package/src/utils/redaction.ts +7 -0
- package/src/utils/safe-abort.ts +45 -0
- package/src/utils/task-name-generator.ts +1 -8
- package/src/workflows/discover-workflows.ts +20 -2
- package/src/workflows/validate-workflow.ts +7 -1
- package/src/workflows/workflow-config.ts +17 -0
- package/src/workflows/workflow-serializer.ts +3 -0
- package/src/worktree/worktree-manager.ts +22 -0
- package/workflows/default.workflow.md +36 -26
- package/workflows/strict-fast-fix.workflow.md +26 -0
- package/src/agents/agent-search.ts +0 -98
- package/src/benchmark/benchmark-runner.ts +0 -313
- package/src/benchmark/feedback-loop.ts +0 -73
- package/src/config/resilient-parser.ts +0 -117
- package/src/extension/crew-vibes/cat-frames.ts +0 -18
- package/src/extension/result-watcher.ts +0 -139
- package/src/observability/exporters/prometheus-exporter.ts +0 -54
- package/src/observability/metric-retention.ts +0 -64
- package/src/runtime/compaction/compaction-summary.ts +0 -278
- package/src/runtime/errors/crew-errors.ts +0 -162
- package/src/runtime/live-session/intercom-bridge.ts +0 -187
- package/src/runtime/loop-gates.ts +0 -128
- package/src/runtime/metric-parser.ts +0 -36
- package/src/runtime/output/stream-preview.ts +0 -184
- package/src/runtime/output/tool-progress.ts +0 -278
- package/src/runtime/phase-tracker.ts +0 -385
- package/src/runtime/pipeline-runner.ts +0 -523
- package/src/runtime/process/process-lifecycle.ts +0 -491
- package/src/runtime/recovery/retry-runner.ts +0 -330
- package/src/runtime/run-drift.ts +0 -219
- package/src/runtime/task-quality.ts +0 -199
- package/src/runtime/task-runner/run-projection.ts +0 -128
- package/src/runtime/verification/post-checks.ts +0 -142
- package/src/state/coordination/schedule.ts +0 -166
- package/src/state/event-log/jsonl-writer.ts +0 -115
- package/src/state/hook-instinct-bridge.ts +0 -94
- package/src/state/hook-integrations.ts +0 -51
- package/src/state/session-state-map.ts +0 -51
- package/src/state/stores/blob-store.ts +0 -308
- package/src/state/stores/instinct-store.ts +0 -275
- package/src/state/stores/observation-store.ts +0 -176
- package/src/state/tiered-eval.ts +0 -480
- package/src/state/types-eval.ts +0 -58
- package/src/tools/safe-bash-extension.ts +0 -54
- package/src/tools/safe-bash.ts +0 -505
- package/src/ui/agent-management-overlay.ts +0 -160
- package/src/ui/crew-footer.ts +0 -102
- package/src/ui/crew-select-list.ts +0 -114
- package/src/ui/dashboard-panes/capability-pane.ts +0 -77
- package/src/ui/transcript-entries.ts +0 -256
- package/src/utils/conflict-detect.ts +0 -721
- package/src/utils/fingerprint.ts +0 -180
- package/src/utils/gh-protocol.ts +0 -556
- package/src/utils/project-detector.ts +0 -160
- package/src/utils/sse-parser.ts +0 -131
- package/src/workflows/cost-estimator.ts +0 -34
- package/src/workflows/intermediate-store.ts +0 -166
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent-pane.ts — full-width transcript pane for a single agent.
|
|
3
|
+
*
|
|
4
|
+
* The rendering core behind the agent view: it tails the agent's on-disk
|
|
5
|
+
* event log and paints a session-shaped transcript. The FULL-SCREEN view
|
|
6
|
+
* (agent-view-overlay.ts) embeds it with `maxBodyLines` computed from the
|
|
7
|
+
* terminal size; a widget-mode instantiation keeps the historical
|
|
8
|
+
* `aboveEditor` layout formula (terminal minus a fixed reserve).
|
|
9
|
+
*
|
|
10
|
+
* Items render through pi's own transcript components (UserMessageComponent,
|
|
11
|
+
* ToolExecutionComponent, Markdown) so the pane has visual parity with the
|
|
12
|
+
* main conversation: real tool cards, markdown rendering, diffs. Components
|
|
13
|
+
* are cached in a WeakMap keyed on the transcript item and dropped on theme
|
|
14
|
+
* change or when a tool item gains its result.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
18
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
19
|
+
import {
|
|
20
|
+
AssistantMessageComponent,
|
|
21
|
+
DynamicBorder,
|
|
22
|
+
getMarkdownTheme,
|
|
23
|
+
ToolExecutionComponent,
|
|
24
|
+
UserMessageComponent,
|
|
25
|
+
} from "@earendil-works/pi-coding-agent";
|
|
26
|
+
import { Markdown, type TUI, truncateToWidth } from "@earendil-works/pi-tui";
|
|
27
|
+
|
|
28
|
+
import { loadRunManifestById } from "../../state/stores/state-store.ts";
|
|
29
|
+
import type { TeamRunManifest, TeamTaskState } from "../../state/types.ts";
|
|
30
|
+
import { asCrewTheme, type CrewTheme } from "../theme-adapter.ts";
|
|
31
|
+
import { type CrewTranscriptItem, readAgentTranscript, resetAgentTranscriptCursor } from "./agent-transcript.ts";
|
|
32
|
+
import { getViewedAgent, subscribePanelChange } from "./panel-store.ts";
|
|
33
|
+
|
|
34
|
+
const MAX_BODY_FRACTION = 14;
|
|
35
|
+
/** Minimum gap between disk re-reads while the pane is open (transcript-viewer parity). */
|
|
36
|
+
const TRANSCRIPT_READ_THROTTLE_MS = 500;
|
|
37
|
+
/** Re-check the manifest/tasks every this often so the header's status/model
|
|
38
|
+
* stay current without a disk read on every host tick. */
|
|
39
|
+
const MANIFEST_REFRESH_MS = 1500;
|
|
40
|
+
/** Terminal task statuses — after these the header shows the full-session hint. */
|
|
41
|
+
const TERMINAL_TASK_STATUSES = new Set(["completed", "failed", "cancelled"]);
|
|
42
|
+
|
|
43
|
+
function formatTokenCount(count: number): string {
|
|
44
|
+
if (count < 1000) return String(count);
|
|
45
|
+
if (count < 1_000_000) return `${Math.round(count / 1000)}k`;
|
|
46
|
+
return `${(count / 1_000_000).toFixed(1)}M`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** pi-style per-turn usage footer: ↑in ↓out Rcache $cost (pi-subtask parity). */
|
|
50
|
+
function usageFooterLine(usage: { input: number; output: number; cacheRead: number; cacheWrite: number; cost: { total: number } }): string {
|
|
51
|
+
const parts: string[] = [];
|
|
52
|
+
if (usage.input) parts.push(`↑${formatTokenCount(usage.input)}`);
|
|
53
|
+
if (usage.output) parts.push(`↓${formatTokenCount(usage.output)}`);
|
|
54
|
+
if (usage.cacheRead) parts.push(`R${formatTokenCount(usage.cacheRead)}`);
|
|
55
|
+
const promptTokens = usage.input + usage.cacheRead;
|
|
56
|
+
if (usage.cacheRead && promptTokens > 0) {
|
|
57
|
+
parts.push(`CH${((usage.cacheRead / promptTokens) * 100).toFixed(1)}%`);
|
|
58
|
+
}
|
|
59
|
+
if (usage.cost.total) parts.push(`$${usage.cost.total.toFixed(4)}`);
|
|
60
|
+
return parts.join(" ");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface Renderable {
|
|
64
|
+
render(width: number): string[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface CrewAgentPaneOptions {
|
|
68
|
+
/** Overlay mode: compute the body's max lines from the terminal size,
|
|
69
|
+
* instead of the widget formula (which reserves room for the transcript
|
|
70
|
+
* above the editor). Evaluated on every render so live chrome (the
|
|
71
|
+
* overlay's input row) is accounted for. */
|
|
72
|
+
maxBodyLines?: (headerLines: number) => number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The pane is a window onto the run, never its owner: closing it must not
|
|
77
|
+
* touch worker state. `dispose()` detaches only.
|
|
78
|
+
*/
|
|
79
|
+
export class CrewAgentPane {
|
|
80
|
+
private disposed = false;
|
|
81
|
+
/** Wrapped-line offset from the END of the transcript; 0 = tailing. */
|
|
82
|
+
scrollBack = 0;
|
|
83
|
+
private tui: TUI;
|
|
84
|
+
private theme: CrewTheme;
|
|
85
|
+
private cwd: string;
|
|
86
|
+
private readonly paneOptions: CrewAgentPaneOptions | undefined;
|
|
87
|
+
/** Current agent target, read fresh from the panel store each render. */
|
|
88
|
+
private currentTaskId: string | undefined;
|
|
89
|
+
/** Cached manifest for the current run, to avoid re-reading on every tick. */
|
|
90
|
+
private cachedRunId: string | undefined;
|
|
91
|
+
private cachedManifest: TeamRunManifest | undefined;
|
|
92
|
+
/** Tasks for the cached run (header shows the agent's real name/status). */
|
|
93
|
+
private cachedTasks: TeamTaskState[] = [];
|
|
94
|
+
/** Last time the manifest was re-read for header freshness. */
|
|
95
|
+
private lastManifestRefreshAt = 0;
|
|
96
|
+
|
|
97
|
+
private componentCache = new WeakMap<CrewTranscriptItem, Renderable>();
|
|
98
|
+
/** Last disk read, so the open pane does not re-parse the JSONL every tick. */
|
|
99
|
+
private lastTranscriptReadAt = 0;
|
|
100
|
+
/** Most recent parsed items; kept alive because componentCache holds weak refs to them. */
|
|
101
|
+
private lastItems: CrewTranscriptItem[] = [];
|
|
102
|
+
/**
|
|
103
|
+
* Rendered-body cache. Rebuilding the pane re-parses every Markdown item,
|
|
104
|
+
* which on a 500-item transcript is the bulk of each ~160ms host tick.
|
|
105
|
+
* The fingerprint covers identity-relevant bits (seq, type, text length,
|
|
106
|
+
* result presence) so tool-result folds still refresh the pane.
|
|
107
|
+
*/
|
|
108
|
+
private bodyKey = 0;
|
|
109
|
+
private cachedBody: string[] = [];
|
|
110
|
+
private unsubscribePanel: () => void;
|
|
111
|
+
|
|
112
|
+
constructor(tui: TUI, theme: Theme, cwd: string, options?: CrewAgentPaneOptions) {
|
|
113
|
+
this.tui = tui;
|
|
114
|
+
this.theme = asCrewTheme(theme);
|
|
115
|
+
this.cwd = cwd;
|
|
116
|
+
this.paneOptions = options;
|
|
117
|
+
// Pane open/close/switch is a panel-store change; repaint immediately
|
|
118
|
+
// instead of waiting for the next host tick, so the overlay swap is
|
|
119
|
+
// instant even when the underlying run state is idle.
|
|
120
|
+
this.unsubscribePanel = subscribePanelChange(() => {
|
|
121
|
+
if (!this.disposed) this.tui.requestRender();
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
requestRender(): void {
|
|
126
|
+
if (!this.disposed) this.tui.requestRender();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
scrollBy(delta: number): void {
|
|
130
|
+
this.scrollBack = Math.max(0, this.scrollBack + delta);
|
|
131
|
+
this.tui.requestRender();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Jump to the top of the buffered transcript (clamped on next render). */
|
|
135
|
+
scrollHome(): void {
|
|
136
|
+
this.scrollBack = Number.MAX_SAFE_INTEGER;
|
|
137
|
+
this.tui.requestRender();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Jump back to tailing the live transcript. */
|
|
141
|
+
scrollEnd(): void {
|
|
142
|
+
this.scrollBack = 0;
|
|
143
|
+
this.tui.requestRender();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private resolveManifest(runId: string): TeamRunManifest | undefined {
|
|
147
|
+
const stale = Date.now() - this.lastManifestRefreshAt >= MANIFEST_REFRESH_MS;
|
|
148
|
+
if (this.cachedRunId === runId && this.cachedManifest) {
|
|
149
|
+
if (stale) {
|
|
150
|
+
this.lastManifestRefreshAt = Date.now();
|
|
151
|
+
const refreshed = loadRunManifestById(this.cwd, runId);
|
|
152
|
+
if (refreshed) {
|
|
153
|
+
this.cachedManifest = refreshed.manifest;
|
|
154
|
+
this.cachedTasks = refreshed.tasks ?? [];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return this.cachedManifest;
|
|
158
|
+
}
|
|
159
|
+
const loaded = loadRunManifestById(this.cwd, runId);
|
|
160
|
+
if (!loaded) return undefined;
|
|
161
|
+
this.cachedRunId = runId;
|
|
162
|
+
this.cachedManifest = loaded.manifest;
|
|
163
|
+
this.cachedTasks = loaded.tasks ?? [];
|
|
164
|
+
this.lastManifestRefreshAt = Date.now();
|
|
165
|
+
return loaded.manifest;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private itemComponent(item: CrewTranscriptItem): Renderable {
|
|
169
|
+
const cached = this.componentCache.get(item);
|
|
170
|
+
if (cached) {
|
|
171
|
+
// Tool results land by MUTATING the pending item in place (the
|
|
172
|
+
// parser folds role:"toolResult" records into it), so a card
|
|
173
|
+
// cached while "started" must pick the result up here — otherwise
|
|
174
|
+
// it stays a running card forever. Re-updating an already-resulted
|
|
175
|
+
// card is a no-op render refresh.
|
|
176
|
+
if (item.type === "tool" && item.result !== undefined) {
|
|
177
|
+
try {
|
|
178
|
+
(cached as ToolExecutionComponent).updateResult(item.result);
|
|
179
|
+
} catch {
|
|
180
|
+
/* shape drift across pi versions — keep the started card */
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return cached;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
let comp: Renderable;
|
|
187
|
+
if (item.type === "user") {
|
|
188
|
+
comp = new UserMessageComponent(item.text);
|
|
189
|
+
} else if (item.type === "assistant") {
|
|
190
|
+
// Full-message parity with a real pi session: pi's own assistant
|
|
191
|
+
// component renders text, thinking blocks, and tool calls from the
|
|
192
|
+
// compacted message. Falls back to markdown for old event logs
|
|
193
|
+
// that predate message retention.
|
|
194
|
+
comp = item.message
|
|
195
|
+
? new AssistantMessageComponent(item.message as unknown as AssistantMessage)
|
|
196
|
+
: new Markdown(item.text.trim(), 0, 0, getMarkdownTheme());
|
|
197
|
+
} else if (item.type === "system") {
|
|
198
|
+
// System lines are plain text — no component needed.
|
|
199
|
+
comp = {
|
|
200
|
+
render: () => [this.theme.fg("dim", truncateToWidth(item.text, 200, "…"))],
|
|
201
|
+
};
|
|
202
|
+
} else {
|
|
203
|
+
// Tool item: use pi's own ToolExecutionComponent for visual parity.
|
|
204
|
+
const tool = new ToolExecutionComponent(item.name, item.toolCallId, item.args, {}, undefined, this.tui, this.cwd);
|
|
205
|
+
tool.markExecutionStarted();
|
|
206
|
+
if (item.result !== undefined) {
|
|
207
|
+
tool.updateResult(item.result as never);
|
|
208
|
+
}
|
|
209
|
+
comp = tool;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
this.componentCache.set(item, comp);
|
|
213
|
+
return comp;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Render one transcript item to lines, degrading to a dim text line when
|
|
218
|
+
* the item's shape confuses a pi component (the JSONL is worker-written
|
|
219
|
+
* and unvalidated — a once-bad record must never kill the pane render).
|
|
220
|
+
*/
|
|
221
|
+
private renderItem(item: CrewTranscriptItem, width: number): string[] {
|
|
222
|
+
try {
|
|
223
|
+
return this.itemComponent(item).render(width);
|
|
224
|
+
} catch {
|
|
225
|
+
const label = item.type === "tool" ? item.name : "text";
|
|
226
|
+
return [this.theme.fg("dim", truncateToWidth(`(unrenderable ${label} item)`, width, "…"))];
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** FNV-1a over the parts that change pane output; 32-bit is plenty for a
|
|
231
|
+
* 500-item cache key (a collision only delays a repaint by one tick). */
|
|
232
|
+
private bodyFingerprint(items: readonly CrewTranscriptItem[], width: number): number {
|
|
233
|
+
let h = (2166136261 ^ width) >>> 0;
|
|
234
|
+
for (const item of items) {
|
|
235
|
+
h ^= item.seq;
|
|
236
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
237
|
+
h ^= item.type.length;
|
|
238
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
239
|
+
if (item.type === "tool") {
|
|
240
|
+
h ^= item.result !== undefined ? 7 : 3;
|
|
241
|
+
} else {
|
|
242
|
+
h ^= item.text.length;
|
|
243
|
+
}
|
|
244
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
245
|
+
}
|
|
246
|
+
return h;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private buildBody(items: readonly CrewTranscriptItem[], width: number): string[] {
|
|
250
|
+
const body: string[] = [];
|
|
251
|
+
for (const item of items) {
|
|
252
|
+
for (const line of this.renderItem(item, width)) body.push(line);
|
|
253
|
+
if (item.type === "assistant" && item.usage) {
|
|
254
|
+
// pi's session transcript prints a usage footer under each
|
|
255
|
+
// assistant message; mirror it so the pane reads like one.
|
|
256
|
+
const footer = usageFooterLine(item.usage);
|
|
257
|
+
if (footer) body.push(this.theme.fg("dim", footer));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return body;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** Session-style header: agent name · task · run · model · state. */
|
|
264
|
+
private headerLines(manifest: TeamRunManifest, width: number): string[] {
|
|
265
|
+
const t = this.theme;
|
|
266
|
+
const task = this.cachedTasks.find((candidate) => candidate.id === this.currentTaskId);
|
|
267
|
+
const name = task?.displayName ?? task?.title ?? this.currentTaskId ?? "agent";
|
|
268
|
+
const model = manifest.modelContext?.parentModel ?? manifest.modelContext?.override;
|
|
269
|
+
const parts: string[] = [
|
|
270
|
+
t.fg("accent", t.bold(name)),
|
|
271
|
+
t.fg("dim", `· ${this.currentTaskId ?? ""}`),
|
|
272
|
+
t.fg("dim", `· …${manifest.runId.slice(-12)}`),
|
|
273
|
+
];
|
|
274
|
+
if (model) parts.push(t.fg("dim", `· ${model}`));
|
|
275
|
+
|
|
276
|
+
const status = task?.status ?? manifest.status;
|
|
277
|
+
let stateText = "";
|
|
278
|
+
if (status === "completed") stateText = t.fg("success", "✓ completed");
|
|
279
|
+
else if (status === "failed") stateText = t.fg("error", "✗ failed");
|
|
280
|
+
else if (status === "cancelled") stateText = t.fg("warning", "■ cancelled");
|
|
281
|
+
else if (status === "running") stateText = t.fg("success", "● running");
|
|
282
|
+
else if (status) stateText = t.fg("dim", status);
|
|
283
|
+
const line = truncateToWidth(`${parts.join(" ")}${stateText ? ` ${stateText}` : ""}`, width, "…");
|
|
284
|
+
const lines = [line];
|
|
285
|
+
if (task && TERMINAL_TASK_STATUSES.has(task.status)) {
|
|
286
|
+
lines.push(t.fg("dim", truncateToWidth(`· finished — full log: /team-dashboard or artifacts/${manifest.runId}`, width, "…")));
|
|
287
|
+
}
|
|
288
|
+
return lines;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
render(width: number): string[] {
|
|
292
|
+
if (this.disposed) return [];
|
|
293
|
+
|
|
294
|
+
const viewed = getViewedAgent();
|
|
295
|
+
if (!viewed) return [];
|
|
296
|
+
|
|
297
|
+
// Detect agent switch: reset the transcript cursor so the pane shows
|
|
298
|
+
// the new agent's full history instead of continuing from the old cursor.
|
|
299
|
+
if (this.currentTaskId !== viewed.taskId) {
|
|
300
|
+
resetAgentTranscriptCursor(viewed.taskId);
|
|
301
|
+
this.currentTaskId = viewed.taskId;
|
|
302
|
+
this.scrollBack = 0;
|
|
303
|
+
this.lastItems = [];
|
|
304
|
+
this.lastTranscriptReadAt = 0;
|
|
305
|
+
this.bodyKey = 0;
|
|
306
|
+
this.cachedBody = [];
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const manifest = this.resolveManifest(viewed.runId);
|
|
310
|
+
if (!manifest) return [this.theme.fg("dim", "(run manifest unavailable)")];
|
|
311
|
+
|
|
312
|
+
// Throttled disk read: the host repaints on its own ~160ms cadence, but
|
|
313
|
+
// the JSONL only advances when the worker does something.
|
|
314
|
+
if (Date.now() - this.lastTranscriptReadAt >= TRANSCRIPT_READ_THROTTLE_MS) {
|
|
315
|
+
this.lastItems = readAgentTranscript(manifest, viewed.taskId);
|
|
316
|
+
this.lastTranscriptReadAt = Date.now();
|
|
317
|
+
}
|
|
318
|
+
const items = this.lastItems;
|
|
319
|
+
|
|
320
|
+
// Render every item at full width — pi's message components paint their
|
|
321
|
+
// own edge-to-edge background and carry their own padding, so indenting
|
|
322
|
+
// them leaves column 0 unpainted and notches the corners. The body is
|
|
323
|
+
// cached under a content fingerprint; unchanged state skips the
|
|
324
|
+
// Markdown re-parse on every host tick.
|
|
325
|
+
const fingerprint = this.bodyFingerprint(items, width);
|
|
326
|
+
if (fingerprint !== this.bodyKey) {
|
|
327
|
+
this.cachedBody = this.buildBody(items, width);
|
|
328
|
+
this.bodyKey = fingerprint;
|
|
329
|
+
}
|
|
330
|
+
const body = this.cachedBody;
|
|
331
|
+
|
|
332
|
+
// Height: in widget mode, size to content capped so the transcript
|
|
333
|
+
// above and the editor/footer below stay reachable on small terminals.
|
|
334
|
+
// In overlay mode the pane is the whole screen: the caller computes
|
|
335
|
+
// the body budget from the terminal size minus its own chrome.
|
|
336
|
+
const header = this.headerLines(manifest, width);
|
|
337
|
+
const rows = this.tui.terminal.rows;
|
|
338
|
+
const maxBody = this.paneOptions?.maxBodyLines
|
|
339
|
+
? Math.max(4, this.paneOptions.maxBodyLines(header.length))
|
|
340
|
+
: Math.max(6, rows - MAX_BODY_FRACTION - header.length);
|
|
341
|
+
const visibleCount = Math.min(maxBody, Math.max(1, body.length));
|
|
342
|
+
this.scrollBack = Math.max(0, Math.min(this.scrollBack, Math.max(0, body.length - visibleCount)));
|
|
343
|
+
const end = body.length - this.scrollBack;
|
|
344
|
+
const visible = body.slice(Math.max(0, end - visibleCount), end);
|
|
345
|
+
|
|
346
|
+
const lines: string[] = [];
|
|
347
|
+
// Session-style header first, then the border, then the transcript.
|
|
348
|
+
for (const line of header) lines.push(line);
|
|
349
|
+
lines.push(...new DynamicBorder((str) => this.theme.fg("border", str)).render(width));
|
|
350
|
+
if (end - visibleCount > 0) {
|
|
351
|
+
lines.push(this.theme.fg("dim", ` ↑ ${Math.max(0, end - visibleCount)} more line(s) (pageUp)`));
|
|
352
|
+
} else {
|
|
353
|
+
lines.push("");
|
|
354
|
+
}
|
|
355
|
+
for (const line of visible) lines.push(line);
|
|
356
|
+
if (this.scrollBack > 0) {
|
|
357
|
+
lines.push(this.theme.fg("dim", ` ↓ ${this.scrollBack} more line(s) (pageDown)`));
|
|
358
|
+
}
|
|
359
|
+
return lines;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
invalidate(): void {
|
|
363
|
+
// Components cache theme colors internally; rebuild on theme change.
|
|
364
|
+
this.componentCache = new WeakMap();
|
|
365
|
+
this.bodyKey = 0;
|
|
366
|
+
this.cachedBody = [];
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
dispose(): void {
|
|
370
|
+
this.disposed = true;
|
|
371
|
+
this.unsubscribePanel();
|
|
372
|
+
this.cachedManifest = undefined;
|
|
373
|
+
this.cachedRunId = undefined;
|
|
374
|
+
}
|
|
375
|
+
}
|