github-router 0.3.152 → 0.3.153
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/dist/browser-ext/manifest.json +1 -1
- package/dist/{engine-CvY9mR3b.js → engine-BP5EnZ-n.js} +4 -4
- package/dist/{lifecycle-DGvk4z63.js → lifecycle-C4k0pEvn.js} +2 -2
- package/dist/{lifecycle-DGvk4z63.js.map → lifecycle-C4k0pEvn.js.map} +1 -1
- package/dist/{lifecycle-BSGk7sdE.js → lifecycle-CbcVnn0e.js} +2 -2
- package/dist/{lifecycle-8EhIXG4L.js → lifecycle-CsxCFx1b.js} +2 -2
- package/dist/{lifecycle-DyEXZu2z.js → lifecycle-VTQI28wT.js} +2 -2
- package/dist/{lifecycle-DyEXZu2z.js.map → lifecycle-VTQI28wT.js.map} +1 -1
- package/dist/main.js +454 -39
- package/dist/main.js.map +1 -1
- package/dist/{paths-D0tJ_tms.js → paths-Bt7sqiVr.js} +11 -9
- package/dist/{paths-D0tJ_tms.js.map → paths-Bt7sqiVr.js.map} +1 -1
- package/dist/{paths-DhLJ9bLG.js → paths-C9Nkr_NK.js} +1 -1
- package/dist/{peer-mcp-personas-BQVOxB1i.js → peer-mcp-personas-DMM1akDa.js} +29 -18
- package/dist/{peer-mcp-personas-BQVOxB1i.js.map → peer-mcp-personas-DMM1akDa.js.map} +1 -1
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as logStreamError, $t as
|
|
3
|
-
import { a as removeOwnClaudeConfigMirror, i as isUnderClaudeConfigMirror, l as writeArtifactCredsToMirror, n as ensureClaudeConfigMirror, r as ensurePaths, t as PATHS, u as writeRuntimeFileSecure } from "./paths-
|
|
4
|
-
import { c as killManagedTree, d as runCommandCapture, f as runCommandVoid, l as parseBoolEnv, s as killChildProcessTree, u as resolveExecutable } from "./lifecycle-
|
|
5
|
-
import { a as sweepRegistry } from "./lifecycle-
|
|
2
|
+
import { $ as logStreamError, $t as GITHUB_API_BASE_URL, At as DEFAULT_PORT, B as toolbeltEnabled, Bt as tryRefreshAndRetry, C as repoFingerprint, Ct as shouldUseInsecureTls, D as trustRepo, Dt as DEFAULT_CLAUDE_MODEL_FALLBACKS, E as stopReviewStateDir, Et as toolbeltPathOverride, Ft as getPackageVersion, G as searchWeb, Gt as isNullish, H as vscodeRipgrepPath, Ht as cacheModels, It as withInstallLock, J as buildAdvisorStream, Jt as sleep, K as ADVISOR_INTERNAL_TOOL_NAME, Kt as resolveCodexModel, Lt as setupCopilotToken, Mt as UPSTREAM_INACTIVITY_TIMEOUT_MS, Nt as generateRandomPort, O as resolveSealedGate, Ot as DEFAULT_CODEX_MODEL, Pt as pickClaudeDefault, Q as isControllerClosedError, Qt as forwardError, R as availableToolCommands, Rt as setupGitHubAgentToken, S as isSubagentContext, St as extractZipMember, T as stopGateEnabledForRepo, Tt as collapsePathKeys, U as TOOLBELT_TOOLS, Ut as cacheVSCodeVersion, V as toolbeltSkipSet, Vt as cacheCopilotVersion, W as assetFor, Wt as filterBetaHeader, X as isAdvisorRequested, Xt as fetchWithTransientRetry, Y as injectAdvisorTool, Yt as getModels, Z as buildOpenAIErrorEvent, Zt as HTTPError, _ as stopReviewEnabled, _t as parseJsonOrDiagnose, a as buildPeerAwarenessSnippet, at as browseAgentEnabled, b as fileLastPromptStore, bt as provisionAndIndexColbert, c as buildSessionBindHookCommand, ct as standInToolEnabled, d as decideStopHook, dt as createMessages, en as copilotBaseUrl, et as readIteratorWithTimeout, f as fileBlockBudget, ft as getTokenCount, g as stopGateId, gt as readResponseBodyCapped, h as stopGateDisabled, ht as MAX_RESPONSE_BODY_BYTES, i as buildAgentPrompt, it as agentToolsEnabled, jt as UPSTREAM_FETCH_TIMEOUT_MS, k as liveExec, kt as DEFAULT_CODEX_MODEL_FALLBACKS, l as buildStopHookCommand, lt as workerToolsEnabled, m as launchBaselineKey, mt as createChatCompletions, n as MCP_GROUPS, nn as githubHeaders, nt as handleMcpDelete, o as personasFor, ot as browserToolsEnabled, p as injectStopHookIntoSettingsFile, pt as createResponses, q as ADVISOR_TOOL_INSTRUCTIONS, qt as resolveModel, r as assertMcpToolSurfaceConsistent, rn as state, rt as handleMcpPost, s as buildArtifactOpenHookCommand, st as fleetToolsEnabled, t as GROUP_META, tn as copilotHeaders, tt as relayAnthropicStream, u as captureLaunchBaseline, ut as countTokens, v as fileBaselineStore, vt as provisionBrowserAssets, w as repoRoot, wt as ArtifactClient, x as fileReviewDebounce, xt as extractTarGzMember, y as fileFindingsStore, yt as hasSupportedBrowserInstalled, z as buildToolbeltAwareness, zt as setupGitHubToken } from "./peer-mcp-personas-DMM1akDa.js";
|
|
3
|
+
import { a as removeOwnClaudeConfigMirror, i as isUnderClaudeConfigMirror, l as writeArtifactCredsToMirror, n as ensureClaudeConfigMirror, r as ensurePaths, t as PATHS, u as writeRuntimeFileSecure } from "./paths-Bt7sqiVr.js";
|
|
4
|
+
import { c as killManagedTree, d as runCommandCapture, f as runCommandVoid, l as parseBoolEnv, s as killChildProcessTree, u as resolveExecutable } from "./lifecycle-VTQI28wT.js";
|
|
5
|
+
import { a as sweepRegistry } from "./lifecycle-C4k0pEvn.js";
|
|
6
6
|
import { defineCommand, runMain } from "citty";
|
|
7
7
|
import consola from "consola";
|
|
8
8
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
@@ -809,6 +809,223 @@ function launchChild(target, server$1, options = {}) {
|
|
|
809
809
|
});
|
|
810
810
|
}
|
|
811
811
|
|
|
812
|
+
//#endregion
|
|
813
|
+
//#region src/lib/worker-dispatch.ts
|
|
814
|
+
/**
|
|
815
|
+
* Frozen contract for the NON-BLOCKING workers surface.
|
|
816
|
+
*
|
|
817
|
+
* The `workers` MCP tools (`explore`/`implement`/`review`/`plan`/`test`, and
|
|
818
|
+
* `browse` when the browse agent is enabled) BLOCK the caller for up to 30 min
|
|
819
|
+
* (`runWorkerAgent`). The MAIN Claude Code agent must never block on one, so a
|
|
820
|
+
* per-mode `worker-*` DISPATCHER SUBAGENT — which Claude Code runs in the
|
|
821
|
+
* background and reports on via a completion notification — is the only
|
|
822
|
+
* sanctioned way to run a worker. This module is the single source of truth for
|
|
823
|
+
* three things that must never drift:
|
|
824
|
+
*
|
|
825
|
+
* 1. the tool → dispatcher map (`mcp__<workersKey>__<mode>` ↔ `worker-<mode>`),
|
|
826
|
+
* 2. the PreToolUse GUARD decision that denies a raw worker call from the main
|
|
827
|
+
* agent (or any non-dispatcher subagent) and redirects it to the matching
|
|
828
|
+
* `worker-*` agent, allowing it only from the dispatcher itself, and
|
|
829
|
+
* 3. the dispatcher subagent bodies (description / system prompt / `tools:`
|
|
830
|
+
* allowlist).
|
|
831
|
+
*
|
|
832
|
+
* Imported by `codex-mcp-config.ts` (dispatcher `.md` generation + the hook
|
|
833
|
+
* command it bakes into settings.json) AND `internal-worker-guard.ts` (the
|
|
834
|
+
* runtime PreToolUse hook). Keeping the map here means the settings matcher, the
|
|
835
|
+
* redirect target, the dispatcher names, and the `tools:` allowlist are all
|
|
836
|
+
* derived from one place.
|
|
837
|
+
*
|
|
838
|
+
* Discrimination mechanism: a Claude Code PreToolUse payload carries
|
|
839
|
+
* `agent_type` (the invoking subagent's name) ONLY inside a subagent context —
|
|
840
|
+
* absent for the top-level/main agent. So `agent_type === "worker-<mode>"` is
|
|
841
|
+
* the reliable "this call came from the dispatcher, allow it" signal, and its
|
|
842
|
+
* absence (or any other value) means "main agent or an unrelated subagent →
|
|
843
|
+
* deny". This is the SAME field this repo's Stop / prompt-submit hooks already
|
|
844
|
+
* key off (`isSubagentContext` in `orchestration/stop-gate-policy.ts`); the
|
|
845
|
+
* guard here is the inverse (allow-only-dispatcher rather than skip-any-sub).
|
|
846
|
+
*/
|
|
847
|
+
/** The five always-available worker modes (gated by `workerToolsEnabled()`). */
|
|
848
|
+
const CORE_WORKER_MODES = [
|
|
849
|
+
"explore",
|
|
850
|
+
"implement",
|
|
851
|
+
"review",
|
|
852
|
+
"plan",
|
|
853
|
+
"test"
|
|
854
|
+
];
|
|
855
|
+
/** The browse worker mode, gated separately by `browseAgentEnabled()`. */
|
|
856
|
+
const BROWSE_WORKER_MODE = "browse";
|
|
857
|
+
/** Every mode the surface can expose — used to build the sweep-regex allowlist
|
|
858
|
+
* and to validate `--modes`. Order is stable (core first, browse last). */
|
|
859
|
+
const ALL_WORKER_DISPATCH_MODES = [...CORE_WORKER_MODES, BROWSE_WORKER_MODE];
|
|
860
|
+
/** The dispatcher subagent name for a mode, e.g. `implement` → `worker-implement`. */
|
|
861
|
+
function dispatcherAgentName(mode) {
|
|
862
|
+
return `worker-${mode}`;
|
|
863
|
+
}
|
|
864
|
+
/** Every possible dispatcher subagent name (used by the stale-`.md` sweep). */
|
|
865
|
+
const ALL_DISPATCHER_AGENT_NAMES = ALL_WORKER_DISPATCH_MODES.map(dispatcherAgentName);
|
|
866
|
+
/** The MCP tool name the workers server exposes for a mode under the resolved key. */
|
|
867
|
+
function workerToolName(workersKey, mode) {
|
|
868
|
+
return `mcp__${workersKey}__${mode}`;
|
|
869
|
+
}
|
|
870
|
+
/** The active dispatch modes for a launch: the five core modes plus `browse`
|
|
871
|
+
* only when the browse agent is enabled. */
|
|
872
|
+
function activeDispatchModes(opts) {
|
|
873
|
+
return opts.browse ? [...CORE_WORKER_MODES, BROWSE_WORKER_MODE] : [...CORE_WORKER_MODES];
|
|
874
|
+
}
|
|
875
|
+
function escapeRegex(s) {
|
|
876
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* The Claude Code PreToolUse `matcher` (a regex over the tool name) that scopes
|
|
880
|
+
* the guard hook to exactly the active worker tools — nothing else invokes the
|
|
881
|
+
* hook. Anchored + exact-alternation so an unrelated `mcp__<key>__status` (a
|
|
882
|
+
* future non-blocking tool with no dispatcher) is never matched, hence never
|
|
883
|
+
* denied.
|
|
884
|
+
*/
|
|
885
|
+
function guardToolMatcher(workersKey, modes) {
|
|
886
|
+
const alt = modes.map((m) => escapeRegex(m)).join("|");
|
|
887
|
+
return `^mcp__${escapeRegex(workersKey)}__(${alt})$`;
|
|
888
|
+
}
|
|
889
|
+
/** Parse the worker mode out of a tool name for the resolved key, or null if it
|
|
890
|
+
* isn't one of the recognized worker tools. */
|
|
891
|
+
function parseWorkerToolCall(toolName, workersKey, modes) {
|
|
892
|
+
const prefix = `mcp__${workersKey}__`;
|
|
893
|
+
if (!toolName.startsWith(prefix)) return null;
|
|
894
|
+
const rest = toolName.slice(prefix.length);
|
|
895
|
+
return modes.includes(rest) ? rest : null;
|
|
896
|
+
}
|
|
897
|
+
/** The `permissionDecisionReason` shown to the model on a deny, steering it to
|
|
898
|
+
* the matching background dispatcher. `mode` is null when the payload was
|
|
899
|
+
* unparseable (fail-closed generic message). */
|
|
900
|
+
function guardDenyReason(mode) {
|
|
901
|
+
return `Workers run as background subagents in this session so your turn never blocks. Re-issue this as ${mode ? `Agent(subagent_type: "${dispatcherAgentName(mode)}", prompt: <your worker brief>)` : `the matching background \`worker-*\` agent via the Agent tool`}. It returns immediately and delivers the worker's result as a completion notification — do not call the raw \`mcp__…__\` worker tool from the main thread.`;
|
|
902
|
+
}
|
|
903
|
+
/** The stdout JSON a PreToolUse hook prints to DENY a tool call. */
|
|
904
|
+
function guardDenyOutput(mode) {
|
|
905
|
+
return JSON.stringify({ hookSpecificOutput: {
|
|
906
|
+
hookEventName: "PreToolUse",
|
|
907
|
+
permissionDecision: "deny",
|
|
908
|
+
permissionDecisionReason: guardDenyReason(mode)
|
|
909
|
+
} });
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Pure PreToolUse guard decision. Given the raw stdin payload, the resolved
|
|
913
|
+
* workers key, and the active modes, decide whether to DENY the tool call.
|
|
914
|
+
*
|
|
915
|
+
* Rules (fail toward protecting the "main never blocks" invariant):
|
|
916
|
+
* - Payload unparseable / no string `tool_name`: the matcher only fires this
|
|
917
|
+
* hook for worker tools, so a payload we can't read is still a worker call
|
|
918
|
+
* → DENY (fail closed, generic redirect).
|
|
919
|
+
* - `tool_name` is not a recognized worker tool for this key: ALLOW (not a
|
|
920
|
+
* tool we guard — never deny a non-worker tool).
|
|
921
|
+
* - `agent_type` equals one of the active dispatcher names: ALLOW (the call
|
|
922
|
+
* came from the dispatcher subagent that is meant to run the worker).
|
|
923
|
+
* - Otherwise (main agent: `agent_type` absent; or a non-dispatcher subagent):
|
|
924
|
+
* DENY with a redirect to the matching `worker-<mode>` agent.
|
|
925
|
+
*/
|
|
926
|
+
function decideWorkerGuard(input) {
|
|
927
|
+
let payload = null;
|
|
928
|
+
try {
|
|
929
|
+
const parsed = JSON.parse(input.stdin);
|
|
930
|
+
if (parsed && typeof parsed === "object") payload = parsed;
|
|
931
|
+
} catch {
|
|
932
|
+
payload = null;
|
|
933
|
+
}
|
|
934
|
+
const toolName = typeof payload?.tool_name === "string" ? payload.tool_name : null;
|
|
935
|
+
if (!toolName) return {
|
|
936
|
+
output: guardDenyOutput(null),
|
|
937
|
+
verdict: "deny-malformed"
|
|
938
|
+
};
|
|
939
|
+
const mode = parseWorkerToolCall(toolName, input.workersKey, input.modes);
|
|
940
|
+
if (mode === null) return {
|
|
941
|
+
output: null,
|
|
942
|
+
verdict: "allow-non-worker"
|
|
943
|
+
};
|
|
944
|
+
const agentType = payload?.agent_type;
|
|
945
|
+
if (typeof agentType === "string" && agentType === dispatcherAgentName(mode)) return {
|
|
946
|
+
output: null,
|
|
947
|
+
verdict: "allow-dispatcher"
|
|
948
|
+
};
|
|
949
|
+
return {
|
|
950
|
+
output: guardDenyOutput(mode),
|
|
951
|
+
verdict: "deny-main"
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
/** One-line human description shown to the lead when picking a subagent. Uses
|
|
955
|
+
* the documented "Use proactively" auto-delegation idiom. */
|
|
956
|
+
function dispatcherDescription(mode) {
|
|
957
|
+
return `${{
|
|
958
|
+
explore: "Non-blocking `explore` worker: dispatches a read-only autonomous worker (its own context) in the background and delivers its summary as a completion notification.",
|
|
959
|
+
implement: "Non-blocking `implement` worker: dispatches an autonomous coding worker (read/write/bash, optional git worktree) in the background and delivers its result as a completion notification.",
|
|
960
|
+
review: "Non-blocking `review` worker: dispatches a read-only reviewer that reads the code itself to verify a change or claim, in the background, and delivers findings as a completion notification.",
|
|
961
|
+
plan: "Non-blocking `plan` worker: dispatches a read-only planner that returns an ordered implementation plan, in the background, and delivers it as a completion notification.",
|
|
962
|
+
test: "Non-blocking `test` worker: dispatches an independent test author that writes tests trying to break the implementation, in the background, and delivers pass/fail as a completion notification.",
|
|
963
|
+
browse: "Non-blocking `browse` worker: dispatches an autonomous browser agent in the background and delivers its result as a completion notification."
|
|
964
|
+
}[mode]} Use proactively for any ${mode}-mode worker task so a long run never blocks your turn: it returns immediately and notifies you when done.`;
|
|
965
|
+
}
|
|
966
|
+
/** The dispatcher subagent's full system prompt: call the one worker tool once,
|
|
967
|
+
* relay verbatim, do nothing else. */
|
|
968
|
+
function dispatcherPrompt(mode, workersKey) {
|
|
969
|
+
const tool = workerToolName(workersKey, mode);
|
|
970
|
+
return [
|
|
971
|
+
`# Subagent: ${dispatcherAgentName(mode)}`,
|
|
972
|
+
"",
|
|
973
|
+
`You are a thin DISPATCHER for the \`${mode}\` worker. You run in the background so the`,
|
|
974
|
+
"lead agent's turn is never blocked while the (up-to-30-minute) worker runs.",
|
|
975
|
+
"",
|
|
976
|
+
"## Your only job",
|
|
977
|
+
"",
|
|
978
|
+
`Call the \`${tool}\` tool EXACTLY ONCE, passing through the fields from the lead's brief:`,
|
|
979
|
+
" - `prompt`: the lead's worker brief, copied verbatim",
|
|
980
|
+
" - `workspace` (optional): absolute path, if the lead specified one",
|
|
981
|
+
" - `model` / `thinking` (optional): only if the lead specified them" + (mode === "implement" || mode === "test" ? "\n - `worktree` (optional): pass `true` if the lead asked for isolated-worktree execution" : ""),
|
|
982
|
+
"",
|
|
983
|
+
"When the tool returns, output its result VERBATIM as your final message. That final",
|
|
984
|
+
"message is what the lead receives in the completion notification — it IS the result.",
|
|
985
|
+
"",
|
|
986
|
+
"## Hard rules",
|
|
987
|
+
"",
|
|
988
|
+
"- Call the worker tool exactly once. Do not retry on a normal (non-error) return.",
|
|
989
|
+
"- Do NOT attempt the task yourself, do NOT read/edit files, do NOT run other tools.",
|
|
990
|
+
"- Do NOT spawn other agents (you have no Agent tool and must not try to gain one).",
|
|
991
|
+
"- Do NOT summarize, paraphrase, or add commentary — relay the worker output verbatim.",
|
|
992
|
+
"- If the worker returns an error, relay that error verbatim (do not mask it)."
|
|
993
|
+
].join("\n");
|
|
994
|
+
}
|
|
995
|
+
/** The `tools:` frontmatter allowlist for a dispatcher: the workers MCP server
|
|
996
|
+
* wildcard (`mcp__<workersKey>__*`). Claude Code's `tools:` field supports
|
|
997
|
+
* MCP patterns only at SERVER granularity (`mcp__<server>__*`), not individual
|
|
998
|
+
* tool names, so this grants exactly the workers tools and NOTHING else — no
|
|
999
|
+
* Agent/Task (so it cannot spawn further agents → no recursion), no Read/Bash
|
|
1000
|
+
* (so it cannot do extra work). The dispatcher's prompt narrows it to the one
|
|
1001
|
+
* mode; the guard allows any dispatcher-named caller regardless. */
|
|
1002
|
+
function dispatcherTools(_mode, workersKey) {
|
|
1003
|
+
return [`mcp__${workersKey}__*`];
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Build the shell command Claude Code runs for the workers `PreToolUse` guard
|
|
1007
|
+
* hook — the running github-router via its node/bun binary so it works
|
|
1008
|
+
* regardless of PATH. Mirrors `buildPromptSubmitHookCommand`.
|
|
1009
|
+
*
|
|
1010
|
+
* The resolved `workersKey` and the active `modes` are baked into the command
|
|
1011
|
+
* ARGS (not env): `mergeStopHookIntoSettings` dedups hooks by the command
|
|
1012
|
+
* string only, so baking the key/modes makes a changed resolution produce a
|
|
1013
|
+
* DISTINCT command — no stale-matcher entry can survive from a prior launch.
|
|
1014
|
+
*/
|
|
1015
|
+
function buildWorkerGuardHookCommand(execPath, scriptPath, workersKey, modes) {
|
|
1016
|
+
const q = (s) => `"${s}"`;
|
|
1017
|
+
const args = `internal-worker-guard --workers-key ${q(workersKey)} --modes ${q(modes.join(","))}`;
|
|
1018
|
+
if (scriptPath && scriptPath !== execPath) return `${q(execPath)} ${q(scriptPath)} ${args}`;
|
|
1019
|
+
return `${q(execPath)} ${args}`;
|
|
1020
|
+
}
|
|
1021
|
+
/** Parse a `--modes` CSV back into validated modes (drops unknown tokens). */
|
|
1022
|
+
function parseModesCsv(csv) {
|
|
1023
|
+
if (!csv) return [...CORE_WORKER_MODES];
|
|
1024
|
+
const known = new Set(ALL_WORKER_DISPATCH_MODES);
|
|
1025
|
+
const out = csv.split(",").map((s) => s.trim()).filter((s) => known.has(s));
|
|
1026
|
+
return out.length > 0 ? out : [...CORE_WORKER_MODES];
|
|
1027
|
+
}
|
|
1028
|
+
|
|
812
1029
|
//#endregion
|
|
813
1030
|
//#region src/lib/codex-mcp-config.ts
|
|
814
1031
|
/** The `peers` server is always enabled, so its resolved key always exists;
|
|
@@ -816,6 +1033,13 @@ function launchChild(target, server$1, options = {}) {
|
|
|
816
1033
|
function peersKeyOf(groupKeys) {
|
|
817
1034
|
return groupKeys.peers ?? GROUP_META.peers.preferredKey;
|
|
818
1035
|
}
|
|
1036
|
+
/** The resolved `workers` server key (bare `workers`, or the `gh-router-workers`
|
|
1037
|
+
* fallback on collision). Used to name the dispatcher tools and the guard
|
|
1038
|
+
* matcher. Falls back to the preferred bare key when the group is absent (the
|
|
1039
|
+
* caller only builds worker dispatchers when the group is enabled anyway). */
|
|
1040
|
+
function workersKeyOf(groupKeys) {
|
|
1041
|
+
return groupKeys.workers ?? GROUP_META.workers.preferredKey;
|
|
1042
|
+
}
|
|
819
1043
|
/**
|
|
820
1044
|
* Decide which MCP backend serves the codex personas.
|
|
821
1045
|
*
|
|
@@ -975,6 +1199,14 @@ function buildPeerAgentDefinitions(opts) {
|
|
|
975
1199
|
codexCli: opts.codexCli,
|
|
976
1200
|
geminiAvailable: opts.geminiAvailable
|
|
977
1201
|
});
|
|
1202
|
+
if (opts.workerToolsAvailable) {
|
|
1203
|
+
const workersKey = workersKeyOf(opts.groupKeys);
|
|
1204
|
+
for (const mode of activeDispatchModes({ browse: opts.browseAvailable === true })) out[dispatcherAgentName(mode)] = {
|
|
1205
|
+
description: dispatcherDescription(mode),
|
|
1206
|
+
prompt: dispatcherPrompt(mode, workersKey),
|
|
1207
|
+
tools: dispatcherTools(mode, workersKey)
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
978
1210
|
return out;
|
|
979
1211
|
}
|
|
980
1212
|
/**
|
|
@@ -1018,17 +1250,24 @@ function escapeYamlString(s) {
|
|
|
1018
1250
|
* component.
|
|
1019
1251
|
*/
|
|
1020
1252
|
const VALID_AGENT_NAME = /^[a-z][a-z0-9-]*$/;
|
|
1021
|
-
/** Build a single subagent .md file body (frontmatter + system prompt).
|
|
1253
|
+
/** Build a single subagent .md file body (frontmatter + system prompt).
|
|
1254
|
+
*
|
|
1255
|
+
* `tools` (optional) becomes a `tools:` frontmatter allowlist RESTRICTING the
|
|
1256
|
+
* subagent to exactly those tools (omission inherits the parent's full toolset,
|
|
1257
|
+
* per Claude Code semantics). Used by the `worker-*` dispatchers to pin each to
|
|
1258
|
+
* its single `mcp__<workersKey>__<mode>` tool — which physically prevents them
|
|
1259
|
+
* from spawning other agents or doing extra work. Names are validated by the
|
|
1260
|
+
* caller (`writePeerAgentMdFiles`) / are proxy-generated, so no escaping needed
|
|
1261
|
+
* beyond the comma-join Claude Code's frontmatter parser expects. */
|
|
1022
1262
|
function buildAgentMd(spec) {
|
|
1023
|
-
|
|
1263
|
+
const lines = [
|
|
1024
1264
|
"---",
|
|
1025
1265
|
`name: ${spec.name}`,
|
|
1026
|
-
`description: ${escapeYamlString(spec.description)}
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
].join("\n");
|
|
1266
|
+
`description: ${escapeYamlString(spec.description)}`
|
|
1267
|
+
];
|
|
1268
|
+
if (spec.tools && spec.tools.length > 0) lines.push(`tools: [${spec.tools.map((t) => JSON.stringify(t)).join(", ")}]`);
|
|
1269
|
+
lines.push("---", "", spec.prompt, "");
|
|
1270
|
+
return lines.join("\n");
|
|
1032
1271
|
}
|
|
1033
1272
|
/**
|
|
1034
1273
|
* Write per-launch subagent .md files into the user's `~/.claude/agents/`
|
|
@@ -1059,7 +1298,8 @@ async function writePeerAgentMdFiles(agents, opts) {
|
|
|
1059
1298
|
await writeRuntimeFileSecure(filePath, buildAgentMd({
|
|
1060
1299
|
name: name$1,
|
|
1061
1300
|
description: def.description,
|
|
1062
|
-
prompt: def.prompt
|
|
1301
|
+
prompt: def.prompt,
|
|
1302
|
+
tools: def.tools
|
|
1063
1303
|
}));
|
|
1064
1304
|
paths.push(filePath);
|
|
1065
1305
|
}
|
|
@@ -1251,6 +1491,8 @@ async function writePeerMcpRuntimeFiles(serverUrl, opts) {
|
|
|
1251
1491
|
codexCli: opts.codexCli,
|
|
1252
1492
|
geminiAvailable: opts.geminiAvailable,
|
|
1253
1493
|
groupKeys: opts.groupKeys,
|
|
1494
|
+
workerToolsAvailable: opts.workerToolsAvailable,
|
|
1495
|
+
browseAvailable: opts.browseAvailable,
|
|
1254
1496
|
nonce,
|
|
1255
1497
|
codexHome
|
|
1256
1498
|
});
|
|
@@ -2036,7 +2278,7 @@ async function discoverGateCommands(cwd, opts) {
|
|
|
2036
2278
|
if (files.length === 0) return null;
|
|
2037
2279
|
let result;
|
|
2038
2280
|
try {
|
|
2039
|
-
const { runWorkerAgent } = await import("./engine-
|
|
2281
|
+
const { runWorkerAgent } = await import("./engine-BP5EnZ-n.js");
|
|
2040
2282
|
result = await runWorkerAgent({
|
|
2041
2283
|
mode: "explore",
|
|
2042
2284
|
workspace: root,
|
|
@@ -2250,9 +2492,9 @@ Use the request's kind and payload as the contract:
|
|
|
2250
2492
|
|
|
2251
2493
|
Delegate heavy reading to workers:
|
|
2252
2494
|
|
|
2253
|
-
- Use
|
|
2254
|
-
- Use
|
|
2255
|
-
- Use
|
|
2495
|
+
- Use the worker-explore subagent (Agent tool) for focused source or history gathering.
|
|
2496
|
+
- Use the worker-review subagent (Agent tool) for compact review of a specific plan, PR summary, or suspicious change.
|
|
2497
|
+
- Use the worker-test subagent (Agent tool) when a missing executable check is the blocker.
|
|
2256
2498
|
|
|
2257
2499
|
Do not read a full diff, full CI log, or full transcript in the lead context. Ask workers for narrow facts and compact excerpts. The ledger is durable memory; context is not.
|
|
2258
2500
|
|
|
@@ -2385,7 +2627,7 @@ Binding rule:
|
|
|
2385
2627
|
|
|
2386
2628
|
Ask whether changed behavior has executable coverage.
|
|
2387
2629
|
|
|
2388
|
-
- If behavior changed and no relevant test exists,
|
|
2630
|
+
- If behavior changed and no relevant test exists, dispatch the worker-test subagent (Agent tool, subagent_type worker-test) to author a focused test through a DIFFERENT lab than the implementer when possible.
|
|
2389
2631
|
- Cap missing-test attempts; default to a small number of focused tries.
|
|
2390
2632
|
- Run the new test and then the relevant existing gate.
|
|
2391
2633
|
- If creating a large new harness, broad fixture system, or slow integration environment is required, ask the user before proceeding.
|
|
@@ -2534,10 +2776,10 @@ Tag every blind spot as executable-checkable or judgment-only.
|
|
|
2534
2776
|
|
|
2535
2777
|
## Phase 3 and 4: decompose and plan (run in parallel)
|
|
2536
2778
|
|
|
2537
|
-
These two are INDEPENDENT: mcp__orchestrate__decompose consumes { ask, context: research brief plus blind-spots }, and
|
|
2779
|
+
These two are INDEPENDENT: mcp__orchestrate__decompose consumes { ask, context: research brief plus blind-spots }, and the worker-plan subagent consumes the ask, acceptance criteria, research pointer, and blind-spot table. Neither needs the other's output. So issue BOTH in a SINGLE parallel batch (same turn) — call mcp__orchestrate__decompose and dispatch the worker-plan subagent together; do not wait for decompose first.
|
|
2538
2780
|
|
|
2539
2781
|
- decompose: mcp__orchestrate__decompose({ ask, context: research brief plus blind-spots }). Treat the output as a proposal, not gospel; reject or revise nodes that do not map to a real blind spot.
|
|
2540
|
-
- plan:
|
|
2782
|
+
- plan: dispatch the worker-plan subagent (Agent tool, subagent_type worker-plan) with the ask, acceptance criteria, research pointer, and blind-spot table. Ask for files, tests, rollback concerns, and minimal safe increments; keep it bounded and suited to the change size.
|
|
2541
2783
|
|
|
2542
2784
|
## Phase 5: compose a native Workflow
|
|
2543
2785
|
|
|
@@ -2559,10 +2801,10 @@ Parallelism (the Workflow tool's core optimization rule):
|
|
|
2559
2801
|
|
|
2560
2802
|
Role to tool mapping:
|
|
2561
2803
|
|
|
2562
|
-
- research:
|
|
2563
|
-
- plan:
|
|
2564
|
-
- implement:
|
|
2565
|
-
- test:
|
|
2804
|
+
- research: worker-explore (Agent subagent) and mcp__search__code for focused follow-ups.
|
|
2805
|
+
- plan: worker-plan (Agent subagent).
|
|
2806
|
+
- implement: worker-implement (Agent subagent), with worktree:true for parallel writers.
|
|
2807
|
+
- test: worker-test (Agent subagent), authored by a DIFFERENT LAB than the implementer when possible. This is an advisory practice, not enforced provenance.
|
|
2566
2808
|
- review: mcp__peers__codex_reviewer plus mcp__peers__gemini_reviewer. Advisory unless findings are converted into executable checks or code changes.
|
|
2567
2809
|
- baseline and selector: OPT-IN only because it doubles cost. Choose max(orchestrated, baseline) by EXECUTABLE gate result, not model judgment. If no executable oracle exists, say the selector is advisory.
|
|
2568
2810
|
- verify: cross-lab checker plus mcp__orchestrate__attest_step with producer not equal to checker lab.
|
|
@@ -2672,7 +2914,7 @@ Default caps unless the user explicitly gives a smaller or larger budget:
|
|
|
2672
2914
|
- Then use mcp__search__code lexically for exact symbols, filenames, errors, routes, flags, and config keys.
|
|
2673
2915
|
- Use git blame or history when authorship, regression timing, or intent matters.
|
|
2674
2916
|
- Use mcp__search__web for upstream APIs, package behavior, protocol docs, or public issues.
|
|
2675
|
-
- Launch parallel
|
|
2917
|
+
- Launch parallel worker-explore background subagents (via the Agent tool, subagent_type worker-explore) for heavy gathering, each with a narrow question and expected artifact. They run non-blocking and report back on completion.
|
|
2676
2918
|
- Keep worker results summarized; do not paste every detail into the main context.
|
|
2677
2919
|
|
|
2678
2920
|
4. Form a root-cause hypothesis.
|
|
@@ -2684,7 +2926,7 @@ Default caps unless the user explicitly gives a smaller or larger budget:
|
|
|
2684
2926
|
5. Verify load-bearing claims adversarially.
|
|
2685
2927
|
- First preference: reproduce the bug, run the failing test, or run the direct check. Tag verified-executable.
|
|
2686
2928
|
- If executable proof is not available, read the actual source or primary artifact and cite the lines. Tag verified-source.
|
|
2687
|
-
-
|
|
2929
|
+
- Dispatch the worker-review subagent (via the Agent tool) to confirm the source-reading for important claims.
|
|
2688
2930
|
- Ask a different-lab refuter through mcp__peers__codex_critic or mcp__peers__gemini_critic to try to refute the hypothesis.
|
|
2689
2931
|
- Give the refuter the symptom, observed facts, and acceptance criteria, but not your proposed root cause. Avoid anchoring them.
|
|
2690
2932
|
- If the refuter finds a plausible alternative, add it to the worklist and spend at most one bounded round resolving it.
|
|
@@ -2723,6 +2965,67 @@ Return a compact brief, not the whole research dump:
|
|
|
2723
2965
|
`
|
|
2724
2966
|
};
|
|
2725
2967
|
|
|
2968
|
+
//#endregion
|
|
2969
|
+
//#region src/lib/injected-skills/worker-skill.ts
|
|
2970
|
+
/**
|
|
2971
|
+
* The `/gh-worker` skill: the operating model for the NON-BLOCKING workers
|
|
2972
|
+
* surface. Materialized into the per-launch mirror whenever `workerToolsEnabled()`
|
|
2973
|
+
* (same gate as the other floor-raising skills). Discoverability + playbook; the
|
|
2974
|
+
* load-bearing enforcement is the PreToolUse guard + the `worker-*` dispatcher
|
|
2975
|
+
* subagents (see src/lib/worker-dispatch.ts).
|
|
2976
|
+
*/
|
|
2977
|
+
const WORKER_SKILL = {
|
|
2978
|
+
name: "gh-worker",
|
|
2979
|
+
md: `---
|
|
2980
|
+
name: gh-worker
|
|
2981
|
+
description: How to run github-router workers without blocking your turn. Workers (explore/implement/review/plan/test) can run up to 30 minutes; dispatch the matching worker-* background subagent so you get a completion notification instead of waiting. Use whenever you would reach for a worker.
|
|
2982
|
+
user-invocable: true
|
|
2983
|
+
---
|
|
2984
|
+
|
|
2985
|
+
# gh-worker: non-blocking workers
|
|
2986
|
+
|
|
2987
|
+
Worker tasks (explore, implement, review, plan, test) can run for up to 30
|
|
2988
|
+
minutes. In this session they are NON-BLOCKING BY DESIGN: you dispatch a
|
|
2989
|
+
background \`worker-*\` subagent, get control back immediately, and receive the
|
|
2990
|
+
worker's result as a completion notification when it finishes. Your turn is
|
|
2991
|
+
never blocked waiting on a worker, and the worker's tool output never fills your
|
|
2992
|
+
context (only its final result comes back).
|
|
2993
|
+
|
|
2994
|
+
## How to run a worker
|
|
2995
|
+
|
|
2996
|
+
Dispatch the matching dispatcher subagent with the Agent tool:
|
|
2997
|
+
|
|
2998
|
+
- worker-explore: read-only investigation / codebase gathering, returns a summary.
|
|
2999
|
+
- worker-review: reads the code itself to verify a change or claim; findings with severity + file:line.
|
|
3000
|
+
- worker-plan: returns an ordered implementation plan from a task + acceptance criteria.
|
|
3001
|
+
- worker-implement: read/write/bash coding worker; pass worktree: true for isolated-worktree execution + a returned diff.
|
|
3002
|
+
- worker-test: independent test author that writes tests trying to break the implementation and reports pass/fail.
|
|
3003
|
+
|
|
3004
|
+
Put the full worker brief in the subagent's prompt (and an absolute workspace
|
|
3005
|
+
path, or model/thinking/worktree, only if you need to override the defaults).
|
|
3006
|
+
The dispatcher calls the worker once and relays its result verbatim.
|
|
3007
|
+
|
|
3008
|
+
## What to expect
|
|
3009
|
+
|
|
3010
|
+
- The dispatch returns immediately; you can keep working or start other workers.
|
|
3011
|
+
- When the worker finishes you get a completion notification carrying its result.
|
|
3012
|
+
- Up to 8 workers run concurrently (the worker-semaphore cap); further dispatches queue.
|
|
3013
|
+
- You do NOT call the raw mcp__...__ worker tools from the main thread: a guard
|
|
3014
|
+
denies that and points you at the matching worker-* subagent. That guard is the
|
|
3015
|
+
guarantee your turn never blocks; dispatching worker-* directly is the normal path.
|
|
3016
|
+
|
|
3017
|
+
## Notes
|
|
3018
|
+
|
|
3019
|
+
- Large worker output may be summarized by the dispatcher relay; for a big
|
|
3020
|
+
artifact, have the worker write it to a file and return the path.
|
|
3021
|
+
- Dispatching the same worker twice runs it twice (no dedup); avoid double-dispatch
|
|
3022
|
+
for side-effecting work like worker-implement.
|
|
3023
|
+
- Background subagents + completion notifications are the interactive default. In
|
|
3024
|
+
headless (claude -p) runs the task surface behaves differently; prefer interactive
|
|
3025
|
+
for long worker fan-out.
|
|
3026
|
+
`
|
|
3027
|
+
};
|
|
3028
|
+
|
|
2726
3029
|
//#endregion
|
|
2727
3030
|
//#region src/lib/injected-skills/artifact-review-skill.ts
|
|
2728
3031
|
const ARTIFACT_REVIEW_SKILL = {
|
|
@@ -2814,6 +3117,8 @@ const PEER_MARKER_OPEN = "<!-- gh-router peer-mcp awareness — auto-injected, r
|
|
|
2814
3117
|
const PEER_MARKER_CLOSE = "<!-- /gh-router peer-mcp awareness -->";
|
|
2815
3118
|
const STYLE_MARKER_OPEN = "<!-- gh-router style directive — auto-injected, regenerated per launch -->";
|
|
2816
3119
|
const STYLE_MARKER_CLOSE = "<!-- /gh-router style directive -->";
|
|
3120
|
+
const OPERATING_MARKER_OPEN = "<!-- gh-router operating defaults — auto-injected, regenerated per launch -->";
|
|
3121
|
+
const OPERATING_MARKER_CLOSE = "<!-- /gh-router operating defaults -->";
|
|
2817
3122
|
const TOOLBELT_MARKER_OPEN = "<!-- gh-router toolbelt awareness — auto-injected, regenerated per launch -->";
|
|
2818
3123
|
const TOOLBELT_MARKER_CLOSE = "<!-- /gh-router toolbelt awareness -->";
|
|
2819
3124
|
const ARTIFACT_MARKER_OPEN = "<!-- gh-router artifact-panel directive — auto-injected when in an ai-or-die tab -->";
|
|
@@ -2829,6 +3134,28 @@ const ARTIFACT_PANEL_DIRECTIVE = "## Review in the artifact panel (HTML by defau
|
|
|
2829
3134
|
*/
|
|
2830
3135
|
const STYLE_DIRECTIVE = "Write concisely without losing detail. Use a natural human voice. Avoid em dashes. Do not attribute work to Claude, AI, LLM, or Anthropic anywhere (commits, PRs, issues, code, comments, docs).";
|
|
2831
3136
|
/**
|
|
3137
|
+
* Operating-defaults directive injected at the TOP of the mirrored CLAUDE.md
|
|
3138
|
+
* AND into the main agent's system prompt (`--append-system-prompt`), so it is
|
|
3139
|
+
* the highest-salience behavioral default. Two defaults, both explicitly
|
|
3140
|
+
* overridden by the user's own direction and the domain's standards:
|
|
3141
|
+
*
|
|
3142
|
+
* 1. Orchestrate (strong default): delegate the heavy / parallel /
|
|
3143
|
+
* context-heavy work to the right subagent / worker / model, keeping the
|
|
3144
|
+
* main context free to reason and collaborate with the user, while still
|
|
3145
|
+
* doing trivial / surgical / last-mile work directly (delegating that
|
|
3146
|
+
* would only add relay-fidelity loss + latency).
|
|
3147
|
+
* 2. Excellence lens (HYBRID per the peer review): the reliable control
|
|
3148
|
+
* signal is the PRINCIPLE stated plainly; the well-known names appear only
|
|
3149
|
+
* as a "bar to clear" calibration, NOT as "channel X", with an explicit
|
|
3150
|
+
* no-impersonation / no-theatrics guardrail (cross-lab critics: a named
|
|
3151
|
+
* entity is a dense, high-variance vector that pulls in noise + persona
|
|
3152
|
+
* mannerisms at top salience, so the principle leads and the name calibrates).
|
|
3153
|
+
*
|
|
3154
|
+
* Self-referentially compliant with the style directive: no em dashes, no
|
|
3155
|
+
* Claude / Anthropic attribution.
|
|
3156
|
+
*/
|
|
3157
|
+
const OPERATING_DEFAULTS_DIRECTIVE = "## Operating defaults (apply when the user has not specified otherwise; the user's explicit direction and the domain's own standards always override)\n\nOrchestrate. Delegate research, implementation, review, and large reads to the right subagent, worker, or model (worker-* agents for background non-blocking runs, Task subagents for parallel work, peer critics for review) so your own context stays free to reason and collaborate with the user. Prefer parallel delegation for independent work. Do trivial, surgical, and last-mile work directly; delegate the rest rather than doing it yourself and filling your own context.\n\nAim high. Default to radical simplicity and relentless focus on the user's real experience (the Jobs and Ive bar for design), whole-systems first-principles thinking that anticipates scale and the long arc (the Gates bar for architecture and tech), and customer obsession that works backwards from the outcome (the Bezos bar for product and business). Question every assumption and prefer what you can derive, reproduce, or test. Adopt the principles, not a persona: no impersonation, name-dropping, or theatrics.";
|
|
3158
|
+
/**
|
|
2832
3159
|
* Skip the helper if the user's `~/.claude/CLAUDE.md` (or, equivalently,
|
|
2833
3160
|
* the would-be post-write file) has grown past this size.
|
|
2834
3161
|
* Read-modify-write becomes pathological at very large sizes; CLAUDE.md
|
|
@@ -3166,6 +3493,23 @@ async function prependStyleDirectiveToMirroredClaudeMd(directive = STYLE_DIRECTI
|
|
|
3166
3493
|
});
|
|
3167
3494
|
}
|
|
3168
3495
|
/**
|
|
3496
|
+
* Prepend the operating-defaults directive (orchestrator posture + hybrid
|
|
3497
|
+
* excellence lens; `OPERATING_DEFAULTS_DIRECTIVE` above) to the TOP of the
|
|
3498
|
+
* mirrored CLAUDE.md so descendant agents (Agent subagents, agent-teams
|
|
3499
|
+
* teammates) inherit it. The main agent gets the same text at higher salience
|
|
3500
|
+
* via `--append-system-prompt`. Separate marker fence from the style / peer
|
|
3501
|
+
* blocks so all coexist; best-effort (warn-and-continue) like its siblings.
|
|
3502
|
+
*/
|
|
3503
|
+
async function prependOperatingDefaultsToMirroredClaudeMd(directive = OPERATING_DEFAULTS_DIRECTIVE) {
|
|
3504
|
+
await injectMarkerBlock({
|
|
3505
|
+
snippet: directive,
|
|
3506
|
+
markerOpen: OPERATING_MARKER_OPEN,
|
|
3507
|
+
markerClose: OPERATING_MARKER_CLOSE,
|
|
3508
|
+
position: "top",
|
|
3509
|
+
label: "operating-defaults"
|
|
3510
|
+
});
|
|
3511
|
+
}
|
|
3512
|
+
/**
|
|
3169
3513
|
* Append the toolbelt awareness one-liner (which CLI tools are on PATH)
|
|
3170
3514
|
* to the bottom of the mirrored CLAUDE.md so descendant agents (Agent
|
|
3171
3515
|
* subagents, agent-teams teammates) learn about the provisioned tools.
|
|
@@ -3257,6 +3601,7 @@ const INJECTED_SKILLS = [
|
|
|
3257
3601
|
RESEARCH_SKILL,
|
|
3258
3602
|
ORCHESTRATE_SKILL,
|
|
3259
3603
|
FLOOR_KEEPER_SKILL,
|
|
3604
|
+
WORKER_SKILL,
|
|
3260
3605
|
FIRST_MATE_SKILL
|
|
3261
3606
|
];
|
|
3262
3607
|
|
|
@@ -3739,7 +4084,7 @@ function initProxyFromEnv() {
|
|
|
3739
4084
|
//#endregion
|
|
3740
4085
|
//#region package.json
|
|
3741
4086
|
var name = "github-router";
|
|
3742
|
-
var version$1 = "0.3.
|
|
4087
|
+
var version$1 = "0.3.153";
|
|
3743
4088
|
|
|
3744
4089
|
//#endregion
|
|
3745
4090
|
//#region src/lib/approval.ts
|
|
@@ -5890,6 +6235,7 @@ const claude = defineCommand({
|
|
|
5890
6235
|
await removeOwnClaudeConfigMirror();
|
|
5891
6236
|
};
|
|
5892
6237
|
let onShutdown = baseShutdown;
|
|
6238
|
+
let peerAwarenessSnippet;
|
|
5893
6239
|
if (args["codex-mcp"] !== false) try {
|
|
5894
6240
|
const requestedCli = args["codex-cli"] ?? false;
|
|
5895
6241
|
const backend = resolveCodexCliBackend({
|
|
@@ -5912,7 +6258,9 @@ const claude = defineCommand({
|
|
|
5912
6258
|
const runtime = await writePeerMcpRuntimeFiles(serverUrl, {
|
|
5913
6259
|
codexCli: backend === "cli",
|
|
5914
6260
|
geminiAvailable,
|
|
5915
|
-
groupKeys
|
|
6261
|
+
groupKeys,
|
|
6262
|
+
workerToolsAvailable: workerToolsEnabled(),
|
|
6263
|
+
browseAvailable: browseAgentEnabled()
|
|
5916
6264
|
});
|
|
5917
6265
|
state.peerMcpNonce = runtime.nonce;
|
|
5918
6266
|
envVars.GH_ROUTER_HOOK_MCP_URL = serverUrl;
|
|
@@ -5945,6 +6293,16 @@ const claude = defineCommand({
|
|
|
5945
6293
|
} catch (err) {
|
|
5946
6294
|
consola.warn(`Could not register the UserPromptSubmit hook: ${String(err)}`);
|
|
5947
6295
|
}
|
|
6296
|
+
if (!injected.ok) consola.warn("Workers non-blocking guard NOT registered: subagent MCP injection fell back to parent-only (--mcp-config), so worker-* dispatchers cannot reach the workers server. Raw (blocking) worker tools remain usable on the main thread this session.");
|
|
6297
|
+
else if (process$1.env.GH_ROUTER_DISABLE_WORKER_GUARD === "1") consola.info("Workers non-blocking guard disabled via GH_ROUTER_DISABLE_WORKER_GUARD=1 — raw mcp__workers__* is callable on the main thread (blocking); the worker-* background agents remain the steered, non-blocking default.");
|
|
6298
|
+
else try {
|
|
6299
|
+
const settingsPath = nodePath.join(PATHS.CLAUDE_CONFIG_DIR, "settings.json");
|
|
6300
|
+
const workersKey = workersKeyOf(groupKeys);
|
|
6301
|
+
const modes = activeDispatchModes({ browse: browseAgentEnabled() });
|
|
6302
|
+
await injectStopHookIntoSettingsFile(settingsPath, buildWorkerGuardHookCommand(process$1.execPath, process$1.argv[1], workersKey, modes), "PreToolUse", 10, guardToolMatcher(workersKey, modes));
|
|
6303
|
+
} catch (err) {
|
|
6304
|
+
consola.warn(`Could not register the workers PreToolUse guard hook: ${String(err)}`);
|
|
6305
|
+
}
|
|
5948
6306
|
if (skillsWritten > 0) {
|
|
5949
6307
|
const skillNames = skillsToWrite.map((s) => `/${s.name}`).join(", ");
|
|
5950
6308
|
process$1.stderr.write(`Injected skills (${skillsWritten}/${skillsToWrite.length}): ${skillNames}.\n`);
|
|
@@ -6084,7 +6442,7 @@ const claude = defineCommand({
|
|
|
6084
6442
|
agentToolsAvailable: agentToolsEnabled(),
|
|
6085
6443
|
groupKeys
|
|
6086
6444
|
});
|
|
6087
|
-
|
|
6445
|
+
peerAwarenessSnippet = peerSnippet;
|
|
6088
6446
|
try {
|
|
6089
6447
|
await appendPeerAwarenessToMirroredClaudeMd(peerSnippet);
|
|
6090
6448
|
} catch (err) {
|
|
@@ -6098,6 +6456,12 @@ const claude = defineCommand({
|
|
|
6098
6456
|
} catch (err) {
|
|
6099
6457
|
consola.warn(`Peer MCP wiring failed (claude will launch without it): ${err instanceof Error ? err.message : String(err)}`);
|
|
6100
6458
|
}
|
|
6459
|
+
extraArgs.push("--append-system-prompt", peerAwarenessSnippet ? `${OPERATING_DEFAULTS_DIRECTIVE}\n\n${peerAwarenessSnippet}` : OPERATING_DEFAULTS_DIRECTIVE);
|
|
6460
|
+
try {
|
|
6461
|
+
await prependOperatingDefaultsToMirroredClaudeMd();
|
|
6462
|
+
} catch (err) {
|
|
6463
|
+
consola.warn(`Operating-defaults CLAUDE.md prepend failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
6464
|
+
}
|
|
6101
6465
|
launchChild({
|
|
6102
6466
|
kind: "claude-code",
|
|
6103
6467
|
envVars,
|
|
@@ -6373,7 +6737,7 @@ async function postJson(url, payload, opts) {
|
|
|
6373
6737
|
* no such handle. Hooks always receive piped/redirected stdin, so this never
|
|
6374
6738
|
* blocks (guarded against an interactive TTY, and any error -> "").
|
|
6375
6739
|
*/
|
|
6376
|
-
function readStdin$
|
|
6740
|
+
function readStdin$4() {
|
|
6377
6741
|
try {
|
|
6378
6742
|
if (process.stdin.isTTY) return "";
|
|
6379
6743
|
return readFileSync(0, "utf8");
|
|
@@ -6404,7 +6768,7 @@ const internalPromptSubmit = defineCommand({
|
|
|
6404
6768
|
},
|
|
6405
6769
|
async run() {
|
|
6406
6770
|
try {
|
|
6407
|
-
const stdin = readStdin$
|
|
6771
|
+
const stdin = readStdin$4();
|
|
6408
6772
|
const steerEnabled = parseBoolEnv(process.env.GH_ROUTER_DISABLE_PROMPT_STEER) !== true;
|
|
6409
6773
|
const runtime = hookMcpRuntimeFromEnv();
|
|
6410
6774
|
let decision;
|
|
@@ -6464,7 +6828,7 @@ const internalPromptSubmit = defineCommand({
|
|
|
6464
6828
|
* teardown and trips a `uv_async_send` assertion; a synchronous read has no such
|
|
6465
6829
|
* handle. Hooks always receive piped stdin (guarded against a TTY; any error -> "").
|
|
6466
6830
|
*/
|
|
6467
|
-
function readStdin$
|
|
6831
|
+
function readStdin$3() {
|
|
6468
6832
|
try {
|
|
6469
6833
|
if (process.stdin.isTTY) return "";
|
|
6470
6834
|
return readFileSync(0, "utf8");
|
|
@@ -6554,7 +6918,7 @@ const internalSessionBind = defineCommand({
|
|
|
6554
6918
|
try {
|
|
6555
6919
|
const out = typeof args.out === "string" ? args.out.trim() : "";
|
|
6556
6920
|
if (!out) return;
|
|
6557
|
-
const record = decodeSessionBind(readStdin$
|
|
6921
|
+
const record = decodeSessionBind(readStdin$3());
|
|
6558
6922
|
if (record) writeSidecar(out, record);
|
|
6559
6923
|
} catch {}
|
|
6560
6924
|
process.exitCode = 0;
|
|
@@ -6723,7 +7087,7 @@ ${body}
|
|
|
6723
7087
|
|
|
6724
7088
|
//#endregion
|
|
6725
7089
|
//#region src/internal-artifact-open.ts
|
|
6726
|
-
function readStdin$
|
|
7090
|
+
function readStdin$2() {
|
|
6727
7091
|
try {
|
|
6728
7092
|
if (process.stdin.isTTY) return "";
|
|
6729
7093
|
return readFileSync(0, "utf8");
|
|
@@ -6817,7 +7181,7 @@ const internalArtifactOpen = defineCommand({
|
|
|
6817
7181
|
},
|
|
6818
7182
|
async run() {
|
|
6819
7183
|
try {
|
|
6820
|
-
const raw = readStdin$
|
|
7184
|
+
const raw = readStdin$2();
|
|
6821
7185
|
let payload;
|
|
6822
7186
|
try {
|
|
6823
7187
|
payload = JSON.parse(raw);
|
|
@@ -6845,7 +7209,7 @@ const internalArtifactOpen = defineCommand({
|
|
|
6845
7209
|
* no such handle. Hooks always receive piped/redirected stdin, so this never
|
|
6846
7210
|
* blocks (guarded against an interactive TTY, and any error -> "").
|
|
6847
7211
|
*/
|
|
6848
|
-
function readStdin() {
|
|
7212
|
+
function readStdin$1() {
|
|
6849
7213
|
try {
|
|
6850
7214
|
if (process.stdin.isTTY) return "";
|
|
6851
7215
|
return readFileSync(0, "utf8");
|
|
@@ -6972,7 +7336,7 @@ const internalStopHook = defineCommand({
|
|
|
6972
7336
|
description: "Internal: the structural-gate Stop hook. Reads the Claude Code hook payload on stdin, runs the sealed gate, exits 2 (blocks the stop) on a red gate or gate-weakening diff."
|
|
6973
7337
|
},
|
|
6974
7338
|
async run() {
|
|
6975
|
-
const stdin = readStdin();
|
|
7339
|
+
const stdin = readStdin$1();
|
|
6976
7340
|
const reviewEnabled = stopReviewEnabled() && hookMcpRuntimeFromEnv() !== void 0;
|
|
6977
7341
|
let transcriptPath = "";
|
|
6978
7342
|
let userPrompt = "";
|
|
@@ -7109,6 +7473,56 @@ const internalStopReview = defineCommand({
|
|
|
7109
7473
|
}
|
|
7110
7474
|
});
|
|
7111
7475
|
|
|
7476
|
+
//#endregion
|
|
7477
|
+
//#region src/internal-worker-guard.ts
|
|
7478
|
+
/**
|
|
7479
|
+
* Read the hook payload from stdin SYNCHRONOUSLY (`readFileSync(0)`) — same
|
|
7480
|
+
* rationale as `internal-prompt-submit`: an async stdin read leaves an in-flight
|
|
7481
|
+
* libuv FS request that races Windows process teardown. Hooks always receive
|
|
7482
|
+
* piped stdin (guarded against an interactive TTY; any error → "").
|
|
7483
|
+
*/
|
|
7484
|
+
function readStdin() {
|
|
7485
|
+
try {
|
|
7486
|
+
if (process.stdin.isTTY) return "";
|
|
7487
|
+
return readFileSync(0, "utf8");
|
|
7488
|
+
} catch {
|
|
7489
|
+
return "";
|
|
7490
|
+
}
|
|
7491
|
+
}
|
|
7492
|
+
const internalWorkerGuard = defineCommand({
|
|
7493
|
+
meta: {
|
|
7494
|
+
name: "internal-worker-guard",
|
|
7495
|
+
description: "Internal: the workers PreToolUse guard. Denies a raw mcp__<workers>__<mode> call from the main agent (redirecting to the worker-<mode> background subagent) and allows it from the dispatcher subagent. Fails closed."
|
|
7496
|
+
},
|
|
7497
|
+
args: {
|
|
7498
|
+
"workers-key": {
|
|
7499
|
+
type: "string",
|
|
7500
|
+
description: "Resolved workers MCP config key (bare `workers` or `gh-router-workers`).",
|
|
7501
|
+
required: true
|
|
7502
|
+
},
|
|
7503
|
+
modes: {
|
|
7504
|
+
type: "string",
|
|
7505
|
+
description: "CSV of active worker modes, e.g. `explore,implement,review,plan,test`.",
|
|
7506
|
+
required: false
|
|
7507
|
+
}
|
|
7508
|
+
},
|
|
7509
|
+
run(ctx) {
|
|
7510
|
+
try {
|
|
7511
|
+
const stdin = readStdin();
|
|
7512
|
+
const workersKey = String(ctx.args["workers-key"] ?? "").trim();
|
|
7513
|
+
if (workersKey.length > 0) {
|
|
7514
|
+
const { output } = decideWorkerGuard({
|
|
7515
|
+
stdin,
|
|
7516
|
+
workersKey,
|
|
7517
|
+
modes: parseModesCsv(typeof ctx.args.modes === "string" ? ctx.args.modes : void 0)
|
|
7518
|
+
});
|
|
7519
|
+
if (output) process.stdout.write(output);
|
|
7520
|
+
}
|
|
7521
|
+
} catch {}
|
|
7522
|
+
process.exitCode = 0;
|
|
7523
|
+
}
|
|
7524
|
+
});
|
|
7525
|
+
|
|
7112
7526
|
//#endregion
|
|
7113
7527
|
//#region src/models.ts
|
|
7114
7528
|
const models = defineCommand({
|
|
@@ -7394,7 +7808,7 @@ process.on("uncaughtException", (error) => {
|
|
|
7394
7808
|
const version = getPackageVersion();
|
|
7395
7809
|
const argv = process.argv.slice(2);
|
|
7396
7810
|
const isVersionFlag = argv.includes("--version");
|
|
7397
|
-
const isInternalHook = argv[0] === "internal-stop-hook" || argv[0] === "internal-prompt-submit" || argv[0] === "internal-stop-review" || argv[0] === "internal-session-bind" || argv[0] === "internal-artifact-open";
|
|
7811
|
+
const isInternalHook = argv[0] === "internal-stop-hook" || argv[0] === "internal-prompt-submit" || argv[0] === "internal-stop-review" || argv[0] === "internal-session-bind" || argv[0] === "internal-artifact-open" || argv[0] === "internal-worker-guard";
|
|
7398
7812
|
if (!isVersionFlag && !isInternalHook) consola.info(`github-router v${version}`);
|
|
7399
7813
|
await runMain(defineCommand({
|
|
7400
7814
|
meta: {
|
|
@@ -7414,7 +7828,8 @@ await runMain(defineCommand({
|
|
|
7414
7828
|
"internal-prompt-submit": internalPromptSubmit,
|
|
7415
7829
|
"internal-stop-review": internalStopReview,
|
|
7416
7830
|
"internal-session-bind": internalSessionBind,
|
|
7417
|
-
"internal-artifact-open": internalArtifactOpen
|
|
7831
|
+
"internal-artifact-open": internalArtifactOpen,
|
|
7832
|
+
"internal-worker-guard": internalWorkerGuard
|
|
7418
7833
|
}
|
|
7419
7834
|
}));
|
|
7420
7835
|
|