pi-crew 0.9.55 → 0.9.57
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/AGENTS.md +1 -1
- package/CHANGELOG.md +58 -0
- package/README.md +53 -5
- package/dist/index.mjs +5666 -4370
- package/docs/README.md +54 -0
- package/package.json +12 -13
- package/skills/real-test-pi-crew/SKILL.md +93 -3
- package/src/agents/discover-agents.ts +68 -10
- package/src/benchmark/feedback-loop.ts +1 -1
- package/src/config/config.ts +2 -1
- package/src/config/types.ts +12 -0
- package/src/extension/action-suggestions.ts +16 -63
- package/src/extension/async-notifier.ts +3 -3
- package/src/extension/command-completions.ts +2 -2
- package/src/extension/crew-cleanup.ts +3 -3
- package/src/extension/crew-vibes/footer.ts +4 -2
- package/src/extension/cross-extension-rpc.ts +10 -5
- package/src/extension/knowledge-injection.ts +57 -10
- package/src/extension/register.ts +2 -2
- package/src/extension/registration/artifact-cleanup.ts +41 -3
- package/src/extension/registration/commands.ts +4 -4
- package/src/extension/registration/context-builder.ts +10 -10
- package/src/extension/registration/crash-recovery-cache.ts +3 -3
- package/src/extension/registration/foreground-run-controller.ts +1 -1
- package/src/extension/registration/lazy-configurers.ts +2 -2
- package/src/extension/registration/lifecycle-handlers.ts +12 -12
- package/src/extension/registration/lifecycle.ts +2 -2
- package/src/extension/registration/observability.ts +2 -2
- package/src/extension/registration/registration-types.ts +3 -3
- package/src/extension/registration/runtime-cleanup.ts +1 -1
- package/src/extension/registration/subagent-helpers.ts +2 -2
- package/src/extension/registration/subagent-manager-setup.ts +2 -2
- package/src/extension/registration/subagent-tools.ts +21 -16
- package/src/extension/registration/team-tool.ts +109 -17
- package/src/extension/registration/viewers.ts +3 -4
- package/src/extension/registration/wire-cross-extension.ts +7 -7
- package/src/extension/rpc-hmac.ts +3 -2
- package/src/extension/run-bundle-schema.ts +13 -4
- package/src/extension/run-export.ts +2 -2
- package/src/extension/run-index.ts +2 -3
- package/src/extension/run-maintenance.ts +57 -1
- package/src/extension/team-manager-command.ts +3 -1
- package/src/extension/team-tool/anchor.ts +8 -2
- package/src/extension/team-tool/api.ts +117 -39
- package/src/extension/team-tool/cancel.ts +23 -8
- package/src/extension/team-tool/chain-executor.ts +1 -1
- package/src/extension/team-tool/dispatch/automate.ts +17 -2
- package/src/extension/team-tool/dispatch/control.ts +17 -2
- package/src/extension/team-tool/dispatch/manage.ts +34 -2
- package/src/extension/team-tool/dispatch/run.ts +17 -2
- package/src/extension/team-tool/dispatch/status.ts +37 -5
- package/src/extension/team-tool/doctor.ts +2 -2
- package/src/extension/team-tool/explain.ts +1 -1
- package/src/extension/team-tool/goal-wrap.ts +5 -5
- package/src/extension/team-tool/goal.ts +30 -9
- package/src/extension/team-tool/handle-schedule.ts +37 -19
- package/src/extension/team-tool/inspect.ts +21 -5
- package/src/extension/team-tool/lifecycle-actions.ts +53 -10
- package/src/extension/team-tool/parallel-dispatch.ts +14 -5
- package/src/extension/team-tool/param-error.ts +138 -0
- package/src/extension/team-tool/respond.ts +20 -6
- package/src/extension/team-tool/run.ts +27 -17
- package/src/extension/team-tool/status.ts +17 -6
- package/src/extension/team-tool.ts +26 -19
- package/src/hooks/registry.ts +1 -1
- package/src/i18n.ts +22 -0
- package/src/observability/event-bus.ts +1 -1
- package/src/prompt/prompt-runtime.ts +2 -2
- package/src/runtime/README.md +35 -0
- package/src/runtime/agent-control.ts +1 -1
- package/src/runtime/anchor-manager.ts +0 -2
- package/src/runtime/async-runner.ts +1 -1
- package/src/runtime/attention-events.ts +3 -2
- package/src/runtime/auto-summarize.ts +0 -2
- package/src/runtime/background-runner.ts +103 -30
- package/src/runtime/{crew-broker-child.ts → broker/crew-broker-child.ts} +2 -2
- package/src/runtime/{crew-broker-client.ts → broker/crew-broker-client.ts} +4 -7
- package/src/runtime/{crew-broker.ts → broker/crew-broker.ts} +13 -21
- package/src/runtime/chain-runner.ts +50 -5
- package/src/runtime/{child-pi-constants.ts → child-pi/child-pi-constants.ts} +1 -1
- package/src/runtime/{child-pi-kill.ts → child-pi/child-pi-kill.ts} +3 -3
- package/src/runtime/{child-pi-spawn.ts → child-pi/child-pi-spawn.ts} +28 -12
- package/src/runtime/{child-pi-steering.ts → child-pi/child-pi-steering.ts} +1 -1
- package/src/runtime/{child-pi-streams.ts → child-pi/child-pi-streams.ts} +23 -4
- package/src/runtime/{child-pi-transcript.ts → child-pi/child-pi-transcript.ts} +6 -6
- package/src/runtime/{child-pi.ts → child-pi/child-pi.ts} +47 -78
- package/src/runtime/compaction/compact-stages/bounded-tail.ts +84 -0
- package/src/runtime/{compact-stages → compaction/compact-stages}/index.ts +2 -2
- package/src/runtime/{compaction-summary.ts → compaction/compaction-summary.ts} +3 -3
- package/src/runtime/{tool-output-pruner.ts → compaction/tool-output-pruner.ts} +1 -1
- package/src/runtime/crew-agent-records.ts +48 -2
- package/src/runtime/custom-tools/irc-tool.ts +2 -2
- package/src/runtime/delivery-coordinator.ts +0 -3
- package/src/runtime/diagnostic-export.ts +5 -4
- package/src/runtime/foreground-control.ts +1 -1
- package/src/runtime/foreground-watchdog.ts +1 -1
- package/src/runtime/{adaptive-plan.ts → goal-workflow/adaptive-plan.ts} +8 -8
- package/src/runtime/{dynamic-workflow-context.ts → goal-workflow/dynamic-workflow-context.ts} +16 -16
- package/src/runtime/{dynamic-workflow-runner.ts → goal-workflow/dynamic-workflow-runner.ts} +10 -10
- package/src/runtime/{goal-achievement.ts → goal-workflow/goal-achievement.ts} +3 -3
- package/src/runtime/{goal-evaluator.ts → goal-workflow/goal-evaluator.ts} +8 -8
- package/src/runtime/{goal-loop-runner.ts → goal-workflow/goal-loop-runner.ts} +27 -25
- package/src/runtime/{goal-state-store.ts → goal-workflow/goal-state-store.ts} +8 -8
- package/src/runtime/{plan-templates.ts → goal-workflow/plan-templates.ts} +1 -1
- package/src/runtime/group-join.ts +3 -3
- package/src/runtime/handoff-manager.ts +0 -2
- package/src/runtime/{heartbeat-watcher.ts → heartbeat/heartbeat-watcher.ts} +7 -7
- package/src/runtime/iteration-hooks.ts +1 -1
- package/src/runtime/{live-agent-control.ts → live-session/live-agent-control.ts} +2 -2
- package/src/runtime/{live-agent-manager.ts → live-session/live-agent-manager.ts} +4 -4
- package/src/runtime/{live-session-runtime.ts → live-session/live-session-runtime.ts} +39 -41
- package/src/runtime/manifest-cache.ts +6 -2
- package/src/runtime/{model-fallback.ts → model/model-fallback.ts} +1 -1
- package/src/runtime/{pi-args.ts → model/pi-args.ts} +20 -5
- package/src/runtime/model/provider-extensions.ts +116 -0
- package/src/runtime/{runtime-policy.ts → model/runtime-policy.ts} +2 -2
- package/src/runtime/{runtime-resolver.ts → model/runtime-resolver.ts} +3 -3
- package/src/runtime/{runtime-warmup.ts → model/runtime-warmup.ts} +8 -8
- package/src/runtime/orphan-worker-registry.ts +1 -1
- package/src/runtime/{progress-tracker.ts → output/progress-tracker.ts} +1 -1
- package/src/runtime/{result-extractor.ts → output/result-extractor.ts} +4 -4
- package/src/runtime/{sidechain-output.ts → output/sidechain-output.ts} +2 -2
- package/src/runtime/{stream-preview.ts → output/stream-preview.ts} +1 -1
- package/src/runtime/{streaming-output.ts → output/streaming-output.ts} +2 -2
- package/src/runtime/{tool-progress.ts → output/tool-progress.ts} +1 -1
- package/src/runtime/parent-guard.ts +30 -10
- package/src/runtime/pipeline-runner.ts +2 -2
- package/src/runtime/policy-engine.ts +2 -2
- package/src/runtime/{cancellation.ts → process/cancellation.ts} +1 -1
- package/src/runtime/{post-exit-stdio-guard.ts → process/post-exit-stdio-guard.ts} +1 -1
- package/src/runtime/{process-lifecycle.ts → process/process-lifecycle.ts} +3 -3
- package/src/runtime/{checkpoint.ts → recovery/checkpoint.ts} +4 -4
- package/src/runtime/{crash-recovery.ts → recovery/crash-recovery.ts} +93 -21
- package/src/runtime/{overflow-recovery.ts → recovery/overflow-recovery.ts} +1 -1
- package/src/runtime/{recovery-recipes.ts → recovery/recovery-recipes.ts} +1 -1
- package/src/runtime/{retry-executor.ts → recovery/retry-executor.ts} +2 -2
- package/src/runtime/{retry-runner.ts → recovery/retry-runner.ts} +2 -8
- package/src/runtime/run-tracker.ts +1 -1
- package/src/runtime/run-worker.ts +3 -3
- package/src/runtime/{coalesce-tasks.ts → scheduling/coalesce-tasks.ts} +3 -3
- package/src/runtime/{concurrency.ts → scheduling/concurrency.ts} +9 -3
- package/src/runtime/{parallel-research.ts → scheduling/parallel-research.ts} +4 -2
- package/src/runtime/{run-coalesced-task-group.ts → scheduling/run-coalesced-task-group.ts} +95 -36
- package/src/runtime/{semaphore.ts → scheduling/semaphore.ts} +4 -1
- package/src/runtime/{task-graph-scheduler.ts → scheduling/task-graph-scheduler.ts} +1 -1
- package/src/runtime/session-resources.ts +1 -1
- package/src/runtime/settings-store.ts +1 -1
- package/src/runtime/single-agent-compose.ts +1 -0
- package/src/runtime/stale-reconciler.ts +1 -1
- package/src/runtime/subagent-manager.ts +1 -1
- package/src/runtime/supervisor-contact.ts +28 -13
- package/src/runtime/task-output-context.ts +7 -5
- package/src/runtime/task-runner/child-executor.ts +67 -31
- package/src/runtime/task-runner/live-executor.ts +8 -8
- package/src/runtime/task-runner/post-execution.ts +12 -12
- package/src/runtime/task-runner/pre-execution.ts +6 -6
- package/src/runtime/task-runner/progress.ts +1 -1
- package/src/runtime/task-runner/run-projection.ts +1 -1
- package/src/runtime/task-runner/scaffold-executor.ts +1 -1
- package/src/runtime/task-runner/state-helpers.ts +2 -2
- package/src/runtime/task-runner.ts +4 -4
- package/src/runtime/team-runner.ts +441 -175
- package/src/runtime/{completion-guard.ts → verification/completion-guard.ts} +1 -1
- package/src/runtime/{green-contract.ts → verification/green-contract.ts} +1 -1
- package/src/runtime/{post-checks.ts → verification/post-checks.ts} +4 -4
- package/src/runtime/{verification-gates.ts → verification/verification-gates.ts} +5 -5
- package/src/runtime/{verification-integrity.ts → verification/verification-integrity.ts} +1 -1
- package/src/schema/config-schema.ts +1 -0
- package/src/schema/team-tool-schema.ts +149 -185
- package/src/state/README.md +44 -0
- package/src/state/atomic-write.ts +24 -3
- package/src/state/{locks.ts → coordination/locks.ts} +118 -38
- package/src/state/{mailbox.ts → coordination/mailbox.ts} +14 -8
- package/src/state/{schedule.ts → coordination/schedule.ts} +3 -3
- package/src/state/{task-claims.ts → coordination/task-claims.ts} +1 -1
- package/src/state/decision-ledger.ts +1 -1
- package/src/state/{event-log-rotation.ts → event-log/event-log-rotation.ts} +189 -31
- package/src/state/{event-log.ts → event-log/event-log.ts} +77 -39
- package/src/state/{jsonl-writer.ts → event-log/jsonl-writer.ts} +14 -6
- package/src/state/hook-instinct-bridge.ts +2 -2
- package/src/state/{active-run-registry.ts → stores/active-run-registry.ts} +7 -7
- package/src/state/{artifact-store.ts → stores/artifact-store.ts} +62 -4
- package/src/state/{blob-store.ts → stores/blob-store.ts} +3 -3
- package/src/state/{health-store.ts → stores/health-store.ts} +3 -3
- package/src/state/{instinct-store.ts → stores/instinct-store.ts} +3 -3
- package/src/state/{observation-store.ts → stores/observation-store.ts} +2 -2
- package/src/state/{run-cache.ts → stores/run-cache.ts} +4 -4
- package/src/state/{run-graph.ts → stores/run-graph.ts} +4 -4
- package/src/state/{run-metrics.ts → stores/run-metrics.ts} +3 -3
- package/src/state/{state-store.ts → stores/state-store.ts} +254 -21
- package/src/state/types.ts +27 -3
- package/src/ui/component.ts +55 -0
- package/src/ui/crew-select-list.ts +3 -1
- package/src/ui/dashboard-panes/agents-pane.ts +1 -1
- package/src/ui/dwf-phase-display.ts +1 -1
- package/src/ui/heartbeat-aggregator.ts +1 -1
- package/src/ui/keybinding-map.ts +179 -6
- package/src/ui/layout-primitives.ts +9 -5
- package/src/ui/live-conversation-overlay.ts +1 -1
- package/src/ui/live-run-sidebar.ts +1 -1
- package/src/ui/overlay-stack.ts +148 -0
- package/src/ui/overlays/agent-picker-overlay.ts +1 -1
- package/src/ui/overlays/mailbox-compose-overlay.ts +1 -1
- package/src/ui/overlays/mailbox-detail-overlay.ts +2 -2
- package/src/ui/powerbar-publisher.ts +1 -1
- package/src/ui/render-scheduler.ts +1 -1
- package/src/ui/run-action-dispatcher.ts +3 -3
- package/src/ui/run-dashboard.ts +5 -33
- package/src/ui/run-event-bus.ts +3 -3
- package/src/ui/run-snapshot-cache.ts +37 -18
- package/src/ui/settings-overlay.ts +13 -21
- package/src/ui/snapshot-types.ts +1 -1
- package/src/ui/status-colors.ts +0 -2
- package/src/ui/terminal-status.ts +1 -1
- package/src/ui/theme-adapter.ts +33 -13
- package/src/ui/tool-renderers/index.ts +8 -0
- package/src/ui/transcript-viewer.ts +37 -13
- package/src/ui/widget/index.ts +32 -9
- package/src/ui/widget/widget-formatters.ts +62 -4
- package/src/ui/widget/widget-model.ts +2 -2
- package/src/ui/widget/widget-renderer.ts +1 -1
- package/src/utils/conflict-detect.ts +58 -5
- package/src/utils/ndjson.ts +2 -2
- package/src/utils/redaction.ts +39 -0
- package/src/utils/socket-path.ts +1 -1
- package/src/workflows/workflow-config.ts +1 -1
- package/src/worktree/cleanup.ts +1 -1
- package/src/worktree/worktree-manager.ts +111 -47
- package/workflows/chain.workflow.md +2 -2
- package/dist/build-meta.json +0 -16807
- package/index.bundle.ts +0 -25
- package/scripts/bench-check.mjs +0 -96
- package/scripts/bench-cold-start.mjs +0 -216
- package/scripts/build-bundle.mjs +0 -92
- package/scripts/check-all-skills.ts +0 -294
- package/scripts/check-bundle-size.mjs +0 -38
- package/scripts/check-bundle-staleness.mjs +0 -97
- package/scripts/check-conflict-markers.mjs +0 -91
- package/scripts/check-lazy-imports.mjs +0 -28
- package/scripts/dev-runner.mjs +0 -106
- package/scripts/profile-startup.mjs +0 -125
- package/scripts/release-smoke.mjs +0 -74
- package/scripts/run-bench.mjs +0 -47
- package/scripts/run-real-chain.ts +0 -41
- package/scripts/test-issue-29-crash.ts +0 -111
- package/scripts/test-issue-29-e2e.ts +0 -183
- package/scripts/test-issue-29-real-runtime.ts +0 -330
- package/scripts/test-issue-29-real-tasks.ts +0 -387
- package/scripts/test-issue-29-team-tool.ts +0 -105
- package/scripts/test-runner.mjs +0 -84
- package/scripts/verify-flicker-fix.ts +0 -109
- package/scripts/verify-skill.ts +0 -550
- package/scripts/watch-bundle.mjs +0 -259
- package/scripts/watchdog-harness.ts +0 -119
- package/src/adapters/claude-adapter.ts +0 -23
- package/src/adapters/codex-adapter.ts +0 -21
- package/src/adapters/cursor-adapter.ts +0 -17
- package/src/adapters/export-util.ts +0 -143
- package/src/adapters/index.ts +0 -15
- package/src/adapters/registry.ts +0 -18
- package/src/adapters/types.ts +0 -23
- package/src/runtime/code-summary.ts +0 -292
- package/src/runtime/cross-extension-rpc.ts +0 -143
- package/src/runtime/hidden-handoff.ts +0 -407
- package/src/runtime/prose-compressor.ts +0 -162
- package/src/subagents/async-entry.ts +0 -1
- package/src/subagents/index.ts +0 -3
- package/src/subagents/live/control.ts +0 -1
- package/src/subagents/live/manager.ts +0 -1
- package/src/subagents/live/realtime.ts +0 -1
- package/src/subagents/live/session-runtime.ts +0 -1
- package/src/subagents/manager.ts +0 -1
- package/src/subagents/spawn.ts +0 -1
- package/test-integration-check.ts +0 -118
- /package/src/runtime/{broker-issuer.ts → broker/broker-issuer.ts} +0 -0
- /package/src/runtime/{crew-broker-tokens.ts → broker/crew-broker-tokens.ts} +0 -0
- /package/src/runtime/{child-pi-pool.ts → child-pi/child-pi-pool.ts} +0 -0
- /package/src/runtime/{compact-pipeline.ts → compaction/compact-pipeline.ts} +0 -0
- /package/src/runtime/{compact-stages → compaction/compact-stages}/ansi-strip-stage.ts +0 -0
- /package/src/runtime/{compact-stages → compaction/compact-stages}/blank-collapse-stage.ts +0 -0
- /package/src/runtime/{compact-stages → compaction/compact-stages}/deduplicate-stage.ts +0 -0
- /package/src/runtime/{compact-stages → compaction/compact-stages}/head-snap-stage.ts +0 -0
- /package/src/runtime/{compact-stages → compaction/compact-stages}/tail-capture-stage.ts +0 -0
- /package/src/runtime/{compact-stages → compaction/compact-stages}/truncation-stage.ts +0 -0
- /package/src/runtime/{important-line-classifier.ts → compaction/important-line-classifier.ts} +0 -0
- /package/src/runtime/{heartbeat-gradient.ts → heartbeat/heartbeat-gradient.ts} +0 -0
- /package/src/runtime/{worker-heartbeat.ts → heartbeat/worker-heartbeat.ts} +0 -0
- /package/src/runtime/{worker-startup.ts → heartbeat/worker-startup.ts} +0 -0
- /package/src/runtime/{intercom-bridge.ts → live-session/intercom-bridge.ts} +0 -0
- /package/src/runtime/{live-control-realtime.ts → live-session/live-control-realtime.ts} +0 -0
- /package/src/runtime/{live-extension-bridge.ts → live-session/live-extension-bridge.ts} +0 -0
- /package/src/runtime/{live-irc.ts → live-session/live-irc.ts} +0 -0
- /package/src/runtime/{live-session-health.ts → live-session/live-session-health.ts} +0 -0
- /package/src/runtime/{model-resolver.ts → model/model-resolver.ts} +0 -0
- /package/src/runtime/{model-scope.ts → model/model-scope.ts} +0 -0
- /package/src/runtime/{output-validator.ts → output/output-validator.ts} +0 -0
- /package/src/runtime/{pi-json-output.ts → output/pi-json-output.ts} +0 -0
- /package/src/runtime/{progress-event-coalescer.ts → output/progress-event-coalescer.ts} +0 -0
- /package/src/runtime/{cancellation-token.ts → process/cancellation-token.ts} +0 -0
- /package/src/runtime/{zombie-scanner.ts → process/zombie-scanner.ts} +0 -0
- /package/src/runtime/{crash-classification.ts → recovery/crash-classification.ts} +0 -0
- /package/src/runtime/{batch-barrier.ts → scheduling/batch-barrier.ts} +0 -0
- /package/src/runtime/{global-worker-cap.ts → scheduling/global-worker-cap.ts} +0 -0
- /package/src/runtime/{parallel-utils.ts → scheduling/parallel-utils.ts} +0 -0
- /package/src/runtime/{scheduler.ts → scheduling/scheduler.ts} +0 -0
- /package/src/runtime/{task-graph.ts → scheduling/task-graph.ts} +0 -0
- /package/src/runtime/{verification-worktree.ts → verification/verification-worktree.ts} +0 -0
- /package/src/state/{event-reconstructor.ts → event-log/event-reconstructor.ts} +0 -0
- /package/src/state/{worker-atomic-writer.ts → event-log/worker-atomic-writer.ts} +0 -0
package/src/ui/keybinding-map.ts
CHANGED
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
* `use-global-shortcuts.ts:38-61`.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
+
import * as fs from "node:fs";
|
|
27
|
+
import * as path from "node:path";
|
|
26
28
|
import { type KeyId, matchesKey } from "@earendil-works/pi-tui";
|
|
27
29
|
import { keyOf } from "./key-utils.ts";
|
|
28
30
|
|
|
@@ -113,7 +115,10 @@ export type DashboardKeyAction =
|
|
|
113
115
|
| "notifications-dismiss";
|
|
114
116
|
|
|
115
117
|
/**
|
|
116
|
-
* The dispatch table. ORDER MATTERS — first match wins.
|
|
118
|
+
* The default dispatch table. ORDER MATTERS — first match wins. These
|
|
119
|
+
* hardcoded defaults may be overridden per-action via `.crew/config.json`
|
|
120
|
+
* (`keybindings` section) and/or the `PI_CREW_KEYBINDINGS` env var — see
|
|
121
|
+
* `getEffectiveBindings` below.
|
|
117
122
|
*
|
|
118
123
|
* Precedence notes (must match the pre-L2 if-chain exactly):
|
|
119
124
|
* 1. `close` always wins (q / Esc).
|
|
@@ -129,7 +134,7 @@ export type DashboardKeyAction =
|
|
|
129
134
|
* reservation but are handled by the mailbox overlay's own `handleInput`,
|
|
130
135
|
* not by the dashboard dispatch. Adding them here would change behavior.
|
|
131
136
|
*/
|
|
132
|
-
const
|
|
137
|
+
const DEFAULT_BINDINGS: readonly KeyBinding[] = [
|
|
133
138
|
{ keys: DASHBOARD_KEYS.close, action: "close" },
|
|
134
139
|
{ keys: DASHBOARD_KEYS.help, action: "help" },
|
|
135
140
|
{
|
|
@@ -202,13 +207,178 @@ const KEY_RESERVED = new Set<string>([
|
|
|
202
207
|
|
|
203
208
|
export { KEY_RESERVED };
|
|
204
209
|
|
|
210
|
+
// ─── Keybinding overrides (UI-2) ───────────────────────────────────────────
|
|
211
|
+
//
|
|
212
|
+
// The hardcoded DEFAULT_BINDINGS above can be overridden per-action via two
|
|
213
|
+
// layered sources (later wins):
|
|
214
|
+
// 1. `.crew/config.json` → top-level `keybindings` object, e.g.
|
|
215
|
+
// { "keybindings": { "reload": ["z"], "events": ["E"] } }
|
|
216
|
+
// 2. `PI_CREW_KEYBINDINGS` env var — a JSON object string of the same shape
|
|
217
|
+
// (highest precedence; handy for ad-hoc / test overrides).
|
|
218
|
+
//
|
|
219
|
+
// Each entry REPLACES the default key list for that action (the action keeps
|
|
220
|
+
// its original pane scope). Actions not listed keep their defaults, so the
|
|
221
|
+
// parity golden snapshot is unaffected when no override is configured.
|
|
222
|
+
//
|
|
223
|
+
// Collision validation: an overridden key that would clash with another
|
|
224
|
+
// (default or overridden) binding in a compatible pane scope is treated as a
|
|
225
|
+
// collision; the offending action's override is reverted to its default and a
|
|
226
|
+
// warning is recorded (getKeybindingOverrideWarnings). This keeps the
|
|
227
|
+
// first-match-wins dispatch unambiguous — a shadowed override never silently
|
|
228
|
+
// changes behaviour.
|
|
229
|
+
|
|
230
|
+
/** Override map: action → replacement keys. `Partial` ⇒ only listed actions. */
|
|
231
|
+
export type KeybindingOverride = Partial<Record<DashboardKeyAction, readonly string[]>>;
|
|
232
|
+
|
|
233
|
+
const KEYBINDINGS_ENV = "PI_CREW_KEYBINDINGS";
|
|
234
|
+
|
|
235
|
+
/** Every dispatched action is a valid override target. */
|
|
236
|
+
const VALID_OVERRIDE_ACTIONS: ReadonlySet<string> = new Set(DEFAULT_BINDINGS.map((b) => b.action));
|
|
237
|
+
|
|
238
|
+
/** Coerce an unknown parsed value into a safe {@link KeybindingOverride}. */
|
|
239
|
+
function parseKeybindingOverride(raw: unknown): KeybindingOverride {
|
|
240
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
241
|
+
const result: KeybindingOverride = {};
|
|
242
|
+
for (const [action, keys] of Object.entries(raw as Record<string, unknown>)) {
|
|
243
|
+
if (!VALID_OVERRIDE_ACTIONS.has(action)) continue;
|
|
244
|
+
if (!Array.isArray(keys)) continue;
|
|
245
|
+
const clean = keys.filter((k): k is string => typeof k === "string" && k.length > 0);
|
|
246
|
+
if (clean.length > 0) result[action as DashboardKeyAction] = clean;
|
|
247
|
+
}
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Two pane scopes are "compatible" when some `activePane` could make both
|
|
253
|
+
* bindings fire at once (so a shared key is genuinely ambiguous). Global
|
|
254
|
+
* (`undefined`) matches anything; two different concrete panes never overlap.
|
|
255
|
+
*/
|
|
256
|
+
function paneScopesCompatible(a: ActivePane | undefined, b: ActivePane | undefined): boolean {
|
|
257
|
+
if (a === undefined || b === undefined) return true;
|
|
258
|
+
return a === b;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
interface EffectiveBindingsResult {
|
|
262
|
+
readonly bindings: readonly KeyBinding[];
|
|
263
|
+
/** Actions whose override was rejected due to a collision. */
|
|
264
|
+
readonly reverted: readonly DashboardKeyAction[];
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Apply `overrides` onto {@link DEFAULT_BINDINGS} (replace keys per action,
|
|
269
|
+
* preserving each action's pane scope) and detect collisions. A colliding
|
|
270
|
+
* override is reverted to its default so the dispatch stays unambiguous.
|
|
271
|
+
*/
|
|
272
|
+
function computeEffectiveBindings(overrides: KeybindingOverride): EffectiveBindingsResult {
|
|
273
|
+
const applied = new Map<DashboardKeyAction, KeyBinding>();
|
|
274
|
+
for (const def of DEFAULT_BINDINGS) {
|
|
275
|
+
const ov = overrides[def.action];
|
|
276
|
+
applied.set(def.action, ov && ov.length > 0 ? { keys: [...ov], action: def.action, pane: def.pane } : def);
|
|
277
|
+
}
|
|
278
|
+
const effective = [...applied.values()];
|
|
279
|
+
const reverted = new Set<DashboardKeyAction>();
|
|
280
|
+
for (const def of DEFAULT_BINDINGS) {
|
|
281
|
+
const ov = overrides[def.action];
|
|
282
|
+
if (!ov || ov.length === 0) continue; // not overridden
|
|
283
|
+
const ob = applied.get(def.action);
|
|
284
|
+
if (!ob) continue;
|
|
285
|
+
for (const other of effective) {
|
|
286
|
+
if (other.action === def.action) continue;
|
|
287
|
+
if (!paneScopesCompatible(ob.pane, other.pane)) continue;
|
|
288
|
+
if (ob.keys.some((k) => other.keys.includes(k))) {
|
|
289
|
+
reverted.add(def.action);
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
const bindings =
|
|
295
|
+
reverted.size > 0
|
|
296
|
+
? effective.map((b) => (reverted.has(b.action) ? (DEFAULT_BINDINGS.find((d) => d.action === b.action) ?? b) : b))
|
|
297
|
+
: effective;
|
|
298
|
+
return { bindings, reverted: [...reverted] };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** Read the `keybindings` section from `<cwd>/.crew/config.json`. */
|
|
302
|
+
function readConfigKeybindings(cwd: string): KeybindingOverride {
|
|
303
|
+
try {
|
|
304
|
+
const raw: unknown = JSON.parse(fs.readFileSync(path.join(cwd, ".crew", "config.json"), "utf-8"));
|
|
305
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
306
|
+
return parseKeybindingOverride((raw as Record<string, unknown>).keybindings);
|
|
307
|
+
} catch {
|
|
308
|
+
return {};
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** Read the `PI_CREW_KEYBINDINGS` env var (JSON object string). */
|
|
313
|
+
function readEnvKeybindings(): KeybindingOverride {
|
|
314
|
+
const raw = process.env[KEYBINDINGS_ENV];
|
|
315
|
+
if (!raw) return {};
|
|
316
|
+
try {
|
|
317
|
+
return parseKeybindingOverride(JSON.parse(raw));
|
|
318
|
+
} catch {
|
|
319
|
+
return {};
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function configKeybindingsMtime(cwd: string): number | undefined {
|
|
324
|
+
try {
|
|
325
|
+
return fs.statSync(path.join(cwd, ".crew", "config.json")).mtimeMs;
|
|
326
|
+
} catch {
|
|
327
|
+
return undefined;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
interface EffectiveCache {
|
|
332
|
+
readonly env: string | undefined;
|
|
333
|
+
readonly configMtime: number | undefined;
|
|
334
|
+
readonly cwd: string;
|
|
335
|
+
readonly bindings: readonly KeyBinding[];
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
let _effectiveCache: EffectiveCache | null = null;
|
|
339
|
+
let _overrideWarnings: readonly string[] = [];
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Resolve the effective dispatch table: {@link DEFAULT_BINDINGS} with config +
|
|
343
|
+
* env overrides applied (env wins per action). Memoised on (env value, config
|
|
344
|
+
* mtime, cwd); a single `statSync` per call detects on-disk config changes.
|
|
345
|
+
*/
|
|
346
|
+
function getEffectiveBindings(cwd: string = process.cwd()): readonly KeyBinding[] {
|
|
347
|
+
const envRaw = process.env[KEYBINDINGS_ENV];
|
|
348
|
+
const configMtime = configKeybindingsMtime(cwd);
|
|
349
|
+
if (_effectiveCache && _effectiveCache.env === envRaw && _effectiveCache.configMtime === configMtime && _effectiveCache.cwd === cwd) {
|
|
350
|
+
return _effectiveCache.bindings;
|
|
351
|
+
}
|
|
352
|
+
const merged: KeybindingOverride = { ...readConfigKeybindings(cwd), ...readEnvKeybindings() };
|
|
353
|
+
const { bindings, reverted } = computeEffectiveBindings(merged);
|
|
354
|
+
_overrideWarnings = reverted.map((a) => `keybinding override for '${a}' collides with another binding — reverting to default`);
|
|
355
|
+
_effectiveCache = { env: envRaw, configMtime, cwd, bindings };
|
|
356
|
+
return bindings;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** Warnings from the most recent override resolution (e.g. collisions). */
|
|
360
|
+
export function getKeybindingOverrideWarnings(): readonly string[] {
|
|
361
|
+
// Ensure a resolution has run so warnings are populated.
|
|
362
|
+
getEffectiveBindings();
|
|
363
|
+
return _overrideWarnings;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** @internal — drop the memoised effective-binding cache (tests). */
|
|
367
|
+
export function __test__resetKeybindingCache(): void {
|
|
368
|
+
_effectiveCache = null;
|
|
369
|
+
_overrideWarnings = [];
|
|
370
|
+
}
|
|
371
|
+
|
|
205
372
|
/**
|
|
206
373
|
* Resolve a raw input `data` string to a dashboard action.
|
|
207
374
|
*
|
|
208
|
-
* Data-driven dispatch: iterates
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
375
|
+
* Data-driven dispatch: iterates the effective binding table (hardcoded
|
|
376
|
+
* {@link DEFAULT_BINDINGS}, optionally overridden per-action via config/env —
|
|
377
|
+
* see {@link getEffectiveBindings}) in order and returns the action of the
|
|
378
|
+
* first binding whose `keys` contain `data` and whose optional `pane`
|
|
379
|
+
* restriction matches `activePane`. With no override configured the result is
|
|
380
|
+
* identical to the pre-L2 if-chain (verified by
|
|
381
|
+
* `test/unit/keybinding-map.parity.test.ts`).
|
|
212
382
|
*
|
|
213
383
|
* @param data Raw key input (single char or escape sequence).
|
|
214
384
|
* @param activePane Currently focused pane; pane-scoped bindings only fire
|
|
@@ -217,6 +387,9 @@ export { KEY_RESERVED };
|
|
|
217
387
|
* arg skipped the `activePane === ...` branches).
|
|
218
388
|
*/
|
|
219
389
|
export function dashboardActionForKey(data: string, activePane?: ActivePane): DashboardKeyAction | undefined {
|
|
390
|
+
// Effective table = hardcoded DEFAULT_BINDINGS with optional config/env
|
|
391
|
+
// overrides applied (see getEffectiveBindings). Memoised; one statSync/call.
|
|
392
|
+
const BINDINGS = getEffectiveBindings();
|
|
220
393
|
// Two-pass dispatch to preserve case-sensitivity for plain ASCII keys
|
|
221
394
|
// while still normalizing escape sequences via matchesKey().
|
|
222
395
|
//
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { pad, wrapHard } from "../utils/visual.ts";
|
|
2
|
+
import type { CrewComponent } from "./component.ts";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
// PR-G3 (UI-3): the shared CrewComponent contract (render + invalidate).
|
|
5
|
+
// RenderableComponent now extends it so every layout primitive is usable
|
|
6
|
+
// wherever a CrewComponent is expected. Structurally identical — no behavior
|
|
7
|
+
// change.
|
|
8
|
+
export interface RenderableComponent extends CrewComponent {}
|
|
7
9
|
|
|
8
10
|
export class Container implements RenderableComponent {
|
|
9
11
|
private children: RenderableComponent[] = [];
|
|
@@ -102,5 +104,7 @@ export class Spacer implements RenderableComponent {
|
|
|
102
104
|
return Array.from({ length: Math.max(0, this.rows) }, () => pad("", width));
|
|
103
105
|
}
|
|
104
106
|
|
|
105
|
-
invalidate(): void {
|
|
107
|
+
invalidate(): void {
|
|
108
|
+
/* no-op */
|
|
109
|
+
}
|
|
106
110
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* R8: Subscribes to session events for real-time streaming updates.
|
|
5
5
|
* Falls back to polling LiveAgentHandle.activity when subscribe is unavailable.
|
|
6
6
|
*/
|
|
7
|
-
import type { LiveAgentHandle } from "../runtime/live-agent-manager.ts";
|
|
7
|
+
import type { LiveAgentHandle } from "../runtime/live-session/live-agent-manager.ts";
|
|
8
8
|
import { pad, truncate } from "../utils/visual.ts";
|
|
9
9
|
import { computeLiveDurationMs } from "./live-duration.ts";
|
|
10
10
|
import { spinnerFrame } from "./spinner.ts";
|
|
@@ -3,7 +3,7 @@ import type { CrewUiConfig } from "../config/config.ts";
|
|
|
3
3
|
import { applyAttentionState, resolveCrewControlConfig } from "../runtime/agent-control.ts";
|
|
4
4
|
import { readCrewAgents } from "../runtime/crew-agent-records.ts";
|
|
5
5
|
import { formatTaskGraphLines, waitingReason } from "../runtime/task-display.ts";
|
|
6
|
-
import { loadRunManifestById } from "../state/state-store.ts";
|
|
6
|
+
import { loadRunManifestById } from "../state/stores/state-store.ts";
|
|
7
7
|
import type { TeamTaskState } from "../state/types.ts";
|
|
8
8
|
import { aggregateUsage, formatUsage } from "../state/usage.ts";
|
|
9
9
|
import { readJsonFileCoalesced } from "../utils/file-coalescer.ts";
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PR-G3 (UI-4) — minimal overlay stack for z-order, focus, and dismissal
|
|
3
|
+
* chaining.
|
|
4
|
+
*
|
|
5
|
+
* Before this, src/ui/ had no central router for overlay z-order/focus/
|
|
6
|
+
* dismissal. Overlays (ConfirmOverlay, MailboxDetailOverlay, AgentPickerOverlay,
|
|
7
|
+
* HelpOverlay, …) each manage their own `handleInput`, and a caller that wants
|
|
8
|
+
* two overlays up at once had no way to express "the top one gets input and
|
|
9
|
+
* ESC dismisses it, restoring focus to the one below".
|
|
10
|
+
*
|
|
11
|
+
* The `OverlayStack` is a LIFO stack of overlays. The top of the stack owns
|
|
12
|
+
* input focus and is rendered last (i.e. on top). Popping or dismissing the
|
|
13
|
+
* top restores focus to the next overlay down. Every overlay already exposes
|
|
14
|
+
* the `InteractiveComponent` contract (render + invalidate + handleInput), so
|
|
15
|
+
* existing overlays opt in automatically with zero rewrite.
|
|
16
|
+
*
|
|
17
|
+
* This is intentionally additive: no existing overlay is required to use it.
|
|
18
|
+
* Callers that want centralized z-order/focus management opt in by pushing
|
|
19
|
+
* overlays here.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type { InteractiveComponent } from "./component.ts";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* An overlay is an interactive component occupying one z-layer. Anything with
|
|
26
|
+
* `render` + `invalidate` + `handleInput` qualifies (ConfirmOverlay,
|
|
27
|
+
* MailboxDetailOverlay, AgentPickerOverlay, RunDashboard, …).
|
|
28
|
+
*/
|
|
29
|
+
export type Overlay = InteractiveComponent;
|
|
30
|
+
|
|
31
|
+
export interface OverlayStackOptions {
|
|
32
|
+
/**
|
|
33
|
+
* Invoked after the top-of-stack changes (push / pop / dismissTop / clear).
|
|
34
|
+
* Use it to (re)wire host focus or request a repaint. Optional.
|
|
35
|
+
*/
|
|
36
|
+
onTopChanged?: () => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* LIFO overlay stack providing z-order, focus, and dismissal chaining.
|
|
41
|
+
*
|
|
42
|
+
* - `push(o)` → `o` becomes the new focus/top layer.
|
|
43
|
+
* - `top()` → the focused overlay (or undefined when empty).
|
|
44
|
+
* - `handleInput(data)` → routed to the top overlay only; returns false when
|
|
45
|
+
* the stack is empty so callers can fall back to base input handling.
|
|
46
|
+
* - `dismissTop()` / `pop()` → removes the top overlay, restoring focus below.
|
|
47
|
+
* - `render(width)` → renders the top overlay by default; `{ composite: true }`
|
|
48
|
+
* returns every layer bottom-to-top for custom compositing.
|
|
49
|
+
*/
|
|
50
|
+
export class OverlayStack {
|
|
51
|
+
private readonly stack: Overlay[] = [];
|
|
52
|
+
private readonly onTopChanged?: () => void;
|
|
53
|
+
|
|
54
|
+
constructor(options: OverlayStackOptions = {}) {
|
|
55
|
+
this.onTopChanged = options.onTopChanged;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Number of overlays currently on the stack. */
|
|
59
|
+
get size(): number {
|
|
60
|
+
return this.stack.length;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Whether the stack currently holds any overlays. */
|
|
64
|
+
isEmpty(): boolean {
|
|
65
|
+
return this.stack.length === 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Push a new overlay on top. It becomes the input/render focus. */
|
|
69
|
+
push(overlay: Overlay): void {
|
|
70
|
+
this.stack.push(overlay);
|
|
71
|
+
this.notifyTopChanged();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Remove and return the top overlay, or undefined if the stack is empty. */
|
|
75
|
+
pop(): Overlay | undefined {
|
|
76
|
+
const overlay = this.stack.pop();
|
|
77
|
+
this.notifyTopChanged();
|
|
78
|
+
return overlay;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The top-of-stack overlay (the focus/render target), or undefined when empty. */
|
|
82
|
+
top(): Overlay | undefined {
|
|
83
|
+
return this.stack.at(-1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Peek at the overlay at a given depth (0 = bottom). */
|
|
87
|
+
at(index: number): Overlay | undefined {
|
|
88
|
+
return this.stack[index];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Dismiss the top overlay. Equivalent to `pop()` but returns a boolean so
|
|
93
|
+
* callers can chain dismissal (e.g. ESC closes the topmost, then the next).
|
|
94
|
+
* Returns true when an overlay was dismissed, false when the stack is empty.
|
|
95
|
+
*/
|
|
96
|
+
dismissTop(): boolean {
|
|
97
|
+
if (this.stack.length === 0) return false;
|
|
98
|
+
this.stack.pop();
|
|
99
|
+
this.notifyTopChanged();
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Route a raw input chunk to the top-of-stack overlay.
|
|
105
|
+
* @returns true if an overlay handled it (stack non-empty); false if the
|
|
106
|
+
* stack is empty so the caller may fall back to base input handling.
|
|
107
|
+
*/
|
|
108
|
+
handleInput(data: string): boolean {
|
|
109
|
+
const overlay = this.top();
|
|
110
|
+
if (!overlay) return false;
|
|
111
|
+
overlay.handleInput(data);
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Render the stack. By default returns the top overlay's lines (the common
|
|
117
|
+
* modal case where the top covers what is below). Pass `{ composite: true }`
|
|
118
|
+
* to receive every layer's lines in bottom-to-top order for custom
|
|
119
|
+
* compositing. Returns `[]` when the stack is empty.
|
|
120
|
+
*/
|
|
121
|
+
render(width: number, options: { composite?: boolean } = {}): string[] {
|
|
122
|
+
if (this.stack.length === 0) return [];
|
|
123
|
+
if (options.composite) {
|
|
124
|
+
const lines: string[] = [];
|
|
125
|
+
for (const overlay of this.stack) {
|
|
126
|
+
lines.push(...overlay.render(width));
|
|
127
|
+
}
|
|
128
|
+
return lines;
|
|
129
|
+
}
|
|
130
|
+
return this.stack.at(-1)!.render(width);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Invalidate every overlay on the stack (e.g. on theme change). */
|
|
134
|
+
invalidateAll(): void {
|
|
135
|
+
for (const overlay of this.stack) overlay.invalidate();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Remove every overlay from the stack. */
|
|
139
|
+
clear(): void {
|
|
140
|
+
const hadOverlays = this.stack.length > 0;
|
|
141
|
+
this.stack.length = 0;
|
|
142
|
+
if (hadOverlays) this.notifyTopChanged();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private notifyTopChanged(): void {
|
|
146
|
+
this.onTopChanged?.();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readCrewAgents } from "../../runtime/crew-agent-records.ts";
|
|
2
2
|
import type { CrewAgentRecord } from "../../runtime/crew-agent-runtime.ts";
|
|
3
|
-
import { loadRunManifestById } from "../../state/state-store.ts";
|
|
3
|
+
import { loadRunManifestById } from "../../state/stores/state-store.ts";
|
|
4
4
|
import { pad, truncate } from "../../utils/visual.ts";
|
|
5
5
|
import { keyOf } from "../key-utils.ts";
|
|
6
6
|
import { asCrewTheme, type CrewTheme } from "../theme-adapter.ts";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MailboxDirection } from "../../state/mailbox.ts";
|
|
1
|
+
import type { MailboxDirection } from "../../state/coordination/mailbox.ts";
|
|
2
2
|
import { pad, truncate } from "../../utils/visual.ts";
|
|
3
3
|
import { asCrewTheme, type CrewTheme } from "../theme-adapter.ts";
|
|
4
4
|
import { ConfirmOverlay } from "./confirm-overlay.ts";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type MailboxMessage, readDeliveryState, readMailbox } from "../../state/mailbox.ts";
|
|
2
|
-
import { loadRunManifestById } from "../../state/state-store.ts";
|
|
1
|
+
import { type MailboxMessage, readDeliveryState, readMailbox } from "../../state/coordination/mailbox.ts";
|
|
2
|
+
import { loadRunManifestById } from "../../state/stores/state-store.ts";
|
|
3
3
|
import { pad, truncate } from "../../utils/visual.ts";
|
|
4
4
|
import { keyOf } from "../key-utils.ts";
|
|
5
5
|
import { asCrewTheme, type CrewTheme } from "../theme-adapter.ts";
|
|
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import type { CrewUiConfig } from "../config/config.ts";
|
|
3
3
|
import { listRecentRuns } from "../extension/run-index.ts";
|
|
4
4
|
import { readCrewAgents } from "../runtime/crew-agent-records.ts";
|
|
5
|
-
import { listLiveAgents } from "../runtime/live-agent-manager.ts";
|
|
5
|
+
import { listLiveAgents } from "../runtime/live-session/live-agent-manager.ts";
|
|
6
6
|
import type { ManifestCache } from "../runtime/manifest-cache.ts";
|
|
7
7
|
import { isDisplayActiveRun } from "../runtime/process-status.ts";
|
|
8
8
|
import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { logInternalError } from "../utils/internal-error.ts";
|
|
2
2
|
|
|
3
3
|
export interface RenderSchedulerEventBus {
|
|
4
|
-
on?: (event: string, handler: (payload: unknown) => void) => (() => void) |
|
|
4
|
+
on?: (event: string, handler: (payload: unknown) => void) => (() => void) | undefined;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export interface RenderSchedulerOptions {
|
|
@@ -19,9 +19,9 @@ async function handleTeamTool(
|
|
|
19
19
|
import { isToolError, textFromToolResult } from "../extension/tool-result.ts";
|
|
20
20
|
import { readCrewAgents } from "../runtime/crew-agent-records.ts";
|
|
21
21
|
import { exportDiagnostic } from "../runtime/diagnostic-export.ts";
|
|
22
|
-
import {
|
|
23
|
-
import
|
|
24
|
-
import { loadRunManifestById, saveRunTasks } from "../state/state-store.ts";
|
|
22
|
+
import type { MailboxDirection, MailboxMessage } from "../state/coordination/mailbox.ts";
|
|
23
|
+
import { appendEvent } from "../state/event-log/event-log.ts";
|
|
24
|
+
import { loadRunManifestById, saveRunTasks } from "../state/stores/state-store.ts";
|
|
25
25
|
|
|
26
26
|
export interface RunActionResult {
|
|
27
27
|
ok: boolean;
|
package/src/ui/run-dashboard.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import type { MetricRegistry } from "../observability/metric-registry.ts";
|
|
3
3
|
import { readCrewAgents } from "../runtime/crew-agent-records.ts";
|
|
4
4
|
import type { CrewAgentRecord } from "../runtime/crew-agent-runtime.ts";
|
|
5
|
-
import { getLiveAgentContextPercent } from "../runtime/live-agent-manager.ts";
|
|
5
|
+
import { getLiveAgentContextPercent } from "../runtime/live-session/live-agent-manager.ts";
|
|
6
6
|
import { isDisplayActiveRun, isLikelyOrphanedActiveRun } from "../runtime/process-status.ts";
|
|
7
7
|
import type { TeamRunManifest, TeamTaskState, UsageState } from "../state/types.ts";
|
|
8
8
|
import { aggregateUsage } from "../state/usage.ts";
|
|
@@ -10,6 +10,7 @@ import { readJsonFileCoalesced } from "../utils/file-coalescer.ts";
|
|
|
10
10
|
import { logInternalError } from "../utils/internal-error.ts";
|
|
11
11
|
import { resolveRealContainedPath } from "../utils/safe-paths.ts";
|
|
12
12
|
import { pad, sanitizeLine, truncate, visibleWidth } from "../utils/visual.ts";
|
|
13
|
+
import type { InteractiveComponent } from "./component.ts";
|
|
13
14
|
import { renderAgentsPane } from "./dashboard-panes/agents-pane.ts";
|
|
14
15
|
import { summarizeTerminalReason } from "./dashboard-panes/cancellation-pane.ts";
|
|
15
16
|
import { renderHealthPane } from "./dashboard-panes/health-pane.ts";
|
|
@@ -40,11 +41,9 @@ function safeRenderPane(name: string, fn: () => string[]): string[] {
|
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
handleInput(data: string): void;
|
|
47
|
-
}
|
|
44
|
+
// PR-G3 (UI-3): extend the shared InteractiveComponent contract (render +
|
|
45
|
+
// invalidate + handleInput). Structurally identical — no behavior change.
|
|
46
|
+
interface DashboardComponent extends InteractiveComponent {}
|
|
48
47
|
|
|
49
48
|
export interface RunDashboardOptions {
|
|
50
49
|
placement?: "center" | "right";
|
|
@@ -401,11 +400,6 @@ function countByStatus(runs: TeamRunManifest[], snapshotCache?: RunSnapshotCache
|
|
|
401
400
|
}
|
|
402
401
|
|
|
403
402
|
export class RunDashboard implements DashboardComponent {
|
|
404
|
-
// TEMP DIAGNOSTIC (remove after verifying keybind fix on Pi 0.81.1)
|
|
405
|
-
private static _instanceCounter = 0;
|
|
406
|
-
private readonly _instanceId: number;
|
|
407
|
-
// END TEMP DIAGNOSTIC
|
|
408
|
-
|
|
409
403
|
private selected = 0;
|
|
410
404
|
private runScrollOffset = 0;
|
|
411
405
|
private showFullProgress = false;
|
|
@@ -431,18 +425,6 @@ export class RunDashboard implements DashboardComponent {
|
|
|
431
425
|
theme: unknown = {},
|
|
432
426
|
options: RunDashboardOptions = {},
|
|
433
427
|
) {
|
|
434
|
-
// TEMP DIAGNOSTIC: log every constructor + handleInput + focus change
|
|
435
|
-
this._instanceId = ++RunDashboard._instanceCounter;
|
|
436
|
-
if (process.env.PI_CREW_BROKER_DIAG_UI === "1") {
|
|
437
|
-
try {
|
|
438
|
-
process.stderr.write(
|
|
439
|
-
`[PI-CREW-DIAG] RunDashboard#${this._instanceId}.constructor runs=${runs.length} workspaceId=${options.workspaceId ?? "n/a"}\n`,
|
|
440
|
-
);
|
|
441
|
-
} catch (error) {
|
|
442
|
-
logInternalError("run-dashboard.constructor-diag", error, undefined, "debug");
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
// END TEMP DIAGNOSTIC
|
|
446
428
|
// Filter runs by workspaceId for session isolation
|
|
447
429
|
// If workspaceId is provided, only show runs owned by that session or runs with no owner (legacy)
|
|
448
430
|
const filteredRuns = options.workspaceId
|
|
@@ -822,16 +804,6 @@ export class RunDashboard implements DashboardComponent {
|
|
|
822
804
|
public focused = false;
|
|
823
805
|
|
|
824
806
|
handleInput(data: string): void {
|
|
825
|
-
// TEMP DIAGNOSTIC (remove after verifying keybind fix on Pi 0.81.1)
|
|
826
|
-
if (process.env.PI_CREW_BROKER_DIAG_UI === "1") {
|
|
827
|
-
try {
|
|
828
|
-
process.stderr.write(
|
|
829
|
-
`[PI-CREW-DIAG] RunDashboard#${this._instanceId}.handleInput data=${JSON.stringify(data)} focused=${this.focused}\n`,
|
|
830
|
-
);
|
|
831
|
-
} catch (error) {
|
|
832
|
-
logInternalError("run-dashboard.handleInput-diag", error, undefined, "debug");
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
807
|
const action = dashboardActionForKey(data, this.activePane);
|
|
836
808
|
// K-1: "?" toggles the help overlay; while it is shown, any other key
|
|
837
809
|
// (including Esc) just dismisses it first instead of acting.
|
package/src/ui/run-event-bus.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { TeamEvent } from "../state/event-log.ts";
|
|
2
|
-
import { readEventsCursor } from "../state/event-log.ts";
|
|
1
|
+
import type { TeamEvent } from "../state/event-log/event-log.ts";
|
|
2
|
+
import { readEventsCursor } from "../state/event-log/event-log.ts";
|
|
3
3
|
import type { RenderSchedulerEventBus } from "./render-scheduler.ts";
|
|
4
4
|
|
|
5
5
|
export type RunEventType =
|
|
@@ -342,7 +342,7 @@ export const runEventBus = new RunEventBus();
|
|
|
342
342
|
export function runEventBusAsRenderScheduler(channels: readonly EventChannel[]): RenderSchedulerEventBus {
|
|
343
343
|
const allowed = new Set<EventChannel>(channels);
|
|
344
344
|
return {
|
|
345
|
-
on: (event: string, handler: (payload: unknown) => void): (() => void) |
|
|
345
|
+
on: (event: string, handler: (payload: unknown) => void): (() => void) | undefined => {
|
|
346
346
|
if (!allowed.has(event as EventChannel)) return undefined;
|
|
347
347
|
return runEventBus.onChannel(event as EventChannel, handler);
|
|
348
348
|
},
|
|
@@ -4,10 +4,10 @@ import * as path from "node:path";
|
|
|
4
4
|
import { agentOutputPath, agentsPath, readCrewAgents, readCrewAgentsAsync } from "../runtime/crew-agent-records.ts";
|
|
5
5
|
import type { CrewAgentRecord } from "../runtime/crew-agent-runtime.ts";
|
|
6
6
|
import { isActiveRunStatus } from "../runtime/process-status.ts";
|
|
7
|
-
import type {
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
10
|
-
import { loadRunManifestById, loadRunManifestByIdAsync } from "../state/state-store.ts";
|
|
7
|
+
import type { MailboxMessageStatus } from "../state/coordination/mailbox.ts";
|
|
8
|
+
import type { TeamEvent } from "../state/event-log/event-log.ts";
|
|
9
|
+
import { sequencePath } from "../state/event-log/event-log.ts";
|
|
10
|
+
import { loadRunManifestById, loadRunManifestByIdAsync } from "../state/stores/state-store.ts";
|
|
11
11
|
import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
|
|
12
12
|
import { extractDwfPhaseState } from "./dwf-phase-display.ts";
|
|
13
13
|
import { runEventBus } from "./run-event-bus.ts";
|
|
@@ -629,15 +629,19 @@ async function mailboxFromAsync(manifest: TeamRunManifest, agents: CrewAgentReco
|
|
|
629
629
|
let outbox = await readMailboxCountsAsync(path.join(root, "outbox.jsonl"), delivery);
|
|
630
630
|
const tasksRoot = path.join(root, "tasks");
|
|
631
631
|
try {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
632
|
+
// PR-F2 / UI-5 — batch the O(tasks) per-task inbox/outbox reads in
|
|
633
|
+
// parallel instead of sequential awaits on the async render path.
|
|
634
|
+
const taskDirs = (
|
|
635
|
+
await fs.promises.readdir(tasksRoot, {
|
|
636
|
+
withFileTypes: true,
|
|
637
|
+
})
|
|
638
|
+
).filter((entry) => entry.isDirectory());
|
|
639
|
+
const [taskInboxes, taskOutboxes] = await Promise.all([
|
|
640
|
+
Promise.all(taskDirs.map((entry) => readMailboxCountsAsync(path.join(tasksRoot, entry.name, "inbox.jsonl"), delivery))),
|
|
641
|
+
Promise.all(taskDirs.map((entry) => readMailboxCountsAsync(path.join(tasksRoot, entry.name, "outbox.jsonl"), delivery))),
|
|
642
|
+
]);
|
|
643
|
+
for (const ti of taskInboxes) inbox = mergeKindCounts(inbox, ti);
|
|
644
|
+
for (const to of taskOutboxes) outbox = mergeKindCounts(outbox, to);
|
|
641
645
|
} catch {
|
|
642
646
|
// No task mailboxes yet.
|
|
643
647
|
}
|
|
@@ -991,6 +995,21 @@ export function createRunSnapshotCache(cwd: string, options: RunSnapshotCacheOpt
|
|
|
991
995
|
evictIfNeeded();
|
|
992
996
|
return entry.snapshot;
|
|
993
997
|
}
|
|
998
|
+
// PR-F2 / UI-1 — in-flight async refresh dedup. Prevents multiple
|
|
999
|
+
// concurrent `preloadStale` calls for the same runId when the render
|
|
1000
|
+
// path (refreshIfStale) and event-bus handler fire close together.
|
|
1001
|
+
const inFlightRefreshes = new Set<string>();
|
|
1002
|
+
function triggerAsyncRefresh(runId: string): void {
|
|
1003
|
+
if (inFlightRefreshes.has(runId)) return;
|
|
1004
|
+
inFlightRefreshes.add(runId);
|
|
1005
|
+
void preloadStale(runId)
|
|
1006
|
+
.catch(() => {
|
|
1007
|
+
/* best-effort; widget falls back to cached snapshot */
|
|
1008
|
+
})
|
|
1009
|
+
.finally(() => {
|
|
1010
|
+
inFlightRefreshes.delete(runId);
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
994
1013
|
function localRefreshIfStale(runId: string): RunUiSnapshot {
|
|
995
1014
|
const previous = entries.get(runId);
|
|
996
1015
|
if (!previous) return localRefresh(runId);
|
|
@@ -1007,11 +1026,10 @@ export function createRunSnapshotCache(cwd: string, options: RunSnapshotCacheOpt
|
|
|
1007
1026
|
if (existing) clearTimeout(existing);
|
|
1008
1027
|
const timer = setTimeout(() => {
|
|
1009
1028
|
pendingRefreshes.delete(runId);
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
}
|
|
1029
|
+
// PR-F2 / UI-1 — use the async refresh path (no sync fs on the
|
|
1030
|
+
// event-bus hot path). triggerAsyncRefresh deduplicates
|
|
1031
|
+
// concurrent refreshes.
|
|
1032
|
+
triggerAsyncRefresh(runId);
|
|
1015
1033
|
}, INVAL_COALESCE_MS);
|
|
1016
1034
|
timer.unref();
|
|
1017
1035
|
pendingRefreshes.set(runId, timer);
|
|
@@ -1051,6 +1069,7 @@ export function createRunSnapshotCache(cwd: string, options: RunSnapshotCacheOpt
|
|
|
1051
1069
|
},
|
|
1052
1070
|
dispose(): void {
|
|
1053
1071
|
unsubscribe();
|
|
1072
|
+
inFlightRefreshes.clear();
|
|
1054
1073
|
entries.clear();
|
|
1055
1074
|
},
|
|
1056
1075
|
};
|