bosun 0.42.5 → 0.42.6
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/.env.example +24 -50
- package/README.md +28 -2
- package/agent/agent-custom-tools.mjs +138 -26
- package/agent/agent-endpoint.mjs +1 -2
- package/agent/agent-hooks.mjs +53 -5
- package/agent/agent-pool.mjs +201 -37
- package/agent/agent-prompt-catalog.mjs +2 -2
- package/agent/agent-prompts.mjs +62 -6
- package/agent/agent-sdk.mjs +133 -0
- package/agent/autofix-prompts.mjs +2 -2
- package/agent/autofix.mjs +2 -2
- package/agent/bosun-skills.mjs +49 -29
- package/agent/fleet-coordinator.mjs +20 -7
- package/agent/hook-library.mjs +2 -2
- package/agent/hook-profiles.mjs +106 -31
- package/agent/primary-agent.mjs +10 -61
- package/agent/review-agent.mjs +1 -1
- package/bench/swebench/bosun-swebench.mjs +20 -6
- package/bosun.config.example.json +55 -2
- package/bosun.schema.json +241 -5
- package/cli.mjs +86 -41
- package/config/config-doctor.mjs +4 -24
- package/config/config.mjs +356 -111
- package/config/repo-config.mjs +48 -40
- package/desktop/main.mjs +116 -16
- package/git/git-editor-fix.mjs +2 -42
- package/github/github-app-auth.mjs +6 -0
- package/github/github-oauth-portal.mjs +20 -0
- package/infra/anomaly-detector.mjs +7 -7
- package/infra/container-runner.mjs +0 -1
- package/infra/error-detector.mjs +1 -1
- package/infra/library-manager.mjs +239 -17
- package/infra/maintenance.mjs +26 -14
- package/infra/monitor.mjs +505 -1869
- package/infra/preflight.mjs +37 -2
- package/infra/session-tracker.mjs +251 -99
- package/infra/tracing.mjs +125 -13
- package/infra/worktree-recovery-state.mjs +5 -2
- package/kanban/kanban-adapter.mjs +43 -284
- package/lib/logger.mjs +21 -25
- package/lib/session-insights.mjs +64 -0
- package/lib/skill-markdown-safety.mjs +394 -0
- package/monitor-tail-sanitizer.mjs +1 -2
- package/package.json +17 -24
- package/postinstall.mjs +0 -1
- package/server/bosun-mcp-server.mjs +15 -7
- package/server/setup-web-server.mjs +277 -23
- package/server/ui-server.mjs +1308 -247
- package/setup.mjs +181 -296
- package/shared-workspaces.json +1 -1
- package/shell/codex-config.mjs +143 -244
- package/shell/codex-model-profiles.mjs +108 -14
- package/shell/codex-shell.mjs +144 -21
- package/shell/copilot-shell.mjs +23 -5
- package/shell/opencode-providers.mjs +357 -72
- package/shell/pwsh-runtime.mjs +9 -2
- package/task/task-claims.mjs +1 -1
- package/task/task-cli.mjs +11 -10
- package/task/task-complexity.mjs +6 -6
- package/task/task-executor.mjs +11 -12
- package/task/task-store.mjs +36 -4
- package/telegram/get-telegram-chat-id.mjs +0 -0
- package/telegram/telegram-bot.mjs +195 -118
- package/telegram/telegram-sentinel.mjs +1 -1
- package/tools/import-check.mjs +234 -234
- package/tools/packed-cli-smoke.mjs +67 -6
- package/tools/site-serve.mjs +112 -0
- package/tools/test-kanban-enhancement.mjs +7 -7
- package/tools/test-shared-state-integration.mjs +5 -19
- package/tools/vitest-runner.mjs +32 -5
- package/tui/app.mjs +43 -3
- package/tui/components/status-header.mjs +4 -1
- package/tui/lib/header-config.mjs +0 -2
- package/tui/lib/navigation.mjs +3 -2
- package/tui/screens/agents.mjs +6 -2
- package/tui/screens/logs.mjs +329 -0
- package/tui/screens/status.mjs +4 -1
- package/ui/app.js +412 -122
- package/ui/app.monolith.js +2 -3
- package/ui/components/commit-graph.js +648 -0
- package/ui/components/diff-viewer.js +88 -21
- package/ui/components/forms.js +13 -2
- package/ui/components/kanban-board.js +36 -6
- package/ui/components/session-list.js +11 -1
- package/ui/components/shared.js +9 -1
- package/ui/components/workspace-executor-settings.js +142 -0
- package/ui/components/workspace-switcher.js +24 -77
- package/ui/demo-defaults.js +2714 -2574
- package/ui/demo.html +6102 -5196
- package/ui/index.html +164 -113
- package/ui/modules/icon-utils.js +9 -1
- package/ui/modules/icons.js +9 -1
- package/ui/modules/repo-area-contention.js +97 -0
- package/ui/modules/settings-schema.js +4 -3
- package/ui/modules/state.js +67 -11
- package/ui/setup.html +3152 -2531
- package/ui/styles/components.css +32 -0
- package/ui/styles/layout.css +246 -34
- package/ui/styles.css +1 -5
- package/ui/tabs/agents.js +209 -9
- package/ui/tabs/control.js +226 -61
- package/ui/tabs/dashboard.js +92 -41
- package/ui/tabs/infra.js +91 -12
- package/ui/tabs/library.js +142 -17
- package/ui/tabs/logs.js +410 -52
- package/ui/tabs/settings.js +513 -1
- package/ui/tabs/tasks.js +328 -24
- package/ui/tabs/telemetry.js +144 -2
- package/ui/tabs/workflows.js +214 -155
- package/ui/tui/App.js +10 -2
- package/ui/tui/TasksScreen.js +11 -2
- package/ui/tui/logs-screen-helpers.js +292 -0
- package/ui/tui/useTasks.js +6 -1
- package/ui/tui/useWebSocket.js +7 -1
- package/ui/tui/useWorkflows.js +6 -1
- package/ui/vendor/preact-jsx-runtime.js +5 -0
- package/utils.mjs +24 -11
- package/workflow/heavy-runner-pool.mjs +479 -0
- package/workflow/mcp-discovery-proxy.mjs +14 -6
- package/workflow/mcp-registry.mjs +177 -39
- package/workflow/workflow-engine.mjs +126 -43
- package/workflow/workflow-migration.mjs +0 -1
- package/workflow/workflow-nodes/definitions.mjs +34 -14
- package/workflow/workflow-nodes/transforms.mjs +33 -6
- package/workflow/workflow-nodes.mjs +1014 -193
- package/workflow/workflow-templates.mjs +114 -4
- package/workflow-templates/code-quality.mjs +216 -0
- package/workflow-templates/continuation-loop.mjs +2 -1
- package/workflow-templates/github.mjs +347 -86
- package/workflow-templates/planning.mjs +22 -1
- package/workflow-templates/reliability.mjs +82 -25
- package/workflow-templates/task-batch.mjs +4 -12
- package/workflow-templates/task-lifecycle.mjs +27 -256
- package/workspace/command-diagnostics.mjs +111 -0
- package/workspace/context-cache.mjs +404 -52
- package/workspace/shared-workspace-cli.mjs +0 -0
- package/workspace/shared-workspace-registry.mjs +2 -2
- package/workspace/workspace-monitor.mjs +1 -1
- package/workspace/worktree-manager.mjs +92 -43
- package/workspace/worktree-setup.mjs +231 -0
- package/git/sdk-conflict-resolver.mjs +0 -971
- package/infra/sync-engine.mjs +0 -1160
- package/kanban/ve-kanban.mjs +0 -664
- package/kanban/ve-kanban.ps1 +0 -1365
- package/kanban/ve-kanban.sh +0 -18
- package/kanban/ve-orchestrator.mjs +0 -340
- package/kanban/ve-orchestrator.ps1 +0 -6762
- package/kanban/ve-orchestrator.sh +0 -18
- package/kanban/vibe-kanban-wrapper.mjs +0 -41
- package/kanban/vk-error-resolver.mjs +0 -474
- package/kanban/vk-log-stream.mjs +0 -932
- package/task/task-archiver.mjs +0 -813
- package/tools/publish.mjs +0 -239
- package/ui/components/chat-view.js.bak +0 -1
- package/ui/tabs/infra.js.bak +0 -1
package/.env.example
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
# Max characters of matched built-in/local skills injected into an agent task prompt.
|
|
2
|
+
# Skills are only injected when task title/description/labels match skill tags.
|
|
3
|
+
BOSUN_SKILLS_MAX_CHARS=4000
|
|
1
4
|
# ─── Bosun — Environment Configuration ───────────────────────────────
|
|
2
5
|
# Copy this file to .env and fill in your values.
|
|
3
6
|
# Or run: bosun --setup
|
|
@@ -88,6 +91,8 @@ TELEGRAM_MINIAPP_ENABLED=false
|
|
|
88
91
|
# BOSUN_UI_BROWSER_OPEN_MODE=manual
|
|
89
92
|
# Legacy auto-open toggle for UI server (requires BOSUN_UI_BROWSER_OPEN_MODE=auto)
|
|
90
93
|
# BOSUN_UI_AUTO_OPEN_BROWSER=false
|
|
94
|
+
# Daemon startup keeps browser auto-open disabled unless this is explicitly true.
|
|
95
|
+
# BOSUN_UI_AUTO_OPEN_ON_DAEMON=false
|
|
91
96
|
# Show full /?token=... browser URL in logs (default: false; token is hidden)
|
|
92
97
|
# BOSUN_UI_LOG_TOKENIZED_BROWSER_URL=false
|
|
93
98
|
# Setup wizard browser auto-open (default: true when mode=auto)
|
|
@@ -350,12 +355,8 @@ VOICE_DELEGATE_EXECUTOR=codex-sdk
|
|
|
350
355
|
# FAILOVER_DISABLE_AFTER=3
|
|
351
356
|
|
|
352
357
|
# ─── Internal Executor ───────────────────────────────────────────────────────
|
|
353
|
-
# Controls whether tasks are executed locally via agent-pool
|
|
354
|
-
#
|
|
355
|
-
# "vk" — all tasks via VK executor (default, existing behavior)
|
|
356
|
-
# "internal" — all tasks via local agent-pool (bypass wrapper orchestrator script)
|
|
357
|
-
# "hybrid" — both VK and internal run simultaneously for overflow
|
|
358
|
-
# EXECUTOR_MODE=vk
|
|
358
|
+
# Controls whether tasks are executed locally via agent-pool.
|
|
359
|
+
# EXECUTOR_MODE=internal
|
|
359
360
|
# Max concurrent agent slots for internal executor (default: 3)
|
|
360
361
|
# INTERNAL_EXECUTOR_PARALLEL=3
|
|
361
362
|
# INTERNAL_EXECUTOR_BASE_BRANCH_PARALLEL=0
|
|
@@ -495,7 +496,6 @@ VOICE_DELEGATE_EXECUTOR=codex-sdk
|
|
|
495
496
|
# ─── Kanban Backend ──────────────────────────────────────────────────────────
|
|
496
497
|
# Task-board backend:
|
|
497
498
|
# internal - local task-store source of truth (recommended primary)
|
|
498
|
-
# vk - Vibe-Kanban (secondary adapter)
|
|
499
499
|
# github - GitHub Issues
|
|
500
500
|
# jira - Jira Issues
|
|
501
501
|
# KANBAN_BACKEND=internal
|
|
@@ -661,37 +661,6 @@ VOICE_DELEGATE_EXECUTOR=codex-sdk
|
|
|
661
661
|
# Stop auto-restarts after this many instant failures in a row (default: 3)
|
|
662
662
|
# BOSUN_DAEMON_MAX_INSTANT_RESTARTS=3
|
|
663
663
|
|
|
664
|
-
# ─── Vibe-Kanban ──────────────────────────────────────────────────────────────
|
|
665
|
-
# Base URL for the Vibe-Kanban API (default: http://127.0.0.1:54089)
|
|
666
|
-
VK_BASE_URL=http://127.0.0.1:54089
|
|
667
|
-
# Alternate endpoint URL for VK (overrides VK_BASE_URL if set)
|
|
668
|
-
# VK_ENDPOINT_URL=http://127.0.0.1:54089
|
|
669
|
-
# Port for vibe-kanban API (default: 54089)
|
|
670
|
-
VK_RECOVERY_PORT=54089
|
|
671
|
-
# Host for VK recovery (default: 0.0.0.0)
|
|
672
|
-
# VK_RECOVERY_HOST=0.0.0.0
|
|
673
|
-
# VK_HOST=0.0.0.0
|
|
674
|
-
# Public URL shown in Telegram links (optional)
|
|
675
|
-
# VK_PUBLIC_URL=https://kanban.yoursite.com
|
|
676
|
-
# VK_WEB_URL=https://kanban.yoursite.com
|
|
677
|
-
# VK HTTP timeout/retry controls (used by ve-kanban.ps1)
|
|
678
|
-
# VK_HTTP_TIMEOUT_SEC=45
|
|
679
|
-
# VK_HTTP_RETRIES=2
|
|
680
|
-
# VK_HTTP_RETRY_DELAY_MS=1500
|
|
681
|
-
# Set to true to prevent the monitor from spawning vibe-kanban automatically
|
|
682
|
-
# VK_NO_SPAWN=false
|
|
683
|
-
# Cooldown minutes between VK recovery attempts (default: 10)
|
|
684
|
-
# VK_RECOVERY_COOLDOWN_MIN=10
|
|
685
|
-
# VK health check interval in ms (default: 60000)
|
|
686
|
-
# VK_ENSURE_INTERVAL=60000
|
|
687
|
-
# VK project name (auto-detected)
|
|
688
|
-
# VK_PROJECT_NAME=my-project
|
|
689
|
-
# Explicit VK project/repo IDs (auto-detected if empty)
|
|
690
|
-
# VK_PROJECT_ID=
|
|
691
|
-
# VK_REPO_ID=
|
|
692
|
-
# Override task URL template (optional)
|
|
693
|
-
# VK_TASK_URL_TEMPLATE=https://kanban.yoursite.com/projects/{projectId}/tasks/{taskId}
|
|
694
|
-
|
|
695
664
|
# ─── Shared Workspace Registry ───────────────────────────────────────────────
|
|
696
665
|
# Optional registry path for shared workspace leasing
|
|
697
666
|
# VE_SHARED_WORKSPACE_REGISTRY=.cache/bosun/shared-workspaces.json
|
|
@@ -713,13 +682,8 @@ VK_RECOVERY_PORT=54089
|
|
|
713
682
|
# GITHUB_TOKEN=
|
|
714
683
|
# GH_TOKEN=
|
|
715
684
|
# GITHUB_PAT=
|
|
716
|
-
# Owner/repo for gh CLI in ve-kanban
|
|
717
|
-
# GH_OWNER=virtengine
|
|
718
|
-
# GH_REPO=virtengine
|
|
719
685
|
# Target branch for PR checks/merge (default: origin/main)
|
|
720
|
-
#
|
|
721
|
-
# Default upstream/base branch for bosun tasks (overrides VK_TARGET_BRANCH)
|
|
722
|
-
# BOSUN_TASK_UPSTREAM=origin/ve/bosun-generic
|
|
686
|
+
# BOSUN_TASK_UPSTREAM=origin/main
|
|
723
687
|
|
|
724
688
|
# ─── Codex / AI Provider ─────────────────────────────────────────────────────
|
|
725
689
|
# The Codex SDK uses OpenAI-compatible configuration that has been setup in ~/.codex/config.toml -
|
|
@@ -1024,7 +988,7 @@ COPILOT_CLOUD_DISABLED=true
|
|
|
1024
988
|
|
|
1025
989
|
# ─── Task Planner ─────────────────────────────────────────────────────────────
|
|
1026
990
|
# How to plan new tasks when backlog is empty:
|
|
1027
|
-
# "kanban" - (default) create a
|
|
991
|
+
# "kanban" - (default) create a planning task for an agent to refine
|
|
1028
992
|
# "codex-sdk" - run Codex SDK directly to generate tasks
|
|
1029
993
|
# "disabled" - do nothing, wait for manual task creation
|
|
1030
994
|
# TASK_PLANNER_MODE=kanban
|
|
@@ -1056,6 +1020,17 @@ COPILOT_CLOUD_DISABLED=true
|
|
|
1056
1020
|
# WORKFLOW_RECOVERY_BACKOFF_MAX_MS=60000
|
|
1057
1021
|
# Random jitter ratio (0.0-0.9) applied to backoff to prevent retry storms.
|
|
1058
1022
|
# WORKFLOW_RECOVERY_BACKOFF_JITTER_RATIO=0.2
|
|
1023
|
+
# Delay startup workflow recovery actions so the daemon can settle before
|
|
1024
|
+
# resuming interrupted runs or firing schedule/task-poll recovery.
|
|
1025
|
+
# WORKFLOW_RECOVERY_STARTUP_GRACE_MS=30000
|
|
1026
|
+
# Additional delay inserted between each startup recovery action.
|
|
1027
|
+
# WORKFLOW_RECOVERY_STARTUP_STEP_DELAY_MS=15000
|
|
1028
|
+
|
|
1029
|
+
# Bosun MCP policy: by default Bosun-launched agents only receive validated
|
|
1030
|
+
# library-managed MCP servers, with required auth pulled from environment.
|
|
1031
|
+
# BOSUN_MCP_REQUIRE_AUTH=true
|
|
1032
|
+
# BOSUN_MCP_ALLOW_EXTERNAL_SOURCES=false
|
|
1033
|
+
# BOSUN_MCP_ALLOW_DEFAULT_SERVERS=false
|
|
1059
1034
|
|
|
1060
1035
|
# ─── GitHub Issue Reconciler ─────────────────────────────────────────────────
|
|
1061
1036
|
# Periodically reconciles open GitHub issues against open/merged PRs.
|
|
@@ -1135,12 +1110,12 @@ COPILOT_CLOUD_DISABLED=true
|
|
|
1135
1110
|
# Repository root (auto-detected from git; setup writes this)
|
|
1136
1111
|
# REPO_ROOT=/path/to/repo
|
|
1137
1112
|
# Watch path to trigger restarts (default: script path)
|
|
1138
|
-
# WATCH_PATH=/path/to/
|
|
1113
|
+
# WATCH_PATH=/path/to/orchestrator.sh
|
|
1139
1114
|
# Monitor source hot-reload watcher. Default: enabled in devmode, disabled otherwise.
|
|
1140
1115
|
# Set to true to force-enable monitor source hot-restart, false to force-disable.
|
|
1141
1116
|
# SELF_RESTART_WATCH_ENABLED=true
|
|
1142
|
-
# Status file path (default: .cache/
|
|
1143
|
-
# STATUS_FILE=.cache/
|
|
1117
|
+
# Status file path (default: .cache/orchestrator-status.json)
|
|
1118
|
+
# STATUS_FILE=.cache/orchestrator-status.json
|
|
1144
1119
|
# Log directory (default: ./logs)
|
|
1145
1120
|
# LOG_DIR=./logs
|
|
1146
1121
|
# Max total log folder size in MB. Oldest logs are deleted when exceeded. 0 = unlimited.
|
|
@@ -1161,8 +1136,6 @@ COPILOT_CLOUD_DISABLED=true
|
|
|
1161
1136
|
# AGENT_WORK_LOGGING_ENABLED=true
|
|
1162
1137
|
# Enable/disable live stream analyzer (default: true)
|
|
1163
1138
|
# AGENT_WORK_ANALYZER_ENABLED=true
|
|
1164
|
-
# Enrich missing task metadata from VK for agent work logs (default: true)
|
|
1165
|
-
# AGENT_WORK_LOGGING_ENRICH_VK=true
|
|
1166
1139
|
# Task metadata cache (auto-managed): .cache/agent-work-logs/task-metadata.json
|
|
1167
1140
|
# Log directory (default: .cache/agent-work-logs)
|
|
1168
1141
|
# AGENT_WORK_LOG_DIR=.cache/agent-work-logs
|
|
@@ -1184,3 +1157,4 @@ COPILOT_CLOUD_DISABLED=true
|
|
|
1184
1157
|
# OpenTelemetry tracing (optional)
|
|
1185
1158
|
# BOSUN_OTEL_ENDPOINT=http://localhost:4318/v1/traces
|
|
1186
1159
|
|
|
1160
|
+
|
package/README.md
CHANGED
|
@@ -108,7 +108,7 @@ Fallback admin auth (secondary path) is available and stores only Argon2id hash
|
|
|
108
108
|
- Persists workflow runs to disk and auto-resumes on restart
|
|
109
109
|
- Monitors runs and recovers from stalled or broken states
|
|
110
110
|
- Provides Telegram control and a Mini App dashboard
|
|
111
|
-
- Integrates with GitHub
|
|
111
|
+
- Integrates with GitHub and Jira boards
|
|
112
112
|
|
|
113
113
|
## Autonomous Engineer Workflow Capabilities
|
|
114
114
|
|
|
@@ -144,6 +144,16 @@ Set `primaryAgent` in `.bosun/bosun.config.json` or choose an executor preset du
|
|
|
144
144
|
- `bosun --daemon --sentinel` starts daemon + sentinel together (recommended for unattended operation).
|
|
145
145
|
- `bosun --terminate` is the clean reset command when you suspect stale/ghost processes.
|
|
146
146
|
|
|
147
|
+
## VS Code debugging
|
|
148
|
+
|
|
149
|
+
Bosun now includes workspace debug entries in `.vscode/launch.json` and helper tasks in `.vscode/tasks.json`.
|
|
150
|
+
|
|
151
|
+
- `Debug Bosun CLI` launches `cli.mjs` with the repo-local `.bosun` config and attaches the debugger to the real CLI entry path.
|
|
152
|
+
- `Debug Bosun Monitor Direct` launches `infra/monitor.mjs` directly when you want to debug monitor logic without stepping through the CLI worker bootstrap.
|
|
153
|
+
- `Debug Bosun Daemon Child (foreground)` runs the daemon-child path without detaching, which is useful for restart-loop and daemon-specific behavior.
|
|
154
|
+
- `Attach to Bosun CLI Startup (9229)` and `Attach to Bosun Monitor Startup (9230)` start Bosun under `--inspect-brk` so you can catch startup failures before normal breakpoints would bind.
|
|
155
|
+
- `Bosun: Terminate Runtime` is the cleanup task to use if a stale monitor/daemon is holding the lock before a debug session.
|
|
156
|
+
|
|
147
157
|
Telegram operators can pull the weekly agent work summary with `/weekly [days]` or `/report weekly [days]`. To post it automatically once per week, set `TELEGRAM_WEEKLY_REPORT_ENABLED=true` together with `TELEGRAM_WEEKLY_REPORT_DAY`, `TELEGRAM_WEEKLY_REPORT_HOUR`, and optional `TELEGRAM_WEEKLY_REPORT_DAYS`.
|
|
148
158
|
|
|
149
159
|
## Documentation
|
|
@@ -162,6 +172,20 @@ Key places to start:
|
|
|
162
172
|
- `docs/agent-logging-quickstart.md` - agent work logging quickstart
|
|
163
173
|
- `docs/agent-work-logging-design.md` - logging design and event model
|
|
164
174
|
|
|
175
|
+
## Troubleshooting
|
|
176
|
+
|
|
177
|
+
### Preflight warns about an interactive git editor
|
|
178
|
+
|
|
179
|
+
If preflight reports an interactive git editor such as `code --wait`, `vim`, or `nano`, Bosun can deadlock while Git waits for an editor session to close.
|
|
180
|
+
|
|
181
|
+
Run this from the repo root to switch the local repo config to a non-interactive editor:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
node git-editor-fix.mjs
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Preflight checks both `GIT_EDITOR` and `git config --get core.editor`. No warning is shown when `core.editor` is already non-interactive, for example `:`.
|
|
188
|
+
|
|
165
189
|
---
|
|
166
190
|
|
|
167
191
|
## CI/CD and quality gates
|
|
@@ -228,7 +252,7 @@ npm run hooks:install
|
|
|
228
252
|
- `server/` — setup server, Mini App backend, and API endpoints
|
|
229
253
|
- `ui/` — Mini App frontend assets and operator dashboard modules
|
|
230
254
|
- `telegram/` — Telegram bot, sentinel, and channel integrations
|
|
231
|
-
- `github/` and `kanban/` — GitHub auth/webhooks and
|
|
255
|
+
- `github/` and `kanban/` — GitHub auth/webhooks and kanban adapters
|
|
232
256
|
- `workspace/` — shared workspace registry, context indexing, and worktree lifecycle
|
|
233
257
|
- `shell/` and `agent/` — executor integrations, prompts, hooks, and fleet coordination
|
|
234
258
|
- `site/` — marketing site and generated docs website assets
|
|
@@ -251,3 +275,5 @@ If you find this project useful or would like to stay up to date with new releas
|
|
|
251
275
|
## License
|
|
252
276
|
|
|
253
277
|
Apache-2.0
|
|
278
|
+
|
|
279
|
+
|
|
@@ -47,7 +47,7 @@ import {
|
|
|
47
47
|
rmSync,
|
|
48
48
|
writeFileSync,
|
|
49
49
|
} from "node:fs";
|
|
50
|
-
import { copyFile
|
|
50
|
+
import { copyFile } from "node:fs/promises";
|
|
51
51
|
import { homedir } from "node:os";
|
|
52
52
|
import { basename, dirname, extname, resolve } from "node:path";
|
|
53
53
|
import { fileURLToPath } from "node:url";
|
|
@@ -212,7 +212,10 @@ function safeReadIndex(storeDir) {
|
|
|
212
212
|
if (!existsSync(idx)) return [];
|
|
213
213
|
try {
|
|
214
214
|
const parsed = JSON.parse(readFileSync(idx, "utf8"));
|
|
215
|
-
|
|
215
|
+
if (!Array.isArray(parsed)) return [];
|
|
216
|
+
return parsed
|
|
217
|
+
.map((entry) => sanitizeIndexEntry(entry))
|
|
218
|
+
.filter(Boolean);
|
|
216
219
|
} catch {
|
|
217
220
|
return [];
|
|
218
221
|
}
|
|
@@ -243,6 +246,77 @@ function scriptPath(storeDir, id, lang) {
|
|
|
243
246
|
return resolve(storeDir, `${id}.${lang}`);
|
|
244
247
|
}
|
|
245
248
|
|
|
249
|
+
function isPlainObject(value) {
|
|
250
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function normalizeStringList(values, { lowercase = true } = {}) {
|
|
254
|
+
if (!Array.isArray(values)) return [];
|
|
255
|
+
const normalized = values
|
|
256
|
+
.map((value) => String(value ?? "").trim())
|
|
257
|
+
.filter(Boolean)
|
|
258
|
+
.map((value) => (lowercase ? value.toLowerCase() : value));
|
|
259
|
+
return Array.from(new Set(normalized));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function normalizeUsageCount(value) {
|
|
263
|
+
const numeric = Number(value);
|
|
264
|
+
return Number.isFinite(numeric) && numeric >= 0 ? numeric : 0;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function normalizeToolId(toolId) {
|
|
268
|
+
if (typeof toolId !== "string") return null;
|
|
269
|
+
const trimmed = toolId.trim();
|
|
270
|
+
if (!trimmed) return null;
|
|
271
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,59}$/.test(trimmed)) return null;
|
|
272
|
+
return trimmed;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function sanitizeIndexEntry(raw) {
|
|
276
|
+
if (!isPlainObject(raw)) return null;
|
|
277
|
+
|
|
278
|
+
const id = normalizeToolId(raw.id);
|
|
279
|
+
const lang = VALID_LANGS.includes(raw.lang) ? raw.lang : null;
|
|
280
|
+
if (!id || !lang) return null;
|
|
281
|
+
|
|
282
|
+
const category = TOOL_CATEGORIES.includes(raw.category)
|
|
283
|
+
? raw.category
|
|
284
|
+
: "utility";
|
|
285
|
+
const skills = normalizeStringList(raw.skills, { lowercase: false });
|
|
286
|
+
const agents = normalizeStringList(raw.agents, { lowercase: false });
|
|
287
|
+
const templates = normalizeStringList(raw.templates, { lowercase: false });
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
...raw,
|
|
291
|
+
id,
|
|
292
|
+
title: typeof raw.title === "string" && raw.title.trim() ? raw.title : id,
|
|
293
|
+
description: typeof raw.description === "string" ? raw.description : "",
|
|
294
|
+
tags: normalizeStringList(raw.tags),
|
|
295
|
+
category,
|
|
296
|
+
lang,
|
|
297
|
+
createdBy: typeof raw.createdBy === "string" && raw.createdBy.trim()
|
|
298
|
+
? raw.createdBy
|
|
299
|
+
: "agent",
|
|
300
|
+
createdAt: typeof raw.createdAt === "string" && raw.createdAt.trim()
|
|
301
|
+
? raw.createdAt
|
|
302
|
+
: nowISO(),
|
|
303
|
+
updatedAt: typeof raw.updatedAt === "string" && raw.updatedAt.trim()
|
|
304
|
+
? raw.updatedAt
|
|
305
|
+
: nowISO(),
|
|
306
|
+
usageCount: normalizeUsageCount(raw.usageCount),
|
|
307
|
+
...(typeof raw.lastUsed === "string" && raw.lastUsed.trim()
|
|
308
|
+
? { lastUsed: raw.lastUsed }
|
|
309
|
+
: {}),
|
|
310
|
+
...(skills.length > 0 ? { skills } : {}),
|
|
311
|
+
...(agents.length > 0 ? { agents } : {}),
|
|
312
|
+
...(templates.length > 0 ? { templates } : {}),
|
|
313
|
+
...(raw.autoInject ? { autoInject: true } : {}),
|
|
314
|
+
...(typeof raw.version === "string" && raw.version.trim()
|
|
315
|
+
? { version: raw.version }
|
|
316
|
+
: {}),
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
246
320
|
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
247
321
|
|
|
248
322
|
/**
|
|
@@ -302,6 +376,9 @@ export function listCustomTools(rootDir, opts = {}) {
|
|
|
302
376
|
includeBuiltins = true,
|
|
303
377
|
} = opts;
|
|
304
378
|
|
|
379
|
+
const requestedTags = normalizeStringList(tags);
|
|
380
|
+
const searchQuery = typeof search === "string" ? search.trim().toLowerCase() : "";
|
|
381
|
+
|
|
305
382
|
let entries = [];
|
|
306
383
|
|
|
307
384
|
// Workspace tools
|
|
@@ -339,23 +416,24 @@ export function listCustomTools(rootDir, opts = {}) {
|
|
|
339
416
|
if (category) {
|
|
340
417
|
entries = entries.filter((e) => e.category === category);
|
|
341
418
|
}
|
|
342
|
-
if (
|
|
419
|
+
if (requestedTags.length > 0) {
|
|
343
420
|
entries = entries.filter((e) =>
|
|
344
|
-
|
|
421
|
+
requestedTags.some((t) => (e.tags || []).includes(t)),
|
|
345
422
|
);
|
|
346
423
|
}
|
|
347
|
-
if (
|
|
348
|
-
const q = search.toLowerCase();
|
|
424
|
+
if (searchQuery) {
|
|
349
425
|
entries = entries.filter(
|
|
350
426
|
(e) =>
|
|
351
|
-
e.id.includes(
|
|
352
|
-
e.title.toLowerCase().includes(
|
|
353
|
-
e.description.toLowerCase().includes(
|
|
354
|
-
(e.tags || []).some((t) => t.includes(
|
|
427
|
+
e.id.toLowerCase().includes(searchQuery) ||
|
|
428
|
+
e.title.toLowerCase().includes(searchQuery) ||
|
|
429
|
+
e.description.toLowerCase().includes(searchQuery) ||
|
|
430
|
+
(e.tags || []).some((t) => t.toLowerCase().includes(searchQuery)),
|
|
355
431
|
);
|
|
356
432
|
}
|
|
357
433
|
|
|
358
|
-
return entries.sort(
|
|
434
|
+
return entries.sort(
|
|
435
|
+
(a, b) => normalizeUsageCount(b.usageCount) - normalizeUsageCount(a.usageCount),
|
|
436
|
+
);
|
|
359
437
|
}
|
|
360
438
|
|
|
361
439
|
/**
|
|
@@ -366,11 +444,14 @@ export function listCustomTools(rootDir, opts = {}) {
|
|
|
366
444
|
* @returns {{ entry: CustomToolEntry, script: string }|null}
|
|
367
445
|
*/
|
|
368
446
|
export function getCustomTool(rootDir, toolId) {
|
|
447
|
+
const normalizedToolId = normalizeToolId(toolId);
|
|
448
|
+
if (!normalizedToolId) return null;
|
|
449
|
+
|
|
369
450
|
// Workspace-scoped takes precedence, then global, then builtin
|
|
370
451
|
for (const isGlobal of [false, true]) {
|
|
371
452
|
const storeDir = getToolStore(rootDir, { global: isGlobal });
|
|
372
453
|
const index = safeReadIndex(storeDir);
|
|
373
|
-
const entry = index.find((e) => e.id ===
|
|
454
|
+
const entry = index.find((e) => e.id === normalizedToolId);
|
|
374
455
|
if (!entry) continue;
|
|
375
456
|
|
|
376
457
|
const sPath = scriptPath(storeDir, entry.id, entry.lang);
|
|
@@ -383,7 +464,7 @@ export function getCustomTool(rootDir, toolId) {
|
|
|
383
464
|
}
|
|
384
465
|
|
|
385
466
|
// Fall back to built-in tools shipped with bosun
|
|
386
|
-
const builtinDef = BUILTIN_TOOLS.find((b) => b.id ===
|
|
467
|
+
const builtinDef = BUILTIN_TOOLS.find((b) => b.id === normalizedToolId);
|
|
387
468
|
if (builtinDef) {
|
|
388
469
|
const sPath = resolve(BUILTIN_TOOLS_DIR, `${builtinDef.id}.${builtinDef.lang}`);
|
|
389
470
|
if (existsSync(sPath)) {
|
|
@@ -414,6 +495,10 @@ export function getCustomTool(rootDir, toolId) {
|
|
|
414
495
|
* @returns {CustomToolEntry}
|
|
415
496
|
*/
|
|
416
497
|
export function registerCustomTool(rootDir, def) {
|
|
498
|
+
if (!isPlainObject(def)) {
|
|
499
|
+
throw new TypeError("registerCustomTool: definition object is required");
|
|
500
|
+
}
|
|
501
|
+
|
|
417
502
|
const {
|
|
418
503
|
title,
|
|
419
504
|
description,
|
|
@@ -449,19 +534,29 @@ export function registerCustomTool(rootDir, def) {
|
|
|
449
534
|
);
|
|
450
535
|
}
|
|
451
536
|
|
|
537
|
+
const explicitId = def.id == null ? null : normalizeToolId(def.id);
|
|
538
|
+
if (def.id != null && !explicitId) {
|
|
539
|
+
throw new TypeError(
|
|
540
|
+
"registerCustomTool: id must match /^[A-Za-z0-9][A-Za-z0-9_-]{0,59}$/ and must not contain path separators",
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
|
|
452
544
|
const storeDir = getToolStore(rootDir, { global: isGlobal });
|
|
453
545
|
const index = safeReadIndex(storeDir);
|
|
454
546
|
|
|
455
|
-
const id =
|
|
547
|
+
const id = explicitId || slugify(title) || `tool-${Date.now()}`;
|
|
456
548
|
const existingIdx = index.findIndex((e) => e.id === id);
|
|
457
549
|
const now = nowISO();
|
|
550
|
+
const normalizedSkills = normalizeStringList(skills, { lowercase: false });
|
|
551
|
+
const normalizedAgents = normalizeStringList(agents, { lowercase: false });
|
|
552
|
+
const normalizedTemplates = normalizeStringList(templates, { lowercase: false });
|
|
458
553
|
|
|
459
554
|
/** @type {CustomToolEntry} */
|
|
460
555
|
const entry = {
|
|
461
556
|
id,
|
|
462
557
|
title,
|
|
463
558
|
description: description || "",
|
|
464
|
-
tags:
|
|
559
|
+
tags: normalizeStringList(tags),
|
|
465
560
|
category,
|
|
466
561
|
lang,
|
|
467
562
|
createdBy,
|
|
@@ -474,9 +569,9 @@ export function registerCustomTool(rootDir, def) {
|
|
|
474
569
|
: {}),
|
|
475
570
|
scope: isGlobal ? "global" : "workspace",
|
|
476
571
|
// Affinity metadata (persisted for future skill/agent matching)
|
|
477
|
-
...(
|
|
478
|
-
...(
|
|
479
|
-
...(
|
|
572
|
+
...(normalizedSkills.length > 0 ? { skills: normalizedSkills } : {}),
|
|
573
|
+
...(normalizedAgents.length > 0 ? { agents: normalizedAgents } : {}),
|
|
574
|
+
...(normalizedTemplates.length > 0 ? { templates: normalizedTemplates } : {}),
|
|
480
575
|
...(autoInject ? { autoInject } : {}),
|
|
481
576
|
...(version ? { version } : {}),
|
|
482
577
|
};
|
|
@@ -518,6 +613,10 @@ export async function invokeCustomTool(rootDir, toolId, args = [], opts = {}) {
|
|
|
518
613
|
throw new Error(`invokeCustomTool: tool "${toolId}" not found`);
|
|
519
614
|
}
|
|
520
615
|
|
|
616
|
+
const cliArgs = Array.isArray(args)
|
|
617
|
+
? args.map((arg) => String(arg))
|
|
618
|
+
: [String(args)];
|
|
619
|
+
|
|
521
620
|
const { entry } = result;
|
|
522
621
|
let sPath;
|
|
523
622
|
if (entry.scope === "builtin") {
|
|
@@ -536,15 +635,15 @@ export async function invokeCustomTool(rootDir, toolId, args = [], opts = {}) {
|
|
|
536
635
|
switch (entry.lang) {
|
|
537
636
|
case "mjs":
|
|
538
637
|
cmd = process.execPath; // use same node binary
|
|
539
|
-
cmdArgs = [sPath, ...
|
|
638
|
+
cmdArgs = [sPath, ...cliArgs];
|
|
540
639
|
break;
|
|
541
640
|
case "sh":
|
|
542
641
|
cmd = process.platform === "win32" ? "bash" : "/bin/sh";
|
|
543
|
-
cmdArgs = [sPath, ...
|
|
642
|
+
cmdArgs = [sPath, ...cliArgs];
|
|
544
643
|
break;
|
|
545
644
|
case "py":
|
|
546
|
-
cmd = "python3";
|
|
547
|
-
cmdArgs = [sPath, ...
|
|
645
|
+
cmd = process.platform === "win32" ? "python" : "python3";
|
|
646
|
+
cmdArgs = [sPath, ...cliArgs];
|
|
548
647
|
break;
|
|
549
648
|
default:
|
|
550
649
|
throw new Error(`invokeCustomTool: unsupported lang "${entry.lang}"`);
|
|
@@ -601,10 +700,13 @@ export async function invokeCustomTool(rootDir, toolId, args = [], opts = {}) {
|
|
|
601
700
|
* @returns {Promise<void>}
|
|
602
701
|
*/
|
|
603
702
|
export async function recordToolUsage(rootDir, toolId) {
|
|
703
|
+
const normalizedToolId = normalizeToolId(toolId);
|
|
704
|
+
if (!normalizedToolId) return;
|
|
705
|
+
|
|
604
706
|
for (const isGlobal of [false, true]) {
|
|
605
707
|
const storeDir = getToolStore(rootDir, { global: isGlobal });
|
|
606
708
|
const index = safeReadIndex(storeDir);
|
|
607
|
-
const idx = index.findIndex((e) => e.id ===
|
|
709
|
+
const idx = index.findIndex((e) => e.id === normalizedToolId);
|
|
608
710
|
if (idx < 0) continue;
|
|
609
711
|
index[idx].usageCount = (index[idx].usageCount ?? 0) + 1;
|
|
610
712
|
index[idx].lastUsed = nowISO();
|
|
@@ -622,9 +724,12 @@ export async function recordToolUsage(rootDir, toolId) {
|
|
|
622
724
|
* @returns {boolean} true if the tool was found and removed
|
|
623
725
|
*/
|
|
624
726
|
export function deleteCustomTool(rootDir, toolId, { global: isGlobal = false } = {}) {
|
|
727
|
+
const normalizedToolId = normalizeToolId(toolId);
|
|
728
|
+
if (!normalizedToolId) return false;
|
|
729
|
+
|
|
625
730
|
const storeDir = getToolStore(rootDir, { global: isGlobal });
|
|
626
731
|
const index = safeReadIndex(storeDir);
|
|
627
|
-
const idx = index.findIndex((e) => e.id ===
|
|
732
|
+
const idx = index.findIndex((e) => e.id === normalizedToolId);
|
|
628
733
|
if (idx < 0) return false;
|
|
629
734
|
|
|
630
735
|
const entry = index[idx];
|
|
@@ -651,9 +756,14 @@ export function deleteCustomTool(rootDir, toolId, { global: isGlobal = false } =
|
|
|
651
756
|
* @returns {Promise<CustomToolEntry>} the entry as it now exists in global scope
|
|
652
757
|
*/
|
|
653
758
|
export async function promoteToGlobal(rootDir, toolId) {
|
|
759
|
+
const normalizedToolId = normalizeToolId(toolId);
|
|
760
|
+
if (!normalizedToolId) {
|
|
761
|
+
throw new Error(`promoteToGlobal: workspace tool "${toolId}" not found`);
|
|
762
|
+
}
|
|
763
|
+
|
|
654
764
|
const wsStore = getToolStore(rootDir, { global: false });
|
|
655
765
|
const wsIndex = safeReadIndex(wsStore);
|
|
656
|
-
const wsEntry = wsIndex.find((e) => e.id ===
|
|
766
|
+
const wsEntry = wsIndex.find((e) => e.id === normalizedToolId);
|
|
657
767
|
if (!wsEntry) {
|
|
658
768
|
throw new Error(
|
|
659
769
|
`promoteToGlobal: workspace tool "${toolId}" not found`,
|
|
@@ -676,7 +786,7 @@ export async function promoteToGlobal(rootDir, toolId) {
|
|
|
676
786
|
|
|
677
787
|
// Upsert in global index
|
|
678
788
|
const globalEntry = { ...wsEntry, scope: "global", updatedAt: nowISO() };
|
|
679
|
-
const existingIdx = globalIndex.findIndex((e) => e.id ===
|
|
789
|
+
const existingIdx = globalIndex.findIndex((e) => e.id === normalizedToolId);
|
|
680
790
|
if (existingIdx >= 0) {
|
|
681
791
|
globalIndex[existingIdx] = globalEntry;
|
|
682
792
|
} else {
|
|
@@ -722,6 +832,8 @@ export function getToolsPromptBlock(rootDir, opts = {}) {
|
|
|
722
832
|
template,
|
|
723
833
|
limit,
|
|
724
834
|
includeBuiltins,
|
|
835
|
+
category,
|
|
836
|
+
tags,
|
|
725
837
|
});
|
|
726
838
|
const affinityIds = new Set(affinityTools.map((t) => t.id));
|
|
727
839
|
const remaining = listCustomTools(rootDir, { category, tags, includeBuiltins })
|
package/agent/agent-endpoint.mjs
CHANGED
|
@@ -201,8 +201,7 @@ function isLikelyBosunCommandLine(commandLine) {
|
|
|
201
201
|
normalized.includes("/bosun/") &&
|
|
202
202
|
(normalized.includes("monitor.mjs") ||
|
|
203
203
|
normalized.includes("cli.mjs") ||
|
|
204
|
-
normalized.includes("agent-endpoint.mjs")
|
|
205
|
-
normalized.includes("ve-orchestrator"))
|
|
204
|
+
normalized.includes("agent-endpoint.mjs"))
|
|
206
205
|
) {
|
|
207
206
|
return true;
|
|
208
207
|
}
|
package/agent/agent-hooks.mjs
CHANGED
|
@@ -51,6 +51,9 @@ const MAX_OUTPUT_BYTES = 64 * 1024;
|
|
|
51
51
|
/** Whether we're running on Windows */
|
|
52
52
|
const IS_WINDOWS = process.platform === "win32";
|
|
53
53
|
|
|
54
|
+
/** Preferred Windows shell for hook execution. */
|
|
55
|
+
const WINDOWS_SHELL = process.env.ComSpec || "cmd.exe";
|
|
56
|
+
|
|
54
57
|
/** Default max retries for retryable hooks */
|
|
55
58
|
const DEFAULT_MAX_RETRIES = 2;
|
|
56
59
|
|
|
@@ -643,7 +646,7 @@ export function registerBuiltinHooks(options = {}) {
|
|
|
643
646
|
|
|
644
647
|
// ── PrePush: agent preflight quality gate ──
|
|
645
648
|
if (!skipPrePush) {
|
|
646
|
-
const preflightScript = "node preflight.mjs";
|
|
649
|
+
const preflightScript = "node infra/preflight.mjs";
|
|
647
650
|
|
|
648
651
|
registerHook("PrePush", {
|
|
649
652
|
id: "builtin-prepush-preflight",
|
|
@@ -841,6 +844,42 @@ function _buildEnv(ctx) {
|
|
|
841
844
|
return env;
|
|
842
845
|
}
|
|
843
846
|
|
|
847
|
+
function _getSpawnCommand(command) {
|
|
848
|
+
const trimmed = String(command ?? "").trim();
|
|
849
|
+
|
|
850
|
+
if (IS_WINDOWS) {
|
|
851
|
+
const lower = trimmed.toLowerCase();
|
|
852
|
+
if (lower.startsWith("powershell ") || lower.startsWith("powershell.exe ")) {
|
|
853
|
+
const inlineCommand = trimmed
|
|
854
|
+
.replace(/^powershell(?:\.exe)?\s+-NoProfile\s+-Command\s+/i, "")
|
|
855
|
+
.replace(/^powershell(?:\.exe)?\s+-Command\s+/i, "")
|
|
856
|
+
.replace(/^"|"$/g, "");
|
|
857
|
+
return {
|
|
858
|
+
file: "powershell.exe",
|
|
859
|
+
args: ["-NoProfile", "-Command", inlineCommand],
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
if (lower.startsWith("cmd ") || lower.startsWith("cmd.exe ")) {
|
|
863
|
+
const inlineCommand = trimmed
|
|
864
|
+
.replace(/^cmd(?:\.exe)?\s+\/d\s+\/s\s+\/c\s+/i, "")
|
|
865
|
+
.replace(/^cmd(?:\.exe)?\s+\/c\s+/i, "");
|
|
866
|
+
return {
|
|
867
|
+
file: WINDOWS_SHELL,
|
|
868
|
+
args: ["/d", "/s", "/c", inlineCommand],
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
return {
|
|
872
|
+
file: WINDOWS_SHELL,
|
|
873
|
+
args: ["/d", "/s", "/c", trimmed],
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
return {
|
|
878
|
+
file: "/bin/sh",
|
|
879
|
+
args: ["-c", trimmed],
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
|
|
844
883
|
// ── Internal: Synchronous Hook Execution ────────────────────────────────────
|
|
845
884
|
|
|
846
885
|
/**
|
|
@@ -892,12 +931,13 @@ function _executeHookSync(hook, ctx, env) {
|
|
|
892
931
|
};
|
|
893
932
|
|
|
894
933
|
try {
|
|
895
|
-
const
|
|
934
|
+
const spawnTarget = _getSpawnCommand(hook.command);
|
|
935
|
+
const result = spawnSync(spawnTarget.file, spawnTarget.args, {
|
|
896
936
|
cwd,
|
|
897
937
|
env: hookEnv,
|
|
898
938
|
encoding: "utf8",
|
|
899
939
|
timeout,
|
|
900
|
-
shell:
|
|
940
|
+
shell: false,
|
|
901
941
|
windowsHide: true,
|
|
902
942
|
maxBuffer: MAX_OUTPUT_BYTES,
|
|
903
943
|
});
|
|
@@ -999,10 +1039,11 @@ function _executeHookAsyncOnce(hook, ctx, env, attempt) {
|
|
|
999
1039
|
|
|
1000
1040
|
let child;
|
|
1001
1041
|
try {
|
|
1002
|
-
|
|
1042
|
+
const spawnTarget = _getSpawnCommand(hook.command);
|
|
1043
|
+
child = spawn(spawnTarget.file, spawnTarget.args, {
|
|
1003
1044
|
cwd,
|
|
1004
1045
|
env: hookEnv,
|
|
1005
|
-
shell:
|
|
1046
|
+
shell: false,
|
|
1006
1047
|
windowsHide: true,
|
|
1007
1048
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1008
1049
|
});
|
|
@@ -1253,3 +1294,10 @@ export function registerLibraryHooks(hooksByEvent) {
|
|
|
1253
1294
|
}
|
|
1254
1295
|
return { registered, skipped };
|
|
1255
1296
|
}
|
|
1297
|
+
|
|
1298
|
+
|
|
1299
|
+
|
|
1300
|
+
|
|
1301
|
+
|
|
1302
|
+
|
|
1303
|
+
|