pi-crew 0.9.54 → 0.9.56
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 +53 -0
- package/dist/index.mjs +1153 -857
- package/package.json +5 -3
- package/scripts/clean-strip-types.mjs +60 -0
- package/scripts/smoke-team-perf.ts +43 -0
- package/src/adapters/claude-adapter.ts +23 -0
- package/src/adapters/codex-adapter.ts +21 -0
- package/src/adapters/cursor-adapter.ts +17 -0
- package/src/adapters/export-util.ts +143 -0
- package/src/adapters/index.ts +15 -0
- package/src/adapters/registry.ts +18 -0
- package/src/adapters/types.ts +23 -0
- package/src/agents/agent-config.ts +194 -0
- package/src/agents/agent-search.ts +98 -0
- package/src/agents/agent-serializer.ts +38 -0
- package/src/agents/discover-agents.ts +623 -0
- package/src/benchmark/benchmark-runner.ts +313 -0
- package/src/benchmark/feedback-loop.ts +73 -0
- package/src/config/config.ts +1270 -0
- package/src/config/defaults.ts +193 -0
- package/src/config/drift-detector.ts +290 -0
- package/src/config/markers.ts +330 -0
- package/src/config/resilient-parser.ts +117 -0
- package/src/config/role-tools.ts +118 -0
- package/src/config/suggestions.ts +75 -0
- package/src/config/types.ts +280 -0
- package/src/errors.ts +194 -0
- package/src/extension/action-suggestions.ts +117 -0
- package/src/extension/async-notifier.ts +179 -0
- package/src/extension/autonomous-policy.ts +209 -0
- package/src/extension/command-completions.ts +120 -0
- package/src/extension/context-status-injection.ts +185 -0
- package/src/extension/crew-autocomplete.ts +131 -0
- package/src/extension/crew-cleanup.ts +168 -0
- package/src/extension/crew-input-router.ts +93 -0
- package/src/extension/crew-shortcuts.ts +87 -0
- package/src/extension/crew-vibes/cat-frames.ts +18 -0
- package/src/extension/crew-vibes/config.ts +195 -0
- package/src/extension/crew-vibes/figures.ts +101 -0
- package/src/extension/crew-vibes/font-detect.ts +71 -0
- package/src/extension/crew-vibes/footer.ts +292 -0
- package/src/extension/crew-vibes/index.ts +432 -0
- package/src/extension/crew-vibes/provider-usage.ts +396 -0
- package/src/extension/crew-vibes/render.ts +206 -0
- package/src/extension/crew-vibes/speed.ts +286 -0
- package/src/extension/cross-extension-rpc.ts +350 -0
- package/src/extension/help.ts +62 -0
- package/src/extension/import-index.ts +74 -0
- package/src/extension/knowledge-injection.ts +402 -0
- package/src/extension/management.ts +571 -0
- package/src/extension/message-renderers.ts +108 -0
- package/src/extension/notification-router.ts +152 -0
- package/src/extension/notification-sink.ts +54 -0
- package/src/extension/pi-api.ts +56 -0
- package/src/extension/plan-orchestrate.ts +302 -0
- package/src/extension/project-init.ts +170 -0
- package/src/extension/register.ts +113 -0
- package/src/extension/registration/artifact-cleanup.ts +20 -0
- package/src/extension/registration/command-registration.ts +58 -0
- package/src/extension/registration/command-utils.ts +58 -0
- package/src/extension/registration/commands.ts +1228 -0
- package/src/extension/registration/compaction-guard.ts +333 -0
- package/src/extension/registration/context-builder.ts +135 -0
- package/src/extension/registration/crash-recovery-cache.ts +54 -0
- package/src/extension/registration/foreground-run-controller.ts +238 -0
- package/src/extension/registration/hook-registration.ts +119 -0
- package/src/extension/registration/lazy-configurers.ts +124 -0
- package/src/extension/registration/lifecycle-handlers.ts +923 -0
- package/src/extension/registration/lifecycle.ts +259 -0
- package/src/extension/registration/observability.ts +322 -0
- package/src/extension/registration/registration-types.ts +158 -0
- package/src/extension/registration/runtime-cleanup.ts +230 -0
- package/src/extension/registration/subagent-helpers.ts +125 -0
- package/src/extension/registration/subagent-manager-setup.ts +330 -0
- package/src/extension/registration/subagent-tools.ts +456 -0
- package/src/extension/registration/team-tool.ts +226 -0
- package/src/extension/registration/tool-registration.ts +50 -0
- package/src/extension/registration/ui.ts +172 -0
- package/src/extension/registration/viewers.ts +121 -0
- package/src/extension/registration/wire-cross-extension.ts +37 -0
- package/src/extension/result-watcher.ts +139 -0
- package/src/extension/rpc-hmac.ts +256 -0
- package/src/extension/run-bundle-schema.ts +104 -0
- package/src/extension/run-export.ts +97 -0
- package/src/extension/run-import.ts +168 -0
- package/src/extension/run-index.ts +131 -0
- package/src/extension/run-maintenance.ts +190 -0
- package/src/extension/session-summary.ts +18 -0
- package/src/extension/team-manager-command.ts +153 -0
- package/src/extension/team-onboard.ts +180 -0
- package/src/extension/team-recommendation.ts +288 -0
- package/src/extension/team-tool/anchor.ts +172 -0
- package/src/extension/team-tool/api.ts +1244 -0
- package/src/extension/team-tool/auto-summarize.ts +142 -0
- package/src/extension/team-tool/cache-control.ts +19 -0
- package/src/extension/team-tool/cancel.ts +337 -0
- package/src/extension/team-tool/chain-dispatch.ts +95 -0
- package/src/extension/team-tool/chain-executor.ts +379 -0
- package/src/extension/team-tool/config-patch.ts +50 -0
- package/src/extension/team-tool/context.ts +149 -0
- package/src/extension/team-tool/destructive-gate.ts +52 -0
- package/src/extension/team-tool/dispatch/automate.ts +80 -0
- package/src/extension/team-tool/dispatch/control.ts +42 -0
- package/src/extension/team-tool/dispatch/index.ts +95 -0
- package/src/extension/team-tool/dispatch/manage.ts +161 -0
- package/src/extension/team-tool/dispatch/run.ts +53 -0
- package/src/extension/team-tool/dispatch/status.ts +187 -0
- package/src/extension/team-tool/doctor.ts +431 -0
- package/src/extension/team-tool/explain.ts +282 -0
- package/src/extension/team-tool/failure-patterns.ts +118 -0
- package/src/extension/team-tool/goal-wrap.ts +330 -0
- package/src/extension/team-tool/goal.ts +556 -0
- package/src/extension/team-tool/handle-schedule.ts +332 -0
- package/src/extension/team-tool/handle-settings.ts +520 -0
- package/src/extension/team-tool/health-monitor.ts +481 -0
- package/src/extension/team-tool/inspect.ts +88 -0
- package/src/extension/team-tool/intent-policy.ts +45 -0
- package/src/extension/team-tool/lifecycle-actions.ts +615 -0
- package/src/extension/team-tool/orchestrate.ts +98 -0
- package/src/extension/team-tool/parallel-dispatch.ts +183 -0
- package/src/extension/team-tool/param-error.ts +114 -0
- package/src/extension/team-tool/plan.ts +50 -0
- package/src/extension/team-tool/respond.ts +150 -0
- package/src/extension/team-tool/run-deadline.ts +66 -0
- package/src/extension/team-tool/run-not-found.ts +49 -0
- package/src/extension/team-tool/run.ts +1051 -0
- package/src/extension/team-tool/status.ts +265 -0
- package/src/extension/team-tool/workflow-manage.ts +261 -0
- package/src/extension/team-tool-types.ts +26 -0
- package/src/extension/team-tool.ts +750 -0
- package/src/extension/tool-result.ts +16 -0
- package/src/extension/validate-resources.ts +108 -0
- package/src/hooks/registry.ts +200 -0
- package/src/hooks/types.ts +69 -0
- package/src/i18n.ts +212 -0
- package/src/observability/correlation.ts +50 -0
- package/src/observability/event-bus.ts +86 -0
- package/src/observability/event-to-metric.ts +170 -0
- package/src/observability/exporters/adapter.ts +27 -0
- package/src/observability/exporters/otlp-exporter.ts +356 -0
- package/src/observability/exporters/prometheus-exporter.ts +54 -0
- package/src/observability/metric-registry.ts +100 -0
- package/src/observability/metric-retention.ts +64 -0
- package/src/observability/metric-sink.ts +99 -0
- package/src/observability/metrics-primitives.ts +219 -0
- package/src/plugins/plugin-define.ts +6 -0
- package/src/plugins/plugin-registry.ts +32 -0
- package/src/plugins/plugins/index.ts +3 -0
- package/src/plugins/plugins/nextjs.ts +19 -0
- package/src/plugins/plugins/vite.ts +10 -0
- package/src/plugins/plugins/vitest.ts +9 -0
- package/src/prompt/prompt-runtime.ts +380 -0
- package/src/runtime/adaptive-plan.ts +563 -0
- package/src/runtime/agent-control.ts +215 -0
- package/src/runtime/agent-memory.ts +81 -0
- package/src/runtime/agent-observability.ts +122 -0
- package/src/runtime/anchor-manager.ts +475 -0
- package/src/runtime/async-marker.ts +32 -0
- package/src/runtime/async-runner.ts +381 -0
- package/src/runtime/attention-events.ts +29 -0
- package/src/runtime/auto-summarize.ts +344 -0
- package/src/runtime/background-runner.ts +840 -0
- package/src/runtime/batch-barrier.ts +147 -0
- package/src/runtime/broker-issuer.ts +39 -0
- package/src/runtime/cancellation-token.ts +99 -0
- package/src/runtime/cancellation.ts +99 -0
- package/src/runtime/capability-inventory.ts +137 -0
- package/src/runtime/chain-parser.ts +237 -0
- package/src/runtime/chain-runner.ts +606 -0
- package/src/runtime/checkpoint.ts +291 -0
- package/src/runtime/child-pi-constants.ts +42 -0
- package/src/runtime/child-pi-kill.ts +180 -0
- package/src/runtime/child-pi-pool.ts +68 -0
- package/src/runtime/child-pi-spawn.ts +287 -0
- package/src/runtime/child-pi-steering.ts +128 -0
- package/src/runtime/child-pi-streams.ts +308 -0
- package/src/runtime/child-pi-transcript.ts +169 -0
- package/src/runtime/child-pi.ts +1118 -0
- package/src/runtime/coalesce-tasks.ts +268 -0
- package/src/runtime/code-summary.ts +292 -0
- package/src/runtime/command-trace.ts +105 -0
- package/src/runtime/compact-pipeline.ts +56 -0
- package/src/runtime/compact-stages/ansi-strip-stage.ts +25 -0
- package/src/runtime/compact-stages/blank-collapse-stage.ts +31 -0
- package/src/runtime/compact-stages/bounded-tail.ts +84 -0
- package/src/runtime/compact-stages/deduplicate-stage.ts +34 -0
- package/src/runtime/compact-stages/head-snap-stage.ts +57 -0
- package/src/runtime/compact-stages/index.ts +23 -0
- package/src/runtime/compact-stages/tail-capture-stage.ts +77 -0
- package/src/runtime/compact-stages/truncation-stage.ts +74 -0
- package/src/runtime/compaction-summary.ts +278 -0
- package/src/runtime/completion-guard.ts +207 -0
- package/src/runtime/concurrency.ts +64 -0
- package/src/runtime/crash-classification.ts +229 -0
- package/src/runtime/crash-recovery.ts +594 -0
- package/src/runtime/crew-agent-records.ts +641 -0
- package/src/runtime/crew-agent-runtime.ts +62 -0
- package/src/runtime/crew-broker-child.ts +88 -0
- package/src/runtime/crew-broker-client.ts +673 -0
- package/src/runtime/crew-broker-tokens.ts +159 -0
- package/src/runtime/crew-broker.ts +1304 -0
- package/src/runtime/crew-hooks.ts +227 -0
- package/src/runtime/cross-extension-rpc.ts +143 -0
- package/src/runtime/custom-tools/irc-tool.ts +282 -0
- package/src/runtime/custom-tools/submit-result-tool.ts +102 -0
- package/src/runtime/deadletter.ts +47 -0
- package/src/runtime/delivery-coordinator.ts +211 -0
- package/src/runtime/delta-conflict.ts +348 -0
- package/src/runtime/deterministic-ast.ts +161 -0
- package/src/runtime/diagnostic-export.ts +172 -0
- package/src/runtime/direct-run.ts +45 -0
- package/src/runtime/dwf-state-store.ts +102 -0
- package/src/runtime/dynamic-workflow-context.ts +1013 -0
- package/src/runtime/dynamic-workflow-runner.ts +325 -0
- package/src/runtime/effectiveness.ts +90 -0
- package/src/runtime/errors/crew-errors.ts +162 -0
- package/src/runtime/event-stream-bridge.ts +98 -0
- package/src/runtime/foreground-control.ts +193 -0
- package/src/runtime/foreground-watchdog.ts +132 -0
- package/src/runtime/global-worker-cap.ts +96 -0
- package/src/runtime/goal-achievement.ts +148 -0
- package/src/runtime/goal-evaluator.ts +365 -0
- package/src/runtime/goal-loop-runner.ts +825 -0
- package/src/runtime/goal-state-store.ts +219 -0
- package/src/runtime/green-contract.ts +55 -0
- package/src/runtime/group-join.ts +267 -0
- package/src/runtime/handoff-manager.ts +598 -0
- package/src/runtime/heartbeat-gradient.ts +36 -0
- package/src/runtime/heartbeat-watcher.ts +239 -0
- package/src/runtime/hidden-handoff.ts +407 -0
- package/src/runtime/important-line-classifier.ts +140 -0
- package/src/runtime/intercom-bridge.ts +187 -0
- package/src/runtime/iteration-hooks.ts +305 -0
- package/src/runtime/live-agent-control.ts +136 -0
- package/src/runtime/live-agent-manager.ts +704 -0
- package/src/runtime/live-control-realtime.ts +56 -0
- package/src/runtime/live-extension-bridge.ts +148 -0
- package/src/runtime/live-irc.ts +97 -0
- package/src/runtime/live-session-health.ts +108 -0
- package/src/runtime/live-session-runtime.ts +1146 -0
- package/src/runtime/loop-gates.ts +128 -0
- package/src/runtime/manifest-cache.ts +379 -0
- package/src/runtime/mcp-proxy.ts +105 -0
- package/src/runtime/metric-parser.ts +36 -0
- package/src/runtime/model-fallback.ts +409 -0
- package/src/runtime/model-resolver.ts +126 -0
- package/src/runtime/model-scope.ts +158 -0
- package/src/runtime/orphan-worker-registry.ts +465 -0
- package/src/runtime/output-validator.ts +202 -0
- package/src/runtime/overflow-recovery.ts +206 -0
- package/src/runtime/parallel-research.ts +68 -0
- package/src/runtime/parallel-utils.ts +160 -0
- package/src/runtime/parent-guard.ts +134 -0
- package/src/runtime/path-overlap.ts +150 -0
- package/src/runtime/peer-dep.ts +292 -0
- package/src/runtime/per-write-validator.ts +181 -0
- package/src/runtime/phase-progress.ts +217 -0
- package/src/runtime/phase-tracker.ts +385 -0
- package/src/runtime/pi-args.ts +655 -0
- package/src/runtime/pi-json-output.ts +170 -0
- package/src/runtime/pi-spawn.ts +273 -0
- package/src/runtime/pipeline-runner.ts +523 -0
- package/src/runtime/plan-templates.ts +201 -0
- package/src/runtime/policy-engine.ts +115 -0
- package/src/runtime/post-checks.ts +142 -0
- package/src/runtime/post-exit-stdio-guard.ts +109 -0
- package/src/runtime/process-lifecycle.ts +491 -0
- package/src/runtime/process-status.ts +154 -0
- package/src/runtime/progress-event-coalescer.ts +44 -0
- package/src/runtime/progress-tracker.ts +124 -0
- package/src/runtime/prose-compressor.ts +162 -0
- package/src/runtime/recovery-recipes.ts +203 -0
- package/src/runtime/replace.ts +570 -0
- package/src/runtime/resilient-edit.ts +153 -0
- package/src/runtime/result-extractor.ts +217 -0
- package/src/runtime/retry-executor.ts +109 -0
- package/src/runtime/retry-runner.ts +336 -0
- package/src/runtime/role-permission.ts +59 -0
- package/src/runtime/run-coalesced-task-group.ts +308 -0
- package/src/runtime/run-drift.ts +219 -0
- package/src/runtime/run-tracker.ts +116 -0
- package/src/runtime/run-worker.ts +78 -0
- package/src/runtime/runtime-policy.ts +29 -0
- package/src/runtime/runtime-resolver.ts +172 -0
- package/src/runtime/runtime-warmup.ts +160 -0
- package/src/runtime/scheduler.ts +353 -0
- package/src/runtime/semaphore.ts +144 -0
- package/src/runtime/sensitive-paths.ts +93 -0
- package/src/runtime/session-resources.ts +25 -0
- package/src/runtime/session-snapshot.ts +59 -0
- package/src/runtime/session-usage.ts +79 -0
- package/src/runtime/settings-store.ts +155 -0
- package/src/runtime/sidechain-output.ts +35 -0
- package/src/runtime/single-agent-compose.ts +84 -0
- package/src/runtime/skill-effectiveness.ts +524 -0
- package/src/runtime/skill-instructions.ts +394 -0
- package/src/runtime/stale-reconciler.ts +680 -0
- package/src/runtime/stream-preview.ts +184 -0
- package/src/runtime/streaming-output.ts +50 -0
- package/src/runtime/subagent-manager.ts +561 -0
- package/src/runtime/subprocess-tool-registry.ts +70 -0
- package/src/runtime/supervisor-contact.ts +79 -0
- package/src/runtime/task-display.ts +52 -0
- package/src/runtime/task-graph-scheduler.ts +227 -0
- package/src/runtime/task-graph.ts +290 -0
- package/src/runtime/task-health.ts +83 -0
- package/src/runtime/task-id.ts +161 -0
- package/src/runtime/task-output-context.ts +602 -0
- package/src/runtime/task-packet.ts +268 -0
- package/src/runtime/task-quality.ts +199 -0
- package/src/runtime/task-runner/capabilities.ts +78 -0
- package/src/runtime/task-runner/child-executor.ts +786 -0
- package/src/runtime/task-runner/context-retrieval.ts +163 -0
- package/src/runtime/task-runner/live-executor.ts +227 -0
- package/src/runtime/task-runner/output-splitter.ts +152 -0
- package/src/runtime/task-runner/post-execution.ts +480 -0
- package/src/runtime/task-runner/pre-execution.ts +365 -0
- package/src/runtime/task-runner/progress.ts +157 -0
- package/src/runtime/task-runner/prompt-builder.ts +278 -0
- package/src/runtime/task-runner/prompt-pipeline.ts +88 -0
- package/src/runtime/task-runner/result-utils.ts +16 -0
- package/src/runtime/task-runner/retrieval-orchestrator.ts +310 -0
- package/src/runtime/task-runner/run-projection.ts +125 -0
- package/src/runtime/task-runner/scaffold-executor.ts +25 -0
- package/src/runtime/task-runner/state-helpers.ts +176 -0
- package/src/runtime/task-runner/tail-read.ts +34 -0
- package/src/runtime/task-runner.ts +216 -0
- package/src/runtime/team-runner-artifacts.ts +13 -0
- package/src/runtime/team-runner.ts +2421 -0
- package/src/runtime/tool-output-pruner.ts +333 -0
- package/src/runtime/tool-progress.ts +278 -0
- package/src/runtime/usage-tracker.ts +73 -0
- package/src/runtime/verification-gates.ts +579 -0
- package/src/runtime/verification-integrity.ts +108 -0
- package/src/runtime/verification-worktree.ts +186 -0
- package/src/runtime/worker-heartbeat.ts +25 -0
- package/src/runtime/worker-startup.ts +83 -0
- package/src/runtime/workflow-state.ts +195 -0
- package/src/runtime/workspace-lock.ts +443 -0
- package/src/runtime/workspace-tree.ts +312 -0
- package/src/runtime/yield-handler.ts +224 -0
- package/src/runtime/zombie-scanner.ts +298 -0
- package/src/schema/config-schema.ts +327 -0
- package/src/schema/team-tool-schema.ts +500 -0
- package/src/schema/validation-types.ts +159 -0
- package/src/skills/discover-skills.ts +203 -0
- package/src/skills/skill-templates.ts +456 -0
- package/src/skills/validate.ts +285 -0
- package/src/state/active-run-registry.ts +439 -0
- package/src/state/artifact-store.ts +176 -0
- package/src/state/atomic-write.ts +970 -0
- package/src/state/blob-store.ts +308 -0
- package/src/state/contracts.ts +160 -0
- package/src/state/crew-init.ts +269 -0
- package/src/state/decision-ledger.ts +372 -0
- package/src/state/event-log-rotation.ts +427 -0
- package/src/state/event-log.ts +1491 -0
- package/src/state/event-reconstructor.ts +229 -0
- package/src/state/gitignore-manager.ts +46 -0
- package/src/state/health-store.ts +79 -0
- package/src/state/hook-instinct-bridge.ts +94 -0
- package/src/state/hook-integrations.ts +51 -0
- package/src/state/instinct-store.ts +275 -0
- package/src/state/jsonl-writer.ts +107 -0
- package/src/state/locks.ts +562 -0
- package/src/state/mailbox.ts +916 -0
- package/src/state/observation-store.ts +176 -0
- package/src/state/run-cache.ts +193 -0
- package/src/state/run-graph.ts +183 -0
- package/src/state/run-metrics.ts +165 -0
- package/src/state/schedule.ts +166 -0
- package/src/state/session-state-map.ts +51 -0
- package/src/state/state-store.ts +987 -0
- package/src/state/task-claims.ts +61 -0
- package/src/state/tiered-eval.ts +480 -0
- package/src/state/types-eval.ts +58 -0
- package/src/state/types.ts +447 -0
- package/src/state/usage.ts +138 -0
- package/src/state/worker-atomic-writer.ts +198 -0
- package/src/subagents/async-entry.ts +1 -0
- package/src/subagents/index.ts +3 -0
- package/src/subagents/live/control.ts +1 -0
- package/src/subagents/live/manager.ts +1 -0
- package/src/subagents/live/realtime.ts +1 -0
- package/src/subagents/live/session-runtime.ts +1 -0
- package/src/subagents/manager.ts +1 -0
- package/src/subagents/spawn.ts +1 -0
- package/src/teams/discover-teams.ts +187 -0
- package/src/teams/team-config.ts +27 -0
- package/src/teams/team-serializer.ts +38 -0
- package/src/tools/safe-bash-extension.ts +54 -0
- package/src/tools/safe-bash.ts +505 -0
- package/src/types/diff.d.ts +18 -0
- package/src/types/new-api-types.ts +35 -0
- package/src/ui/agent-management-overlay.ts +160 -0
- package/src/ui/card-colors.ts +139 -0
- package/src/ui/crew-footer.ts +102 -0
- package/src/ui/crew-select-list.ts +112 -0
- package/src/ui/dashboard-panes/agents-pane.ts +164 -0
- package/src/ui/dashboard-panes/cancellation-pane.ts +66 -0
- package/src/ui/dashboard-panes/capability-pane.ts +77 -0
- package/src/ui/dashboard-panes/health-pane.ts +31 -0
- package/src/ui/dashboard-panes/mailbox-pane.ts +35 -0
- package/src/ui/dashboard-panes/metrics-pane.ts +37 -0
- package/src/ui/dashboard-panes/progress-pane.ts +38 -0
- package/src/ui/dashboard-panes/transcript-pane.ts +10 -0
- package/src/ui/deploy-bundled-themes.ts +71 -0
- package/src/ui/dwf-phase-display.ts +151 -0
- package/src/ui/dynamic-border.ts +35 -0
- package/src/ui/format-helpers.ts +31 -0
- package/src/ui/heartbeat-aggregator.ts +74 -0
- package/src/ui/key-utils.ts +42 -0
- package/src/ui/keybinding-map.ts +248 -0
- package/src/ui/layout-primitives.ts +106 -0
- package/src/ui/live-conversation-overlay.ts +183 -0
- package/src/ui/live-duration.ts +55 -0
- package/src/ui/live-run-sidebar.ts +293 -0
- package/src/ui/loaders.ts +6 -0
- package/src/ui/mascot.ts +449 -0
- package/src/ui/overlays/agent-picker-overlay.ts +66 -0
- package/src/ui/overlays/confirm-overlay.ts +60 -0
- package/src/ui/overlays/help-overlay.ts +177 -0
- package/src/ui/overlays/mailbox-compose-overlay.ts +181 -0
- package/src/ui/overlays/mailbox-compose-preview.ts +78 -0
- package/src/ui/overlays/mailbox-detail-overlay.ts +155 -0
- package/src/ui/pi-ui-compat.ts +68 -0
- package/src/ui/powerbar-publisher.ts +459 -0
- package/src/ui/render-coalescer.ts +101 -0
- package/src/ui/render-diff.ts +160 -0
- package/src/ui/render-scheduler.ts +278 -0
- package/src/ui/run-action-dispatcher.ts +182 -0
- package/src/ui/run-dashboard.ts +910 -0
- package/src/ui/run-event-bus.ts +381 -0
- package/src/ui/run-snapshot-cache.ts +1057 -0
- package/src/ui/settings-overlay.ts +1067 -0
- package/src/ui/shared-overlay-scheduler.ts +96 -0
- package/src/ui/snapshot-types.ts +90 -0
- package/src/ui/spinner.ts +17 -0
- package/src/ui/status-colors.ts +143 -0
- package/src/ui/syntax-highlight.ts +136 -0
- package/src/ui/terminal-status.ts +269 -0
- package/src/ui/theme-adapter.ts +271 -0
- package/src/ui/theme-discovery.ts +199 -0
- package/src/ui/tool-progress-formatter.ts +93 -0
- package/src/ui/tool-renderers/brief-mode.ts +225 -0
- package/src/ui/tool-renderers/index.ts +726 -0
- package/src/ui/transcript-cache.ts +128 -0
- package/src/ui/transcript-entries.ts +256 -0
- package/src/ui/transcript-viewer.ts +424 -0
- package/src/ui/widget/index.ts +418 -0
- package/src/ui/widget/widget-formatters.ts +182 -0
- package/src/ui/widget/widget-model.ts +110 -0
- package/src/ui/widget/widget-renderer.ts +182 -0
- package/src/ui/widget/widget-types.ts +36 -0
- package/src/utils/bm25-search.ts +235 -0
- package/src/utils/completion-dedupe.ts +63 -0
- package/src/utils/conflict-detect.ts +668 -0
- package/src/utils/env-allowlist.ts +30 -0
- package/src/utils/env-filter.ts +155 -0
- package/src/utils/file-coalescer.ts +98 -0
- package/src/utils/fingerprint.ts +180 -0
- package/src/utils/frontmatter.ts +70 -0
- package/src/utils/fs-watch.ts +37 -0
- package/src/utils/gh-protocol.ts +556 -0
- package/src/utils/git.ts +260 -0
- package/src/utils/guards.ts +107 -0
- package/src/utils/ids.ts +24 -0
- package/src/utils/incremental-reader.ts +220 -0
- package/src/utils/internal-error.ts +10 -0
- package/src/utils/names.ts +36 -0
- package/src/utils/ndjson.ts +115 -0
- package/src/utils/paths.ts +227 -0
- package/src/utils/project-detector.ts +160 -0
- package/src/utils/redaction.ts +409 -0
- package/src/utils/resolve-shell.ts +36 -0
- package/src/utils/run-watcher-registry.ts +152 -0
- package/src/utils/safe-paths.ts +393 -0
- package/src/utils/scan-cache.ts +144 -0
- package/src/utils/session-utils.ts +110 -0
- package/src/utils/sleep.ts +54 -0
- package/src/utils/socket-path.ts +164 -0
- package/src/utils/sse-parser.ts +131 -0
- package/src/utils/task-name-generator.ts +337 -0
- package/src/utils/timings.ts +33 -0
- package/src/utils/token-counter.ts +200 -0
- package/src/utils/visual.ts +207 -0
- package/src/workflows/cost-estimator.ts +34 -0
- package/src/workflows/discover-workflows.ts +308 -0
- package/src/workflows/intermediate-store.ts +166 -0
- package/src/workflows/preflight-validator.ts +168 -0
- package/src/workflows/topology-analyzer.ts +203 -0
- package/src/workflows/validate-workflow.ts +50 -0
- package/src/workflows/workflow-config.ts +75 -0
- package/src/workflows/workflow-serializer.ts +32 -0
- package/src/worktree/branch-freshness.ts +112 -0
- package/src/worktree/cleanup.ts +341 -0
- package/src/worktree/worktree-manager.ts +1219 -0
- package/dist/build-meta.json +0 -16807
package/src/utils/git.ts
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
|
|
3
|
+
type HostedGitInfoResult = {
|
|
4
|
+
repo?: unknown;
|
|
5
|
+
domain?: string;
|
|
6
|
+
project?: string;
|
|
7
|
+
user?: string;
|
|
8
|
+
committish?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type HostedGitInfoModule = {
|
|
12
|
+
fromUrl: (url: string) => HostedGitInfoResult | null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Parsed git source information.
|
|
17
|
+
*/
|
|
18
|
+
export type GitSource = {
|
|
19
|
+
/**
|
|
20
|
+
* Always "git" for git sources.
|
|
21
|
+
*/
|
|
22
|
+
type: "git";
|
|
23
|
+
/**
|
|
24
|
+
* Clone URL.
|
|
25
|
+
*/
|
|
26
|
+
repo: string;
|
|
27
|
+
/**
|
|
28
|
+
* Git host domain (for example, "github.com").
|
|
29
|
+
*/
|
|
30
|
+
host: string;
|
|
31
|
+
/**
|
|
32
|
+
* Repository path (for example, "org/repo").
|
|
33
|
+
*/
|
|
34
|
+
path: string;
|
|
35
|
+
/**
|
|
36
|
+
* Git ref (branch, tag, or commit) if specified.
|
|
37
|
+
*/
|
|
38
|
+
ref?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Whether the source includes a pinned ref.
|
|
41
|
+
*/
|
|
42
|
+
pinned: boolean;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function getHostedGitInfo(): HostedGitInfoModule | null {
|
|
46
|
+
const require = createRequire(import.meta.url);
|
|
47
|
+
try {
|
|
48
|
+
return require("hosted-git-info") as HostedGitInfoModule;
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const hostedGitInfo = getHostedGitInfo();
|
|
55
|
+
|
|
56
|
+
function splitRef(url: string): { repo: string; ref?: string } {
|
|
57
|
+
const scpLikeMatch = url.match(/^git@([^:]+):(.+)$/);
|
|
58
|
+
if (scpLikeMatch) {
|
|
59
|
+
const pathWithMaybeRef = scpLikeMatch[2] ?? "";
|
|
60
|
+
const atRefIndex = pathWithMaybeRef.indexOf("@");
|
|
61
|
+
const hashRefIndex = pathWithMaybeRef.indexOf("#");
|
|
62
|
+
let refSeparator = -1;
|
|
63
|
+
if (atRefIndex >= 0 && hashRefIndex >= 0) {
|
|
64
|
+
refSeparator = Math.min(atRefIndex, hashRefIndex);
|
|
65
|
+
} else {
|
|
66
|
+
refSeparator = atRefIndex >= 0 ? atRefIndex : hashRefIndex;
|
|
67
|
+
}
|
|
68
|
+
if (refSeparator < 0) {
|
|
69
|
+
return { repo: url };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const repoPath = pathWithMaybeRef.slice(0, refSeparator);
|
|
73
|
+
const ref = pathWithMaybeRef.slice(refSeparator + 1);
|
|
74
|
+
if (!repoPath || !ref) {
|
|
75
|
+
return { repo: url };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
repo: `git@${scpLikeMatch[1] ?? ""}:${repoPath}`,
|
|
80
|
+
ref,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (url.includes("://")) {
|
|
85
|
+
try {
|
|
86
|
+
const parsed = new URL(url);
|
|
87
|
+
const fragmentRef = parsed.hash.startsWith("#") ? parsed.hash.slice(1) : "";
|
|
88
|
+
parsed.hash = "";
|
|
89
|
+
const pathWithMaybeRef = parsed.pathname.replace(/^\/+/, "");
|
|
90
|
+
const atRefIndex = pathWithMaybeRef.indexOf("@");
|
|
91
|
+
if (atRefIndex >= 0) {
|
|
92
|
+
const repoPath = pathWithMaybeRef.slice(0, atRefIndex);
|
|
93
|
+
const ref = pathWithMaybeRef.slice(atRefIndex + 1);
|
|
94
|
+
if (!repoPath || !ref) {
|
|
95
|
+
return { repo: parsed.toString().replace(/\/$/, "") };
|
|
96
|
+
}
|
|
97
|
+
parsed.pathname = `/${repoPath}`;
|
|
98
|
+
return {
|
|
99
|
+
repo: parsed.toString().replace(/\/$/, ""),
|
|
100
|
+
ref,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (fragmentRef) {
|
|
105
|
+
return {
|
|
106
|
+
repo: parsed.toString().replace(/\/$/, ""),
|
|
107
|
+
ref: fragmentRef,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return { repo: parsed.toString().replace(/\/$/, "") };
|
|
112
|
+
} catch {
|
|
113
|
+
return { repo: url };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const slashIndex = url.indexOf("/");
|
|
118
|
+
if (slashIndex < 0) {
|
|
119
|
+
return { repo: url };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const pathWithMaybeRef = url.slice(slashIndex + 1);
|
|
123
|
+
const atRefIndex = pathWithMaybeRef.indexOf("@");
|
|
124
|
+
const hashRefIndex = pathWithMaybeRef.indexOf("#");
|
|
125
|
+
let refSeparator = -1;
|
|
126
|
+
if (atRefIndex >= 0 && hashRefIndex >= 0) {
|
|
127
|
+
refSeparator = Math.min(atRefIndex, hashRefIndex);
|
|
128
|
+
} else {
|
|
129
|
+
refSeparator = atRefIndex >= 0 ? atRefIndex : hashRefIndex;
|
|
130
|
+
}
|
|
131
|
+
if (refSeparator < 0) {
|
|
132
|
+
return { repo: url };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const repoPath = pathWithMaybeRef.slice(0, refSeparator);
|
|
136
|
+
const ref = pathWithMaybeRef.slice(refSeparator + 1);
|
|
137
|
+
if (!repoPath || !ref) {
|
|
138
|
+
return { repo: url };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
repo: `${url.slice(0, slashIndex)}/${repoPath}`,
|
|
143
|
+
ref,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function parseGenericGitUrl(url: string): GitSource | null {
|
|
148
|
+
const { repo: repoWithoutRef, ref } = splitRef(url);
|
|
149
|
+
let repo = repoWithoutRef;
|
|
150
|
+
let host = "";
|
|
151
|
+
let path = "";
|
|
152
|
+
|
|
153
|
+
const scpLikeMatch = repoWithoutRef.match(/^git@([^:]+):(.+)$/);
|
|
154
|
+
if (scpLikeMatch) {
|
|
155
|
+
host = scpLikeMatch[1] ?? "";
|
|
156
|
+
path = scpLikeMatch[2] ?? "";
|
|
157
|
+
} else if (
|
|
158
|
+
repoWithoutRef.startsWith("https://") ||
|
|
159
|
+
repoWithoutRef.startsWith("http://") ||
|
|
160
|
+
repoWithoutRef.startsWith("ssh://") ||
|
|
161
|
+
repoWithoutRef.startsWith("git://")
|
|
162
|
+
) {
|
|
163
|
+
try {
|
|
164
|
+
const parsed = new URL(repoWithoutRef);
|
|
165
|
+
host = parsed.hostname;
|
|
166
|
+
path = parsed.pathname.replace(/^\/+/, "");
|
|
167
|
+
} catch {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
} else {
|
|
171
|
+
const slashIndex = repoWithoutRef.indexOf("/");
|
|
172
|
+
if (slashIndex < 0) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
host = repoWithoutRef.slice(0, slashIndex);
|
|
177
|
+
path = repoWithoutRef.slice(slashIndex + 1);
|
|
178
|
+
if (!host.includes(".") && host !== "localhost") {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
repo = `https://${repoWithoutRef}`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const normalizedPath = path.replace(/\.git$/, "").replace(/^\/+/, "");
|
|
186
|
+
if (!host || !normalizedPath || normalizedPath.split("/").length < 2) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
type: "git",
|
|
192
|
+
repo,
|
|
193
|
+
host,
|
|
194
|
+
path: normalizedPath,
|
|
195
|
+
ref,
|
|
196
|
+
pinned: Boolean(ref),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Parse git source into normalized source information.
|
|
202
|
+
*/
|
|
203
|
+
export function parseGitUrl(source: string): GitSource | null {
|
|
204
|
+
const trimmed = source.trim();
|
|
205
|
+
const hasGitPrefix = trimmed.startsWith("git:");
|
|
206
|
+
const normalizedSource = hasGitPrefix ? trimmed.slice(4).trim() : trimmed.replace(/^git\+/i, "");
|
|
207
|
+
const url = normalizedSource;
|
|
208
|
+
|
|
209
|
+
if (!hasGitPrefix && !/^(https?|ssh|git):\/\//i.test(url) && !/^git@[^:]+:/.test(url)) {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const split = splitRef(url);
|
|
214
|
+
|
|
215
|
+
const parseCandidate = (candidate: string): GitSource | null => {
|
|
216
|
+
if (!hostedGitInfo) {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
const info = hostedGitInfo.fromUrl(candidate) as HostedGitInfoResult | null;
|
|
220
|
+
if (!info) return null;
|
|
221
|
+
if (split.ref && info.project?.includes("@")) {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
type: "git",
|
|
226
|
+
repo:
|
|
227
|
+
split.repo.startsWith("http://") ||
|
|
228
|
+
split.repo.startsWith("https://") ||
|
|
229
|
+
split.repo.startsWith("ssh://") ||
|
|
230
|
+
split.repo.startsWith("git://") ||
|
|
231
|
+
split.repo.startsWith("git@")
|
|
232
|
+
? split.repo
|
|
233
|
+
: `https://${split.repo}`,
|
|
234
|
+
host: info.domain ?? "",
|
|
235
|
+
path: `${info.user ?? ""}/${info.project ?? ""}`.replace(/\.git$/, ""),
|
|
236
|
+
ref: info.committish || split.ref || undefined,
|
|
237
|
+
pinned: Boolean(info.committish || split.ref),
|
|
238
|
+
};
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const hostedCandidates = [split.ref ? `${split.repo}#${split.ref}` : undefined, url].filter((value): value is string => Boolean(value));
|
|
242
|
+
for (const candidate of hostedCandidates) {
|
|
243
|
+
const info = parseCandidate(candidate);
|
|
244
|
+
if (info) {
|
|
245
|
+
return info;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const httpsCandidates = [split.ref ? `https://${split.repo}#${split.ref}` : undefined, `https://${url}`].filter(
|
|
250
|
+
(value): value is string => Boolean(value),
|
|
251
|
+
);
|
|
252
|
+
for (const candidate of httpsCandidates) {
|
|
253
|
+
const info = parseCandidate(candidate);
|
|
254
|
+
if (info) {
|
|
255
|
+
return info;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return parseGenericGitUrl(url);
|
|
260
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized type guard library for pi-crew.
|
|
3
|
+
*
|
|
4
|
+
* Inspired by @ayulab/runtime-core — all unknown-to-narrowed checks
|
|
5
|
+
* go through these helpers instead of inline typeof/instanceof.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ── Primitive guards ──────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
/** Narrow `unknown` to `Record<string, unknown>`. */
|
|
11
|
+
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
12
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Narrow `unknown` to `string`. */
|
|
16
|
+
export function isString(value: unknown): value is string {
|
|
17
|
+
return typeof value === "string";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Narrow `unknown` to a non-empty string. */
|
|
21
|
+
export function isNonEmptyString(value: unknown): value is string {
|
|
22
|
+
return typeof value === "string" && value.length > 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Narrow `unknown` to `number` (excludes NaN). */
|
|
26
|
+
export function isNumber(value: unknown): value is number {
|
|
27
|
+
return typeof value === "number" && !Number.isNaN(value);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Narrow `unknown` to `boolean`. */
|
|
31
|
+
export function isBoolean(value: unknown): value is boolean {
|
|
32
|
+
return typeof value === "boolean";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Narrow `unknown` to `readonly string[]`. */
|
|
36
|
+
export function isStringArray(value: unknown): value is readonly string[] {
|
|
37
|
+
return Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Higher-order guard: build a guard that checks every element of an array.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* const isNumberArray = isArrayOf(isNumber);
|
|
45
|
+
*/
|
|
46
|
+
export function isArrayOf<T>(guard: (item: unknown) => item is T): (value: unknown) => value is readonly T[] {
|
|
47
|
+
return (value: unknown): value is readonly T[] => Array.isArray(value) && value.every(guard);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Record field extractors ───────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
/** Extract a string field from a record, returning `undefined` if absent. */
|
|
53
|
+
export function getStringField(value: unknown, key: string): string | undefined {
|
|
54
|
+
if (!isRecord(value)) return undefined;
|
|
55
|
+
const field = value[key];
|
|
56
|
+
return typeof field === "string" ? field : undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Extract a number field from a record, returning `undefined` if absent. */
|
|
60
|
+
export function getNumberField(value: unknown, key: string): number | undefined {
|
|
61
|
+
if (!isRecord(value)) return undefined;
|
|
62
|
+
const field = value[key];
|
|
63
|
+
return typeof field === "number" && !Number.isNaN(field) ? field : undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Extract a boolean field from a record, returning `undefined` if absent. */
|
|
67
|
+
export function getBooleanField(value: unknown, key: string): boolean | undefined {
|
|
68
|
+
if (!isRecord(value)) return undefined;
|
|
69
|
+
const field = value[key];
|
|
70
|
+
return typeof field === "boolean" ? field : undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Extract a nested record field from a record, returning `undefined` if absent. */
|
|
74
|
+
export function getRecordField(value: unknown, key: string): Record<string, unknown> | undefined {
|
|
75
|
+
if (!isRecord(value)) return undefined;
|
|
76
|
+
const field = value[key];
|
|
77
|
+
return isRecord(field) ? field : undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Extract an array field from a record, returning `undefined` if absent. */
|
|
81
|
+
export function getArrayField(value: unknown, key: string): unknown[] | undefined {
|
|
82
|
+
if (!isRecord(value)) return undefined;
|
|
83
|
+
const field = value[key];
|
|
84
|
+
return Array.isArray(field) ? field : undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── Error helpers ─────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Extract a human-readable message from an unknown error value.
|
|
91
|
+
*
|
|
92
|
+
* Prefer this over manual `instanceof Error` checks to keep error
|
|
93
|
+
* handling uniform across the codebase.
|
|
94
|
+
*/
|
|
95
|
+
export function errorMessage(err: unknown): string {
|
|
96
|
+
return err instanceof Error ? err.message : String(err);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ── Utility types ─────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
/** A non-empty readonly array type. */
|
|
102
|
+
export type NonEmptyReadonlyArray<T> = readonly [T, ...T[]];
|
|
103
|
+
|
|
104
|
+
/** Narrow a readonly array to a non-empty readonly array. */
|
|
105
|
+
export function hasItems<T>(items: readonly T[]): items is NonEmptyReadonlyArray<T> {
|
|
106
|
+
return items.length > 0;
|
|
107
|
+
}
|
package/src/utils/ids.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { generateTaskName } from "./task-name-generator.ts";
|
|
3
|
+
|
|
4
|
+
export function createRunId(prefix = "team"): string {
|
|
5
|
+
const stamp = new Date()
|
|
6
|
+
.toISOString()
|
|
7
|
+
.replace(/[-:.TZ]/g, "")
|
|
8
|
+
.slice(0, 14);
|
|
9
|
+
const suffix = randomBytes(8).toString("hex");
|
|
10
|
+
return `${prefix}_${stamp}_${suffix}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function createTaskId(stepId: string, index: number): string {
|
|
14
|
+
const normalized =
|
|
15
|
+
stepId
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(/[^a-z0-9-]+/g, "-")
|
|
18
|
+
.replace(/^-+|-+$/g, "") || "task";
|
|
19
|
+
return `${String(index + 1).padStart(2, "0")}_${normalized}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createDisplayName(): string {
|
|
23
|
+
return generateTaskName();
|
|
24
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
|
|
3
|
+
export interface IncrementalReadState {
|
|
4
|
+
byteOffset: number;
|
|
5
|
+
lineCount: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface IncrementalReadResult {
|
|
9
|
+
lines: string[];
|
|
10
|
+
state: IncrementalReadState;
|
|
11
|
+
eof: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const CHUNK_SIZE = 64 * 1024;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Read the last `tailBytes` bytes of a file and parse it as JSONL,
|
|
18
|
+
* returning each successfully-parsed item in file order. If the file is
|
|
19
|
+
* shorter than `tailBytes`, the entire file is read from offset 0.
|
|
20
|
+
*
|
|
21
|
+
* Returns:
|
|
22
|
+
* - items: parsed JSONL items (T may be any deserializable type)
|
|
23
|
+
* - fileSize: total file size in bytes
|
|
24
|
+
* - bytesRead: number of bytes actually read (≤ min(fileSize, tailBytes))
|
|
25
|
+
* - truncated: true when `bytesRead < fileSize` (a prefix was dropped)
|
|
26
|
+
*
|
|
27
|
+
* The function bounds the read to `tailBytes`, so CPU is O(tail bytes)
|
|
28
|
+
* regardless of how large the file grows. Used by the event-log tail
|
|
29
|
+
* read path (FIND-05) to avoid the O(total events) cost of the legacy
|
|
30
|
+
* `readFileSync`+`split("\n")`+`JSON.parse` approach.
|
|
31
|
+
*/
|
|
32
|
+
export function readJsonlTail<T>(
|
|
33
|
+
filePath: string,
|
|
34
|
+
tailBytes: number,
|
|
35
|
+
): {
|
|
36
|
+
items: T[];
|
|
37
|
+
fileSize: number;
|
|
38
|
+
bytesRead: number;
|
|
39
|
+
truncated: boolean;
|
|
40
|
+
} {
|
|
41
|
+
const limit = Math.max(0, Math.floor(tailBytes));
|
|
42
|
+
let stat: fs.Stats;
|
|
43
|
+
try {
|
|
44
|
+
stat = fs.statSync(filePath);
|
|
45
|
+
} catch {
|
|
46
|
+
return { items: [], fileSize: 0, bytesRead: 0, truncated: false };
|
|
47
|
+
}
|
|
48
|
+
const fileSize = stat.size;
|
|
49
|
+
if (fileSize === 0 || limit === 0) {
|
|
50
|
+
return { items: [], fileSize, bytesRead: 0, truncated: false };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const startOffset = Math.max(0, fileSize - limit);
|
|
54
|
+
const bytesToRead = fileSize - startOffset;
|
|
55
|
+
const truncated = startOffset > 0;
|
|
56
|
+
|
|
57
|
+
let fd: number | undefined;
|
|
58
|
+
try {
|
|
59
|
+
// FIND-05 follow-up: O_NOFOLLOW refuses symlinks (defense-in-depth,
|
|
60
|
+
// consistent with atomicWriteFile / resolveRealContainedPath). The
|
|
61
|
+
// eventsPath is validated inside stateRoot, but this guards against a
|
|
62
|
+
// symlinked file appearing after validation. O_NOFOLLOW on a symlink
|
|
63
|
+
// yields ELOOP, which the catch below treats like ENOENT (return empty).
|
|
64
|
+
fd = fs.openSync(filePath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
65
|
+
} catch {
|
|
66
|
+
return { items: [], fileSize, bytesRead: 0, truncated: false };
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const buf = Buffer.alloc(bytesToRead);
|
|
70
|
+
let totalRead = 0;
|
|
71
|
+
while (totalRead < bytesToRead) {
|
|
72
|
+
const chunkSize = Math.min(CHUNK_SIZE, bytesToRead - totalRead);
|
|
73
|
+
const bytesRead = fs.readSync(fd, buf, totalRead, chunkSize, startOffset + totalRead);
|
|
74
|
+
if (bytesRead === 0) break;
|
|
75
|
+
totalRead += bytesRead;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Decode the slice we actually read; the buffer may be partially
|
|
79
|
+
// filled on short reads. We use totalRead (not bytesToRead) so the
|
|
80
|
+
// JSONL split lands on actual data, not a zero-padded tail.
|
|
81
|
+
const content = buf.toString("utf-8", 0, totalRead);
|
|
82
|
+
|
|
83
|
+
// If we truncated the file, the first decoded line may be a
|
|
84
|
+
// half-line from the cut. Drop it — we can't trust the JSON.
|
|
85
|
+
// Use a single indexOf("\n") to find the first newline; if there
|
|
86
|
+
// is none, the whole tail is one partial line and we drop it.
|
|
87
|
+
let body = content;
|
|
88
|
+
if (truncated) {
|
|
89
|
+
const firstNewline = body.indexOf("\n");
|
|
90
|
+
if (firstNewline < 0) {
|
|
91
|
+
// No newline at all in the tail → the entire tail is one
|
|
92
|
+
// partial line. Return no items; the caller should fall
|
|
93
|
+
// back to a wider tail or full read.
|
|
94
|
+
return { items: [], fileSize, bytesRead: totalRead, truncated: true };
|
|
95
|
+
}
|
|
96
|
+
body = body.slice(firstNewline + 1);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const items: T[] = [];
|
|
100
|
+
for (const line of body.split("\n")) {
|
|
101
|
+
const trimmed = line.trim();
|
|
102
|
+
if (!trimmed) continue;
|
|
103
|
+
try {
|
|
104
|
+
items.push(JSON.parse(trimmed) as T);
|
|
105
|
+
} catch {
|
|
106
|
+
// Skip malformed lines (mirrors readJsonlSince's behavior).
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return { items, fileSize, bytesRead: totalRead, truncated };
|
|
110
|
+
} finally {
|
|
111
|
+
if (fd !== undefined) {
|
|
112
|
+
try {
|
|
113
|
+
fs.closeSync(fd);
|
|
114
|
+
} catch {
|
|
115
|
+
/* ignore */
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Read new lines from a text file since last known byte offset.
|
|
123
|
+
* Uses fs.openSync + fs.readSync for efficient incremental reading.
|
|
124
|
+
*/
|
|
125
|
+
export function readLinesSince(filePath: string, state: IncrementalReadState): IncrementalReadResult {
|
|
126
|
+
let fd: number | undefined;
|
|
127
|
+
try {
|
|
128
|
+
fd = fs.openSync(filePath, "r");
|
|
129
|
+
} catch {
|
|
130
|
+
return {
|
|
131
|
+
lines: [],
|
|
132
|
+
state: { byteOffset: state.byteOffset, lineCount: state.lineCount },
|
|
133
|
+
eof: true,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const stat = fs.fstatSync(fd);
|
|
139
|
+
const fileSize = stat.size;
|
|
140
|
+
|
|
141
|
+
if (fileSize <= state.byteOffset) {
|
|
142
|
+
return {
|
|
143
|
+
lines: [],
|
|
144
|
+
state: { byteOffset: fileSize, lineCount: state.lineCount },
|
|
145
|
+
eof: true,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const bytesToRead = fileSize - state.byteOffset;
|
|
150
|
+
const buf = Buffer.alloc(bytesToRead);
|
|
151
|
+
let totalRead = 0;
|
|
152
|
+
|
|
153
|
+
while (totalRead < bytesToRead) {
|
|
154
|
+
const chunkSize = Math.min(CHUNK_SIZE, bytesToRead - totalRead);
|
|
155
|
+
const bytesRead = fs.readSync(fd, buf, totalRead, chunkSize, state.byteOffset + totalRead);
|
|
156
|
+
if (bytesRead === 0) break;
|
|
157
|
+
totalRead += bytesRead;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const content = buf.toString("utf-8", 0, totalRead);
|
|
161
|
+
const lines: string[] = [];
|
|
162
|
+
let lineCount = state.lineCount;
|
|
163
|
+
let committedOffset = state.byteOffset;
|
|
164
|
+
|
|
165
|
+
let searchFrom = 0;
|
|
166
|
+
let newlineIdx: number;
|
|
167
|
+
|
|
168
|
+
while ((newlineIdx = content.indexOf("\n", searchFrom)) !== -1) {
|
|
169
|
+
const lineText = content.slice(searchFrom, newlineIdx);
|
|
170
|
+
committedOffset = state.byteOffset + newlineIdx + 1;
|
|
171
|
+
searchFrom = newlineIdx + 1;
|
|
172
|
+
if (lineText.length > 0) {
|
|
173
|
+
lines.push(lineText);
|
|
174
|
+
lineCount++;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const eof = committedOffset >= fileSize;
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
lines,
|
|
182
|
+
state: { byteOffset: committedOffset, lineCount },
|
|
183
|
+
eof,
|
|
184
|
+
};
|
|
185
|
+
} finally {
|
|
186
|
+
if (fd !== undefined) {
|
|
187
|
+
try {
|
|
188
|
+
fs.closeSync(fd);
|
|
189
|
+
} catch {
|
|
190
|
+
/* ignore */
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Read parsed JSON objects from a JSONL file since last known byte offset.
|
|
198
|
+
* Skips malformed lines.
|
|
199
|
+
*/
|
|
200
|
+
export function readJsonlSince<T>(
|
|
201
|
+
filePath: string,
|
|
202
|
+
state: IncrementalReadState,
|
|
203
|
+
): { items: T[]; state: IncrementalReadState; eof: boolean } {
|
|
204
|
+
const result = readLinesSince(filePath, state);
|
|
205
|
+
const items: T[] = [];
|
|
206
|
+
|
|
207
|
+
for (const line of result.lines) {
|
|
208
|
+
try {
|
|
209
|
+
items.push(JSON.parse(line) as T);
|
|
210
|
+
} catch {
|
|
211
|
+
// Skip malformed lines
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
items,
|
|
217
|
+
state: result.state,
|
|
218
|
+
eof: result.eof,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function logInternalError(scope: string, error: unknown, details?: string, severity?: "error" | "warn" | "debug"): void {
|
|
2
|
+
// "error" and "warn" always emit; "debug" (default) is gated behind PI_TEAMS_DEBUG
|
|
3
|
+
if (!severity || severity === "debug") {
|
|
4
|
+
if (!process.env.PI_TEAMS_DEBUG) return;
|
|
5
|
+
}
|
|
6
|
+
const message =
|
|
7
|
+
error instanceof Error ? error.message : typeof error === "object" && error !== null ? JSON.stringify(error) : String(error);
|
|
8
|
+
const suffix = details ? `: ${details}` : "";
|
|
9
|
+
console.error(`[pi-crew:${scope}] ${message}${suffix}`);
|
|
10
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export function sanitizeName(name: string): string {
|
|
2
|
+
const result = name
|
|
3
|
+
.toLowerCase()
|
|
4
|
+
.trim()
|
|
5
|
+
.replace(/\s+/g, "-")
|
|
6
|
+
.replace(/[^a-z0-9-]/g, "")
|
|
7
|
+
.replace(/-+/g, "-")
|
|
8
|
+
.replace(/^-+|-+$/g, "");
|
|
9
|
+
return result || "unnamed";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function requireString(value: unknown, label: string): { value?: string; error?: string } {
|
|
13
|
+
if (typeof value !== "string" || !value.trim()) return { error: `${label} must be a non-empty string.` };
|
|
14
|
+
return { value: value.trim() };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function parseConfigObject(config: unknown): {
|
|
18
|
+
value?: Record<string, unknown>;
|
|
19
|
+
error?: string;
|
|
20
|
+
} {
|
|
21
|
+
let parsed = config;
|
|
22
|
+
if (typeof parsed === "string") {
|
|
23
|
+
try {
|
|
24
|
+
parsed = JSON.parse(parsed) as unknown;
|
|
25
|
+
} catch (error) {
|
|
26
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
27
|
+
return { error: `config must be valid JSON: ${message}` };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return { error: "config must be an object." };
|
|
31
|
+
return { value: parsed as Record<string, unknown> };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function hasOwn(obj: Record<string, unknown>, key: string): boolean {
|
|
35
|
+
return Object.hasOwn(obj, key);
|
|
36
|
+
}
|