dsh-plugin-upgrader 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,16 @@
1
1
  import { type PackumentLike, type RepoManifest } from './repo.ts';
2
2
  /** How long one registry packument stays fresh. */
3
3
  export declare const PACKUMENT_TTL_MS: number;
4
- /** Registry lookups must not hang a page render. */
4
+ /**
5
+ * Registry lookups must not hang a page render.
6
+ *
7
+ * Raised from 10 s once the documents were measured: the largest package in a
8
+ * normal profile (`billion-context`, 6 624 versions) is a 26 MB JSON document
9
+ * and 10 MB even abbreviated, which takes a second on a fast mirror and much
10
+ * longer on a busy one. A deadline that the healthy case can miss is not a
11
+ * deadline, it is a coin flip — and its losing side was being drawn as "this
12
+ * plugin has no version history".
13
+ */
5
14
  export declare const PACKUMENT_TIMEOUT_MS: number;
6
15
  /** Version lists are capped so one huge package cannot flood the tab. */
7
16
  export declare const MAX_VERSIONS = 80;
@@ -36,6 +45,8 @@ export interface FullPackument extends PackumentLike {
36
45
  export interface PackumentEntry {
37
46
  at: number;
38
47
  data: unknown;
48
+ /** Set when the last attempt failed, so a later render can say why. */
49
+ error?: string;
39
50
  }
40
51
  /** One cached tag lookup. */
41
52
  export interface TagEntry {
@@ -43,6 +54,18 @@ export interface TagEntry {
43
54
  tags?: Set<string>;
44
55
  error?: string;
45
56
  }
57
+ /**
58
+ * How long a failed packument lookup is remembered.
59
+ *
60
+ * A failure has to be remembered at all: the tab rebuilds the overview on every
61
+ * mount, and retrying a mirror that just timed out on every mount turns one
62
+ * slow render into a permanently slow page. It is kept much shorter than a
63
+ * successful entry, though — the failure is usually contention, not a broken
64
+ * package, so the next open should get a fresh chance.
65
+ */
66
+ export declare const PACKUMENT_RETRY_MS: number;
67
+ /** How many package lookups may be in flight at once. */
68
+ export declare const PACKUMENT_CONCURRENCY = 4;
46
69
  /** One version line the tab renders. */
47
70
  export interface VersionRow {
48
71
  version: string;
@@ -62,10 +85,25 @@ export interface ShapedPackument {
62
85
  tags: Record<string, string>;
63
86
  totalVersions?: number;
64
87
  versionError?: string;
88
+ /** The `dist-tags.latest` that was rejected as broken, when there was one. */
89
+ staleTag?: string;
65
90
  }
66
91
  /**
67
92
  * Fetch one package's packument, with a short-lived cache.
68
93
  *
94
+ * Two failure modes were measured on the npmmirror mirror, and both produced
95
+ * the same symptom — a plugin drawn with no version history at all:
96
+ *
97
+ * - the tab asks for every installed package at once, and eleven concurrent
98
+ * connections to the mirror time out at a 10 s deadline while the same
99
+ * eleven asked one at a time answer in under 1.2 s;
100
+ * - the largest document in a normal profile is 26 MB, so even a healthy
101
+ * request is slow enough to lose that race.
102
+ *
103
+ * The abbreviated document cuts the transfer, the deadline was raised, and a
104
+ * timeout is retried once — a `UND_ERR_CONNECT_TIMEOUT` is contention on the
105
+ * socket, not a verdict about the package.
106
+ *
69
107
  * @param base - registry base URL.
70
108
  * @param packageName - package name.
71
109
  * @param cache - shared cache.
@@ -107,6 +145,15 @@ export declare function cachedTags(repoUrl: string, cache: Map<string, TagEntry>
107
145
  /**
108
146
  * Trim one packument down to what the tab renders.
109
147
  *
148
+ * `dist-tags.latest` is normally the newest stable release, and it is trusted as
149
+ * is — a prerelease above it must NOT become "latest", or every `-next` build
150
+ * would advertise itself over the stable line. The one case it cannot be
151
+ * trusted is a tag that sorts *below the version already installed*: that is not
152
+ * a release decision, it is a broken tag. npmmirror serves
153
+ * `@deepseek-ai/dsh-subagent-codex` with `latest: 0.0.1-rc.1` while the profile
154
+ * runs `0.2.1-rc.3`, which made the row claim the installed build was newer than
155
+ * latest and offered an "upgrade" that was really a downgrade.
156
+ *
110
157
  * @param packument - the full registry document.
111
158
  * @param installedVersion - the version in the profile.
112
159
  * @param tagNames - the repository's tag names, when known.
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/host/registry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/host/registry.ts"],"names":[],"mappings":"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"}
@@ -11,8 +11,25 @@ import { compareVersions, releasePathOf } from './version.js';
11
11
  import { githubUrlOf } from './repo.js';
12
12
  /** How long one registry packument stays fresh. */
13
13
  export const PACKUMENT_TTL_MS = 5 * 60 * 1000;
14
- /** Registry lookups must not hang a page render. */
15
- export const PACKUMENT_TIMEOUT_MS = 10 * 1000;
14
+ /**
15
+ * Registry lookups must not hang a page render.
16
+ *
17
+ * Raised from 10 s once the documents were measured: the largest package in a
18
+ * normal profile (`billion-context`, 6 624 versions) is a 26 MB JSON document
19
+ * and 10 MB even abbreviated, which takes a second on a fast mirror and much
20
+ * longer on a busy one. A deadline that the healthy case can miss is not a
21
+ * deadline, it is a coin flip — and its losing side was being drawn as "this
22
+ * plugin has no version history".
23
+ */
24
+ export const PACKUMENT_TIMEOUT_MS = 20 * 1000;
25
+ /**
26
+ * The npm registry's abbreviated document, which is what pnpm itself downloads.
27
+ *
28
+ * It drops the readme, the per-version tarball metadata and the maintainer
29
+ * lists — 26 MB becomes 10 MB for `billion-context` — while keeping the three
30
+ * fields the version list renders: `time`, `engines` and `deprecated`.
31
+ */
32
+ const ABBREVIATED_ACCEPT = 'application/vnd.npm.install-v1+json';
16
33
  /** Version lists are capped so one huge package cannot flood the tab. */
17
34
  export const MAX_VERSIONS = 80;
18
35
  /**
@@ -25,9 +42,34 @@ export const TAGS_TTL_MS = 30 * 60 * 1000;
25
42
  export const TAGS_RETRY_MS = 5 * 60 * 1000;
26
43
  /** Tag lookups must not hang a page render. */
27
44
  export const TAGS_TIMEOUT_MS = 8 * 1000;
45
+ /**
46
+ * How long a failed packument lookup is remembered.
47
+ *
48
+ * A failure has to be remembered at all: the tab rebuilds the overview on every
49
+ * mount, and retrying a mirror that just timed out on every mount turns one
50
+ * slow render into a permanently slow page. It is kept much shorter than a
51
+ * successful entry, though — the failure is usually contention, not a broken
52
+ * package, so the next open should get a fresh chance.
53
+ */
54
+ export const PACKUMENT_RETRY_MS = 45 * 1000;
55
+ /** How many package lookups may be in flight at once. */
56
+ export const PACKUMENT_CONCURRENCY = 4;
28
57
  /**
29
58
  * Fetch one package's packument, with a short-lived cache.
30
59
  *
60
+ * Two failure modes were measured on the npmmirror mirror, and both produced
61
+ * the same symptom — a plugin drawn with no version history at all:
62
+ *
63
+ * - the tab asks for every installed package at once, and eleven concurrent
64
+ * connections to the mirror time out at a 10 s deadline while the same
65
+ * eleven asked one at a time answer in under 1.2 s;
66
+ * - the largest document in a normal profile is 26 MB, so even a healthy
67
+ * request is slow enough to lose that race.
68
+ *
69
+ * The abbreviated document cuts the transfer, the deadline was raised, and a
70
+ * timeout is retried once — a `UND_ERR_CONNECT_TIMEOUT` is contention on the
71
+ * socket, not a verdict about the package.
72
+ *
31
73
  * @param base - registry base URL.
32
74
  * @param packageName - package name.
33
75
  * @param cache - shared cache.
@@ -37,26 +79,62 @@ export const TAGS_TIMEOUT_MS = 8 * 1000;
37
79
  export async function fetchPackument(base, packageName, cache, force) {
38
80
  const key = `${base}|${packageName}`;
39
81
  const hit = cache.get(key);
40
- if (force !== true && hit !== undefined && Date.now() - hit.at < PACKUMENT_TTL_MS) {
41
- return { data: hit.data };
82
+ if (force !== true && hit !== undefined) {
83
+ // A remembered failure is served only within its own, shorter window.
84
+ if (hit.error === undefined && Date.now() - hit.at < PACKUMENT_TTL_MS)
85
+ return { data: hit.data };
86
+ if (hit.error !== undefined && Date.now() - hit.at < PACKUMENT_RETRY_MS)
87
+ return { error: hit.error };
42
88
  }
43
89
  const url = `${base}/${packageName.startsWith('@') ? '@' + encodeURIComponent(packageName.slice(1)) : encodeURIComponent(packageName)}`;
44
- try {
45
- const response = await fetch(url, {
46
- headers: { accept: 'application/json' },
47
- signal: AbortSignal.timeout(PACKUMENT_TIMEOUT_MS),
48
- });
49
- if (!response.ok) {
50
- return { error: `registry ${response.status}${response.status === 404 ? ' (not found)' : ''}` };
90
+ let lastError = 'registry unreachable';
91
+ for (let attempt = 0; attempt < 2; attempt += 1) {
92
+ try {
93
+ const response = await fetch(url, {
94
+ headers: { accept: ABBREVIATED_ACCEPT },
95
+ signal: AbortSignal.timeout(PACKUMENT_TIMEOUT_MS),
96
+ });
97
+ if (!response.ok) {
98
+ // A missing package is a verdict, not contention: do not retry it.
99
+ const error = `registry ${response.status}${response.status === 404 ? ' (not found)' : ''}`;
100
+ cache.set(key, { at: Date.now(), data: undefined, error });
101
+ return { error };
102
+ }
103
+ const data = await response.json();
104
+ cache.set(key, { at: Date.now(), data });
105
+ return { data };
106
+ }
107
+ catch (error) {
108
+ lastError = `registry unreachable: ${describeFetchError(error)}`;
51
109
  }
52
- const data = await response.json();
53
- cache.set(key, { at: Date.now(), data });
54
- return { data };
55
110
  }
56
- catch (error) {
57
- const message = error instanceof Error ? error.message : String(error);
58
- return { error: `registry unreachable: ${message}` };
111
+ cache.set(key, { at: Date.now(), data: undefined, error: lastError });
112
+ return { error: lastError };
113
+ }
114
+ /**
115
+ * Say what actually failed, not just that `fetch` did.
116
+ *
117
+ * Node reports every transport fault as `TypeError: fetch failed` and hangs the
118
+ * reason off `cause`; without unwrapping it, a DNS rejection, a refused
119
+ * connection and a socket timeout all read the same in the tab, which is the
120
+ * difference between "try again" and "this registry is wrong".
121
+ *
122
+ * @param error - whatever `fetch` threw.
123
+ * @returns a sentence with the underlying cause and code.
124
+ */
125
+ function describeFetchError(error) {
126
+ if (!(error instanceof Error))
127
+ return String(error);
128
+ const cause = error.cause;
129
+ if (cause === undefined || cause === null) {
130
+ return error.name === 'Error' ? error.message : `${error.name}: ${error.message}`;
59
131
  }
132
+ const record = cause;
133
+ const code = typeof record.code === 'string' ? record.code : undefined;
134
+ const message = typeof record.message === 'string' ? record.message : String(cause);
135
+ if (code !== undefined)
136
+ return `${code}: ${message}`;
137
+ return message === '' ? error.message : message;
60
138
  }
61
139
  /**
62
140
  * Read a repository's tag names from the GitHub API.
@@ -127,6 +205,15 @@ export async function cachedTags(repoUrl, cache, force) {
127
205
  /**
128
206
  * Trim one packument down to what the tab renders.
129
207
  *
208
+ * `dist-tags.latest` is normally the newest stable release, and it is trusted as
209
+ * is — a prerelease above it must NOT become "latest", or every `-next` build
210
+ * would advertise itself over the stable line. The one case it cannot be
211
+ * trusted is a tag that sorts *below the version already installed*: that is not
212
+ * a release decision, it is a broken tag. npmmirror serves
213
+ * `@deepseek-ai/dsh-subagent-codex` with `latest: 0.0.1-rc.1` while the profile
214
+ * runs `0.2.1-rc.3`, which made the row claim the installed build was newer than
215
+ * latest and offered an "upgrade" that was really a downgrade.
216
+ *
130
217
  * @param packument - the full registry document.
131
218
  * @param installedVersion - the version in the profile.
132
219
  * @param tagNames - the repository's tag names, when known.
@@ -141,13 +228,13 @@ export function shapePackument(packument, installedVersion, tagNames) {
141
228
  const tags = rawTags !== null && rawTags !== undefined && typeof rawTags === 'object'
142
229
  ? rawTags
143
230
  : {};
144
- const newest = tags.latest;
231
+ const tagged = typeof tags.latest === 'string' ? tags.latest : undefined;
145
232
  const timeRecord = packument?.time !== null && packument?.time !== undefined && typeof packument.time === 'object'
146
233
  ? packument.time
147
234
  : {};
148
235
  const names = Object.keys(versionsRecord);
149
- const newestManifest = typeof newest === 'string' ? versionsRecord[newest] : undefined;
150
- const repoUrl = githubUrlOf(packument, newestManifest);
236
+ const taggedManifest = tagged === undefined ? undefined : versionsRecord[tagged];
237
+ const repoUrl = githubUrlOf(packument, taggedManifest);
151
238
  const rows = names.map((version) => {
152
239
  const manifest = versionsRecord[version] ?? {};
153
240
  const engine = manifest?.engines?.dsh ?? manifest?.dsh?.engines?.dsh;
@@ -157,21 +244,36 @@ export function shapePackument(packument, installedVersion, tagNames) {
157
244
  deprecated: typeof manifest.deprecated === 'string' ? manifest.deprecated : undefined,
158
245
  engine: typeof engine === 'string' ? engine : undefined,
159
246
  current: version === installedVersion,
160
- latest: version === newest,
247
+ latest: false,
161
248
  url: repoUrl === undefined ? undefined : releasePathOf(tagNames, version),
162
249
  };
163
250
  });
164
251
  rows.sort((left, right) => compareVersions(right.version, left.version));
252
+ /** A tag below what is already installed is broken, not a release channel. */
253
+ const broken = tagged !== undefined &&
254
+ installedVersion !== undefined &&
255
+ compareVersions(tagged, installedVersion) < 0;
256
+ let newest = tagged;
257
+ if (broken) {
258
+ // Recover the real head of the line: the highest version above the
259
+ // installed one, preferring a stable release over a prerelease.
260
+ const above = rows.filter((row) => compareVersions(row.version, installedVersion) > 0);
261
+ const stable = above.find((row) => !row.version.includes('-'));
262
+ newest = (stable ?? above[0])?.version ?? tagged;
263
+ }
264
+ for (const row of rows)
265
+ row.latest = row.version === newest;
165
266
  const kept = rows.slice(0, MAX_VERSIONS);
166
267
  const currentRow = rows.find((row) => row.current);
167
268
  if (currentRow !== undefined && !kept.includes(currentRow))
168
269
  kept.push(currentRow);
169
270
  return {
170
- latest: typeof newest === 'string' ? newest : undefined,
271
+ latest: newest,
171
272
  repoUrl,
172
273
  versions: kept,
173
274
  tags: Object.fromEntries(Object.entries(tags).filter((entry) => typeof entry[1] === 'string')),
174
275
  totalVersions: rows.length,
276
+ ...(broken ? { staleTag: tagged } : {}),
175
277
  };
176
278
  }
177
279
  //# sourceMappingURL=registry.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/host/registry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/host/registry.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA2BA,OAAO,EAEL,KAAK,WAAW,EAIjB,MAAM,mBAAmB,CAAA;AAE1B,kEAAkE;AAClE,eAAO,MAAM,IAAI,oBAAoB,CAAA;AAErC,gFAAgF;AAChF,eAAO,MAAM,MAAM,UAAgB,CAAA;AA2DnC;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CAkO5C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA4BA,OAAO,EAEL,KAAK,WAAW,EAMjB,MAAM,mBAAmB,CAAA;AAE1B,kEAAkE;AAClE,eAAO,MAAM,IAAI,oBAAoB,CAAA;AAErC,gFAAgF;AAChF,eAAO,MAAM,MAAM,UAAgB,CAAA;AA8EnC;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CA6X5C"}
package/lib/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { writeJson, isLoopbackRequest, readJsonBody } from './host/http.js';
2
2
  import { buildOverview } from './host/overview.js';
3
3
  import { runStep, validateStep } from './host/install.js';
4
+ import { nextPhase } from './host/progress.js';
4
5
  import { asPluginManager, } from './host/manager.js';
5
6
  /** Stable plugin name; matches the cordis.patch.yml insert id. */
6
7
  export const name = 'plugin-upgrader';
@@ -18,14 +19,21 @@ const API_BASE = `${API_PREFIX}/`;
18
19
  const JOB_RETENTION_MS = 10 * 60 * 1000;
19
20
  /**
20
21
  * A job that stops reporting progress for this long is treated as abandoned
21
- * and no longer blocks new work. Installs report progress per step, so a step
22
- * that legitimately runs longer than this is a hung child process, not work.
22
+ * and no longer blocks new work.
23
+ *
24
+ * The heartbeat is a timer, not a per-step write: a single installation can
25
+ * legitimately run for minutes while pnpm verifies a lockfile, and that work is
26
+ * not visible as a finished step.
23
27
  */
24
28
  const STALE_JOB_MS = 15 * 60 * 1000;
29
+ /** How often a running job re-stamps its heartbeat, independently of steps. */
30
+ const HEARTBEAT_MS = 2 * 1000;
25
31
  /** How many log lines one job keeps for the panel. */
26
- const JOB_LOG_LIMIT = 200;
32
+ const JOB_LOG_LIMIT = 400;
33
+ /** How many characters of pnpm output one job keeps across all its steps. */
34
+ const JOB_LOG_CHARS = 24 * 1024;
27
35
  /** How many log lines the overview ships for a job the tab adopts. */
28
- const ADOPTED_LOG_LIMIT = 40;
36
+ const ADOPTED_LOG_LIMIT = 60;
29
37
  /**
30
38
  * Mount the host half.
31
39
  *
@@ -36,6 +44,8 @@ export function apply(ctx) {
36
44
  const tagCache = new Map();
37
45
  const jobs = new Map();
38
46
  let inFlight = null;
47
+ /** The job a manager event belongs to, while one is running. */
48
+ let liveJob = null;
39
49
  const pruneJobs = () => {
40
50
  const cutoff = Date.now() - JOB_RETENTION_MS;
41
51
  for (const [id, job] of jobs) {
@@ -43,11 +53,39 @@ export function apply(ctx) {
43
53
  jobs.delete(id);
44
54
  }
45
55
  };
56
+ /**
57
+ * Append one line, dropping the oldest once the job holds too much text.
58
+ *
59
+ * @param job - the job to append to.
60
+ * @param message - the text of the line.
61
+ */
62
+ const appendLog = (job, message) => {
63
+ const text = message.trimEnd();
64
+ if (text === '')
65
+ return;
66
+ for (const line of text.split(/\r?\n/)) {
67
+ if (line.trim() === '')
68
+ continue;
69
+ job.log.push(line);
70
+ job.logChars += line.length;
71
+ }
72
+ while (job.logChars > JOB_LOG_CHARS && job.log.length > 1) {
73
+ job.logChars -= job.log.shift()?.length ?? 0;
74
+ }
75
+ while (job.log.length > JOB_LOG_LIMIT) {
76
+ job.logChars -= job.log.shift()?.length ?? 0;
77
+ }
78
+ };
46
79
  /** The manager, or a reason the request cannot be served. */
47
80
  const requireManager = () => asPluginManager(ctx.get('pluginManager', false));
48
81
  /**
49
82
  * Run a batch job to completion in the background.
50
83
  *
84
+ * The heartbeat is a timer, not a per-step write. One upgrade can spend
85
+ * minutes inside a single pnpm run — verifying a lockfile, retrying a
86
+ * registry — and none of that finishes a step, so a step-driven heartbeat
87
+ * would declare a healthy job abandoned and let a second batch start.
88
+ *
51
89
  * @param job - the mutable job record.
52
90
  * @param steps - the requested steps.
53
91
  */
@@ -56,34 +94,49 @@ export function apply(ctx) {
56
94
  if (manager === undefined) {
57
95
  job.status = 'finished';
58
96
  job.finishedAt = Date.now();
59
- job.log.push('job failed: plugin manager unavailable');
97
+ appendLog(job, 'job failed: plugin manager unavailable');
60
98
  return;
61
99
  }
62
100
  job.status = 'running';
63
101
  job.heartbeatAt = Date.now();
64
- for (const step of steps) {
65
- if (job.cancelled)
66
- break;
67
- inFlight = { requestId: step.requestId ?? '' };
68
- job.current = `${step.name}@${step.version}`;
69
- job.log.push(`→ ${job.current}`);
70
- const outcome = await runStep(manager, step, (message) => {
71
- job.log.push(message);
72
- if (job.log.length > JOB_LOG_LIMIT)
73
- job.log.splice(0, job.log.length - JOB_LOG_LIMIT);
74
- });
75
- inFlight = null;
102
+ liveJob = job;
103
+ const beat = setInterval(() => {
76
104
  job.heartbeatAt = Date.now();
77
- job.results.push(outcome);
78
- job.done = job.results.length;
79
- if (!outcome.ok)
80
- job.failed += 1;
81
- job.log.push(outcome.ok
82
- ? `✓ ${job.current} ${outcome.application ?? 'applied'}`
83
- : `✗ ${job.current} ${outcome.message ?? 'failed'}`);
105
+ }, HEARTBEAT_MS);
106
+ try {
107
+ for (const step of steps) {
108
+ if (job.cancelled)
109
+ break;
110
+ job.current = `${step.name}@${step.version}`;
111
+ job.stepStartedAt = Date.now();
112
+ job.phase = 'waiting';
113
+ job.attempt = undefined;
114
+ appendLog(job, `→ ${job.current}`);
115
+ const outcome = await runStep(manager, step, (message) => appendLog(job, message), (requestId) => {
116
+ job.requestIds.push(requestId);
117
+ inFlight = { requestId };
118
+ });
119
+ inFlight = null;
120
+ job.heartbeatAt = Date.now();
121
+ job.results.push(outcome);
122
+ job.done = job.results.length;
123
+ if (!outcome.ok)
124
+ job.failed += 1;
125
+ appendLog(job, outcome.ok
126
+ ? `✓ ${job.current} ${outcome.application ?? 'applied'}`
127
+ : `✗ ${job.current} ${outcome.message ?? 'failed'}`);
128
+ }
129
+ }
130
+ finally {
131
+ clearInterval(beat);
132
+ liveJob = null;
133
+ inFlight = null;
84
134
  }
85
135
  job.status = job.cancelled ? 'cancelled' : 'finished';
86
136
  job.current = undefined;
137
+ job.stepStartedAt = undefined;
138
+ job.phase = undefined;
139
+ job.attempt = undefined;
87
140
  job.finishedAt = Date.now();
88
141
  job.cacheDirty = true;
89
142
  };
@@ -103,7 +156,7 @@ export function apply(ctx) {
103
156
  job.abandoned = true;
104
157
  job.current = undefined;
105
158
  job.finishedAt = Date.now();
106
- job.log.push('! job abandoned: no progress for 15 minutes');
159
+ appendLog(job, '! job abandoned: no progress for 15 minutes');
107
160
  }
108
161
  return undefined;
109
162
  };
@@ -147,6 +200,9 @@ export function apply(ctx) {
147
200
  failed: live.failed,
148
201
  current: live.current,
149
202
  startedAt: live.startedAt,
203
+ stepStartedAt: live.stepStartedAt,
204
+ phase: live.phase,
205
+ attempt: live.attempt,
150
206
  log: live.log.slice(-ADOPTED_LOG_LIMIT),
151
207
  },
152
208
  jobs: [...jobs.entries()]
@@ -198,14 +254,16 @@ export function apply(ctx) {
198
254
  failed: 0,
199
255
  results: [],
200
256
  log: [],
257
+ logChars: 0,
201
258
  cancelled: false,
202
259
  startedAt: Date.now(),
260
+ requestIds: [],
203
261
  };
204
262
  jobs.set(id, job);
205
263
  void runJob(job, steps).catch((error) => {
206
264
  job.status = 'finished';
207
265
  job.finishedAt = Date.now();
208
- job.log.push(`job failed: ${error instanceof Error ? error.message : String(error)}`);
266
+ appendLog(job, `job failed: ${error instanceof Error ? error.message : String(error)}`);
209
267
  });
210
268
  writeJson(res, 202, { id });
211
269
  return;
@@ -217,7 +275,22 @@ export function apply(ctx) {
217
275
  writeJson(res, 404, { error: 'unknown-job' });
218
276
  return;
219
277
  }
220
- writeJson(res, 200, job);
278
+ writeJson(res, 200, {
279
+ id: job.id,
280
+ status: job.status,
281
+ total: job.total,
282
+ done: job.done,
283
+ failed: job.failed,
284
+ current: job.current,
285
+ phase: job.phase,
286
+ attempt: job.attempt,
287
+ startedAt: job.startedAt,
288
+ stepStartedAt: job.stepStartedAt,
289
+ finishedAt: job.finishedAt,
290
+ abandoned: job.abandoned,
291
+ log: job.log,
292
+ results: job.results,
293
+ });
221
294
  return;
222
295
  }
223
296
  if (req.method === 'POST' && route === 'cancel') {
@@ -227,11 +300,17 @@ export function apply(ctx) {
227
300
  return;
228
301
  }
229
302
  running.cancelled = true;
303
+ appendLog(running, '! cancel requested');
230
304
  const manager = requireManager();
231
- const target = inFlight;
232
- if (manager !== undefined && target !== null && target.requestId !== '') {
305
+ const target = inFlight?.requestId ?? running.requestIds.at(-1);
306
+ if (manager !== undefined && target !== undefined && target !== '') {
233
307
  try {
234
- await manager.cancelInstall(target.requestId);
308
+ const outcome = await manager.cancelInstall(target);
309
+ appendLog(running, outcome.status === 'not-running'
310
+ ? '! the installation already settled; the batch stops after it'
311
+ : outcome.status === 'too-late'
312
+ ? '! past the point of cancellation; finishing the current step'
313
+ : 'stopping the running installation…');
235
314
  }
236
315
  catch {
237
316
  /* the step settles on its own; the flag above already stops the batch */
@@ -249,6 +328,71 @@ export function apply(ctx) {
249
328
  writeJson(res, status, { error: error instanceof Error ? error.message : String(error) });
250
329
  }
251
330
  };
331
+ /**
332
+ * Mirror the manager's running pnpm output and phases into the job log.
333
+ *
334
+ * Without this the panel has nothing to show between a step starting and
335
+ * finishing: a lockfile verification or a registry retry can occupy minutes.
336
+ * Chunks from a removal carry no request id (the manager only tags the runs
337
+ * its caller identified), so a chunk with no id is attributed to whatever job
338
+ * is live — there is only ever one.
339
+ *
340
+ * The listeners are optional: a host whose manager does not emit these
341
+ * events must still load the tab, so a failed subscription is ignored and
342
+ * the panel simply falls back to per-step progress.
343
+ *
344
+ * @param name - the event name.
345
+ * @param listener - the callback.
346
+ * @param label - the effect label.
347
+ */
348
+ const subscribe = (name, listener, label) => {
349
+ try {
350
+ ctx.effect(() => ctx.on(name, listener), label);
351
+ }
352
+ catch {
353
+ /* the panel degrades to step-level progress */
354
+ }
355
+ };
356
+ subscribe('plugin-manager/install-log', (chunk) => {
357
+ const job = liveJob;
358
+ if (job === null)
359
+ return;
360
+ const tagged = chunk.requestId !== undefined && chunk.requestId !== '';
361
+ if (tagged && !job.requestIds.includes(chunk.requestId))
362
+ return;
363
+ if (chunk.exitCode !== undefined && chunk.exitCode !== 0 && chunk.exitCode !== null) {
364
+ appendLog(job, `! pnpm exited with ${chunk.exitCode}`);
365
+ }
366
+ appendLog(job, chunk.text);
367
+ /**
368
+ * The manager's `install-state` event is optional. Without this, a host
369
+ * that does not emit it leaves the panel on `waiting` for the whole
370
+ * install, which is what makes a two-minute step read as a hang.
371
+ */
372
+ job.phase = nextPhase(job.phase, chunk);
373
+ }, 'plugin-upgrader: install output');
374
+ subscribe('plugin-manager/install-state', (state) => {
375
+ const job = liveJob;
376
+ if (job === null)
377
+ return;
378
+ if (!job.requestIds.includes(state.requestId))
379
+ return;
380
+ job.phase = state.phase;
381
+ job.attempt =
382
+ state.attempt === undefined
383
+ ? undefined
384
+ : {
385
+ index: state.attempt.index,
386
+ total: state.attempt.total,
387
+ registry: state.attempt.registry,
388
+ };
389
+ if (state.attempt !== undefined && state.attempt.total > 1) {
390
+ appendLog(job, `registry ${state.attempt.index}/${state.attempt.total}: ${state.attempt.registry ?? 'default'}`);
391
+ }
392
+ else if (state.phase === 'applying') {
393
+ appendLog(job, 'applying the change…');
394
+ }
395
+ }, 'plugin-upgrader: install phase');
252
396
  ctx.effect(() => ctx.webServer.register({ kind: 'prefix', path: API_PREFIX, handler }), 'plugin-upgrader: routes');
253
397
  }
254
398
  //# sourceMappingURL=index.js.map