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
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
+
import { getCrewEnv } from "../config/env-vars.ts";
|
|
4
5
|
import { agentOutputPath, agentsPath, readCrewAgents, readCrewAgentsAsync } from "../runtime/crew-agent-records.ts";
|
|
5
6
|
import type { CrewAgentRecord } from "../runtime/crew-agent-runtime.ts";
|
|
6
7
|
import { isActiveRunStatus } from "../runtime/process-status.ts";
|
|
7
8
|
import type { MailboxMessageStatus } from "../state/coordination/mailbox.ts";
|
|
8
9
|
import type { TeamEvent } from "../state/event-log/event-log.ts";
|
|
9
10
|
import { sequencePath } from "../state/event-log/event-log.ts";
|
|
11
|
+
import { loadPlanRecords, planFilePath } from "../state/stores/plan-store.ts";
|
|
10
12
|
import { loadRunManifestById, loadRunManifestByIdAsync } from "../state/stores/state-store.ts";
|
|
11
13
|
import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
|
|
12
14
|
import { extractDwfPhaseState } from "./dwf-phase-display.ts";
|
|
@@ -25,6 +27,12 @@ export interface RunSnapshotCache extends RunSnapshotCacheBase {
|
|
|
25
27
|
preloadAllStale(runIds: string[]): Promise<void>;
|
|
26
28
|
}
|
|
27
29
|
|
|
30
|
+
/** WP-7 (R7): the plans slice + Plan pane load only when this flag is set —
|
|
31
|
+
* flag-off keeps the snapshot build byte-identical (zero extra I/O). */
|
|
32
|
+
export function isPlanUiEnabled(): boolean {
|
|
33
|
+
return getCrewEnv("PI_CREW_PLAN_UI") === "1";
|
|
34
|
+
}
|
|
35
|
+
|
|
28
36
|
const DEFAULT_TTL_MS = 1500;
|
|
29
37
|
const DEFAULT_MAX_ENTRIES = 24;
|
|
30
38
|
const DEFAULT_RECENT_EVENTS = 20;
|
|
@@ -44,6 +52,8 @@ interface SnapshotStamps {
|
|
|
44
52
|
agents: FileStamp;
|
|
45
53
|
events: FileStamp;
|
|
46
54
|
mailbox: FileStamp;
|
|
55
|
+
/** WP-7 (R7): present only when PI_CREW_PLAN_UI=1. */
|
|
56
|
+
plans?: FileStamp;
|
|
47
57
|
}
|
|
48
58
|
|
|
49
59
|
interface CacheEntry {
|
|
@@ -163,15 +173,10 @@ function safeAgentOutputPath(manifest: TeamRunManifest, agent: CrewAgentRecord):
|
|
|
163
173
|
}
|
|
164
174
|
}
|
|
165
175
|
|
|
166
|
-
function
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
async function outputStampAsync(manifest: TeamRunManifest, agents: CrewAgentRecord[]): Promise<FileStamp> {
|
|
171
|
-
return combineStamps(await Promise.all(agents.map((agent) => stampFileAsync(safeAgentOutputPath(manifest, agent)))));
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function sameStamp(a: FileStamp, b: FileStamp): boolean {
|
|
176
|
+
function sameStamp(a: FileStamp | undefined, b: FileStamp | undefined): boolean {
|
|
177
|
+
// Optional stamps (WP-7 plans): absent on both sides = unchanged; absent
|
|
178
|
+
// on one side = the flag flipped — force a rebuild.
|
|
179
|
+
if (a === undefined || b === undefined) return a === b;
|
|
175
180
|
return a.mtimeMs === b.mtimeMs && a.size === b.size;
|
|
176
181
|
}
|
|
177
182
|
|
|
@@ -181,19 +186,11 @@ function sameStamps(a: SnapshotStamps, b: SnapshotStamps): boolean {
|
|
|
181
186
|
sameStamp(a.tasks, b.tasks) &&
|
|
182
187
|
sameStamp(a.agents, b.agents) &&
|
|
183
188
|
sameStamp(a.events, b.events) &&
|
|
184
|
-
sameStamp(a.mailbox, b.mailbox)
|
|
189
|
+
sameStamp(a.mailbox, b.mailbox) &&
|
|
190
|
+
sameStamp(a.plans, b.plans)
|
|
185
191
|
);
|
|
186
192
|
}
|
|
187
193
|
|
|
188
|
-
function readTasks(tasksPath: string): TeamTaskState[] {
|
|
189
|
-
try {
|
|
190
|
-
const parsed = JSON.parse(fs.readFileSync(tasksPath, "utf-8")) as unknown;
|
|
191
|
-
return Array.isArray(parsed) ? (parsed as TeamTaskState[]) : [];
|
|
192
|
-
} catch {
|
|
193
|
-
throw new Error(`Failed to parse tasks at ${tasksPath}`);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
194
|
/** Tail-read JSONL lines from a file, returning parsed objects (limited). */
|
|
198
195
|
function tailJsonlLines<T>(filePath: string, limit: number, parse: (line: string) => T | undefined): T[] {
|
|
199
196
|
if (limit <= 0) return [];
|
|
@@ -711,6 +708,21 @@ function computeSliceSignatures(input: Omit<RunUiSnapshot, "signature" | "fetche
|
|
|
711
708
|
event.data?.reason,
|
|
712
709
|
]),
|
|
713
710
|
),
|
|
711
|
+
...(input.plans
|
|
712
|
+
? {
|
|
713
|
+
// WP-7 (R7): plan writes (revision append / approval flip / item
|
|
714
|
+
// linkage) must invalidate the Plan pane. Plans live outside the
|
|
715
|
+
// stamped files' content — the slice hashes the records directly.
|
|
716
|
+
plans: hash(
|
|
717
|
+
input.plans.map((record) => [
|
|
718
|
+
record.version,
|
|
719
|
+
record.approval?.status,
|
|
720
|
+
record.items.map((item) => [item.id, item.status, item.taskIds]),
|
|
721
|
+
record.phases.map((p) => [p.id, p.status]),
|
|
722
|
+
]),
|
|
723
|
+
),
|
|
724
|
+
}
|
|
725
|
+
: {}),
|
|
714
726
|
};
|
|
715
727
|
}
|
|
716
728
|
|
|
@@ -723,7 +735,21 @@ function signatureFor(
|
|
|
723
735
|
const digest = createHash("sha256");
|
|
724
736
|
digest.update(
|
|
725
737
|
JSON.stringify({
|
|
726
|
-
|
|
738
|
+
// WP-3 (H4): planApproval.status is a RUN-level field surfaced by the
|
|
739
|
+
// widget badge / progress banner / powerbar segment — pending→approved
|
|
740
|
+
// must flip the signature so per-run render caches (run-dashboard keys
|
|
741
|
+
// on snapshot.signature) invalidate. `undefined` serializes as null,
|
|
742
|
+
// keeping the array shape stable for older manifests without the field.
|
|
743
|
+
run: [
|
|
744
|
+
input.manifest.runId,
|
|
745
|
+
input.manifest.status,
|
|
746
|
+
input.manifest.updatedAt,
|
|
747
|
+
input.manifest.artifacts.length,
|
|
748
|
+
input.manifest.planApproval?.status,
|
|
749
|
+
// T2/R4 (ADR-4 §2): a re-plan (revision switch) or record-side
|
|
750
|
+
// approval flip must invalidate per-run render caches too.
|
|
751
|
+
input.manifest.plan?.version ?? null,
|
|
752
|
+
],
|
|
727
753
|
tasks: sliceSignatures.tasks,
|
|
728
754
|
agents: sliceSignatures.agents,
|
|
729
755
|
progress: input.progress,
|
|
@@ -731,6 +757,7 @@ function signatureFor(
|
|
|
731
757
|
mailbox: input.mailbox,
|
|
732
758
|
groupJoins: input.groupJoins,
|
|
733
759
|
events: sliceSignatures.events,
|
|
760
|
+
...(sliceSignatures.plans ? { plans: sliceSignatures.plans } : {}),
|
|
734
761
|
cancellationReason: input.cancellationReason,
|
|
735
762
|
dwfPhaseState: input.dwfPhaseState,
|
|
736
763
|
output: input.recentOutputLines,
|
|
@@ -744,16 +771,8 @@ function signatureFor(
|
|
|
744
771
|
}
|
|
745
772
|
}
|
|
746
773
|
|
|
747
|
-
/**
|
|
748
|
-
* 1.6 / 1.7 — compute one short hash per logical slice of the snapshot so
|
|
749
|
-
* dashboard panes / widget can short-circuit when their slice hasn't moved.
|
|
750
|
-
* The slice contents must mirror what `signatureFor` packs into each branch.
|
|
751
|
-
*/
|
|
752
|
-
function sliceSignaturesFor(sliceSignatures: SliceSignatures): SliceSignatures {
|
|
753
|
-
return sliceSignatures;
|
|
754
|
-
}
|
|
755
|
-
|
|
756
774
|
function stampsFor(manifest: TeamRunManifest, _agents: CrewAgentRecord[]): SnapshotStamps {
|
|
775
|
+
// WP-7: optional plans stamp — one extra statSync, flag-gated.
|
|
757
776
|
// 1.4: use events sequence file instead of stat-ing the events log directly.
|
|
758
777
|
// 1.5: drop per-agent output.log stamping; rely on event-bus invalidation
|
|
759
778
|
// (`crew.subagent.*` and stream events) and on agents.json mtime which
|
|
@@ -765,6 +784,7 @@ function stampsFor(manifest: TeamRunManifest, _agents: CrewAgentRecord[]): Snaps
|
|
|
765
784
|
agents: stampFile(agentsPath(manifest)),
|
|
766
785
|
events: eventsStamp(manifest.eventsPath),
|
|
767
786
|
mailbox: mailboxStamp(manifest),
|
|
787
|
+
...(isPlanUiEnabled() ? { plans: stampFile(planFilePath(manifest)) } : {}),
|
|
768
788
|
};
|
|
769
789
|
}
|
|
770
790
|
|
|
@@ -834,7 +854,12 @@ export function createRunSnapshotCache(cwd: string, options: RunSnapshotCacheOpt
|
|
|
834
854
|
let tasks: TeamTaskState[];
|
|
835
855
|
let agents: CrewAgentRecord[];
|
|
836
856
|
try {
|
|
837
|
-
|
|
857
|
+
// R10-4 (docs/refactor-plan.review.md §ROUND 10): sync/async parity.
|
|
858
|
+
// loadRunManifestById already returns tasks validated against the
|
|
859
|
+
// current tasks.json (mtime+size+generation check in state-store),
|
|
860
|
+
// so the old readTasks() re-read only doubled tasks.json I/O per
|
|
861
|
+
// sync rebuild. buildAsync() has always used loaded.tasks.
|
|
862
|
+
tasks = loaded.tasks;
|
|
838
863
|
agents = readCrewAgents(loaded.manifest);
|
|
839
864
|
} catch {
|
|
840
865
|
if (previous) return previous;
|
|
@@ -857,6 +882,7 @@ export function createRunSnapshotCache(cwd: string, options: RunSnapshotCacheOpt
|
|
|
857
882
|
dwfPhaseState: extractDwfPhaseState(recentEvents),
|
|
858
883
|
recentEvents,
|
|
859
884
|
recentOutputLines: recentOutputLines(loaded.manifest, agents, recentOutputLimit),
|
|
885
|
+
...(isPlanUiEnabled() ? { plans: loadPlanRecords(loaded.manifest) } : {}),
|
|
860
886
|
};
|
|
861
887
|
const stamps = stampsFor(loaded.manifest, agents);
|
|
862
888
|
const sliceSignatures = computeSliceSignatures(base);
|
|
@@ -915,6 +941,7 @@ export function createRunSnapshotCache(cwd: string, options: RunSnapshotCacheOpt
|
|
|
915
941
|
dwfPhaseState: extractDwfPhaseState(recentEvents),
|
|
916
942
|
recentEvents,
|
|
917
943
|
recentOutputLines: recentOutput,
|
|
944
|
+
...(isPlanUiEnabled() ? { plans: loadPlanRecords(loaded.manifest) } : {}),
|
|
918
945
|
};
|
|
919
946
|
const stamps = await stampsForAsync(loaded.manifest, agents);
|
|
920
947
|
const sliceSignatures = computeSliceSignatures(base);
|
package/src/ui/snapshot-types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CrewAgentRecord } from "../runtime/crew-agent-runtime.ts";
|
|
2
2
|
import type { TeamEvent } from "../state/event-log/event-log.ts";
|
|
3
|
-
import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
|
|
3
|
+
import type { PlanRecord, TeamRunManifest, TeamTaskState } from "../state/types.ts";
|
|
4
4
|
import type { DwfPhaseState } from "./dwf-phase-display.ts";
|
|
5
5
|
|
|
6
6
|
export interface RunUiProgress {
|
|
@@ -64,6 +64,8 @@ export interface RunUiSnapshot {
|
|
|
64
64
|
mailbox: string;
|
|
65
65
|
progress: string;
|
|
66
66
|
events: string;
|
|
67
|
+
/** WP-7 (R7): present only when the plans slice is loaded (flag on). */
|
|
68
|
+
plans?: string;
|
|
67
69
|
};
|
|
68
70
|
manifest: TeamRunManifest;
|
|
69
71
|
tasks: TeamTaskState[];
|
|
@@ -76,6 +78,9 @@ export interface RunUiSnapshot {
|
|
|
76
78
|
cancellationReason?: string;
|
|
77
79
|
/** DWF phase state derived from `recentEvents`. Null/absent for non-DWF runs. */
|
|
78
80
|
dwfPhaseState?: DwfPhaseState | null;
|
|
81
|
+
/** WP-7 (R7): plan records for the Plan pane — populated ONLY when
|
|
82
|
+
* PI_CREW_PLAN_UI=1 (flag-off → field absent, zero extra I/O). */
|
|
83
|
+
plans?: PlanRecord[];
|
|
79
84
|
recentEvents: TeamEvent[];
|
|
80
85
|
recentOutputLines: string[];
|
|
81
86
|
}
|
package/src/ui/widget/index.ts
CHANGED
|
@@ -11,6 +11,9 @@ import { DEFAULT_UI } from "../../config/defaults.ts";
|
|
|
11
11
|
import type { ManifestCache } from "../../runtime/manifest-cache.ts";
|
|
12
12
|
import type { TeamRunManifest } from "../../state/types.ts";
|
|
13
13
|
import { truncate } from "../../utils/visual.ts";
|
|
14
|
+
import { isFooterDockSinkActive, setFooterDockProvider } from "../dock-footer.ts";
|
|
15
|
+
import { panelRowsFromRuns } from "../inline-panel/panel-rows.ts";
|
|
16
|
+
import { panelDisplayState, setPanelRowsProvider, subscribePanelChange } from "../inline-panel/panel-store.ts";
|
|
14
17
|
import { requestRender, requestRenderTarget, setExtensionWidget } from "../pi-ui-compat.ts";
|
|
15
18
|
import type { OverlaySchedulerHandle } from "../shared-overlay-scheduler.ts";
|
|
16
19
|
import { registerOverlayScheduler } from "../shared-overlay-scheduler.ts";
|
|
@@ -18,6 +21,7 @@ import type { RunSnapshotCache } from "../snapshot-types.ts";
|
|
|
18
21
|
import { spinnerBucket, spinnerFrame } from "../spinner.ts";
|
|
19
22
|
import type { CrewTheme } from "../theme-adapter.ts";
|
|
20
23
|
import { asCrewTheme, subscribeThemeChange } from "../theme-adapter.ts";
|
|
24
|
+
import { buildTaskListLines } from "./task-list.ts";
|
|
21
25
|
import { activeWidgetRuns, statusSummary } from "./widget-model.ts";
|
|
22
26
|
import { buildWidgetLines, colorWidgetLine, DEFAULT_WIDGET_WIDTH, renderLines } from "./widget-renderer.ts";
|
|
23
27
|
import type { CrewWidgetModel, CrewWidgetState, WidgetRun } from "./widget-types.ts";
|
|
@@ -62,6 +66,8 @@ export {
|
|
|
62
66
|
const MAX_LINES_DEFAULT = DEFAULT_UI.widgetMaxLines;
|
|
63
67
|
const LEGACY_WIDGET_KEY = "pi-crew";
|
|
64
68
|
const WIDGET_KEY = "pi-crew-active";
|
|
69
|
+
/** The run's plan progress, painted ABOVE the editor (task-list.ts). */
|
|
70
|
+
const TASKS_WIDGET_KEY = "pi-crew-tasks";
|
|
65
71
|
const STATUS_KEY = "pi-crew";
|
|
66
72
|
|
|
67
73
|
/**
|
|
@@ -78,11 +84,12 @@ const SIGNATURE_CACHE_TTL_MS = 100;
|
|
|
78
84
|
// next invalidate; a mid-run resize could briefly paint a frame at the old
|
|
79
85
|
// width. We register ONE debounced process-level listener (guarded so it never
|
|
80
86
|
// accumulates across widget reinstalls) that busts the active widget's cache
|
|
81
|
-
// and pokes Pi to repaint at the new width. The listener references
|
|
82
|
-
// module-level `
|
|
83
|
-
//
|
|
87
|
+
// and pokes Pi to repaint at the new width. The listener references the
|
|
88
|
+
// module-level `activeResizeTargets` set (every mounted widget) rather than
|
|
89
|
+
// specific instances, so replaced widgets do not leak listeners.
|
|
84
90
|
let resizeListenerInstalled = false;
|
|
85
|
-
|
|
91
|
+
/** All mounted widgets (dock + task list) — every one gets resize-busted. */
|
|
92
|
+
const activeResizeTargets = new Set<{ invalidate(): void; requestRepaint(): void }>();
|
|
86
93
|
let resizeTimer: ReturnType<typeof setTimeout> | undefined;
|
|
87
94
|
|
|
88
95
|
/**
|
|
@@ -95,8 +102,10 @@ const onResize = (): void => {
|
|
|
95
102
|
// Debounce (~120ms) so a drag-resize doesn't thrash renders.
|
|
96
103
|
resizeTimer = setTimeout(() => {
|
|
97
104
|
resizeTimer = undefined;
|
|
98
|
-
|
|
99
|
-
|
|
105
|
+
for (const target of activeResizeTargets) {
|
|
106
|
+
target.invalidate();
|
|
107
|
+
target.requestRepaint();
|
|
108
|
+
}
|
|
100
109
|
}, 120);
|
|
101
110
|
};
|
|
102
111
|
|
|
@@ -134,7 +143,7 @@ export function uninstallResizeListener(): void {
|
|
|
134
143
|
clearTimeout(resizeTimer);
|
|
135
144
|
resizeTimer = undefined;
|
|
136
145
|
}
|
|
137
|
-
|
|
146
|
+
activeResizeTargets.clear();
|
|
138
147
|
process.off("SIGWINCH", onResize);
|
|
139
148
|
// Windows has no SIGWINCH; Node emits "resize" on stdout instead.
|
|
140
149
|
// Remove via whichever API is present. The listener was only registered
|
|
@@ -159,6 +168,8 @@ interface WidgetComponent extends CrewComponent {}
|
|
|
159
168
|
class CrewWidgetComponent implements WidgetComponent {
|
|
160
169
|
private readonly model: CrewWidgetModel;
|
|
161
170
|
private theme: CrewTheme;
|
|
171
|
+
/** Which surface this instance paints: the agent dock, or the task list. */
|
|
172
|
+
private readonly variant: "dock" | "tasks";
|
|
162
173
|
private cacheSignature = "";
|
|
163
174
|
/** C4 — invalidate-on-write cache for the buildSignature() result. */
|
|
164
175
|
private cachedBuildSignature = "";
|
|
@@ -169,10 +180,12 @@ class CrewWidgetComponent implements WidgetComponent {
|
|
|
169
180
|
private cachedTheme: CrewTheme;
|
|
170
181
|
private readonly tui: unknown;
|
|
171
182
|
private readonly unsubscribeTheme: () => void;
|
|
183
|
+
private readonly unsubscribePanel: () => void;
|
|
172
184
|
private readonly schedulerHandle: OverlaySchedulerHandle;
|
|
173
185
|
|
|
174
|
-
constructor(model: CrewWidgetModel, themeLike: unknown, tui?: unknown) {
|
|
186
|
+
constructor(model: CrewWidgetModel, themeLike: unknown, tui?: unknown, variant: "dock" | "tasks" = "dock") {
|
|
175
187
|
this.model = model;
|
|
188
|
+
this.variant = variant;
|
|
176
189
|
this.theme = asCrewTheme(themeLike);
|
|
177
190
|
this.cachedTheme = this.theme;
|
|
178
191
|
this.tui = tui;
|
|
@@ -180,9 +193,16 @@ class CrewWidgetComponent implements WidgetComponent {
|
|
|
180
193
|
// terminal-resize listener is installed. On a resize the cached width
|
|
181
194
|
// goes stale; busting the cache + requesting a repaint refreshes the
|
|
182
195
|
// widget at the new width without waiting for the next event tick (T-2).
|
|
183
|
-
|
|
196
|
+
activeResizeTargets.add(this);
|
|
184
197
|
installResizeListener();
|
|
185
198
|
this.unsubscribeTheme = subscribeThemeChange(themeLike, () => this.invalidate());
|
|
199
|
+
// Cursor movement is a keypress, not a run event, so it never reaches the
|
|
200
|
+
// shared scheduler. Repaint directly instead of waiting for the next host
|
|
201
|
+
// tick — a lagging cursor reads as a dropped keystroke.
|
|
202
|
+
this.unsubscribePanel = subscribePanelChange(() => {
|
|
203
|
+
this.invalidate();
|
|
204
|
+
this.requestRepaint();
|
|
205
|
+
});
|
|
186
206
|
// 1.10 (UI-P1-1): route run:state / worker:lifecycle / ui:invalidate
|
|
187
207
|
// through a RenderScheduler (debounce + fallback) instead of three
|
|
188
208
|
// direct runEventBus.onChannel subscriptions. With 3 overlays
|
|
@@ -265,8 +285,9 @@ class CrewWidgetComponent implements WidgetComponent {
|
|
|
265
285
|
|
|
266
286
|
dispose(): void {
|
|
267
287
|
this.unsubscribeTheme();
|
|
288
|
+
this.unsubscribePanel();
|
|
268
289
|
this.schedulerHandle.dispose();
|
|
269
|
-
|
|
290
|
+
activeResizeTargets.delete(this);
|
|
270
291
|
}
|
|
271
292
|
|
|
272
293
|
render(width: number): string[] {
|
|
@@ -295,7 +316,35 @@ class CrewWidgetComponent implements WidgetComponent {
|
|
|
295
316
|
const signature = `${sigBase}:${this.model.notificationCount ?? 0}`;
|
|
296
317
|
const runningGlyph = spinnerFrame("widget-header");
|
|
297
318
|
|
|
298
|
-
|
|
319
|
+
// Task-list variant: the run's plan above the editor (task-list.ts).
|
|
320
|
+
// No panel state, no spinner glyph — the list changes only on task
|
|
321
|
+
// transitions, which the run signature already covers.
|
|
322
|
+
if (this.variant === "tasks") {
|
|
323
|
+
if (this.cacheSignature !== signature || width !== this.cachedWidth || this.cachedTheme !== this.theme) {
|
|
324
|
+
this.cachedBaseLines = buildTaskListLines(runs, width);
|
|
325
|
+
this.cachedLines = this.colorize(this.cachedBaseLines, width);
|
|
326
|
+
this.cachedWidth = width;
|
|
327
|
+
this.cachedTheme = this.theme;
|
|
328
|
+
this.cacheSignature = signature;
|
|
329
|
+
}
|
|
330
|
+
if (runs.length === 0) {
|
|
331
|
+
this.invalidate();
|
|
332
|
+
return [];
|
|
333
|
+
}
|
|
334
|
+
return this.cachedLines.map((line) => truncate(line, width));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Panel cursor/pane state is part of the rendered output, so it belongs in
|
|
338
|
+
// the cache key — otherwise moving the cursor would not repaint.
|
|
339
|
+
const panel = panelDisplayState();
|
|
340
|
+
const signatureWithPanel = `${signature}|panel:${panel.selectedTaskId ?? ""}/${panel.viewedTaskId ?? ""}/${panel.focused ? 1 : 0}`;
|
|
341
|
+
|
|
342
|
+
// The spinner-frame swap only belongs on the LEGACY header, whose line 0
|
|
343
|
+
// already starts with a glyph position (`<frame> Crew agents …`). The
|
|
344
|
+
// compact dock's line 0 is the HINT text ("agents (N) — ↓ to select"):
|
|
345
|
+
// swapping would visibly eat its first character on every frame.
|
|
346
|
+
const compactDock = this.model.rowStyle === "compact";
|
|
347
|
+
if (this.cacheSignature !== signatureWithPanel || width !== this.cachedWidth || this.cachedTheme !== this.theme) {
|
|
299
348
|
this.cachedBaseLines = buildWidgetLines(
|
|
300
349
|
this.model.cwd,
|
|
301
350
|
0,
|
|
@@ -303,14 +352,15 @@ class CrewWidgetComponent implements WidgetComponent {
|
|
|
303
352
|
runs,
|
|
304
353
|
this.model.notificationCount ?? 0,
|
|
305
354
|
width,
|
|
355
|
+
{ rowStyle: this.model.rowStyle, ...panel },
|
|
306
356
|
).map((line, index) => {
|
|
307
|
-
if (index === 0 && line.length > 0) return `${runningGlyph}${line.slice(1)}`;
|
|
357
|
+
if (!compactDock && index === 0 && line.length > 0) return `${runningGlyph}${line.slice(1)}`;
|
|
308
358
|
return line;
|
|
309
359
|
});
|
|
310
360
|
this.cachedLines = this.colorize(this.cachedBaseLines, width);
|
|
311
361
|
this.cachedWidth = width;
|
|
312
362
|
this.cachedTheme = this.theme;
|
|
313
|
-
this.cacheSignature =
|
|
363
|
+
this.cacheSignature = signatureWithPanel;
|
|
314
364
|
}
|
|
315
365
|
|
|
316
366
|
if (runs.length === 0) {
|
|
@@ -323,12 +373,43 @@ class CrewWidgetComponent implements WidgetComponent {
|
|
|
323
373
|
return [];
|
|
324
374
|
}
|
|
325
375
|
|
|
326
|
-
|
|
327
|
-
|
|
376
|
+
if (!compactDock) {
|
|
377
|
+
const updatedHeader = `${runningGlyph}${this.cachedBaseLines[0]?.slice(1) ?? ""}`;
|
|
378
|
+
this.cachedLines[0] = truncate(colorWidgetLine(updatedHeader, 0, this.theme), width);
|
|
379
|
+
}
|
|
328
380
|
return this.cachedLines.map((line) => truncate(line, width));
|
|
329
381
|
}
|
|
330
382
|
}
|
|
331
383
|
|
|
384
|
+
// ── Footer dock host (widgetPlacement: "bottom") ──────────────────────
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Dock host for `widgetPlacement: "bottom"`. Keeps a single CrewWidgetComponent
|
|
388
|
+
* (theme-less → raw lines, no ANSI) per session and feeds its render output to
|
|
389
|
+
* the crew-vibes footer through the dock-footer registry. The footer colors the
|
|
390
|
+
* lines with ITS OWN theme so the dock matches the footer context. All caching,
|
|
391
|
+
* event wiring (panel changes, run events, resize) lives in the wrapped
|
|
392
|
+
* component; the footer is re-rendered by pi on every host repaint.
|
|
393
|
+
*/
|
|
394
|
+
class FooterDockHost {
|
|
395
|
+
private component: CrewWidgetComponent | undefined;
|
|
396
|
+
private readonly model: CrewWidgetModel;
|
|
397
|
+
|
|
398
|
+
constructor(model: CrewWidgetModel) {
|
|
399
|
+
this.model = model;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
render(width: number): string[] {
|
|
403
|
+
if (!this.component) this.component = new CrewWidgetComponent(this.model, undefined, undefined);
|
|
404
|
+
return this.component.render(width);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
dispose(): void {
|
|
408
|
+
this.component?.dispose();
|
|
409
|
+
this.component = undefined;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
332
413
|
// ── Re-export listLiveAgents for buildSignature ───────────────────────
|
|
333
414
|
|
|
334
415
|
import { listLiveAgents } from "../../runtime/live-session/live-agent-manager.ts";
|
|
@@ -355,20 +436,39 @@ export function updateCrewWidget(
|
|
|
355
436
|
}
|
|
356
437
|
|
|
357
438
|
const runs = activeWidgetRuns(ctx.cwd, manifestCache, snapshotCache, preloadedManifests, workspaceId);
|
|
358
|
-
const
|
|
439
|
+
const rowStyle = config?.widgetRowStyle ?? DEFAULT_UI.widgetRowStyle;
|
|
440
|
+
// The inline panel navigates the same run list the widget paints, and this is
|
|
441
|
+
// the only place already holding the manifest/snapshot caches — so the row
|
|
442
|
+
// projection is registered here instead of re-reading state on every keypress.
|
|
443
|
+
setPanelRowsProvider(() => panelRowsFromRuns(activeWidgetRuns(ctx.cwd, manifestCache, snapshotCache, preloadedManifests, workspaceId)));
|
|
444
|
+
const lines = buildWidgetLines(ctx.cwd, state.frame, maxLines, runs, state.notificationCount ?? 0, getRenderWidth(), {
|
|
445
|
+
rowStyle,
|
|
446
|
+
...panelDisplayState(),
|
|
447
|
+
});
|
|
359
448
|
const placement = config?.widgetPlacement ?? DEFAULT_UI.widgetPlacement;
|
|
449
|
+
// `bottom` is not a pi widget slot: the dock then renders inside the
|
|
450
|
+
// crew-vibes footer (dock-footer registry). pi's slot calls always use a
|
|
451
|
+
// real slot so legacy-clear/installs stay on maps pi understands.
|
|
452
|
+
const bottomMode = placement === "bottom";
|
|
453
|
+
const dockInFooter = bottomMode && isFooterDockSinkActive();
|
|
454
|
+
const piPlacement: "aboveEditor" | "belowEditor" = bottomMode ? "belowEditor" : placement;
|
|
360
455
|
|
|
361
456
|
ctx.ui.setStatus(STATUS_KEY, lines.length ? statusSummary(runs) : undefined);
|
|
362
457
|
|
|
363
458
|
const shouldClearLegacy = state.legacyCleared !== true || state.lastPlacement !== placement;
|
|
364
459
|
if (shouldClearLegacy) {
|
|
365
|
-
setExtensionWidget(ctx, LEGACY_WIDGET_KEY, undefined, { placement });
|
|
460
|
+
setExtensionWidget(ctx, LEGACY_WIDGET_KEY, undefined, { placement: piPlacement });
|
|
366
461
|
state.legacyCleared = true;
|
|
367
462
|
}
|
|
368
463
|
|
|
369
464
|
if (!lines.length) {
|
|
370
465
|
if (state.lastVisibility !== "hidden" || state.lastPlacement !== placement) {
|
|
371
|
-
setExtensionWidget(ctx, WIDGET_KEY, undefined, { placement });
|
|
466
|
+
setExtensionWidget(ctx, WIDGET_KEY, undefined, { placement: piPlacement });
|
|
467
|
+
setExtensionWidget(ctx, TASKS_WIDGET_KEY, undefined, { placement: "aboveEditor" });
|
|
468
|
+
state.lastTasksVisibility = "hidden";
|
|
469
|
+
state.footerDock?.dispose();
|
|
470
|
+
state.footerDock = undefined;
|
|
471
|
+
setFooterDockProvider(undefined);
|
|
372
472
|
state.lastVisibility = "hidden";
|
|
373
473
|
state.lastPlacement = placement;
|
|
374
474
|
state.lastKey = WIDGET_KEY;
|
|
@@ -398,6 +498,7 @@ export function updateCrewWidget(
|
|
|
398
498
|
snapshotCache,
|
|
399
499
|
preloadManifests: preloadedManifests,
|
|
400
500
|
workspaceId,
|
|
501
|
+
rowStyle,
|
|
401
502
|
};
|
|
402
503
|
else {
|
|
403
504
|
state.model.cwd = ctx.cwd;
|
|
@@ -408,12 +509,33 @@ export function updateCrewWidget(
|
|
|
408
509
|
state.model.snapshotCache = snapshotCache;
|
|
409
510
|
state.model.preloadManifests = preloadedManifests;
|
|
410
511
|
state.model.workspaceId = workspaceId;
|
|
512
|
+
state.model.rowStyle = rowStyle;
|
|
411
513
|
}
|
|
412
514
|
|
|
413
|
-
if (
|
|
515
|
+
if (dockInFooter) {
|
|
516
|
+
// Keep pi's widget slot free: the crew-vibes footer paints the dock at
|
|
517
|
+
// the very bottom, below the quota/meter lines. A widget-slot install
|
|
518
|
+
// from a PREVIOUS placement (or a sink that was just enabled) must be
|
|
519
|
+
// removed first.
|
|
520
|
+
if (needsWidgetInstall && state.lastKey === WIDGET_KEY) {
|
|
521
|
+
setExtensionWidget(ctx, WIDGET_KEY, undefined, { placement: piPlacement });
|
|
522
|
+
}
|
|
523
|
+
if (!state.footerDock) state.footerDock = new FooterDockHost(state.model);
|
|
524
|
+
setFooterDockProvider((width) => state.footerDock!.render(width));
|
|
525
|
+
} else {
|
|
526
|
+
// Widget-slot path (aboveEditor/belowEditor, or no footer sink for
|
|
527
|
+
// "bottom"): ensure any stale footer dock is detached first.
|
|
528
|
+
if (state.footerDock) {
|
|
529
|
+
state.footerDock.dispose();
|
|
530
|
+
state.footerDock = undefined;
|
|
531
|
+
}
|
|
532
|
+
setFooterDockProvider(undefined);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
if (needsWidgetInstall && !dockInFooter) {
|
|
414
536
|
const model = state.model;
|
|
415
537
|
setExtensionWidget(ctx, WIDGET_KEY, ((_tui: unknown, theme: unknown) => new CrewWidgetComponent(model, theme, _tui)) as never, {
|
|
416
|
-
placement,
|
|
538
|
+
placement: piPlacement,
|
|
417
539
|
persist: true,
|
|
418
540
|
});
|
|
419
541
|
state.lastVisibility = "visible";
|
|
@@ -421,6 +543,32 @@ export function updateCrewWidget(
|
|
|
421
543
|
state.lastKey = WIDGET_KEY;
|
|
422
544
|
state.lastMaxLines = maxLines;
|
|
423
545
|
state.lastCwd = ctx.cwd;
|
|
546
|
+
} else if (dockInFooter) {
|
|
547
|
+
state.lastVisibility = "visible";
|
|
548
|
+
state.lastPlacement = placement;
|
|
549
|
+
state.lastKey = WIDGET_KEY;
|
|
550
|
+
state.lastMaxLines = maxLines;
|
|
551
|
+
state.lastCwd = ctx.cwd;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Task list (aboveEditor): the run's plan progress — Claude Code / droid
|
|
555
|
+
// style, independent of the dock's placement (so it also shows when the
|
|
556
|
+
// dock renders in the crew-vibes footer). Installed once while any
|
|
557
|
+
// display-active run exists; the component paints nothing until a run
|
|
558
|
+
// carries a tasks slice.
|
|
559
|
+
const tasksVisible = runs.length > 0 && Boolean(state.model);
|
|
560
|
+
if (tasksVisible && state.lastTasksVisibility !== "visible") {
|
|
561
|
+
const model = state.model;
|
|
562
|
+
setExtensionWidget(
|
|
563
|
+
ctx,
|
|
564
|
+
TASKS_WIDGET_KEY,
|
|
565
|
+
((_tui: unknown, theme: unknown) => new CrewWidgetComponent(model, theme, _tui, "tasks")) as never,
|
|
566
|
+
{ placement: "aboveEditor" },
|
|
567
|
+
);
|
|
568
|
+
state.lastTasksVisibility = "visible";
|
|
569
|
+
} else if (!tasksVisible && state.lastTasksVisibility === "visible") {
|
|
570
|
+
setExtensionWidget(ctx, TASKS_WIDGET_KEY, undefined, { placement: "aboveEditor" });
|
|
571
|
+
state.lastTasksVisibility = "hidden";
|
|
424
572
|
}
|
|
425
573
|
|
|
426
574
|
requestRender(ctx);
|
|
@@ -436,9 +584,15 @@ export function stopCrewWidget(
|
|
|
436
584
|
uninstallResizeListener();
|
|
437
585
|
if (ctx?.hasUI) {
|
|
438
586
|
const placement = config?.widgetPlacement ?? DEFAULT_UI.widgetPlacement;
|
|
587
|
+
const piPlacement: "aboveEditor" | "belowEditor" = placement === "bottom" ? "belowEditor" : placement;
|
|
439
588
|
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
440
|
-
setExtensionWidget(ctx, LEGACY_WIDGET_KEY, undefined, { placement });
|
|
441
|
-
setExtensionWidget(ctx, WIDGET_KEY, undefined, { placement });
|
|
589
|
+
setExtensionWidget(ctx, LEGACY_WIDGET_KEY, undefined, { placement: piPlacement });
|
|
590
|
+
setExtensionWidget(ctx, WIDGET_KEY, undefined, { placement: piPlacement });
|
|
591
|
+
setExtensionWidget(ctx, TASKS_WIDGET_KEY, undefined, { placement: "aboveEditor" });
|
|
592
|
+
state.lastTasksVisibility = "hidden";
|
|
593
|
+
state.footerDock?.dispose();
|
|
594
|
+
state.footerDock = undefined;
|
|
595
|
+
setFooterDockProvider(undefined);
|
|
442
596
|
state.lastVisibility = "hidden";
|
|
443
597
|
state.lastPlacement = placement;
|
|
444
598
|
state.lastKey = WIDGET_KEY;
|