refactor(lanes): drop the workflow skills and Superpowers link; pipelines go dormant

The plugin stops shipping its workflow layer:
- skills/ship-feature and skills/ship-feature-lane, the 18 stage/ship slash
  commands (plugin.json keeps agenthub-doctor/update/open), and all four
  agents/ (qc-local, senior-gate-reviewer, backend-reviewer, frontend-reviewer).
- CLI `agenthub skills install`, `agenthub lanes agents install`,
  `agenthub ship-full|ship-quick`; routes POST /api/skills/install and
  POST /api/lanes/:id/agents/install; the Settings button, the lane Tools
  menu item and the Add-lane setup step that called them.
- The Superpowers auto-install: server/lib/skill-plugins.js, the bootstrap's
  autoInstallSkillPlugins / skillPluginsSettled gate and the doctor row.

Pipelines are kept but dormant: server/data/pipelines/ is deleted, so no
template ships. `default` resolves to EMPTY_PIPELINE (no nodes, progress 0),
createLane still accepts it, POST /api/lanes/:id/stage answers 400
ENOPIPELINE, and the Workspace hides the stage timer, pickers, PipelineMap
and strip stage chip for a lane with no nodes. A template dropped into
DASHBOARD_PIPELINES_DIR turns it all back on. The server suite loads its
templates from server/__tests__/fixtures/pipelines/.

Docs: CLAUDE.md (marker, retired rules), README(.vi), INSTALL, ARCHITECTURE,
docs/LANES|PLUGINS|CLI|API, agenthub-introduction.html.
This commit is contained in:
2026-09-28 17:44:27 +07:00
parent 3c238bc5c4
commit 2b8f83d2dd
72 changed files with 325 additions and 3305 deletions
-54
View File
@@ -161,10 +161,6 @@ function fastPathOk(rt = runtimeDir()) {
if (state.pluginRoot !== PLUGIN_ROOT) return false;
if (state.depsHash !== depsHash()) return false;
if (!fs.existsSync(path.join(rt, "node_modules"))) return false;
// A machine can be fully set up and still be missing the plugins the
// pipelines drive. Reading one recorded flag keeps this hot path cheap — the
// actual check (manifest + every user SKILL.md) runs once, in the worker.
if (!state.skillPluginsSettled) return false;
// A live server says nothing about whether it will come back. When our unit
// still names a previous plugin cache directory, everything looks healthy
// until the next reboot — and then the dashboard is simply gone, from a path
@@ -500,46 +496,6 @@ function log(rt, line) {
}
}
/**
* Install the plugins AgentHub's pipelines drive but this machine cannot resolve —
* today `superpowers`, which `/agenthub:ship-feature(-lane)` calls step by step. A
* fresh `agenthub` install otherwise runs those skills partway and fails at the
* first call with no warning beforehand.
*
* Off by setting `AGENTHUB_AUTO_INSTALL_PLUGINS` to `0`, `false` or `off`; that
* leaves `/agenthub-doctor`'s WARN row as the only notice, which is the right
* behaviour for anyone who wants to choose their own plugins.
*
* One-shot: the outcome is recorded in the runtime state and the fast path
* takes it into account, so a machine with no network does not re-attempt a
* clone on every session. `/agenthub-update` (`--force`) retries.
*
* Best-effort throughout. Nothing here may throw or block: the dashboard,
* hooks and CLI all work without these plugins.
*
* @returns {"off"|"nothing-missing"|"installed"|"failed"|"error"} or the previously
* recorded outcome when this run had nothing to settle.
*/
function autoInstallSkillPlugins(rt, { force = false } = {}) {
const flag = process.env.AGENTHUB_AUTO_INSTALL_PLUGINS;
if (flag === "0" || flag === "false" || flag === "off") return "off";
// Return the PREVIOUS outcome, not a marker: writeState records whatever this
// returns, and overwriting "installed" with "already-settled" would lose the
// only record of what actually happened.
const settled = readState(rt)?.skillPluginsSettled;
if (!force && settled) return settled;
try {
const { autoInstallSkillPlugins: install } = require("../server/lib/skill-plugins");
const summary = install((line) => log(rt, line));
if (!summary.attempted.length) return "nothing-missing";
return summary.failed.length ? "failed" : "installed";
} catch (err) {
log(rt, `skill-plugin auto-install skipped: ${err.message}`);
return "error";
}
}
/* ------------------------------------------------------ plugin-cache pruning */
/** How many retired builds to leave behind, newest first, as a rollback cushion. */
@@ -814,21 +770,12 @@ function bootstrap(rt = runtimeDir(), { force = false } = {}) {
}
}
// Last, and deliberately so: it clones from the network, and nothing above
// may wait on it. Runs here rather than in the foreground pass because this
// whole function is already detached from the 10 s SessionStart hook.
const skillPlugins = autoInstallSkillPlugins(rt, { force });
writeState(
{
pluginVersion: pluginVersion(),
depsHash: wantDeps,
pluginRoot: PLUGIN_ROOT,
claudeHome: getClaudeHome(),
// Settles the fast path: whatever the outcome, the next SessionStart
// must not spawn a worker just to retry a network clone. Cleared by
// `/agenthub-update` (force), which is the retry.
skillPluginsSettled: skillPlugins,
updatedAt: new Date().toISOString(),
},
rt
@@ -930,6 +877,5 @@ module.exports = {
livePids,
stopDashboard,
startDashboard,
autoInstallSkillPlugins,
bootstrap,
};
+4 -38
View File
@@ -6,10 +6,8 @@
* declared package, not just that node_modules exists), server liveness,
* duplicate hook entries (the one failure that silently doubles every token and
* cost figure), the `agenthub` CLI launcher and its PATH, the `tmux` binary
* every browser-started run depends on, whether the slash
* commands the pipeline templates name as node drivers are actually resolvable
* (the `superpowers` plugin, today), and the dashboard bundle — quoting the last
* build error when it is missing.
* every browser-started run depends on, and the dashboard bundle — quoting the
* last build error when it is missing.
* Read-only — it diagnoses, it never repairs.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
@@ -85,7 +83,6 @@ function diagnose() {
add(...cliStatus());
add(...systemdStatus());
add(...tmuxStatus());
add(...skillPluginStatus());
add(...dashboardUiStatus(rt));
@@ -190,37 +187,6 @@ function lastBuildError(logPath) {
return hit ? hit.trim().slice(0, 200) : null;
}
/**
* WARN (never FAIL) when a plugin the pipelines depend on cannot be resolved:
* the dashboard, hooks and CLI all work without it — only the pipeline
* skills that drive it stop partway, so this must not fail a doctor run.
*
* The requirement derivation and the missing/satisfied rules live in
* `server/lib/skill-plugins.js`, shared with the bootstrap that installs them.
*/
function skillPluginStatus() {
const skillPlugins = require("../server/lib/skill-plugins");
const required = skillPlugins.requiredSkillPlugins();
if (!required.size) return ["ok", "Skill plugins", "no pipeline names an external plugin"];
let missing;
try {
missing = skillPlugins.missingSkillPlugins();
} catch {
return ["warn", "Skill plugins", "could not read the installed-plugin manifest"];
}
if (!missing.length) {
return ["ok", "Skill plugins", `${[...required.keys()].sort().join(", ")} available`];
}
return [
"warn",
"Skill plugins",
`${missing.map((m) => `${m.name} (needed for ${m.skills.join(", ")})`).join("; ")} — ` +
`the pipelines name these commands; install with ` +
`claude plugin install ${missing.map((m) => m.autoInstall?.spec || m.name).join(" ")}`,
];
}
/**
* The failure this row exists for is silent by construction: a unit still
* naming a retired plugin cache directory keeps a running dashboard looking
@@ -275,8 +241,8 @@ function systemdStatus() {
* WARN (never FAIL) when `tmux` is absent: every run the dashboard starts is a
* tmux session (`server/lib/tmux.js`), so without the binary the run console,
* `agenthub lanes shell` and the session-reload keystrokes all fail — while
* sessions, analytics, lanes and the CLI keep working. Same reasoning as the
* skill-plugins row: a partial feature loss must not fail a doctor run.
* sessions, analytics, lanes and the CLI keep working. A partial feature
* loss must not fail a doctor run.
*
* This checks the doctor's own PATH. A server started as a systemd `--user`
* service can still see a different one — `GET /api/run/tmux` is the server's
+8 -1
View File
@@ -9,6 +9,9 @@
* (including the pane the test was launched from). Dropping `TMUX` and
* pointing `TMUX_TMPDIR` at a fresh directory gives every tmux call — the
* app's own and the tests' direct `execFileSync("tmux", …)` — its own socket.
* It also points `DASHBOARD_PIPELINES_DIR` at `server/__tests__/fixtures/
* pipelines/`: AgentHub ships no pipeline template any more, and the two old
* ones kept there are what the dormant stage/detection code is tested against.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
@@ -26,7 +29,11 @@ const files = fs
const args = process.argv.slice(2);
const tmuxDir = fs.mkdtempSync(path.join(os.tmpdir(), "agenthub-test-tmux-"));
const env = { ...process.env, TMUX_TMPDIR: tmuxDir };
const env = {
...process.env,
TMUX_TMPDIR: tmuxDir,
DASHBOARD_PIPELINES_DIR: path.join(dir, "fixtures", "pipelines"),
};
delete env.TMUX;
delete env.TMUX_PANE;