github-router 0.3.163 → 0.3.168
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-DHFa2_hK.js → engine-RA1Orodr.js} +1 -1
- package/dist/main.js +280 -7
- package/dist/main.js.map +1 -1
- package/dist/{peer-mcp-personas-Bfq3FPKc.js → peer-mcp-personas-D826LsJJ.js} +4431 -405
- package/dist/peer-mcp-personas-D826LsJJ.js.map +1 -0
- package/package.json +1 -1
- package/dist/peer-mcp-personas-Bfq3FPKc.js.map +0 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "github-router browser bridge",
|
|
4
4
|
"short_name": "gh-router-browser",
|
|
5
|
-
"version": "0.3.
|
|
5
|
+
"version": "0.3.168",
|
|
6
6
|
"description": "Bridge between Claude (via github-router /mcp) and the browser. Implements tab control, navigation, clicks, form fill, downloads, screenshots, devtools eval. Blocks navigation to chrome://settings.",
|
|
7
7
|
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqJElxuBlonBS3TVW9FJN0mGTtShB3L1hoaYf6k39SOr1ogGYmF90EjRxy1i21k9wQQjPf26bcBu/9X67KrQjQV0uB38CaNukgiSeoLjfptN811u+PJHx6BP+jx3Qa6/3VenNPxHC8WEU0GXql8QSjIHEyCwKb6fMASXOK94JyB5Ywov2x8mt/+9ncqBBBMVzf6r5Sagy4PL1XnryLsuADD/vOEkPet8wXgH/Oj7v5tTsQQZ7U1JT51PoDs2BFnXc5v3TkVgZwd32k3ONh+nkDw1Hof+4zwUGOyJE6eMrlYzRlKM4Qxdf9JpavQvqfieAbTRWcyKeclnHeoIfE7cDBQIDAQAB",
|
|
8
8
|
"background": {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { F as PLAN_DEFAULT_MODEL, I as REVIEW_DEFAULT_MODEL, L as appendPlanReminder, M as DEFAULT_MODEL, N as EXPLORE_DEFAULT_MODEL, P as IMPLEMENT_DEFAULT_MODEL, R as runWorkerAgent, j as BROWSE_DEFAULT_MODEL, z as withNoOutputRetry } from "./peer-mcp-personas-D826LsJJ.js";
|
|
2
2
|
import "./paths-CTlT1nTo.js";
|
|
3
3
|
import "./lifecycle-ChPBRt6K.js";
|
|
4
4
|
import "./lifecycle-BUXxiltc.js";
|
package/dist/main.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as
|
|
2
|
+
import { $ as buildAnthropicErrorEvent, $t as resolveCodexModel, A as liveExec, At as shouldUseInsecureTls, B as availableToolCommands, Bt as generateRandomPort, C as isSubagentContext, Ct as readResponseBodyCapped, D as stopReviewStateDir, Dt as provisionAndIndexColbert, E as stopGateEnabledForRepo, Et as hasSupportedBrowserInstalled, Ft as DEFAULT_CODEX_MODEL, G as TOOLBELT_TOOLS, Gt as setupGitHubAgentToken, H as toolbeltEnabled, Ht as getPackageVersion, It as DEFAULT_CODEX_MODEL_FALLBACKS, J as ADVISOR_INTERNAL_TOOL_NAME, Jt as cacheCopilotVersion, K as assetFor, Kt as setupGitHubToken, Lt as DEFAULT_PORT, Mt as collapsePathKeys, Nt as toolbeltPathOverride, O as trustRepo, Ot as extractTarGzMember, Pt as DEFAULT_CLAUDE_MODEL_FALLBACKS, Q as isAdvisorRequested, Qt as isNullish, Rt as UPSTREAM_FETCH_TIMEOUT_MS, S as fileReviewDebounce, St as MAX_RESPONSE_BODY_BYTES, T as repoRoot, Tt as provisionBrowserAssets, U as toolbeltSkipSet, Ut as withInstallLock, V as buildToolbeltAwareness, Vt as pickClaudeDefault, W as vscodeRipgrepPath, Wt as setupCopilotToken, X as buildAdvisorStream, Xt as cacheVSCodeVersion, Y as ADVISOR_TOOL_INSTRUCTIONS, Yt as cacheModels, Z as injectAdvisorTool, Zt as filterBetaHeader, _ as stopGatePlanMode, _t as assembleResponsesPayload, a as buildPeerAwarenessSnippet, an as forwardError, at as handleMcpDelete, b as fileFindingsStore, bt as createResponses, c as buildSessionBindHookCommand, cn as copilotHeaders, ct as browseAgentEnabled, d as decideStopHook, dt as implementerSubagentModel, en as resolveModel, et as buildOpenAIErrorEvent, f as fileBlockBudget, ft as standInToolEnabled, g as stopGateId, gt as getTokenCount, h as stopGateDisabled, ht as createMessages, i as buildAgentPrompt, in as HTTPError, it as relayAnthropicStream, jt as ArtifactClient, k as resolveSealedGate, kt as extractZipMember, l as buildStopHookCommand, ln as githubHeaders, lt as browserToolsEnabled, m as launchBaselineKey, mt as countTokens, n as MCP_GROUPS, nn as getModels, nt as logStreamError, o as personasFor, on as GITHUB_API_BASE_URL, ot as handleMcpPost, p as injectStopHookIntoSettingsFile, pt as workerToolsEnabled, q as searchWeb, qt as tryRefreshAndRetry, r as assertMcpToolSurfaceConsistent, rn as fetchWithTransientRetry, rt as readIteratorWithTimeout, s as buildArtifactOpenHookCommand, sn as copilotBaseUrl, st as agentToolsEnabled, t as GROUP_META, tn as sleep, tt as isControllerClosedError, u as captureLaunchBaseline, un as state, ut as fleetToolsEnabled, v as stopReviewEnabled, vt as resolveMcpToolTimeoutMs, w as repoFingerprint, wt as parseJsonOrDiagnose, x as fileLastPromptStore, xt as createChatCompletions, y as fileBaselineStore, yt as pickEndpoint, zt as UPSTREAM_INACTIVITY_TIMEOUT_MS } from "./peer-mcp-personas-D826LsJJ.js";
|
|
3
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-CTlT1nTo.js";
|
|
4
4
|
import { c as killManagedTree, d as runCommandCapture, f as runCommandVoid, l as parseBoolEnv, s as killChildProcessTree, u as resolveExecutable } from "./lifecycle-ChPBRt6K.js";
|
|
5
5
|
import { a as sweepRegistry } from "./lifecycle-BUXxiltc.js";
|
|
@@ -2289,7 +2289,7 @@ async function discoverGateCommands(cwd, opts) {
|
|
|
2289
2289
|
if (files.length === 0) return null;
|
|
2290
2290
|
let result;
|
|
2291
2291
|
try {
|
|
2292
|
-
const { runWorkerAgent } = await import("./engine-
|
|
2292
|
+
const { runWorkerAgent } = await import("./engine-RA1Orodr.js");
|
|
2293
2293
|
result = await runWorkerAgent({
|
|
2294
2294
|
mode: "explore",
|
|
2295
2295
|
workspace: root,
|
|
@@ -2443,6 +2443,99 @@ function buildPromptSubmitHookCommand(execPath, scriptPath) {
|
|
|
2443
2443
|
return `${q(execPath)} internal-prompt-submit`;
|
|
2444
2444
|
}
|
|
2445
2445
|
|
|
2446
|
+
//#endregion
|
|
2447
|
+
//#region src/lib/first-mate/operator-shaping.ts
|
|
2448
|
+
/** MCP prefixes denied to the main operator; these remain subagent-only. */
|
|
2449
|
+
const OPERATOR_DENIED_MCP_PREFIXES = ["mcp__workers__", "mcp__orchestrate__"];
|
|
2450
|
+
/**
|
|
2451
|
+
* Decide whether a tool call must be blocked for the main operator. Pure — used
|
|
2452
|
+
* both by the PreToolUse hook handler and by config-assertion tests. When
|
|
2453
|
+
* operator mode is off, nothing is blocked (normal sessions unaffected).
|
|
2454
|
+
*/
|
|
2455
|
+
function shouldDenyOperatorTool(toolName, operatorMode) {
|
|
2456
|
+
if (!operatorMode) return false;
|
|
2457
|
+
return OPERATOR_DENIED_MCP_PREFIXES.some((prefix) => toolName.startsWith(prefix));
|
|
2458
|
+
}
|
|
2459
|
+
/**
|
|
2460
|
+
* M4 — fail-CLOSED. If operator/`--agents` mode is active but the reduced
|
|
2461
|
+
* worker/orchestrate guard could NOT be installed (e.g. settings.json unwritable),
|
|
2462
|
+
* the operator session must NOT start with direct main-thread worker/orchestrate
|
|
2463
|
+
* MCP calls available. Non-agents sessions are never affected.
|
|
2464
|
+
*/
|
|
2465
|
+
function assertShapingInstalled(agentsMode, injectionSucceeded) {
|
|
2466
|
+
if (agentsMode && !injectionSucceeded) throw new Error("cloud-agent operator mode requires the worker/orchestrate PreToolUse hook, but it could not be installed — refusing to start an unguarded operator session.");
|
|
2467
|
+
}
|
|
2468
|
+
/**
|
|
2469
|
+
* The PreToolUse hook decision for a given tool name in operator mode.
|
|
2470
|
+
*
|
|
2471
|
+
* Bash and file-authoring tools are always allowed. Only direct main-operator
|
|
2472
|
+
* calls to local worker/orchestrate MCP tools are blocked so those backends stay
|
|
2473
|
+
* reachable through the worker-* Agent subagents rather than the lead context.
|
|
2474
|
+
*/
|
|
2475
|
+
function operatorPreToolUse(toolName, operatorMode, _input) {
|
|
2476
|
+
if (!operatorMode) return { block: false };
|
|
2477
|
+
if (shouldDenyOperatorTool(toolName, operatorMode)) return {
|
|
2478
|
+
block: true,
|
|
2479
|
+
reason: `${toolName} is subagent-only in cloud-agent operator mode — use the worker-* Agent subagents or delegate implementation to a GitHub cloud agent via the first-mate MCP instead of calling local worker/orchestrate MCP tools from the main operator.`
|
|
2480
|
+
};
|
|
2481
|
+
return { block: false };
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
//#endregion
|
|
2485
|
+
//#region src/internal-first-mate-guard.ts
|
|
2486
|
+
function readStdinSync() {
|
|
2487
|
+
try {
|
|
2488
|
+
return readFileSync(0, "utf8");
|
|
2489
|
+
} catch {
|
|
2490
|
+
return "";
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
const internalFirstMateGuard = defineCommand({
|
|
2494
|
+
meta: {
|
|
2495
|
+
name: "internal-first-mate-guard",
|
|
2496
|
+
description: "PreToolUse guard enforcing cloud-agent operator capability shaping."
|
|
2497
|
+
},
|
|
2498
|
+
run() {
|
|
2499
|
+
let toolName = "";
|
|
2500
|
+
let toolInput;
|
|
2501
|
+
let parsed = false;
|
|
2502
|
+
try {
|
|
2503
|
+
const payload = JSON.parse(readStdinSync());
|
|
2504
|
+
if (typeof payload.tool_name === "string") toolName = payload.tool_name;
|
|
2505
|
+
if (typeof payload.tool_input === "object" && payload.tool_input !== null) toolInput = payload.tool_input;
|
|
2506
|
+
parsed = true;
|
|
2507
|
+
} catch {
|
|
2508
|
+
parsed = false;
|
|
2509
|
+
}
|
|
2510
|
+
if (toolName.length === 0) {
|
|
2511
|
+
if (!parsed) {
|
|
2512
|
+
process.stderr.write("operator guard: unparseable PreToolUse payload — blocking (fail-closed)");
|
|
2513
|
+
process.exit(2);
|
|
2514
|
+
}
|
|
2515
|
+
process.exit(0);
|
|
2516
|
+
}
|
|
2517
|
+
const decision = operatorPreToolUse(toolName, true, toolInput);
|
|
2518
|
+
if (decision.block) {
|
|
2519
|
+
process.stderr.write(decision.reason ?? `${toolName} is disabled in operator mode`);
|
|
2520
|
+
process.exit(2);
|
|
2521
|
+
}
|
|
2522
|
+
process.exit(0);
|
|
2523
|
+
}
|
|
2524
|
+
});
|
|
2525
|
+
/** The regex matcher scoping the guard hook to exactly the denied tools. */
|
|
2526
|
+
const FIRST_MATE_GUARD_MATCHER = "mcp__workers__.*|mcp__orchestrate__.*";
|
|
2527
|
+
/** Build the hook command string that runs this subcommand. */
|
|
2528
|
+
function buildFirstMateGuardHookCommand(execPath, entry) {
|
|
2529
|
+
return `${JSON.stringify(execPath)} ${JSON.stringify(entry)} internal-first-mate-guard`;
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
//#endregion
|
|
2533
|
+
//#region src/lib/injected-skills/first-mate-setup-skill.ts
|
|
2534
|
+
const FIRST_MATE_SETUP_SKILL = {
|
|
2535
|
+
name: "gh-first-mate-scaffold",
|
|
2536
|
+
md: "---\nname: gh-first-mate-scaffold\ndescription: Seed a world-class repo-geared agentic-dev foundation through first-mate.\nuser-invocable: true\n---\n\n# gh-first-mate-scaffold\n\nInvoke the `scaffold_repo` MCP tool (`mcp__first-mate__scaffold_repo`) before the first build wave on an owned repository. The goal is not generic TODO stubs; it is a repo-geared foundation that GitHub agents, local agents, reviewers, and CI can read.\n\n## What it seeds\n\n- `AGENTS.md` / `CLAUDE.md` / `GEMINI.md` / `.github/copilot-instructions.md` — identical guidance with overview, detected stack, commands, hard DoD gate, primary OS, conventions, structure, decisions/memory, handoff, testing, and gotchas.\n- `.github/agents/{planner,implementer,reviewer,researcher,tester}.md` mirrored into `.claude/agents/` — role agents with frontmatter, cold-start contract, method, quality bar, output contract, and self-reminder.\n- `docs/adrs/0000-template.md` plus `docs/adr/0001-record-architecture-decisions.md` — Nygard-style decision record foundation.\n- `LEARNINGS.md`, `CHANGELOG.md`, `docs/history/0000-template.md`, `docs/plans/README.md`, and `docs/research/README.md` — durable memory, history, plans, and research conventions.\n- `.github/pull_request_template.md` — summary, type, failure-modes-considered-and-tested, and DoD checklist.\n- `.github/instructions/tests.instructions.md` — path-scoped test guidance filled from detected framework/dir/glob where possible.\n- `.github/workflows/copilot-setup-steps.yml` and starter `.github/workflows/ci.yml` — detected toolchain setup with stable quality-gate job names.\n\nIt does not seed factory-protocol or `docs/factory/` files. Orchestration remains outside the product repo in first-mate.\n\n## Usage\n\n```\nmcp__first-mate__scaffold_repo({ repo: \"owner/repo\" })\nmcp__first-mate__scaffold_repo({ repo: \"owner/repo\", mode: \"enhance\" })\nmcp__first-mate__scaffold_repo({\n repo: \"owner/repo\",\n mode: \"add-missing-only\",\n detection_overrides: { primary_os: \"windows-latest\", test_command: \"npm test\" }\n})\n```\n\nModes:\n\n- `add-missing-only` (default): seed absent files and skip present files.\n- `enhance`: for guidance files, ADR index, changelog, and learnings, append only missing `##` sections; never rewrite existing prose. Other present files are skipped.\n- `overwrite-approved`: replace existing files only when explicitly approved.\n\nAlways inspect the returned per-file report and PR. A no-op result means the repo already has the foundation or has no missing enhanceable sections.\n"
|
|
2537
|
+
};
|
|
2538
|
+
|
|
2446
2539
|
//#endregion
|
|
2447
2540
|
//#region src/lib/injected-skills/first-mate-skill.ts
|
|
2448
2541
|
const FIRST_MATE_SKILL = {
|
|
@@ -2459,6 +2552,18 @@ Use this skill when the user wants first-mate to drive GitHub cloud coding agent
|
|
|
2459
2552
|
The first-mate controller is the durable system of record: missions, units, decisions, handles, and controller state live in its registry and ledger.
|
|
2460
2553
|
Your job is to run the thin protocol, not to hold the mission in context.
|
|
2461
2554
|
|
|
2555
|
+
## Foundation-first mandate
|
|
2556
|
+
|
|
2557
|
+
Before the first build wave on an owned repository, run \`mcp__first-mate__scaffold_repo\` and verify the PR landed or is already present. The scaffold must seed a repo-geared foundation that GitHub agents and CI can read: guidance, role agents, ADRs, changelog, learnings, PR template, test instructions, Copilot setup, and CI. Do not seed factory-protocol files into product repos; first-mate is the external orchestrator.
|
|
2558
|
+
|
|
2559
|
+
Use \`mode: "add-missing-only"\` for new repos, \`mode: "enhance"\` when a repo has existing guidance that should keep its prose while appending missing \`##\` sections, and \`mode: "overwrite-approved"\` only with explicit approval.
|
|
2560
|
+
|
|
2561
|
+
## Scoped-work discipline
|
|
2562
|
+
|
|
2563
|
+
Well-scoped, testable work items succeed; vague meta-work fails. Discovery/decompose must emit concrete units with acceptance criteria, expected evidence, and dependencies. Keep one active build unit per concern. Parallelism is for read-only producers (research, review, planning) and independent units only, not for racing broad implementation waves.
|
|
2564
|
+
|
|
2565
|
+
Judgment and merge policy: merge remains human-gated, evidence-gated, and head/base-bound. Use the best available model tier for plan review, judgment, and merge decisions; never cheap out on plan/judge/merge calls.
|
|
2566
|
+
|
|
2462
2567
|
## Start a mission
|
|
2463
2568
|
|
|
2464
2569
|
For a new goal, call mcp__first-mate__start_mission with:
|
|
@@ -2469,7 +2574,7 @@ For a new goal, call mcp__first-mate__start_mission with:
|
|
|
2469
2574
|
- priority and house_rules only when the user supplied them or they are necessary constraints.
|
|
2470
2575
|
|
|
2471
2576
|
If acceptance criteria are missing or ambiguous, ask the user before starting.
|
|
2472
|
-
Do not decompose the mission yourself at start time;
|
|
2577
|
+
Do not decompose the mission yourself at start time; mission registration is intentionally simple and later controller wakes/model requests drive decomposition and steering. If the user explicitly asks to append scoped work to an existing active mission, use mcp__first-mate__add_units with concrete unit titles.
|
|
2473
2578
|
|
|
2474
2579
|
Invariant (closes the stranding hole): work only ever becomes active inside a turn — start_mission is a tool call, nothing activates a mission server-side. So immediately after start_mission, run one loop turn (advance, then arm the heartbeat) in the SAME turn, before you yield. Never register a mission and stop without arming; otherwise nothing will wake to drive it.
|
|
2475
2580
|
|
|
@@ -2496,6 +2601,7 @@ Keep verdicts small and typed to the request kind.
|
|
|
2496
2601
|
|
|
2497
2602
|
Use the request's kind and payload as the contract:
|
|
2498
2603
|
|
|
2604
|
+
- decompose: split a unit-less active mission into dispatchable units. Return { units: [{ title, repo?, agent?, dependsOn?, model? }] }. \`dependsOn\` entries are 0-based indices into the same units list. Emit once per unit-less active mission; the controller creates durable unit ids and will not ask again after units exist.
|
|
2499
2605
|
- review_plan: review the plan against the mission goal, acceptance criteria, and house rules. Return { decision: "approve" } when the plan is good enough to implement, or { decision: "refine", instruction: "..." } with a short actionable refinement.
|
|
2500
2606
|
- answer_agent_question: answer only from the acceptance criteria and supplied context. Return { answer: "..." }. If the answer is not derivable, do not invent policy; escalate by leaving a short answer that says what the human must decide.
|
|
2501
2607
|
- author_fix: author a concise fix instruction for the cloud agent. Return { instruction: "..." } with the failure, expected behavior, and any bounded check to run.
|
|
@@ -2507,6 +2613,8 @@ Delegate heavy reading to workers:
|
|
|
2507
2613
|
- Use the worker-review subagent (Agent tool) for compact review of a specific plan, PR summary, or suspicious change.
|
|
2508
2614
|
- Use the worker-test subagent (Agent tool) when a missing executable check is the blocker.
|
|
2509
2615
|
|
|
2616
|
+
Operator / --agents mode constraint: delegate product implementation to GitHub cloud agents. Direct \`mcp__workers__*\` / \`mcp__orchestrate__*\` calls are subagent-only for the main operator; use the worker-* Agent subagents when local worker help is genuinely needed. Local tools (Edit/Write/Bash, \`gh\`, \`git\`) remain available, but prefer authoring fix instructions for the cloud agent rather than pulling large diffs or CI logs into the lead context.
|
|
2617
|
+
|
|
2510
2618
|
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.
|
|
2511
2619
|
|
|
2512
2620
|
## Human requests
|
|
@@ -2540,6 +2648,8 @@ Responsiveness (optional): the buckets above already tighten cadence to ~2 min f
|
|
|
2540
2648
|
|
|
2541
2649
|
Guardrails: never leave more than one [fm-heartbeat] job; never busy-loop advance; if no scheduler tool is available, tell the user the next wake is in nextWakeSeconds seconds and stop.
|
|
2542
2650
|
|
|
2651
|
+
MCP unavailable: if the first-mate MCP server is not present (this is not a \`--agents\` session and \`mcp__first-mate__advance\` would return a tool-not-found error), do NOT attempt to advance. CronList then CronDelete every [fm-heartbeat] job (same as Disarm above), and report "first-mate paused — re-run under \`github-router claude --agents\` to resume." Stop without further action.
|
|
2652
|
+
|
|
2543
2653
|
## Board reports
|
|
2544
2654
|
|
|
2545
2655
|
Use mcp__first-mate__board or mcp__first-mate__mission_status for read-only status checks.
|
|
@@ -2549,6 +2659,9 @@ Report compactly:
|
|
|
2549
2659
|
- repositories
|
|
2550
2660
|
- phase counts
|
|
2551
2661
|
- blocked count and why, when available
|
|
2662
|
+
- per-unit handles for non-terminal units: unitId, issue/PR, phase, provider, validation, model, and blockedReason when present
|
|
2663
|
+
- resolved cloud-agent model per unit (surfaced on the board — verify model choice before approving plans)
|
|
2664
|
+
- terminal work only as summary counts; use include_all only when the user asks for completed/abandoned history
|
|
2552
2665
|
- next wake time or the next requested action
|
|
2553
2666
|
|
|
2554
2667
|
Never reconstruct status by rereading raw logs when the controller board already has the handles.
|
|
@@ -3613,7 +3726,8 @@ const INJECTED_SKILLS = [
|
|
|
3613
3726
|
ORCHESTRATE_SKILL,
|
|
3614
3727
|
FLOOR_KEEPER_SKILL,
|
|
3615
3728
|
WORKER_SKILL,
|
|
3616
|
-
FIRST_MATE_SKILL
|
|
3729
|
+
FIRST_MATE_SKILL,
|
|
3730
|
+
FIRST_MATE_SETUP_SKILL
|
|
3617
3731
|
];
|
|
3618
3732
|
|
|
3619
3733
|
//#endregion
|
|
@@ -4046,6 +4160,141 @@ async function stopKeepAwake() {
|
|
|
4046
4160
|
releaseSync();
|
|
4047
4161
|
}
|
|
4048
4162
|
|
|
4163
|
+
//#endregion
|
|
4164
|
+
//#region src/lib/first-mate/scheduler/autospawn.ts
|
|
4165
|
+
/**
|
|
4166
|
+
* Opt-in auto-spawn of the first-mate scheduler daemon as a SEPARATE process
|
|
4167
|
+
* (a hung tick must not starve the proxy event loop).
|
|
4168
|
+
*
|
|
4169
|
+
* DEFAULT OFF — gated by GH_ROUTER_FM_DAEMON === "1" (explicit opt-in). The
|
|
4170
|
+
* durable [fm-heartbeat] cron is the DEFAULT driver and the proven path; the
|
|
4171
|
+
* daemon is experimental and NOT yet safe as the default because two hardening
|
|
4172
|
+
* items remain (a verifier-stall wall-clock escalation, and an atomic-claim
|
|
4173
|
+
* lock in durable-store) — flip the default only once those land.
|
|
4174
|
+
*
|
|
4175
|
+
* Requirements + honest boundaries:
|
|
4176
|
+
* - The daemon entry is `scripts/first-mate-daemon.ts` (a TS source file run via
|
|
4177
|
+
* `bun`); it is NOT in the published dist tarball, so auto-spawn only fires
|
|
4178
|
+
* from a source checkout with `bun` available. If the script is absent we
|
|
4179
|
+
* no-op (never a false "spawned" log).
|
|
4180
|
+
* - Spawn failures NEVER crash the proxy: an async ENOENT (`bun` not on PATH)
|
|
4181
|
+
* arrives as an `'error'` EVENT, not a sync throw, so we attach an `'error'`
|
|
4182
|
+
* listener (without it the emitter re-throws → uncaughtException → exit(1)).
|
|
4183
|
+
* - The returned handle MUST be killed by the caller on shutdown (no `detached`);
|
|
4184
|
+
* an orphaned drive-primary daemon would keep merging PRs after the proxy is
|
|
4185
|
+
* gone. The parent ALSO holds the child's stdin write end (`stdio[0]==="pipe"`)
|
|
4186
|
+
* so it can EOF the child for a GRACEFUL stop (lease + pidfile released
|
|
4187
|
+
* immediately, not by expiry) — the cross-platform teardown trigger, since an
|
|
4188
|
+
* external SIGTERM on Windows is a hard kill the child can't observe. `kill()`
|
|
4189
|
+
* stays the hard backstop so a wedged child is never orphaned (see
|
|
4190
|
+
* `wireDaemonTeardown`).
|
|
4191
|
+
* - It owns the deterministic drive loop only; live judgments still wake the
|
|
4192
|
+
* lead via the heartbeat (no server->lead push).
|
|
4193
|
+
*/
|
|
4194
|
+
function shouldAutoSpawnDaemon(env, agentsEnabled) {
|
|
4195
|
+
if (!agentsEnabled) return false;
|
|
4196
|
+
return env.GH_ROUTER_FM_DAEMON === "1";
|
|
4197
|
+
}
|
|
4198
|
+
/**
|
|
4199
|
+
* The real (non-injected) spawner: node:child_process (not the Bun runtime's spawn) so the
|
|
4200
|
+
* bundled dist/main.js stays node-loadable. No `detached` — the caller kills
|
|
4201
|
+
* this on shutdown. stdin is "pipe" (parent holds the write end for a graceful
|
|
4202
|
+
* EOF stop); stdout/stderr stay "ignore".
|
|
4203
|
+
*/
|
|
4204
|
+
function nodeDaemonSpawn(cmd, spawnOpts, spawnImpl = spawn) {
|
|
4205
|
+
const proc = spawnImpl(cmd[0], cmd.slice(1), {
|
|
4206
|
+
env: spawnOpts.env,
|
|
4207
|
+
stdio: spawnOpts.stdio
|
|
4208
|
+
});
|
|
4209
|
+
proc.on("error", (err) => consola.debug("first-mate daemon spawn error (ignored):", err));
|
|
4210
|
+
return {
|
|
4211
|
+
pid: proc.pid,
|
|
4212
|
+
kill: () => void proc.kill("SIGKILL"),
|
|
4213
|
+
endStdin: () => {
|
|
4214
|
+
try {
|
|
4215
|
+
proc.stdin?.end();
|
|
4216
|
+
} catch {}
|
|
4217
|
+
}
|
|
4218
|
+
};
|
|
4219
|
+
}
|
|
4220
|
+
/**
|
|
4221
|
+
* Spawn the daemon child if the opt-in gate passes AND the daemon script is
|
|
4222
|
+
* present; else return undefined. Never throws, and never lets an async spawn
|
|
4223
|
+
* error crash bootstrap.
|
|
4224
|
+
*/
|
|
4225
|
+
function maybeSpawnDaemon(opts) {
|
|
4226
|
+
const env = opts.env ?? process.env;
|
|
4227
|
+
if (!shouldAutoSpawnDaemon(env, opts.agentsEnabled)) return void 0;
|
|
4228
|
+
const script = nodePath.join(opts.repoRoot ?? process.cwd(), "scripts/first-mate-daemon.ts");
|
|
4229
|
+
if (opts.spawn === void 0 && !existsSync(script)) {
|
|
4230
|
+
consola.debug(`first-mate daemon entry not found (${script}); auto-spawn skipped.`);
|
|
4231
|
+
return;
|
|
4232
|
+
}
|
|
4233
|
+
try {
|
|
4234
|
+
const child = (opts.spawn ?? ((cmd, spawnOpts) => nodeDaemonSpawn(cmd, spawnOpts)))(["bun", script], {
|
|
4235
|
+
env,
|
|
4236
|
+
stdio: [
|
|
4237
|
+
"pipe",
|
|
4238
|
+
"ignore",
|
|
4239
|
+
"ignore"
|
|
4240
|
+
]
|
|
4241
|
+
});
|
|
4242
|
+
return {
|
|
4243
|
+
pid: child.pid,
|
|
4244
|
+
kill: child.kill,
|
|
4245
|
+
endStdin: child.endStdin ?? (() => {})
|
|
4246
|
+
};
|
|
4247
|
+
} catch {
|
|
4248
|
+
return;
|
|
4249
|
+
}
|
|
4250
|
+
}
|
|
4251
|
+
/**
|
|
4252
|
+
* Wire the proxy's teardown to shut the daemon child down GRACEFULLY, then hard
|
|
4253
|
+
* kill as a backstop. Ordering (per teardown path):
|
|
4254
|
+
* 1. `endStdin()` — EOF the child so it releases its lease/pidfile cleanly (the
|
|
4255
|
+
* cross-platform trigger; on Windows an external SIGTERM would never run the
|
|
4256
|
+
* child's handler, so this EOF is the ONLY graceful path there).
|
|
4257
|
+
* 2. `kill()` — hard backstop so a wedged child is never orphaned (a prior
|
|
4258
|
+
* review blocker: an orphaned drive-primary daemon keeps merging PRs).
|
|
4259
|
+
*
|
|
4260
|
+
* On SIGINT/SIGTERM the kill is deferred by a short, NON-BLOCKING, unref'd grace
|
|
4261
|
+
* window (proxy exit is never delayed for it). On `'exit'` — where timers cannot
|
|
4262
|
+
* run — we EOF then kill synchronously as a last resort. All steps are
|
|
4263
|
+
* once-guarded and non-throwing.
|
|
4264
|
+
*/
|
|
4265
|
+
function wireDaemonTeardown(handle, opts = {}) {
|
|
4266
|
+
const proc = opts.proc ?? process;
|
|
4267
|
+
const graceMs = opts.graceMs ?? 300;
|
|
4268
|
+
const setTimer = opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
4269
|
+
let killed = false;
|
|
4270
|
+
const kill = () => {
|
|
4271
|
+
if (killed) return;
|
|
4272
|
+
killed = true;
|
|
4273
|
+
try {
|
|
4274
|
+
handle.kill();
|
|
4275
|
+
} catch {}
|
|
4276
|
+
};
|
|
4277
|
+
let ended = false;
|
|
4278
|
+
const endStdin = () => {
|
|
4279
|
+
if (ended) return;
|
|
4280
|
+
ended = true;
|
|
4281
|
+
try {
|
|
4282
|
+
handle.endStdin();
|
|
4283
|
+
} catch {}
|
|
4284
|
+
};
|
|
4285
|
+
const onSignal = () => {
|
|
4286
|
+
endStdin();
|
|
4287
|
+
const t = setTimer(kill, graceMs);
|
|
4288
|
+
if (t && typeof t.unref === "function") t.unref();
|
|
4289
|
+
};
|
|
4290
|
+
proc.once("SIGINT", onSignal);
|
|
4291
|
+
proc.once("SIGTERM", onSignal);
|
|
4292
|
+
proc.once("exit", () => {
|
|
4293
|
+
endStdin();
|
|
4294
|
+
kill();
|
|
4295
|
+
});
|
|
4296
|
+
}
|
|
4297
|
+
|
|
4049
4298
|
//#endregion
|
|
4050
4299
|
//#region src/lib/proxy.ts
|
|
4051
4300
|
function initProxyFromEnv() {
|
|
@@ -4095,7 +4344,7 @@ function initProxyFromEnv() {
|
|
|
4095
4344
|
//#endregion
|
|
4096
4345
|
//#region package.json
|
|
4097
4346
|
var name = "github-router";
|
|
4098
|
-
var version$1 = "0.3.
|
|
4347
|
+
var version$1 = "0.3.168";
|
|
4099
4348
|
|
|
4100
4349
|
//#endregion
|
|
4101
4350
|
//#region src/lib/approval.ts
|
|
@@ -7283,6 +7532,15 @@ async function setupAndServe(options) {
|
|
|
7283
7532
|
const url = srvxServer.url;
|
|
7284
7533
|
if (!url) throw new Error("Server started but URL is not available");
|
|
7285
7534
|
const serverUrl = url.replace(/\/$/, "");
|
|
7535
|
+
try {
|
|
7536
|
+
const handle = maybeSpawnDaemon({ agentsEnabled: agentToolsEnabled() });
|
|
7537
|
+
if (handle) {
|
|
7538
|
+
wireDaemonTeardown(handle);
|
|
7539
|
+
consola.debug(`first-mate daemon spawn attempted (pid ${handle.pid ?? "?"}).`);
|
|
7540
|
+
}
|
|
7541
|
+
} catch (err) {
|
|
7542
|
+
consola.debug("first-mate daemon auto-spawn skipped:", err);
|
|
7543
|
+
}
|
|
7286
7544
|
return {
|
|
7287
7545
|
server: srvxServer,
|
|
7288
7546
|
serverUrl
|
|
@@ -7639,6 +7897,9 @@ function getCodexEnvVars(serverUrl) {
|
|
|
7639
7897
|
|
|
7640
7898
|
//#endregion
|
|
7641
7899
|
//#region src/claude.ts
|
|
7900
|
+
function isFirstMateSkillName(name$1) {
|
|
7901
|
+
return name$1 === "gh-first-mate" || name$1 === "gh-first-mate-scaffold";
|
|
7902
|
+
}
|
|
7642
7903
|
const claudeArgs = {
|
|
7643
7904
|
...sharedServerArgs,
|
|
7644
7905
|
model: {
|
|
@@ -7886,7 +8147,7 @@ const claude = defineCommand({
|
|
|
7886
8147
|
process$1.stderr.write(`Peer MCP wired (backend=${backend}, personas=[${personaNames}], subagent .md files=${runtime.agentMdPaths.length}, ${subagentVisibility}).${skippedNote}\n`);
|
|
7887
8148
|
const sessionCwd = process$1.cwd();
|
|
7888
8149
|
if (workerToolsEnabled()) {
|
|
7889
|
-
const skillsToWrite = INJECTED_SKILLS.filter((s) => s.name
|
|
8150
|
+
const skillsToWrite = INJECTED_SKILLS.filter((s) => !isFirstMateSkillName(s.name) || agentToolsEnabled());
|
|
7890
8151
|
let skillsWritten = 0;
|
|
7891
8152
|
for (const s of skillsToWrite) if ((await writeInjectedSkill(s.name, s.md).catch(() => ({ written: false }))).written) skillsWritten++;
|
|
7892
8153
|
try {
|
|
@@ -7909,6 +8170,16 @@ const claude = defineCommand({
|
|
|
7909
8170
|
process$1.stderr.write(`Injected skills (${skillsWritten}/${skillsToWrite.length}): ${skillNames}.\n`);
|
|
7910
8171
|
}
|
|
7911
8172
|
}
|
|
8173
|
+
if (agentToolsEnabled()) {
|
|
8174
|
+
let shapingInstalled = false;
|
|
8175
|
+
try {
|
|
8176
|
+
await injectStopHookIntoSettingsFile(nodePath.join(PATHS.CLAUDE_CONFIG_DIR, "settings.json"), buildFirstMateGuardHookCommand(process$1.execPath, process$1.argv[1]), "PreToolUse", void 0, FIRST_MATE_GUARD_MATCHER);
|
|
8177
|
+
shapingInstalled = true;
|
|
8178
|
+
} catch (err) {
|
|
8179
|
+
consola.error(`Could not register the operator worker/orchestrate guard hook: ${String(err)}`);
|
|
8180
|
+
}
|
|
8181
|
+
assertShapingInstalled(true, shapingInstalled);
|
|
8182
|
+
}
|
|
7912
8183
|
const aiordieSidecar = (process$1.env.AIORDIE_CLAUDE_BIND ?? "").trim();
|
|
7913
8184
|
if (aiordieSidecar.length > 0 && !aiordieSidecar.includes("\"")) try {
|
|
7914
8185
|
const settingsPath = nodePath.join(PATHS.CLAUDE_CONFIG_DIR, "settings.json");
|
|
@@ -8965,6 +9236,7 @@ const internalStopHook = defineCommand({
|
|
|
8965
9236
|
isEnabledForRepo: (cwd) => stopGateEnabledForRepo(cwd),
|
|
8966
9237
|
resolveChecks: mode ? buildResolveChecks(mode) : void 0,
|
|
8967
9238
|
timeoutMs: Number.isFinite(timeoutEnv) && timeoutEnv > 0 ? timeoutEnv : void 0,
|
|
9239
|
+
planMode: stopGatePlanMode(),
|
|
8968
9240
|
reviewDebounce: reviewEnabled ? fileReviewDebounce(stopReviewStateDir()) : void 0,
|
|
8969
9241
|
spawnReview: reviewEnabled ? (ctx) => spawnStopReview(ctx, {
|
|
8970
9242
|
prompt: userPrompt,
|
|
@@ -9410,7 +9682,7 @@ process.on("uncaughtException", (error) => {
|
|
|
9410
9682
|
const version = getPackageVersion();
|
|
9411
9683
|
const argv = process.argv.slice(2);
|
|
9412
9684
|
const isVersionFlag = argv.includes("--version");
|
|
9413
|
-
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";
|
|
9685
|
+
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-first-mate-guard" || argv[0] === "internal-worker-guard";
|
|
9414
9686
|
if (!isVersionFlag && !isInternalHook) consola.info(`github-router v${version}`);
|
|
9415
9687
|
await runMain(defineCommand({
|
|
9416
9688
|
meta: {
|
|
@@ -9431,6 +9703,7 @@ await runMain(defineCommand({
|
|
|
9431
9703
|
"internal-stop-review": internalStopReview,
|
|
9432
9704
|
"internal-session-bind": internalSessionBind,
|
|
9433
9705
|
"internal-artifact-open": internalArtifactOpen,
|
|
9706
|
+
"internal-first-mate-guard": internalFirstMateGuard,
|
|
9434
9707
|
"internal-worker-guard": internalWorkerGuard
|
|
9435
9708
|
}
|
|
9436
9709
|
}));
|