@uniqbit/mate-core 0.17.0-canary.3 → 0.17.0-canary.5

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniqbit/mate-core",
3
- "version": "0.17.0-canary.3",
3
+ "version": "0.17.0-canary.5",
4
4
  "description": "Core framework and plugin APIs for Mate.",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -11,6 +11,7 @@ import {
11
11
  validateContextModePackage,
12
12
  } from "../../../lib/context-mode-package";
13
13
  import { warmOpenCodePackageCache } from "../../../lib/opencode-plugin-package";
14
+ import { isPreinstalledPluginPath } from "../../../lib/preinstalled-plugins";
14
15
  import type {
15
16
  CapabilityPlugin,
16
17
  LaunchPreflightContext,
@@ -70,17 +71,60 @@ async function assertNoMcpConflict(ctx: SetupContext, provider: "claude" | "open
70
71
  }
71
72
  }
72
73
 
74
+ /**
75
+ * The version behind a reference bound to an installed copy.
76
+ *
77
+ * A published reference carries its version in the string; a bound one carries
78
+ * it in the package it points at, so staleness has to be read off disk.
79
+ */
80
+ async function installedVersionAt(packageRoot: string): Promise<string | undefined> {
81
+ try {
82
+ const manifest = JSON.parse(
83
+ await fs.readFile(path.join(packageRoot, "package.json"), "utf8"),
84
+ ) as { version?: string };
85
+ return manifest.version;
86
+ } catch {
87
+ return undefined;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Both spellings of a current reference are accepted, because the Runtime
93
+ * Surface writes either one: the published spec on an ordinary workstation, and
94
+ * an absolute path into the machine-local workspace where the distribution
95
+ * supplied an installed copy. Accepting only the first would refuse the launch
96
+ * on exactly the deployments preinstalled binding exists for.
97
+ *
98
+ * Staleness is still caught in both cases — a published reference by its
99
+ * version, a bound one by the version of the copy it points at — so this is not
100
+ * the looser check it might look like.
101
+ */
73
102
  async function validateOpenCodeReferences(ctx: LaunchPreflightContext): Promise<string[]> {
74
103
  const expected = getContextModePackageReference();
75
104
  const diagnostics: string[] = [];
76
105
  for (const name of ["opencode.json", "tui.json"]) {
77
106
  const configPath = path.join(ctx.companionPath, ".opencode", name);
78
107
  const { config } = await readOpenCodeConfig(configPath);
79
- if (!getOpenCodePluginReferences(config).includes(expected)) {
108
+ const references = getOpenCodePluginReferences(config);
109
+ if (references.includes(expected)) continue;
110
+
111
+ const bound = references.find(
112
+ (reference): reference is string =>
113
+ typeof reference === "string" &&
114
+ isPreinstalledPluginPath(reference, CONTEXT_MODE_PACKAGE_NAME),
115
+ );
116
+ if (bound !== undefined) {
117
+ const version = await installedVersionAt(bound);
118
+ if (version === CONTEXT_MODE_VERSION) continue;
80
119
  diagnostics.push(
81
- `Missing or stale context-mode package reference in ${configPath}; expected ${expected}.`,
120
+ `Stale context-mode package reference in ${configPath}; it points at ${bound}, which is ${version ?? "not installed"} rather than ${CONTEXT_MODE_VERSION}.`,
82
121
  );
122
+ continue;
83
123
  }
124
+
125
+ diagnostics.push(
126
+ `Missing or stale context-mode package reference in ${configPath}; expected ${expected}.`,
127
+ );
84
128
  }
85
129
  return diagnostics;
86
130
  }
@@ -6,7 +6,7 @@ import { promisify } from "node:util";
6
6
  import { getOpenSpecSchemaSelection } from "../../../lib/orchestrator/setup-compatibilities";
7
7
  import { getWrapperBinPath } from "../../../lib/package-paths";
8
8
  import { fetchPublicPackageVersion } from "../../../lib/public-npm";
9
- import { isNewer } from "../../../lib/update-checker";
9
+ import { isNewer, isPinnedDeployment } from "../../../lib/update-checker";
10
10
  import { FRAMEWORK_NAME } from "../../../framework";
11
11
  import { confirm } from "../../../cli/confirm";
12
12
  import type { CapabilityPlugin, RuntimeContributionsByRuntime, SetupContext } from "../plugin";
@@ -63,6 +63,7 @@ export interface OpenSpecPluginDeps {
63
63
  wrapperBinPath?: () => string;
64
64
  getInstalledVersion?: (pathValue: string) => Promise<string | undefined>;
65
65
  fetchLatestVersion?: () => Promise<string | undefined>;
66
+ isPinnedDeployment?: () => boolean;
66
67
  }
67
68
 
68
69
  const OPEN_SPEC_SUPPORTED_TOOLS = Object.keys(OPENSPEC_TOOL_DIRS) as OpenSpecTool[];
@@ -170,10 +171,17 @@ async function updateOpenSpecIfOutdated(
170
171
  deps: Required<
171
172
  Pick<
172
173
  OpenSpecPluginDeps,
173
- "installCommand" | "pathValue" | "getInstalledVersion" | "fetchLatestVersion"
174
+ | "installCommand"
175
+ | "pathValue"
176
+ | "getInstalledVersion"
177
+ | "fetchLatestVersion"
178
+ | "isPinnedDeployment"
174
179
  >
175
180
  >,
176
181
  ): Promise<void> {
182
+ /** Pinned artifacts change tool versions by replacement, never reconciliation. */
183
+ if (deps.isPinnedDeployment()) return;
184
+
177
185
  const installed = await deps.getInstalledVersion(deps.pathValue());
178
186
  if (!installed) return;
179
187
 
@@ -204,6 +212,7 @@ async function ensureOpenSpecInstalled(
204
212
  | "pathValue"
205
213
  | "getInstalledVersion"
206
214
  | "fetchLatestVersion"
215
+ | "isPinnedDeployment"
207
216
  >
208
217
  >,
209
218
  ): Promise<boolean> {
@@ -326,6 +335,7 @@ export function createOpenspecPlugin(deps: OpenSpecPluginDeps = {}): CapabilityP
326
335
  wrapperBinPath: deps.wrapperBinPath ?? getWrapperBinPath,
327
336
  getInstalledVersion: deps.getInstalledVersion ?? defaultGetInstalledVersion,
328
337
  fetchLatestVersion: deps.fetchLatestVersion ?? defaultFetchLatestVersion,
338
+ isPinnedDeployment: deps.isPinnedDeployment ?? (() => isPinnedDeployment()),
329
339
  };
330
340
 
331
341
  return {