Merge pull request #3948 from jeremymcs/fix/cmux-auto-enable
fix(gsd): auto-enable cmux when detected
This commit is contained in:
commit
7a44ca7aed
3 changed files with 108 additions and 7 deletions
|
|
@ -19,6 +19,7 @@ import { deriveState } from "../state.js";
|
|||
import { formatOverridesSection, formatShortcut, loadActiveOverrides, loadFile, parseContinue, parseSummary } from "../files.js";
|
||||
import { toPosixPath } from "../../shared/mod.js";
|
||||
import { markCmuxPromptShown, shouldPromptToEnableCmux } from "../../cmux/index.js";
|
||||
import { autoEnableCmuxPreferences } from "../commands-cmux.js";
|
||||
|
||||
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
||||
|
||||
|
|
@ -76,13 +77,16 @@ export async function buildBeforeAgentStartResult(
|
|||
shortcutDashboard: formatShortcut("Ctrl+Alt+G"),
|
||||
shortcutShell: formatShortcut("Ctrl+Alt+B"),
|
||||
});
|
||||
const loadedPreferences = loadEffectiveGSDPreferences();
|
||||
let loadedPreferences = loadEffectiveGSDPreferences();
|
||||
if (shouldPromptToEnableCmux(loadedPreferences?.preferences)) {
|
||||
markCmuxPromptShown();
|
||||
ctx.ui.notify(
|
||||
"cmux detected. Run /gsd cmux on to enable sidebar metadata, notifications, and visual subagent splits for this project.",
|
||||
"info",
|
||||
);
|
||||
if (autoEnableCmuxPreferences()) {
|
||||
loadedPreferences = loadEffectiveGSDPreferences();
|
||||
ctx.ui.notify(
|
||||
"cmux detected — auto-enabled. Run /gsd cmux off to disable.",
|
||||
"info",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let preferenceBlock = "";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { clearCmuxSidebar, CmuxClient, detectCmuxEnvironment, resolveCmuxConfig } from "../cmux/index.js";
|
||||
import { saveFile } from "./files.js";
|
||||
import {
|
||||
|
|
@ -9,6 +9,37 @@ import {
|
|||
} from "./preferences.js";
|
||||
import { ensurePreferencesFile, serializePreferencesToFrontmatter } from "./commands-prefs-wizard.js";
|
||||
|
||||
/**
|
||||
* Auto-enable cmux in project preferences when detected but never configured.
|
||||
* Called at boot (before agent start) — no ExtensionCommandContext needed.
|
||||
* Returns true if preferences were written, false if skipped.
|
||||
*/
|
||||
export function autoEnableCmuxPreferences(): boolean {
|
||||
const path = getProjectGSDPreferencesPath();
|
||||
if (!existsSync(path)) return false;
|
||||
|
||||
const existing = loadProjectGSDPreferences();
|
||||
const prefs: Record<string, unknown> = existing?.preferences ? { ...existing.preferences } : { version: 1 };
|
||||
prefs.cmux = {
|
||||
enabled: true,
|
||||
notifications: true,
|
||||
sidebar: true,
|
||||
splits: false,
|
||||
browser: false,
|
||||
...((prefs.cmux as Record<string, unknown> | undefined) ?? {}),
|
||||
};
|
||||
(prefs.cmux as Record<string, unknown>).enabled = true;
|
||||
prefs.version = prefs.version || 1;
|
||||
|
||||
const frontmatter = serializePreferencesToFrontmatter(prefs);
|
||||
let body = "\n# GSD Skill Preferences\n\nSee `~/.gsd/agent/extensions/gsd/docs/preferences-reference.md` for full field documentation and examples.\n";
|
||||
const preserved = extractBodyAfterFrontmatter(readFileSync(path, "utf-8"));
|
||||
if (preserved) body = preserved;
|
||||
|
||||
writeFileSync(path, `---\n${frontmatter}---${body}`, "utf-8");
|
||||
return true;
|
||||
}
|
||||
|
||||
function extractBodyAfterFrontmatter(content: string): string | null {
|
||||
const start = content.startsWith("---\n") ? 4 : content.startsWith("---\r\n") ? 5 : -1;
|
||||
if (start === -1) return null;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import test, { describe } from "node:test";
|
||||
import test, { describe, beforeEach, afterEach } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import {
|
||||
buildCmuxProgress,
|
||||
|
|
@ -12,6 +13,7 @@ import {
|
|||
resolveCmuxConfig,
|
||||
shouldPromptToEnableCmux,
|
||||
} from "../../cmux/index.ts";
|
||||
import { autoEnableCmuxPreferences } from "../commands-cmux.ts";
|
||||
import type { GSDState } from "../types.ts";
|
||||
|
||||
test("detectCmuxEnvironment requires workspace, surface, and socket", () => {
|
||||
|
|
@ -79,6 +81,70 @@ test("shouldPromptToEnableCmux only prompts once per session", () => {
|
|||
resetCmuxPromptState();
|
||||
});
|
||||
|
||||
describe("autoEnableCmuxPreferences", () => {
|
||||
let tmp: string;
|
||||
let originalCwd: string;
|
||||
|
||||
beforeEach(() => {
|
||||
originalCwd = process.cwd();
|
||||
tmp = fs.mkdtempSync(path.join(tmpdir(), "cmux-auto-test-"));
|
||||
fs.mkdirSync(path.join(tmp, ".gsd"), { recursive: true });
|
||||
process.chdir(tmp);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.chdir(originalCwd);
|
||||
fs.rmSync(tmp, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test("writes cmux.enabled true when preferences file exists with no cmux config", () => {
|
||||
const prefsPath = path.join(tmp, ".gsd", "preferences.md");
|
||||
fs.writeFileSync(prefsPath, [
|
||||
"---",
|
||||
"version: 1",
|
||||
"---",
|
||||
"",
|
||||
"# GSD Skill Preferences",
|
||||
].join("\n"));
|
||||
|
||||
const result = autoEnableCmuxPreferences();
|
||||
assert.equal(result, true);
|
||||
|
||||
const content = fs.readFileSync(prefsPath, "utf-8");
|
||||
assert.ok(content.includes("enabled: true"), "should write enabled: true");
|
||||
assert.ok(content.includes("notifications: true"), "should default notifications on");
|
||||
assert.ok(content.includes("sidebar: true"), "should default sidebar on");
|
||||
assert.ok(content.includes("splits: false"), "should default splits off");
|
||||
});
|
||||
|
||||
test("returns false when preferences file does not exist", () => {
|
||||
const result = autoEnableCmuxPreferences();
|
||||
assert.equal(result, false);
|
||||
});
|
||||
|
||||
test("preserves existing cmux sub-preferences when auto-enabling", () => {
|
||||
const prefsPath = path.join(tmp, ".gsd", "preferences.md");
|
||||
fs.writeFileSync(prefsPath, [
|
||||
"---",
|
||||
"version: 1",
|
||||
"cmux:",
|
||||
" splits: true",
|
||||
" browser: true",
|
||||
"---",
|
||||
"",
|
||||
"# GSD Skill Preferences",
|
||||
].join("\n"));
|
||||
|
||||
const result = autoEnableCmuxPreferences();
|
||||
assert.equal(result, true);
|
||||
|
||||
const content = fs.readFileSync(prefsPath, "utf-8");
|
||||
assert.ok(content.includes("enabled: true"), "should set enabled: true");
|
||||
assert.ok(content.includes("splits: true"), "should preserve existing splits: true");
|
||||
assert.ok(content.includes("browser: true"), "should preserve existing browser: true");
|
||||
});
|
||||
});
|
||||
|
||||
test("buildCmuxStatusLabel and progress prefer deepest active unit", () => {
|
||||
const state: GSDState = {
|
||||
activeMilestone: { id: "M001", title: "Milestone" },
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue