feat(cc-config): update every plugin and marketplace at once, then reload the open sessions
The plugins tab could already update one plugin or one marketplace at a time. It gains an install-wide batch and, separately, the reload step that actually makes an update take effect. Update all runs in a DETACHED worker rather than the request handler, for a structural reason: the batch ends with the plugin the dashboard ITSELF runs from, and that step's plugin-bootstrap.js --force SIGTERMs this very server. An in-process loop would die partway through with the remaining plugins unrun and nothing left to report. Order is marketplaces (one call, no name = all) -> every other installed plugin -> AgentHub last -> one reload. Each transition is journalled to cc-update-all.json in the runtime dir, outside the plugin cache the update replaces, so whichever server is alive serves progress from it; the POST's response is only an acknowledgement. readUpdateAll DERIVES an abandoned status for a running journal older than an hour, because a detached worker killed by a reboot leaves nobody to write the terminal entry and the button would stay wedged forever. isRunning gates a concurrent batch: two claude plugin update chains over one config dir race. Reload is its own capability (server/lib/cc-reload.js, POST /api/cc-config/reload). /reload-plugins and /reload-skills are scoped BY CLAUDE CODE to the session that runs them, so nothing can reload a session from outside - typing into a session already running is the only real mechanism, and that is what this does, over tmux send-keys into the dashboard's live lane panes. It spawns NO claude process on purpose: a one-shot claude -p "/reload-plugins" would reload only its own short-lived session, exit, and leave a junk session in the dashboard's history - a headless run gets a full sessions row and there is no filter anywhere to suppress it. Reusing live panes means no session is created at all. Panes at a shell prompt are skipped (the text would run as a shell command) and only claude lanes are addressed, since Codex has no such command. Update-all reloads once at the end; updating a single plugin passes reload:true and reloads immediately, and never after a failed action. Fixes a real pre-existing bug on the way: pluginUpdate omitted -y while runClaude closes stdin, and the CLI requires it whenever stdin is not a TTY - so the per-plugin Update button failed for any plugin whose marketplace declares an install command. Also completes CcConfigChangedPayload's action union, which claimed write|delete while nine other values were already being emitted. Verified: server 1683/1683, client 486/486 (27 new tests), build, prettier, file-header audit, openapi.yaml regenerated (117 paths). Update all was NOT executed against this machine - it would update 7 real plugins and restart the running dashboard.
This commit is contained in:
+101
-4
@@ -6788,17 +6788,18 @@ paths:
|
||||
post:
|
||||
tags:
|
||||
- Updates
|
||||
summary: Pull, rebuild, and restart the dashboard when fast-forwardable
|
||||
summary: Update this install to the newest release and restart the dashboard
|
||||
description: 'Two modes, chosen by how this copy was installed and never by the request body (which is ignored). checkout: fast-forward onto the release tag, npm run setup, npm run build in production, then self-restart. plugin: the install runs from ~/.claude/plugins/cache/, so a detached helper runs `claude plugin marketplace update`, `claude plugin update -y`, then plugin-bootstrap.js --force from the NEW cache directory, which stops this server and starts the new one. In both modes the response is written before anything stops the process.'
|
||||
operationId: applyUpdate
|
||||
responses:
|
||||
'200':
|
||||
description: Checkout fast-forwarded and rebuilt; the process is about to self-restart
|
||||
description: 'Update is being applied; the process is about to be replaced. mode: "plugin" means the detached plugin chain is running, otherwise the checkout was fast-forwarded and rebuilt in-place'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
description: 'Same shape as GET /api/updates/status, plus applied: true.'
|
||||
description: 'Same shape as GET /api/updates/status, plus applied: true. In plugin mode also mode: "plugin" and runtime_dir (where the chain writes its verdict, read back afterwards as last_plugin_update).'
|
||||
'409':
|
||||
description: Declined - nothing to apply, or the checkout isn't safely fast-forwardable
|
||||
content:
|
||||
@@ -6806,7 +6807,7 @@ paths:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
description: 'applied: false plus reason: "up_to_date" | "not_fast_forwardable".'
|
||||
description: 'applied: false plus reason: "up_to_date" | "not_fast_forwardable" | "plugin_managed". plugin_managed appears here only when a plugin-cache install has nothing newer published; when a newer release exists the plugin chain runs and the answer is 200 instead. Run /agenthub-update to force a refresh.'
|
||||
'500':
|
||||
description: Update apply failed
|
||||
content:
|
||||
@@ -7975,6 +7976,102 @@ paths:
|
||||
pluginJson:
|
||||
name: superpowers
|
||||
version: 1.2.0
|
||||
/api/cc-config/plugins/update-all:
|
||||
get:
|
||||
tags:
|
||||
- CcConfig
|
||||
summary: Progress of the current or last update-all batch
|
||||
description: Read-only. Returns the detached batch worker's on-disk journal from ~/.claude/agenthub/runtime/cc-update-all.json, or `{batch:null}` when no batch has ever run. `status` is `running`|`done`|`partial`|`failed`|`abandoned`; `abandoned` is DERIVED, not stored — a `running` journal older than one hour is reported that way because the worker is detached and a host reboot leaves nobody to write the terminal entry. `partial` means the batch finished with at least one failed step. Malformed or half-written journals return null rather than erroring, since the UI polls this endpoint.
|
||||
operationId: ccConfigGetUpdateAll
|
||||
responses:
|
||||
'200':
|
||||
description: The batch journal, or null.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
example:
|
||||
batch:
|
||||
status: partial
|
||||
started_at: '2026-09-10T08:30:00.000Z'
|
||||
finished_at: '2026-09-10T08:34:12.000Z'
|
||||
steps:
|
||||
- key: marketplaces
|
||||
label: Update all marketplaces
|
||||
status: ok
|
||||
- key: plugin:ponytail@ponytail
|
||||
label: ponytail@ponytail
|
||||
status: ok
|
||||
- key: plugin:vercel@claude-plugins-official
|
||||
label: vercel@claude-plugins-official
|
||||
status: failed
|
||||
error: marketplace unreachable
|
||||
- key: reload
|
||||
label: Reload open Claude Code sessions
|
||||
status: ok
|
||||
lanes_total: 2
|
||||
post:
|
||||
tags:
|
||||
- CcConfig
|
||||
summary: Update every marketplace and plugin, then reload the open sessions
|
||||
description: 'Mutating. Spawns a DETACHED worker and returns immediately — the response is an acknowledgement, not a result; poll GET on this path to follow it. Order: `claude plugin marketplace update` (no name = all), then `claude plugin update <id> -y` for every installed plugin, then `/reload-plugins` + `/reload-skills` typed into the live lane panes. The plugin this dashboard itself runs from is updated LAST and followed by `plugin-bootstrap.js --force`, which stops this server and starts the replacement — which is why the work cannot happen in the request handler and why the journal lives outside the plugin cache. Plugin ids come from `claude plugin list --json`, never from the body, and the "self" plugin is derived from this server''s own path, so no caller can nominate a different plugin for the restart. Answers 409 EBUSY when a batch is already in flight; the body is ignored apart from an optional `reload:false`.'
|
||||
operationId: ccConfigUpdateAll
|
||||
responses:
|
||||
'200':
|
||||
description: Batch worker spawned.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
example:
|
||||
ok: true
|
||||
spawned: true
|
||||
runtime_dir: /Users/son/.claude/agenthub/runtime
|
||||
planned: 7
|
||||
self_plugin:
|
||||
marketplace: sg
|
||||
plugin: agenthub
|
||||
'409':
|
||||
description: A batch is already running.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
/api/cc-config/reload:
|
||||
post:
|
||||
tags:
|
||||
- CcConfig
|
||||
summary: Tell the open Claude Code sessions to re-read plugins and skills
|
||||
description: 'Types `/reload-plugins` and `/reload-skills` into the dashboard''s live lane panes over `tmux send-keys`. Both commands are scoped BY CLAUDE CODE to the session that runs them, so there is no way to reload a session from outside — typing into a session that is already running is what a human would do, and it is what this does. It spawns no `claude` process: a one-shot `claude -p "/reload-plugins"` would reload only its own short-lived session and leave a junk session behind. Changes nothing on disk, so it does not queue on the CLI mutation chain and emits no `cc_config_changed`. Panes sitting at a shell prompt are skipped (the text would run as a shell command); only `claude` lanes are addressed, since Codex has no such command. Optional body `{kinds:["plugins"|"skills"]}` narrows which commands are sent; unknown kinds are ignored, as the command strings are fixed server-side.'
|
||||
operationId: ccConfigReload
|
||||
responses:
|
||||
'200':
|
||||
description: Commands delivered. `lanes_total` counts only panes running a live claude; zero is a normal outcome, not a failure.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
example:
|
||||
ok: true
|
||||
commands:
|
||||
- /reload-plugins
|
||||
- /reload-skills
|
||||
lanes_total: 1
|
||||
lanes:
|
||||
- id: agenthub-lane-3
|
||||
laneId: 3
|
||||
sent:
|
||||
- /reload-plugins
|
||||
- /reload-skills
|
||||
'400':
|
||||
description: '`kinds` was present but not an array.'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
/api/cc-config/mcp:
|
||||
get:
|
||||
tags:
|
||||
|
||||
Reference in New Issue
Block a user