@omercnet/paseo-context-mode 3.1.3 → 3.1.4-next.434.1

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": "@omercnet/paseo-context-mode",
3
- "version": "3.1.3",
3
+ "version": "3.1.4-next.434.1",
4
4
  "type": "module",
5
5
  "description": "Provider-aware Context Mode health, savings, and MCP activation for Paseo.",
6
6
  "license": "MIT",
@@ -19,7 +19,7 @@
19
19
  },
20
20
  "packageManager": "bun@1.4.4",
21
21
  "engines": {
22
- "node": ">=24"
22
+ "node": ">=24.15.0"
23
23
  },
24
24
  "keywords": [
25
25
  "paseo",
@@ -53,10 +53,10 @@
53
53
  },
54
54
  "devDependencies": {
55
55
  "@biomejs/biome": "^2.5.10",
56
- "@getpaseo/cli": "0.15.0-beta.1",
57
- "@getpaseo/client": "0.15.0-beta.1",
58
- "@getpaseo/plugin": "0.15.0-beta.1",
59
- "@getpaseo/protocol": "0.15.0-beta.1",
56
+ "@getpaseo/cli": "0.15.0",
57
+ "@getpaseo/client": "0.15.0",
58
+ "@getpaseo/plugin": "0.15.0",
59
+ "@getpaseo/protocol": "0.15.0",
60
60
  "@tanstack/react-query": "^5.102.3",
61
61
  "@types/node": "^24.10.1",
62
62
  "@types/react": "^19.2.18",
@@ -68,6 +68,6 @@
68
68
  "zod": "4.6.7"
69
69
  },
70
70
  "dependencies": {
71
- "context-mode": "1.0.169"
71
+ "context-mode": "2.1.4"
72
72
  }
73
73
  }
package/server/actions.ts CHANGED
@@ -1,78 +1,117 @@
1
+ import { rm } from "node:fs/promises";
2
+ import { basename, join } from "node:path";
1
3
  import type { PluginSettings } from "@getpaseo/plugin/server";
2
- import {
3
- CONTEXT_MODE_TARGET_VERSION,
4
- type ContextModeAction,
5
- type ContextModeActionInput,
6
- type DoctorReport,
7
- type DoctorStatusRow,
8
- } from "../shared/actions";
4
+ import type { ContextModeActionInput, DoctorReport, DoctorStatusRow } from "../shared/actions";
5
+ import { contextModePlatformFor } from "../shared/providers";
9
6
  import type { contextModeSettings } from "../shared/settings";
10
- import { type BinaryResolution, type LaunchDescriptor, resolveContextModeBinary } from "./binary";
7
+ import { type BinaryResolution, resolveContextModeBinary } from "./binary";
8
+ import { contextModeProbeOptions } from "./handlers";
11
9
  import type { ProviderConfigSource } from "./integration";
12
- import { contextModeEnvironmentFor, contextModePlatformFor } from "./knowledge";
13
- import {
14
- callContextModeTool,
15
- inspectContextMode,
16
- type McpInspection,
17
- type McpProcessDependencies,
18
- type McpToolArguments,
19
- } from "./mcp-process";
10
+ import { callContextModeTool } from "./mcp-process";
20
11
 
21
- type SettingsHandle = PluginSettings<typeof contextModeSettings.schema>;
12
+ type BinarySource = Extract<BinaryResolution, { state: "found" }>["source"];
22
13
 
23
- type ActionToolCaller = (
24
- launch: LaunchDescriptor,
25
- name: "ctx_doctor",
26
- arguments_: McpToolArguments,
27
- dependencies: McpProcessDependencies,
28
- ) => Promise<string>;
14
+ type SettingsHandle = PluginSettings<typeof contextModeSettings.schema>;
29
15
 
30
16
  export interface ActionHandlerDependencies {
31
- osPlatform?: NodeJS.Platform;
32
17
  resolveBinary?: typeof resolveContextModeBinary;
33
- inspect?: (
34
- launch: LaunchDescriptor,
35
- dependencies?: McpProcessDependencies,
36
- ) => Promise<McpInspection>;
37
- callTool?: ActionToolCaller;
18
+ callTool?: typeof callContextModeTool;
19
+ probeOptions?: typeof contextModeProbeOptions;
38
20
  }
39
21
 
40
22
  type ActionContext = { paseo?: ProviderConfigSource };
41
23
 
42
24
  export interface ContextModeActionHandlers {
43
25
  doctor(input: ContextModeActionInput, context?: ActionContext): Promise<DoctorReport>;
44
- install(input: ContextModeActionInput, context?: ActionContext): Promise<ContextModeAction>;
45
- upgrade(input: ContextModeActionInput, context?: ActionContext): Promise<ContextModeAction>;
46
26
  }
47
27
 
48
- export function parseContextModeDoctorOutput(rawOutput: string): DoctorStatusRow[] {
28
+ const DOCTOR_STATUS: Readonly<Record<string, DoctorStatusRow["status"]>> = {
29
+ PASS: "ok",
30
+ WARN: "warning",
31
+ FAIL: "error",
32
+ UNKNOWN: "error",
33
+ };
34
+
35
+ /**
36
+ * D3 compares the agent's own host config with Context Mode's setup and D5 exercises its hooks.
37
+ * Paseo injects only the MCP server, so both fail for every injected agent: advice, not a fault,
38
+ * and their `context-mode setup` fix would replace Paseo's injection with a native install.
39
+ */
40
+ const INFORMATIONAL_CHECK = /^D(3|5b?)\b/;
41
+
42
+ /** Upstream's fix for an outdated runtime; the plugin offers no in-place upgrade. */
43
+ const UPGRADE_FIX = "run ctx_upgrade";
44
+
45
+ const UPGRADE_ADVICE: Readonly<Record<BinarySource, string>> = {
46
+ bundled: "Update the Context Mode plugin in Paseo",
47
+ settings: "Upgrade the Context Mode install configured in settings",
48
+ };
49
+
50
+ function doctorRow(
51
+ check: string,
52
+ status: DoctorStatusRow["status"],
53
+ detail: string,
54
+ upstreamFix: string,
55
+ runtime: BinarySource,
56
+ ): DoctorStatusRow {
57
+ const row = { check: check.slice(0, 256), detail: detail.slice(0, 4_096) };
58
+ if (INFORMATIONAL_CHECK.test(check)) {
59
+ return { ...row, status: status === "ok" ? "ok" : "warning" };
60
+ }
61
+ if (upstreamFix === UPGRADE_FIX) {
62
+ // A bundled runtime that trails npm is expected until the next plugin release.
63
+ const upgradeStatus = runtime === "bundled" ? "warning" : status;
64
+ return { ...row, status: upgradeStatus, fix: UPGRADE_ADVICE[runtime] };
65
+ }
66
+ return upstreamFix ? { ...row, status, fix: upstreamFix.slice(0, 4_096) } : { ...row, status };
67
+ }
68
+
69
+ /**
70
+ * Parses the `| Check | Status | Detail | Fix |` table that `ctx_doctor` prints. Upstream does not
71
+ * escape pipes, so the first two cells are check and status, the last is the fix, and whatever is
72
+ * between them is the detail. The fix cell leads with a restatement; only the command after the
73
+ * last `Fix:` is kept, and advice the plugin cannot act on is rewritten for `runtime`.
74
+ */
75
+ export function parseContextModeDoctorOutput(
76
+ rawOutput: string,
77
+ runtime: BinarySource,
78
+ ): DoctorStatusRow[] {
49
79
  const rows: DoctorStatusRow[] = [];
50
80
  for (const line of rawOutput.split(/\r?\n/)) {
51
- const match = /^\[(OK|WARN|FAIL)\]\s+(.+)$/.exec(line.trim());
52
- if (!match) continue;
53
- const body = match[2] ?? "";
54
- const separator = body.indexOf(":");
55
- const check = (separator < 0 ? body : body.slice(0, separator)).trim().slice(0, 256);
56
- if (!check) continue;
57
- const detail = (separator < 0 ? "" : body.slice(separator + 1).trim()).slice(0, 4_096);
58
- rows.push({
59
- status: match[1] === "OK" ? "ok" : match[1] === "WARN" ? "warning" : "error",
60
- check,
61
- detail,
62
- });
81
+ const trimmed = line.trim();
82
+ if (!trimmed.startsWith("|") || !trimmed.endsWith("|")) continue;
83
+ const cells = trimmed
84
+ .slice(1, -1)
85
+ .split("|")
86
+ .map((cell) => cell.trim());
87
+ const [check = "", statusCell = ""] = cells;
88
+ const status = Object.hasOwn(DOCTOR_STATUS, statusCell) ? DOCTOR_STATUS[statusCell] : null;
89
+ if (cells.length < 4 || !check || !status) continue;
90
+ const detail = cells.slice(2, -1).join(" | ");
91
+ const fix = (cells.at(-1) ?? "").split("Fix:").at(-1)?.trim() ?? "";
92
+ rows.push(doctorRow(check, status, detail, fix, runtime));
63
93
  if (rows.length === 64) break;
64
94
  }
65
95
  return rows;
66
96
  }
67
97
 
98
+ /** Upstream's report file name; the shown path may be `~`-abbreviated, so only the name is trusted. */
99
+ const DOCTOR_REPORT_NAME = /^doctor-[\w-]+\.md$/;
100
+
101
+ /** ctx_doctor writes a report file per failing check and nothing prunes them; the panel keeps none. */
102
+ async function discardDoctorReport(output: string, dataDir: string): Promise<void> {
103
+ const reported = /^Report file: (.+)$/m.exec(output)?.[1]?.trim();
104
+ const name = reported ? basename(reported) : "";
105
+ if (DOCTOR_REPORT_NAME.test(name)) await rm(join(dataDir, "reports", name), { force: true });
106
+ }
107
+
68
108
  export function createContextModeActionHandlers(
69
109
  settingsHandle: SettingsHandle,
70
110
  dependencies: ActionHandlerDependencies = {},
71
111
  ): ContextModeActionHandlers {
72
112
  const resolveBinary = dependencies.resolveBinary ?? resolveContextModeBinary;
73
- const inspect = dependencies.inspect ?? inspectContextMode;
74
113
  const callTool = dependencies.callTool ?? callContextModeTool;
75
- const osPlatform = dependencies.osPlatform ?? process.platform;
114
+ const probeOptions = dependencies.probeOptions ?? contextModeProbeOptions;
76
115
 
77
116
  async function resolvedBinary(
78
117
  paseo: ProviderConfigSource | undefined,
@@ -86,68 +125,18 @@ export function createContextModeActionHandlers(
86
125
  return binary;
87
126
  }
88
127
 
89
- async function currentVersion(
90
- input: ContextModeActionInput,
91
- paseo: ProviderConfigSource | undefined,
92
- ): Promise<string | null> {
93
- try {
94
- const binary = await resolvedBinary(paseo);
95
- const inspection = await inspect(binary.launch, {
96
- env: contextModeEnvironmentFor(input.provider),
97
- });
98
- return inspection.version?.slice(0, 128) ?? null;
99
- } catch {
100
- return null;
101
- }
102
- }
103
-
104
128
  return {
105
129
  async doctor(input, context) {
106
- const binary = await resolvedBinary(context?.paseo);
107
- const rawOutput = await callTool(
108
- binary.launch,
109
- "ctx_doctor",
110
- {},
111
- {
112
- env: contextModeEnvironmentFor(input.provider),
113
- },
114
- );
115
- return {
116
- platform: contextModePlatformFor(input.provider),
117
- rows: parseContextModeDoctorOutput(rawOutput),
118
- rawOutput,
119
- };
120
- },
121
- async install(input, context) {
122
- const platform = contextModePlatformFor(input.provider);
123
- return {
124
- program: osPlatform === "win32" ? "npm.cmd" : "npm",
125
- args: [
126
- "install",
127
- "-g",
128
- `context-mode@${CONTEXT_MODE_TARGET_VERSION}`,
129
- "--no-audit",
130
- "--no-fund",
131
- ],
132
- currentVersion: await currentVersion(input, context?.paseo),
133
- targetVersion: CONTEXT_MODE_TARGET_VERSION,
134
- platform,
135
- requiresRestart: true,
136
- };
137
- },
138
- async upgrade(input, context) {
139
130
  const platform = contextModePlatformFor(input.provider);
140
131
  const binary = await resolvedBinary(context?.paseo);
141
- const inspection = await inspect(binary.launch, {
142
- env: contextModeEnvironmentFor(input.provider),
143
- });
132
+ // ctx_doctor writes a report and index rows, so it runs against the plugin-owned probe store.
133
+ const options = await probeOptions(platform);
134
+ const rawOutput = await callTool(binary.launch, "ctx_doctor", {}, {}, options);
135
+ await discardDoctorReport(rawOutput, options.dataDir);
144
136
  return {
145
- program: binary.launch.program,
146
- args: [...binary.launch.args, "upgrade", "--platform", platform],
147
- currentVersion: inspection.version?.slice(0, 128) ?? null,
148
- targetVersion: "latest",
149
137
  platform,
150
- requiresRestart: true,
138
+ rows: parseContextModeDoctorOutput(rawOutput, binary.source),
139
+ rawOutput,
151
140
  };
152
141
  },
153
142
  };