chore: v2.8.21 — update progress steps, clone in background, extra SSH keys inline, today's spend
- Plugin update reports its running step (plugin-update-progress.json → plugin_update_progress); a hidden update or clone leaves a progress chip. - Clone here can run in the background. - Git identity: extra SSH keys are added inline per host (no More keys tab). - Users: spend today (viewer's local day, ?tz_offset=). - Prettier on the files touched since v2.8.20.
This commit is contained in:
@@ -598,10 +598,9 @@ async function captureOutputsNow(laneId, sessionId) {
|
||||
const since = Date.parse(started?.started_at || "") || Date.now() - 24 * 3600 * 1000;
|
||||
const done = [];
|
||||
for (const { rel, mtimeMs } of await outputCandidates(lane, since)) {
|
||||
const rows = sql("SELECT * FROM artifacts WHERE kind = 'file' AND lane_id = ? AND source = ?").all(
|
||||
lane.id,
|
||||
rel
|
||||
);
|
||||
const rows = sql(
|
||||
"SELECT * FROM artifacts WHERE kind = 'file' AND lane_id = ? AND source = ?"
|
||||
).all(lane.id, rel);
|
||||
if (rows.some((r) => r.auto !== 1)) continue;
|
||||
const row = rows[0];
|
||||
const kept = row?.files ? JSON.parse(row.files)[0] : null;
|
||||
|
||||
@@ -28,6 +28,9 @@ const path = require("path");
|
||||
const { spawn } = require("child_process");
|
||||
const { getDataDir } = require("./claude-home");
|
||||
|
||||
/** Longer than any one step's timeout in the helper (bootstrap: 15 min). */
|
||||
const PROGRESS_STALE_MS = 20 * 60 * 1000;
|
||||
|
||||
/** Runtime directory the helper writes its verdict into. Outside the plugin
|
||||
* cache on purpose — that directory is replaced by the update itself. */
|
||||
function runtimeDir() {
|
||||
@@ -53,6 +56,34 @@ function readLastPluginUpdate(rt = runtimeDir()) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The step a running chain is on (`plugin-update-progress.json`, written by the
|
||||
* helper as each step starts and removed with its final verdict), or null.
|
||||
* Surfaced as `plugin_update_progress` so the dashboard can show how far an
|
||||
* update got. A file older than the chain's longest run is left over from a
|
||||
* helper that died, and reads as nothing.
|
||||
*
|
||||
* @returns {{step: string, index: number, total: number, started_at: string} | null}
|
||||
*/
|
||||
function readPluginUpdateProgress(rt = runtimeDir()) {
|
||||
try {
|
||||
const parsed = JSON.parse(
|
||||
fs.readFileSync(path.join(rt, "plugin-update-progress.json"), "utf8")
|
||||
);
|
||||
if (!parsed || typeof parsed.step !== "string") return null;
|
||||
const started = Date.parse(parsed.started_at);
|
||||
if (!Number.isFinite(started) || Date.now() - started > PROGRESS_STALE_MS) return null;
|
||||
return {
|
||||
step: parsed.step,
|
||||
index: Number(parsed.index) || 0,
|
||||
total: Number(parsed.total) || 0,
|
||||
started_at: parsed.started_at,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawn the detached update chain. Returns immediately; the caller must have
|
||||
* already written its HTTP response, because this process is about to be
|
||||
@@ -69,6 +100,7 @@ function schedulePluginUpdate({ marketplace, plugin, root }) {
|
||||
try {
|
||||
fs.mkdirSync(rt, { recursive: true });
|
||||
fs.rmSync(path.join(rt, "plugin-update.json"), { force: true });
|
||||
fs.rmSync(path.join(rt, "plugin-update-progress.json"), { force: true });
|
||||
} catch {
|
||||
/* best effort — a stale verdict is better than failing the update */
|
||||
}
|
||||
@@ -86,4 +118,9 @@ function schedulePluginUpdate({ marketplace, plugin, root }) {
|
||||
return { spawned: true, runtime_dir: rt };
|
||||
}
|
||||
|
||||
module.exports = { schedulePluginUpdate, readLastPluginUpdate, runtimeDir };
|
||||
module.exports = {
|
||||
schedulePluginUpdate,
|
||||
readLastPluginUpdate,
|
||||
readPluginUpdateProgress,
|
||||
runtimeDir,
|
||||
};
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { execFile } = require("child_process");
|
||||
const { readLastPluginUpdate } = require("./plugin-update");
|
||||
const { readLastPluginUpdate, readPluginUpdateProgress } = require("./plugin-update");
|
||||
const { getClaudeHome } = require("./claude-home");
|
||||
|
||||
const DEFAULT_ROOT = path.join(__dirname, "..", "..");
|
||||
@@ -115,10 +115,16 @@ function execNpm(cwd, args, opts = {}) {
|
||||
if (!err) return resolve(String(stdout).trim());
|
||||
if (err.killed) {
|
||||
return reject(
|
||||
new Error(`${step} did not finish within ${Math.round(timeout / 60_000)} min and was stopped`)
|
||||
new Error(
|
||||
`${step} did not finish within ${Math.round(timeout / 60_000)} min and was stopped`
|
||||
)
|
||||
);
|
||||
}
|
||||
const tail = String(stderr || err.message || err).trim().split(/\r?\n/).slice(-15).join("\n");
|
||||
const tail = String(stderr || err.message || err)
|
||||
.trim()
|
||||
.split(/\r?\n/)
|
||||
.slice(-15)
|
||||
.join("\n");
|
||||
reject(new Error(`${step} failed:\n${tail}`));
|
||||
}
|
||||
);
|
||||
@@ -491,6 +497,7 @@ async function pluginStatus(root, pluginInstall, options = {}) {
|
||||
plugin_marketplace: pluginInstall.marketplace,
|
||||
plugin_name: pluginInstall.plugin,
|
||||
last_plugin_update: readLastPluginUpdate(),
|
||||
plugin_update_progress: readPluginUpdateProgress(),
|
||||
|
||||
local_version: localVersion,
|
||||
// Not a clone, so there is nothing to be shallow about and no distances to
|
||||
@@ -785,6 +792,8 @@ async function getUpdatesStatus(gitRoot = DEFAULT_ROOT, options = {}) {
|
||||
// every consumer (route, WS broadcast, CLI) gets it from one place — and so
|
||||
// a chain that died is visible instead of looking like a slow restart.
|
||||
last_plugin_update: pluginInstall ? readLastPluginUpdate() : null,
|
||||
// The step a running chain is on, while it runs.
|
||||
plugin_update_progress: pluginInstall ? readPluginUpdateProgress() : null,
|
||||
local_version: localVersion,
|
||||
// True when commit distances are unusable and version strings are the only
|
||||
// signal — the UI must not render an "N commits" badge in that case.
|
||||
|
||||
Reference in New Issue
Block a user