feat(ui): AI Tools modal, one-button lane tool picker, AgentHub settings in Overview, markdown preview
- AI Tools moves from a Settings section into a modal behind a button at the Settings tab bar's right edge (AiToolsModal, `actions` on PageTabs, /settings#ai-tools); rows get an enable switch, Default badge, and an Update button for every tool whose catalog names an updater (no version lookup on open); not-installed tools fold behind a disclosure, a finished job's log folds to one line; shared AiToolIcon - codex updates through its own `codex update`, openclaw/hermes gain `update --yes`; update-all runs every installed updater and skips an npm one only when npm does not manage the binary - Add-lane tool picker is one button showing the chosen tool that opens the enabled tools as buttons - Claude Code / Codex AgentHub sub-tab folded into Overview (home editor, hooks, pricing); the path list drops CLAUDE_HOME/CODEX_HOME; HomeDirSetting shows the saved value only while the field differs - Preview/Raw toggle for markdown files in the config FileViewer and the Changes panel's whole-file view (MarkdownPreview: react-markdown + remark-gfm, raw HTML never run, frontmatter as a code block) - docs and the CLAUDE.md marker updated
This commit is contained in:
+2
-2
@@ -542,7 +542,7 @@ Request body shape:
|
||||
| `POST` | `/api/ai-tools/:id/update` | Same-origin guarded: start the update job; npm only for an npm-managed tool (`server/lib/ai-tool-versions.js`), else `409 ENOTNPM`. `400 ENOUPDATE`, `409 ENOTINSTALLED`/`EBUSY`. |
|
||||
| `POST` | `/api/ai-tools/update-all` | Same-origin guarded: one job over every npm-managed tool with a newer version plus every self updater. `400 ENOTHING`. |
|
||||
| `GET` | `/api/ai-tools/job` | The current or last job (in memory), or `null`. |
|
||||
| `GET` | `/api/ai-tools/versions` | npm version badges, cached 24 h. |
|
||||
| `GET` | `/api/ai-tools/versions` | npm-managed check and newest npm version per npm-installed tool, cached 24 h. The AI Tools modal no longer calls it: Update shows for every updater. |
|
||||
| `GET` | `/api/ai-tools/:id/icon` | The server-cached favicon (`server/lib/ai-tool-icons.js`), raster only; `204` when there is none (a normal state — no console error per letter avatar), `404` for an unknown id. |
|
||||
| `POST` | `/api/lanes/:id/git/:action` | Same-origin guarded git write, serialized under the lane lock: `fetch`, `pull` (always `--ff-only`), `push` (`-u origin HEAD`), `stage`/`unstage`/`discard` (per file; a path must be one git reports changed, and `discard` needs `confirm:true`), `stage-hunk`/`discard-hunk` (one hunk by its `@@` line, re-read fresh — `409 EHUNKGONE` when it moved; `discard-hunk` needs `confirm:true`), `commit` (`add -A` + `-m`, or `only:"staged"`), `switch` (refuses a dirty tree), `branch-create` (never switches) `branch-delete` (`-d`, refuses protected/current/busy branches) and `publish` (`{url}`: `remote add origin` + `push -u origin HEAD`, only when the repo has no remote; URL allowlisted to https-without-userinfo/ssh/scp-like; remote rolled back on a failed push). `merge` (clean tree, repo-root lane, a ref that exists; conflicts come back as `conflicts[]`), `resolve` (`side: ours|theirs|done`, conflicted paths only, `done` refuses leftover markers), `merge-commit`, `merge-abort` (`confirm:true`); `EMERGING` blocks pull/switch/merge/hunk actions mid-merge. No rebase/reset/stash by design. A remote wanting a login is `409 EGITAUTH` with `auth: {protocol, host, username, rejected}`; resend with `credentials: {protocol, host, username, password, remember}` (a repo remote's host only, else `400 EBADHOST`; saved only after git succeeds and `remember` is set). |
|
||||
|
||||
@@ -595,7 +595,7 @@ renders a node as `done` (see `docs/LANES.md#stage-detection`).
|
||||
|
||||
A `codex` lane carries no pipeline, so nothing detects or advances its stage.
|
||||
Codex's hooks are opt-in (`server/lib/codex-hooks.js`, installed from Settings →
|
||||
Codex → AgentHub and trusted by the user inside Codex); a hook posted with
|
||||
Codex → Overview and trusted by the user inside Codex); a hook posted with
|
||||
`tool: "codex"` reaches only the codex lane at its cwd and creates a
|
||||
`tool = 'codex'` session (see ARCHITECTURE.md, **Codex hooks**). Otherwise
|
||||
`server/lib/codex-ingest.js` only mirrors its `status` — skipping a lane whose
|
||||
|
||||
@@ -371,7 +371,12 @@ describe("jobs", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("update-all runs the badged npm tools and every self updater", async () => {
|
||||
it("update-all runs every npm-managed tool and every self updater, asking npm no version", async () => {
|
||||
let viewed = 0;
|
||||
versions.__setExec(async (_f, args) => {
|
||||
if (args[0] === "view") viewed++;
|
||||
return { stdout: args[0] === "prefix" ? `${prefix}\n` : "1.0.0\n" };
|
||||
});
|
||||
await jobs.startUpdateAll();
|
||||
const done = await settle();
|
||||
assert.equal(done.status, "ok");
|
||||
@@ -379,5 +384,6 @@ describe("jobs", () => {
|
||||
spawned.map((s) => s.argv.slice(1).join(" ")),
|
||||
["install -g npm-tool@latest", "update --yes"]
|
||||
);
|
||||
assert.equal(viewed, 0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -164,7 +164,7 @@ describe("install / update / icons", () => {
|
||||
const { body } = await request("GET", "/api/ai-tools");
|
||||
const codex = body.tools.find((t) => t.id === "codex");
|
||||
assert.deepEqual(codex.install, { kind: "npm", package: "@openai/codex" });
|
||||
assert.deepEqual(codex.update, { kind: "npm" });
|
||||
assert.deepEqual(codex.update, { kind: "self", args: ["update"] }); // standalone or npm
|
||||
assert.equal(body.catalog.source, "builtin");
|
||||
});
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
"package": "@openai/codex"
|
||||
},
|
||||
"update": {
|
||||
"kind": "npm"
|
||||
"kind": "self",
|
||||
"args": [
|
||||
"update"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,5 +15,12 @@
|
||||
"--yolo"
|
||||
],
|
||||
"homepageUrl": "https://hermes-agent.nousresearch.com/docs/",
|
||||
"faviconDomain": "nousresearch.com"
|
||||
"faviconDomain": "nousresearch.com",
|
||||
"update": {
|
||||
"kind": "self",
|
||||
"args": [
|
||||
"update",
|
||||
"--yes"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,5 +9,12 @@
|
||||
"delayMs": 3000
|
||||
},
|
||||
"homepageUrl": "https://github.com/openclaw/openclaw",
|
||||
"faviconDomain": "openclaw.ai"
|
||||
"faviconDomain": "openclaw.ai",
|
||||
"update": {
|
||||
"kind": "self",
|
||||
"args": [
|
||||
"update",
|
||||
"--yes"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,17 +247,19 @@ async function start(id, action) {
|
||||
return view(j);
|
||||
}
|
||||
|
||||
/** One job that updates every npm-managed tool with a newer version and every self-updater. */
|
||||
/**
|
||||
* One job that runs every installed tool's updater — its own, or npm's when
|
||||
* npm manages it (one installed another way is skipped). No version lookup:
|
||||
* each updater knows whether it has anything to do.
|
||||
*/
|
||||
async function startUpdateAll() {
|
||||
claim();
|
||||
let steps = [];
|
||||
try {
|
||||
const v = await versions.versions();
|
||||
for (const t of aiTools.list()) {
|
||||
if (!aiTools.detect(t.id).installed || !t.update) continue;
|
||||
if (t.update.kind === "self" || (t.update.kind === "npm" && v[t.id]?.updateAvailable)) {
|
||||
steps.push(...(await stepsFor(t.id, "update")));
|
||||
}
|
||||
if (t.update.kind === "npm" && !(await versions.npmManaged(t.id))) continue;
|
||||
steps.push(...(await stepsFor(t.id, "update")));
|
||||
}
|
||||
if (!steps.length) throw makeErr("ENOTHING", "no AI tool has an update to run");
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user