@oharness/engine 0.8.0 → 0.9.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,13 +1,70 @@
1
1
  import type { McpServerConfig } from "./client.ts";
2
+ import type { OttoportCopy } from "./ottoport-updates.ts";
3
+ /**
4
+ * The media server that ships with the harness.
5
+ *
6
+ * Image, video and speech generation is a capability this project does not
7
+ * implement and should not: the models change weekly, every one of them takes
8
+ * different parameters, and the prices are quoted per image and per second
9
+ * rather than per token. OttoPort already serves all of that behind one API,
10
+ * meters it against an account, and publishes the catalog — so the harness
11
+ * reaches for it the way it reaches for any other MCP server, and holds no
12
+ * opinion about what a video model's parameters are.
13
+ *
14
+ * That last part is the point. A tool surface defined here would be a copy of
15
+ * somebody else's parameter list, and a copy that is wrong costs the user a
16
+ * failed generation they may have paid for. Asking the server means the
17
+ * answer is never stale: `ottoport_list_models` is deliberately allowed
18
+ * through so the model can look up what exists and what it takes.
19
+ */
20
+ /** Where newer releases of the server are kept once downloaded. */
21
+ export declare function ottoportCacheRoot(): string;
22
+ /** The copy that shipped with this release, and the version it says it is. */
23
+ export declare function bundledOttoport(): OttoportCopy | undefined;
24
+ /**
25
+ * The server to run: the bundled one, or a newer compatible release the
26
+ * background update has already put on disk.
27
+ *
28
+ * A downloaded copy is a plain file in the user directory, so a packaged
29
+ * desktop app spawns it exactly as it spawns the bundled one beside app.asar.
30
+ * A bundled copy whose version cannot be read is still run — it is what this
31
+ * release was tested with — but nothing newer is chosen over it.
32
+ */
33
+ export declare function serverEntry(): string | undefined;
2
34
  export declare const OTTOPORT_SERVER = "ottoport";
3
35
  /**
4
- * `${auth:ottoport}` rather than `${env:OTTOPORT_API_KEY}`.
36
+ * Tools the server offers that this harness does not pass on.
37
+ *
38
+ * `ottoport_chat`: a second way to reach a chat model would sit outside
39
+ * `/model`, outside this session's context management and outside the
40
+ * provider the user chose, and the model would have no way to know which of
41
+ * the two it should be using.
42
+ *
43
+ * `ottoport_video_webhook`: the account gateway delivers results itself and
44
+ * refuses a caller's webhook, so a tool for inspecting one could only ever
45
+ * report that there is none.
46
+ *
47
+ * A denylist rather than a list of what is allowed, because the server gains
48
+ * tools on its vendor's schedule and each one should reach the model without
49
+ * a release of this harness in between.
50
+ */
51
+ export declare const WITHHELD_TOOLS: readonly string[];
52
+ /**
53
+ * Who a media call is made as, and where it goes: an OHarness account and our
54
+ * metering gateway, which forwards the vendor's paths and charges the account.
55
+ */
56
+ export interface MediaAccess {
57
+ key: string;
58
+ /** The host, without `/api/v1`: the server appends the path itself. */
59
+ baseUrl: string;
60
+ }
61
+ /**
62
+ * The key goes in the child's environment and nowhere else.
5
63
  *
6
- * The environment is the wrong place for this key and not by a little: `Bash`
7
- * hands the whole of `process.env` to every command the model runs, so a key
8
- * exported for this server is a key the model can print. Resolved from the
9
- * auth store it reaches this subprocess and nothing else, and it is the same
10
- * credential the `ottoport` provider already uses for chat — a user who has
11
- * signed in once does not sign in again to generate an image.
64
+ * Not `process.env`, and not by a little: `Bash` hands the whole of it to
65
+ * every command the model runs, so a key exported for this server is a key
66
+ * the model can print. Set on this one subprocess, it reaches the server and
67
+ * nothing else — and it is the credential the user already signed in with,
68
+ * so nobody signs in a second time to generate an image.
12
69
  */
13
- export declare function ottoportMediaServer(): McpServerConfig | undefined;
70
+ export declare function ottoportMediaServer(access: MediaAccess, entry?: string | undefined): McpServerConfig | undefined;
@@ -1,7 +1,8 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { createRequire } from "node:module";
3
- import { join } from "node:path";
4
- import { ottoportBaseUrl } from "../auth/providers/ottoport.js";
3
+ import { dirname, join } from "node:path";
4
+ import { userPath } from "../paths.js";
5
+ import { cachedCopies, packageVersion, pickCopy } from "./ottoport-updates.js";
5
6
  /**
6
7
  * The media server that ships with the harness.
7
8
  *
@@ -19,6 +20,10 @@ import { ottoportBaseUrl } from "../auth/providers/ottoport.js";
19
20
  * answer is never stale: `ottoport_list_models` is deliberately allowed
20
21
  * through so the model can look up what exists and what it takes.
21
22
  */
23
+ /** Where newer releases of the server are kept once downloaded. */
24
+ export function ottoportCacheRoot() {
25
+ return userPath("cache", "ottoport");
26
+ }
22
27
  /**
23
28
  * Bundled rather than fetched.
24
29
  *
@@ -29,8 +34,12 @@ import { ottoportBaseUrl } from "../auth/providers/ottoport.js";
29
34
  * and in both it means the version that runs is whatever npm served that
30
35
  * morning. The package is a dependency of this one, so it is on disk before
31
36
  * the process starts and it is the version this release was tested against.
37
+ *
38
+ * Newer releases still arrive (see `ottoport-updates.ts`), but they are
39
+ * fetched after a session has started and run from the next one, so none of
40
+ * that cost is ever in front of a call.
32
41
  */
33
- function serverEntry() {
42
+ function bundledEntry() {
34
43
  // Inside a packaged desktop app the module graph lives in app.asar, and a
35
44
  // path inside an archive is not something a child process can open — the
36
45
  // spawn below would report ENOENT for a file `require.resolve` had just
@@ -54,19 +63,59 @@ function serverEntry() {
54
63
  return undefined;
55
64
  }
56
65
  }
66
+ /** The copy that shipped with this release, and the version it says it is. */
67
+ export function bundledOttoport() {
68
+ const entry = bundledEntry();
69
+ if (!entry)
70
+ return undefined;
71
+ // `<package>/mcp/server.mjs`, whether resolved or copied beside app.asar:
72
+ // the desktop build copies the whole package, manifest included.
73
+ const version = packageVersion(dirname(dirname(entry)));
74
+ return version ? { version, entry } : undefined;
75
+ }
76
+ /**
77
+ * The server to run: the bundled one, or a newer compatible release the
78
+ * background update has already put on disk.
79
+ *
80
+ * A downloaded copy is a plain file in the user directory, so a packaged
81
+ * desktop app spawns it exactly as it spawns the bundled one beside app.asar.
82
+ * A bundled copy whose version cannot be read is still run — it is what this
83
+ * release was tested with — but nothing newer is chosen over it.
84
+ */
85
+ export function serverEntry() {
86
+ const bundled = bundledOttoport();
87
+ if (!bundled)
88
+ return bundledEntry();
89
+ return pickCopy(bundled, cachedCopies(ottoportCacheRoot()))?.entry;
90
+ }
57
91
  export const OTTOPORT_SERVER = "ottoport";
58
92
  /**
59
- * `${auth:ottoport}` rather than `${env:OTTOPORT_API_KEY}`.
93
+ * Tools the server offers that this harness does not pass on.
94
+ *
95
+ * `ottoport_chat`: a second way to reach a chat model would sit outside
96
+ * `/model`, outside this session's context management and outside the
97
+ * provider the user chose, and the model would have no way to know which of
98
+ * the two it should be using.
99
+ *
100
+ * `ottoport_video_webhook`: the account gateway delivers results itself and
101
+ * refuses a caller's webhook, so a tool for inspecting one could only ever
102
+ * report that there is none.
103
+ *
104
+ * A denylist rather than a list of what is allowed, because the server gains
105
+ * tools on its vendor's schedule and each one should reach the model without
106
+ * a release of this harness in between.
107
+ */
108
+ export const WITHHELD_TOOLS = ["ottoport_chat", "ottoport_video_webhook"];
109
+ /**
110
+ * The key goes in the child's environment and nowhere else.
60
111
  *
61
- * The environment is the wrong place for this key and not by a little: `Bash`
62
- * hands the whole of `process.env` to every command the model runs, so a key
63
- * exported for this server is a key the model can print. Resolved from the
64
- * auth store it reaches this subprocess and nothing else, and it is the same
65
- * credential the `ottoport` provider already uses for chat — a user who has
66
- * signed in once does not sign in again to generate an image.
112
+ * Not `process.env`, and not by a little: `Bash` hands the whole of it to
113
+ * every command the model runs, so a key exported for this server is a key
114
+ * the model can print. Set on this one subprocess, it reaches the server and
115
+ * nothing else — and it is the credential the user already signed in with,
116
+ * so nobody signs in a second time to generate an image.
67
117
  */
68
- export function ottoportMediaServer() {
69
- const entry = serverEntry();
118
+ export function ottoportMediaServer(access, entry = serverEntry()) {
70
119
  if (!entry)
71
120
  return undefined;
72
121
  return {
@@ -77,31 +126,13 @@ export function ottoportMediaServer() {
77
126
  command: process.execPath,
78
127
  args: [entry],
79
128
  env: {
80
- OTTOPORT_API_KEY: "${auth:ottoport}",
81
- // The same host the `ottoport` provider talks to, so `OTTOPORT_BASE_URL`
82
- // moves both. Hardcoding the public one would send a staging or
83
- // self-hosted deployment's chat one way and its images the other, and
84
- // nothing would report the split.
85
- OTTOPORT_BASE_URL: ottoportBaseUrl(),
129
+ OTTOPORT_API_KEY: access.key,
130
+ OTTOPORT_BASE_URL: access.baseUrl,
86
131
  ELECTRON_RUN_AS_NODE: "1",
87
132
  },
88
- /**
89
- * Everything the server offers except `ottoport_chat`.
90
- *
91
- * A second way to reach a chat model is not a feature here. It would sit
92
- * outside `/model`, outside this session's context management and outside
93
- * the provider the user chose, and the model would have no way to know
94
- * which of the two it should be using. The media tools have no such
95
- * counterpart in this harness, and `ottoport_list_models` is how their
96
- * parameters get looked up.
97
- */
98
- allowedTools: [
99
- "ottoport_list_models",
100
- "ottoport_generate_image",
101
- "ottoport_generate_video",
102
- "ottoport_generate_speech",
103
- "ottoport_generate_music",
104
- ],
133
+ // No `allowedTools`: which tools pass is `WITHHELD_TOOLS`' decision, made
134
+ // by the plugin that registers them, so a tool added upstream is not
135
+ // stopped here by a list that has never heard of it.
105
136
  // Video generation is a job the server polls to completion.
106
137
  timeoutMs: 600_000,
107
138
  };
@@ -1 +1 @@
1
- {"version":3,"file":"builtin.js","sourceRoot":"","sources":["../../src/mcp/builtin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAGhE;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;;;;;;GAUG;AACH,SAAS,WAAW;IAClB,0EAA0E;IAC1E,yEAAyE;IACzE,wEAAwE;IACxE,yEAAyE;IACzE,oEAAoE;IACpE,EAAE;IACF,wEAAwE;IACxE,uEAAuE;IACvE,MAAM,SAAS,GAAI,OAAsC,CAAC,aAAa,CAAC;IACxE,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;QAClE,IAAI,UAAU,CAAC,QAAQ,CAAC;YAAE,OAAO,QAAQ,CAAC;IAC5C,CAAC;IACD,IAAI,CAAC;QACH,OAAO,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,yBAAyB,CAAC,CAAC;IAC3E,CAAC;IAAC,MAAM,CAAC;QACP,2EAA2E;QAC3E,qEAAqE;QACrE,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,UAAU,CAAC;AAE1C;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,KAAK,GAAG,WAAW,EAAE,CAAC;IAC5B,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAE7B,OAAO;QACL,IAAI,EAAE,OAAO;QACb,oEAAoE;QACpE,sEAAsE;QACtE,6CAA6C;QAC7C,OAAO,EAAE,OAAO,CAAC,QAAQ;QACzB,IAAI,EAAE,CAAC,KAAK,CAAC;QACb,GAAG,EAAE;YACH,gBAAgB,EAAE,kBAAkB;YACpC,yEAAyE;YACzE,gEAAgE;YAChE,sEAAsE;YACtE,kCAAkC;YAClC,iBAAiB,EAAE,eAAe,EAAE;YACpC,oBAAoB,EAAE,GAAG;SAC1B;QACD;;;;;;;;;WASG;QACH,YAAY,EAAE;YACZ,sBAAsB;YACtB,yBAAyB;YACzB,yBAAyB;YACzB,0BAA0B;YAC1B,yBAAyB;SAC1B;QACD,4DAA4D;QAC5D,SAAS,EAAE,OAAO;KACnB,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"builtin.js","sourceRoot":"","sources":["../../src/mcp/builtin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAG/E;;;;;;;;;;;;;;;;GAgBG;AAEH,mEAAmE;AACnE,MAAM,UAAU,iBAAiB;IAC/B,OAAO,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;AACvC,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAS,YAAY;IACnB,0EAA0E;IAC1E,yEAAyE;IACzE,wEAAwE;IACxE,yEAAyE;IACzE,oEAAoE;IACpE,EAAE;IACF,wEAAwE;IACxE,uEAAuE;IACvE,MAAM,SAAS,GAAI,OAAsC,CAAC,aAAa,CAAC;IACxE,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;QAClE,IAAI,UAAU,CAAC,QAAQ,CAAC;YAAE,OAAO,QAAQ,CAAC;IAC5C,CAAC;IACD,IAAI,CAAC;QACH,OAAO,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,yBAAyB,CAAC,CAAC;IAC3E,CAAC;IAAC,MAAM,CAAC;QACP,2EAA2E;QAC3E,qEAAqE;QACrE,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,eAAe;IAC7B,MAAM,KAAK,GAAG,YAAY,EAAE,CAAC;IAC7B,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,0EAA0E;IAC1E,iEAAiE;IACjE,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACxD,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAClD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW;IACzB,MAAM,OAAO,GAAG,eAAe,EAAE,CAAC;IAClC,IAAI,CAAC,OAAO;QAAE,OAAO,YAAY,EAAE,CAAC;IACpC,OAAO,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC,iBAAiB,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC;AACrE,CAAC;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,UAAU,CAAC;AAE1C;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,cAAc,GAAsB,CAAC,eAAe,EAAE,wBAAwB,CAAC,CAAC;AAY7F;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CACjC,MAAmB,EACnB,QAA4B,WAAW,EAAE;IAEzC,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAE7B,OAAO;QACL,IAAI,EAAE,OAAO;QACb,oEAAoE;QACpE,sEAAsE;QACtE,6CAA6C;QAC7C,OAAO,EAAE,OAAO,CAAC,QAAQ;QACzB,IAAI,EAAE,CAAC,KAAK,CAAC;QACb,GAAG,EAAE;YACH,gBAAgB,EAAE,MAAM,CAAC,GAAG;YAC5B,iBAAiB,EAAE,MAAM,CAAC,OAAO;YACjC,oBAAoB,EAAE,GAAG;SAC1B;QACD,0EAA0E;QAC1E,qEAAqE;QACrE,qDAAqD;QACrD,4DAA4D;QAC5D,SAAS,EAAE,OAAO;KACnB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Newer copies of the media server, fetched in the background.
3
+ *
4
+ * The server belongs to the gateway's vendor and changes on the vendor's
5
+ * schedule: a new tool, a new parameter, a model family that needs a new code
6
+ * path. Waiting for an OHarness release to carry each of those would make the
7
+ * harness the slowest link in a chain it does not own. So the bundled copy is
8
+ * the floor, and a newer one the registry has published is fetched after a
9
+ * session starts and used from the next.
10
+ *
11
+ * Never mid-session. A server swapped under a running conversation would
12
+ * change the tools the model was told about while it was using them.
13
+ *
14
+ * Only within the bundled major version: a major bump is the vendor saying
15
+ * something a caller relies on has changed, and that is exactly the kind of
16
+ * update that should arrive through a release someone tested.
17
+ */
18
+ /** One copy of the server that could be run. */
19
+ export interface OttoportCopy {
20
+ version: string;
21
+ /** Absolute path of `mcp/server.mjs`. */
22
+ entry: string;
23
+ }
24
+ export declare function compareVersions(a: string, b: string): number;
25
+ /** The version a package directory says it is, or nothing if it cannot say. */
26
+ export declare function packageVersion(dir: string): string | undefined;
27
+ /**
28
+ * The downloaded copies that are whole.
29
+ *
30
+ * A directory counts only when its entry exists and its manifest names the
31
+ * version the directory is called: anything else is a download that was
32
+ * interrupted or tampered with, and running it would fail in a way that looks
33
+ * like the gateway's fault.
34
+ */
35
+ export declare function cachedCopies(root: string): OttoportCopy[];
36
+ /**
37
+ * Which copy to run: the newest cached one the bundled copy is compatible
38
+ * with, or the bundled copy itself.
39
+ *
40
+ * Without a bundled copy there is nothing to measure compatibility against,
41
+ * so nothing is run — the same outcome as an install that dropped the package.
42
+ */
43
+ export declare function pickCopy(bundled: OttoportCopy | undefined, cached: readonly OttoportCopy[]): OttoportCopy | undefined;
44
+ /**
45
+ * Whether to look for updates at all.
46
+ *
47
+ * A CI run or a test is not a user, and a network call from one is either
48
+ * noise or a flaky test — so both are off unless asked for by name.
49
+ */
50
+ export declare function autoUpdateEnabled(env?: NodeJS.ProcessEnv): boolean;
51
+ export interface UpdateOptions {
52
+ /** Where versions are cached: `<root>/<version>/package/…`. */
53
+ root: string;
54
+ /** The bundled version; updates stay within its major. */
55
+ current: string;
56
+ fetch?: typeof fetch;
57
+ /** Unpacks a gzipped tarball into a directory. Defaults to the system `tar`. */
58
+ extract?: (archive: string, into: string) => Promise<void>;
59
+ env?: NodeJS.ProcessEnv;
60
+ /** Check even where `autoUpdateEnabled` says not to; for tests. */
61
+ force?: boolean;
62
+ registry?: string;
63
+ signal?: AbortSignal;
64
+ }
65
+ export type UpdateResult = {
66
+ status: "disabled" | "current" | "incompatible" | "cached";
67
+ version?: string;
68
+ } | {
69
+ status: "installed";
70
+ version: string;
71
+ } | {
72
+ status: "failed";
73
+ error: string;
74
+ };
75
+ /**
76
+ * Whether these bytes are the ones the registry vouched for.
77
+ *
78
+ * Only sha512 is accepted. The registry has published it for every package
79
+ * for years, and an entry offering only something weaker is not one to run as
80
+ * a subprocess holding the user's key.
81
+ */
82
+ export declare function matchesIntegrity(bytes: Buffer, integrity: string): boolean;
83
+ /**
84
+ * Fetch the newest compatible release into the cache, if there is one.
85
+ *
86
+ * Resolves rather than throws in every case: the caller fires this and moves
87
+ * on, and a failure here must never be noticed as anything but the bundled
88
+ * server continuing to run.
89
+ */
90
+ export declare function updateOttoport(options: UpdateOptions): Promise<UpdateResult>;
@@ -0,0 +1,228 @@
1
+ import { createHash } from "node:crypto";
2
+ import { execFile } from "node:child_process";
3
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
4
+ import { mkdir, mkdtemp, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
5
+ import path from "node:path";
6
+ import { promisify } from "node:util";
7
+ /** Plain releases only: a pre-release is not something to hand a user unasked. */
8
+ function parse(version) {
9
+ const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version);
10
+ return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : undefined;
11
+ }
12
+ export function compareVersions(a, b) {
13
+ const left = parse(a) ?? [0, 0, 0];
14
+ const right = parse(b) ?? [0, 0, 0];
15
+ for (let i = 0; i < 3; i++)
16
+ if (left[i] !== right[i])
17
+ return left[i] - right[i];
18
+ return 0;
19
+ }
20
+ function sameMajor(a, b) {
21
+ const left = parse(a);
22
+ const right = parse(b);
23
+ return !!left && !!right && left[0] === right[0];
24
+ }
25
+ /** The version a package directory says it is, or nothing if it cannot say. */
26
+ export function packageVersion(dir) {
27
+ try {
28
+ const version = JSON.parse(readFileSync(path.join(dir, "package.json"), "utf8")).version;
29
+ return typeof version === "string" ? version : undefined;
30
+ }
31
+ catch {
32
+ return undefined;
33
+ }
34
+ }
35
+ /**
36
+ * The downloaded copies that are whole.
37
+ *
38
+ * A directory counts only when its entry exists and its manifest names the
39
+ * version the directory is called: anything else is a download that was
40
+ * interrupted or tampered with, and running it would fail in a way that looks
41
+ * like the gateway's fault.
42
+ */
43
+ export function cachedCopies(root) {
44
+ let names;
45
+ try {
46
+ names = readdirSync(root);
47
+ }
48
+ catch {
49
+ return [];
50
+ }
51
+ const copies = [];
52
+ for (const version of names) {
53
+ if (!parse(version))
54
+ continue;
55
+ const dir = path.join(root, version, "package");
56
+ const entry = path.join(dir, "mcp", "server.mjs");
57
+ if (existsSync(entry) && packageVersion(dir) === version)
58
+ copies.push({ version, entry });
59
+ }
60
+ return copies.sort((a, b) => compareVersions(b.version, a.version));
61
+ }
62
+ /**
63
+ * Which copy to run: the newest cached one the bundled copy is compatible
64
+ * with, or the bundled copy itself.
65
+ *
66
+ * Without a bundled copy there is nothing to measure compatibility against,
67
+ * so nothing is run — the same outcome as an install that dropped the package.
68
+ */
69
+ export function pickCopy(bundled, cached) {
70
+ if (!bundled)
71
+ return undefined;
72
+ let best = bundled;
73
+ for (const copy of cached) {
74
+ if (sameMajor(copy.version, bundled.version) && compareVersions(copy.version, best.version) > 0)
75
+ best = copy;
76
+ }
77
+ return best;
78
+ }
79
+ /**
80
+ * Whether to look for updates at all.
81
+ *
82
+ * A CI run or a test is not a user, and a network call from one is either
83
+ * noise or a flaky test — so both are off unless asked for by name.
84
+ */
85
+ export function autoUpdateEnabled(env = process.env) {
86
+ const flag = env["OHARNESS_MEDIA_AUTO_UPDATE"];
87
+ if (flag === "0" || flag === "false")
88
+ return false;
89
+ if (flag === "1" || flag === "true")
90
+ return true;
91
+ return !env["CI"] && !env["NODE_TEST_CONTEXT"];
92
+ }
93
+ /** Enough time to ask; the registry answering slowly is no reason to wait on it. */
94
+ const REGISTRY_TIMEOUT_MS = 5_000;
95
+ /** The package is about a hundred kilobytes; a minute is generous for it. */
96
+ const DOWNLOAD_TIMEOUT_MS = 60_000;
97
+ /** The running version plus the one before it, in case the newest is bad. */
98
+ const KEEP = 2;
99
+ const run = promisify(execFile);
100
+ /**
101
+ * `tar`, as the system ships it.
102
+ *
103
+ * macOS and Linux have always had it, and Windows has shipped bsdtar as
104
+ * `tar.exe` since 10 — which is everything the desktop app supports. A JS
105
+ * implementation would be a dependency for a job the OS already does.
106
+ */
107
+ async function systemTar(archive, into) {
108
+ await run("tar", ["-xzf", archive, "-C", into]);
109
+ }
110
+ /**
111
+ * Whether these bytes are the ones the registry vouched for.
112
+ *
113
+ * Only sha512 is accepted. The registry has published it for every package
114
+ * for years, and an entry offering only something weaker is not one to run as
115
+ * a subprocess holding the user's key.
116
+ */
117
+ export function matchesIntegrity(bytes, integrity) {
118
+ const digest = createHash("sha512").update(bytes).digest("base64");
119
+ const claimed = integrity
120
+ .split(/\s+/)
121
+ .filter((part) => part.startsWith("sha512-"))
122
+ .map((part) => part.slice("sha512-".length).split("?")[0]);
123
+ return claimed.length > 0 && claimed.includes(digest);
124
+ }
125
+ function timed(signal, ms) {
126
+ return signal ? AbortSignal.any([signal, AbortSignal.timeout(ms)]) : AbortSignal.timeout(ms);
127
+ }
128
+ /**
129
+ * Fetch the newest compatible release into the cache, if there is one.
130
+ *
131
+ * Resolves rather than throws in every case: the caller fires this and moves
132
+ * on, and a failure here must never be noticed as anything but the bundled
133
+ * server continuing to run.
134
+ */
135
+ export async function updateOttoport(options) {
136
+ if (!options.force && !autoUpdateEnabled(options.env))
137
+ return { status: "disabled" };
138
+ const doFetch = options.fetch ?? fetch;
139
+ const registry = options.registry ?? "https://registry.npmjs.org";
140
+ let staging;
141
+ try {
142
+ const response = await doFetch(`${registry}/ottoport/latest`, {
143
+ signal: timed(options.signal, REGISTRY_TIMEOUT_MS),
144
+ headers: { accept: "application/json" },
145
+ });
146
+ if (!response.ok)
147
+ throw new Error(`registry answered HTTP ${response.status}`);
148
+ const latest = (await response.json());
149
+ const version = typeof latest.version === "string" ? latest.version : "";
150
+ // Also what makes the version safe to use as a directory name.
151
+ if (!parse(version))
152
+ throw new Error(`registry offered an unusable version: ${String(latest.version)}`);
153
+ if (compareVersions(version, options.current) <= 0)
154
+ return { status: "current", version };
155
+ if (!sameMajor(version, options.current))
156
+ return { status: "incompatible", version };
157
+ if (cachedCopies(options.root).some((copy) => copy.version === version))
158
+ return { status: "cached", version };
159
+ const tarball = latest.dist?.tarball;
160
+ const integrity = latest.dist?.integrity;
161
+ if (typeof tarball !== "string" || !/^https:\/\//.test(tarball))
162
+ throw new Error("registry entry has no https tarball");
163
+ if (typeof integrity !== "string")
164
+ throw new Error("registry entry has no integrity");
165
+ const download = await doFetch(tarball, { signal: timed(options.signal, DOWNLOAD_TIMEOUT_MS) });
166
+ if (!download.ok)
167
+ throw new Error(`tarball download answered HTTP ${download.status}`);
168
+ const bytes = Buffer.from(await download.arrayBuffer());
169
+ // Before a single byte is unpacked: a tarball that is not the one the
170
+ // registry published is not opened at all.
171
+ if (!matchesIntegrity(bytes, integrity))
172
+ throw new Error(`integrity mismatch for ottoport ${version}`);
173
+ await mkdir(options.root, { recursive: true });
174
+ // Inside the cache root, so the final rename never crosses a filesystem
175
+ // and a directory named for a version is only ever a complete one.
176
+ staging = await mkdtemp(path.join(options.root, ".staging-"));
177
+ const archive = path.join(staging, "package.tgz");
178
+ await writeFile(archive, bytes);
179
+ await (options.extract ?? systemTar)(archive, staging);
180
+ await rm(archive, { force: true });
181
+ const dir = path.join(staging, "package");
182
+ if (!existsSync(path.join(dir, "mcp", "server.mjs")))
183
+ throw new Error("the package has no mcp/server.mjs");
184
+ if (packageVersion(dir) !== version)
185
+ throw new Error("the package is not the version the registry named");
186
+ const target = path.join(options.root, version);
187
+ try {
188
+ await rename(staging, target);
189
+ staging = undefined;
190
+ }
191
+ catch (err) {
192
+ // Another session got there first, which is the same outcome.
193
+ if (cachedCopies(options.root).some((copy) => copy.version === version))
194
+ return { status: "cached", version };
195
+ throw err;
196
+ }
197
+ await prune(options.root);
198
+ return { status: "installed", version };
199
+ }
200
+ catch (err) {
201
+ return { status: "failed", error: err instanceof Error ? err.message : String(err) };
202
+ }
203
+ finally {
204
+ if (staging)
205
+ await rm(staging, { recursive: true, force: true }).catch(() => { });
206
+ }
207
+ }
208
+ /**
209
+ * Drop all but the newest few versions, and staging left by a crash.
210
+ *
211
+ * Staging is only cleared once it is an hour old, since a younger one may be
212
+ * another session's download still in progress.
213
+ */
214
+ async function prune(root) {
215
+ const keep = new Set(cachedCopies(root).slice(0, KEEP).map((copy) => copy.version));
216
+ for (const name of await readdir(root).catch(() => [])) {
217
+ const full = path.join(root, name);
218
+ if (name.startsWith(".staging-")) {
219
+ const age = Date.now() - (await stat(full).then((s) => s.mtimeMs).catch(() => Date.now()));
220
+ if (age > 3_600_000)
221
+ await rm(full, { recursive: true, force: true }).catch(() => { });
222
+ continue;
223
+ }
224
+ if (parse(name) && !keep.has(name))
225
+ await rm(full, { recursive: true, force: true }).catch(() => { });
226
+ }
227
+ }
228
+ //# sourceMappingURL=ottoport-updates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ottoport-updates.js","sourceRoot":"","sources":["../../src/mcp/ottoport-updates.ts"],"names":[],"mappings":"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"}