om-memory-system 3.6.0-next.38 → 3.6.0-next.40

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,3 +1,4 @@
1
+ import type { EnsureDeps } from "../../services/web-ensure.js";
1
2
  export declare const CLAUDE_HOOK_EVENTS: readonly ["session-start", "user-prompt-submit", "stop"];
2
3
  export type ClaudeHookEvent = (typeof CLAUDE_HOOK_EVENTS)[number];
3
4
  /** Claude Code caps added context at 10,000 characters; keep a margin. */
@@ -15,16 +16,6 @@ export interface HookServerSettings {
15
16
  password: string;
16
17
  };
17
18
  }
18
- interface SpawnedChild {
19
- unref(): void;
20
- on(event: "error", listener: (error: Error) => void): unknown;
21
- }
22
- type SpawnFn = (command: string, args: string[], options: {
23
- detached: true;
24
- stdio: "ignore";
25
- cwd: string;
26
- windowsHide: true;
27
- }) => SpawnedChild;
28
19
  /** A readable byte stream such as `process.stdin`. */
29
20
  export interface HookInputStream {
30
21
  on(event: "data", listener: (chunk: Buffer | string) => void): unknown;
@@ -37,7 +28,7 @@ export interface ClaudeHookOptions {
37
28
  stdin?: HookInputStream;
38
29
  writeStdout?: (text: string) => void;
39
30
  fetch?: typeof fetch;
40
- spawn?: SpawnFn;
31
+ spawn?: EnsureDeps["spawn"];
41
32
  now?: () => number;
42
33
  sleep?: (ms: number) => Promise<void>;
43
34
  log?: (message: string, data: Record<string, unknown>) => void | Promise<void>;
@@ -46,6 +37,8 @@ export interface ClaudeHookOptions {
46
37
  loadSettings?: () => Promise<HookServerSettings>;
47
38
  /** The `om-memory-system` script that `web` is run from. */
48
39
  cliScript?: string;
40
+ /** Overrides for the shared start rule, such as the start lock. */
41
+ ensureDeps?: Partial<EnsureDeps>;
49
42
  inputTimeoutMs?: number;
50
43
  maxInputBytes?: number;
51
44
  /** Overrides the event's request budget. */
@@ -70,4 +63,3 @@ export declare function readHookInput(stream: HookInputStream, limits: {
70
63
  export declare function truncateContext(text: string, limit?: number): string;
71
64
  /** Run one hook event. Never throws; the result says what happened. */
72
65
  export declare function runClaudeHook(event: ClaudeHookEvent, options?: ClaudeHookOptions): Promise<ClaudeHookResult>;
73
- export {};
@@ -1,6 +1,4 @@
1
- import { spawn as nodeSpawn } from "node:child_process";
2
1
  import { homedir } from "node:os";
3
- import { fileURLToPath } from "node:url";
4
2
  import { AUTH_HEADER } from "../../services/auth-token.js";
5
3
  // Claude Code hook client: read the hook input, find or start the OMMS web
6
4
  // app, send one request, print the added context, and log one metadata line.
@@ -15,8 +13,6 @@ export const CLAUDE_CONTEXT_LIMIT = 9_500;
15
13
  export const RETRIEVAL_TAG = "omms-retrieval";
16
14
  const INPUT_TIMEOUT_MS = 2_000;
17
15
  const MAX_INPUT_BYTES = 1024 * 1024;
18
- const HEALTH_PROBE_MS = 1_000;
19
- const POLL_INTERVAL_MS = 250;
20
16
  const BUDGETS = {
21
17
  "session-start": { startMs: 15_000, requestMs: 3_000 },
22
18
  "user-prompt-submit": { startMs: 5_000, requestMs: 3_000 },
@@ -116,16 +112,12 @@ function productionDefaults() {
116
112
  stdin: process.stdin,
117
113
  writeStdout: (text) => process.stdout.write(text),
118
114
  fetch: globalThis.fetch,
119
- spawn: nodeSpawn,
120
115
  now: () => Date.now(),
121
116
  sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
122
117
  // The shared logger only uses the file system; load it lazily anyway.
123
118
  log: async (message, data) => (await import("../../services/logger.js")).log(message, data),
124
119
  readToken: async () => (await import("../../services/auth-token.js")).getOrCreateAuthToken(),
125
- resolveRuntime: async () => (await import("../../services/web-autostart.js")).resolveWebRuntime(),
126
120
  loadSettings: loadWebSettings,
127
- // From dist/adapters/claude-code/ to the CLI entry point.
128
- cliScript: fileURLToPath(new URL("../../cli/index.js", import.meta.url)),
129
121
  };
130
122
  }
131
123
  /** Port, bind address, and auth as `om-memory-system web` reads them (global config from home). */
@@ -190,7 +182,7 @@ async function runSteps(event, deps, result) {
190
182
  if (!settings.enabled)
191
183
  return "server-disabled";
192
184
  const budget = BUDGETS[event];
193
- const ready = await ensureServer(settings.baseUrl, budget.startMs, deps, result);
185
+ const ready = await ensureServer(settings, budget.startMs, deps, result);
194
186
  if (ready !== "ok")
195
187
  return ready;
196
188
  const headers = {
@@ -238,48 +230,25 @@ async function runSteps(event, deps, result) {
238
230
  result.outputBytes = Buffer.byteLength(output);
239
231
  return "ok";
240
232
  }
241
- async function isHealthy(baseUrl, timeoutMs, deps) {
242
- try {
243
- const response = await deps.fetch(`${baseUrl}/api/health`, {
244
- method: "GET",
245
- signal: AbortSignal.timeout(Math.max(1, timeoutMs)),
246
- });
247
- if (!response.ok)
248
- return false;
249
- // Same envelope check as WebServer.checkServerAvailable: another local
250
- // service on the port must not count as OMMS.
251
- const body = (await response.json());
252
- return body.success === true && body.status === "ok";
253
- }
254
- catch {
255
- return false;
256
- }
257
- }
258
- /** Use a running web app, or start `om-memory-system web` and wait for it within the budget. */
259
- async function ensureServer(baseUrl, budgetMs, deps, result) {
260
- const started = deps.now();
261
- const remaining = () => budgetMs - (deps.now() - started);
262
- if (await isHealthy(baseUrl, Math.min(HEALTH_PROBE_MS, remaining()), deps))
263
- return "ok";
264
- const runtime = await deps.resolveRuntime();
265
- if (!runtime)
266
- return "server-unreachable";
267
- const child = deps.spawn(runtime, [deps.cliScript, "web"], {
268
- detached: true,
269
- stdio: "ignore",
270
- cwd: homedir(),
271
- windowsHide: true,
233
+ /** Use a running web app, or start one through the shared rule and wait for it within the budget. */
234
+ async function ensureServer(settings, budgetMs, deps, result) {
235
+ const { ensureWebApp } = await import("../../services/web-ensure.js");
236
+ const answer = await ensureWebApp({
237
+ settings: { enabled: settings.enabled, baseUrl: settings.baseUrl },
238
+ budgetMs,
239
+ wait: true,
240
+ deps: {
241
+ fetch: deps.fetch,
242
+ now: deps.now,
243
+ sleep: deps.sleep,
244
+ ...(deps.spawn ? { spawn: deps.spawn } : {}),
245
+ ...(deps.resolveRuntime ? { resolveRuntime: deps.resolveRuntime } : {}),
246
+ ...(deps.cliScript ? { cliScript: deps.cliScript } : {}),
247
+ ...deps.ensureDeps,
248
+ },
272
249
  });
273
- // A missing runtime reports through this event; polling then runs out.
274
- child.on("error", () => undefined);
275
- child.unref();
276
- result.spawned = true;
277
- while (remaining() > 0) {
278
- await deps.sleep(Math.min(POLL_INTERVAL_MS, remaining()));
279
- if (remaining() <= 0)
280
- break;
281
- if (await isHealthy(baseUrl, Math.min(HEALTH_PROBE_MS, remaining()), deps))
282
- return "ok";
283
- }
284
- return "start-timeout";
250
+ result.spawned = answer === "started";
251
+ if (answer === "running" || answer === "started")
252
+ return "ok";
253
+ return answer === "start-timeout" ? "start-timeout" : "server-unreachable";
285
254
  }
@@ -111,6 +111,24 @@ export default function ommsPiExtension(pi) {
111
111
  code: error instanceof Error ? error.name : "unknown",
112
112
  }));
113
113
  }
114
+ if (process.env.OMMS_DISABLE_WEB_AUTOSTART !== "1") {
115
+ // Start the shared web app if none runs. The session does not wait for it.
116
+ void Promise.all([
117
+ import("../../services/web-ensure.js"),
118
+ import("../../services/web-api-auth.js"),
119
+ ])
120
+ .then(([{ ensureWebApp }, { webServerUrl }]) => ensureWebApp({
121
+ settings: {
122
+ enabled: CONFIG.webServerEnabled,
123
+ baseUrl: webServerUrl(CONFIG.webServerHost, CONFIG.webServerPort),
124
+ },
125
+ budgetMs: 0,
126
+ wait: false,
127
+ }))
128
+ .catch((error) => log("Pi web app start failed", {
129
+ code: error instanceof Error ? error.name : "unknown",
130
+ }));
131
+ }
114
132
  // Retry turns whose capture failed earlier, with this session's capture model.
115
133
  void import("../../services/capture-retry-drain.js")
116
134
  .then(({ drainCaptureRetries, registerCaptureRetryDrain, startCaptureRetryDrain }) => {
@@ -1,3 +1,10 @@
1
1
  import type { WebAutostartOptions } from "../services/web-autostart.js";
2
+ /** Test seams for the handover in `web install`. */
3
+ export interface WebHandoverDeps {
4
+ fetch?: typeof fetch;
5
+ sleep?: (ms: number) => Promise<void>;
6
+ /** The version this command runs as. */
7
+ version?: string;
8
+ }
2
9
  /** Run the standalone web app or manage its per-user login item. */
3
- export declare function runWebCommand(args: string[], options?: WebAutostartOptions, online?: () => Promise<boolean>): Promise<number>;
10
+ export declare function runWebCommand(args: string[], options?: WebAutostartOptions, online?: () => Promise<boolean>, handover?: WebHandoverDeps): Promise<number>;
@@ -1,6 +1,95 @@
1
1
  import { homedir } from "node:os";
2
+ const POLL_MS = 250;
3
+ const POLL_LIMIT = 40;
4
+ const REQUEST_TIMEOUT_MS = 2_000;
5
+ /** The version an OMMS web app reports on `url`, or null when none answers with one. */
6
+ async function readWebVersion(fetchFn, url, headers) {
7
+ try {
8
+ const response = await fetchFn(`${url}/api/settings/version`, {
9
+ headers,
10
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
11
+ });
12
+ if (!response.ok)
13
+ return null;
14
+ const body = (await response.json());
15
+ return typeof body.running === "string" ? body.running : null;
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
21
+ async function portAnswers(fetchFn, url) {
22
+ try {
23
+ await fetchFn(`${url}/api/health`, { signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
24
+ return true;
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ }
30
+ /** Ask an older OMMS web app on the configured port to step aside, and wait for the port. */
31
+ async function negotiateOwner(ctx) {
32
+ const { compareVersions } = await import("../services/version-compare.js");
33
+ const owner = await readWebVersion(ctx.fetchFn, ctx.url, ctx.headers);
34
+ const order = owner === null ? null : compareVersions(owner, ctx.version);
35
+ // A version that cannot be placed is never stopped.
36
+ if (owner === null || order === null)
37
+ return { kind: "none" };
38
+ if (order === 0)
39
+ return { kind: "same", owner };
40
+ if (order > 0)
41
+ return { kind: "newer", owner };
42
+ try {
43
+ const reply = await ctx.fetchFn(`${ctx.url}/api/web/step-aside`, {
44
+ method: "POST",
45
+ headers: { ...ctx.headers, "content-type": "application/json" },
46
+ body: JSON.stringify({ version: ctx.version }),
47
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
48
+ });
49
+ if (reply.status !== 202)
50
+ return { kind: "stuck", owner };
51
+ }
52
+ catch {
53
+ return { kind: "stuck", owner };
54
+ }
55
+ // If the old app is slow to stop, the login item still takes the port later.
56
+ for (let i = 0; i < POLL_LIMIT && (await portAnswers(ctx.fetchFn, ctx.url)); i++) {
57
+ await ctx.sleep(POLL_MS);
58
+ }
59
+ return { kind: "handed" };
60
+ }
61
+ /** Print which version serves the port after `web install`. */
62
+ async function announceHandover(ctx, handover) {
63
+ const { url, port, version } = ctx;
64
+ if (handover.kind === "stuck") {
65
+ console.log(`OMMS ${handover.owner} holds port ${port} and cannot hand it over. Stop the web app (Ctrl+C in its terminal, or quit the session that runs it), then run om-memory-system web install again.`);
66
+ }
67
+ else if (handover.kind === "newer") {
68
+ console.log(`OMMS ${handover.owner} already serves port ${port}. It is newer than this command (${version}). Update the global command: npm i -g om-memory-system`);
69
+ }
70
+ else if (handover.kind === "same") {
71
+ console.log(`OMMS web app: ${url} (version ${handover.owner})`);
72
+ }
73
+ else if (handover.kind === "none") {
74
+ console.log(`OMMS web app: ${url}`);
75
+ }
76
+ else {
77
+ let serving = null;
78
+ for (let i = 0; i < POLL_LIMIT && !serving; i++) {
79
+ serving = await readWebVersion(ctx.fetchFn, url, ctx.headers);
80
+ if (!serving)
81
+ await ctx.sleep(POLL_MS);
82
+ }
83
+ if (!serving)
84
+ console.log(`OMMS web app: ${url}`);
85
+ else if (serving === version)
86
+ console.log(`OMMS web app: ${url} (version ${serving})`);
87
+ else
88
+ console.log(`OMMS web app: ${url} (version ${serving}). This is not the version of this command (${version}).`);
89
+ }
90
+ }
2
91
  /** Run the standalone web app or manage its per-user login item. */
3
- export async function runWebCommand(args, options = {}, online) {
92
+ export async function runWebCommand(args, options = {}, online, handover = {}) {
4
93
  const home = options.home ?? homedir();
5
94
  const config = await import("../config.js");
6
95
  config.initConfig(home);
@@ -21,12 +110,33 @@ export async function runWebCommand(args, options = {}, online) {
21
110
  const { readGlobalConfigRevision, writeGlobalConfigKeys } = await import("../services/global-config-writer.js");
22
111
  await writeGlobalConfigKeys({ webServerAutoStart: action === "install" }, readGlobalConfigRevision());
23
112
  config.initConfig(home);
113
+ let context;
114
+ let outcome = { kind: "none" };
115
+ if (action === "install") {
116
+ const [{ getOrCreateAuthToken, AUTH_HEADER }, { packageVersion }] = await Promise.all([
117
+ import("../services/auth-token.js"),
118
+ import("../services/package-version.js"),
119
+ ]);
120
+ const apiToken = config.CONFIG.webServerApiToken;
121
+ context = {
122
+ fetchFn: handover.fetch ?? fetch,
123
+ sleep: handover.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
124
+ url,
125
+ port: config.CONFIG.webServerPort,
126
+ headers: {
127
+ [AUTH_HEADER]: getOrCreateAuthToken(),
128
+ ...(apiToken ? { authorization: `Bearer ${apiToken}` } : {}),
129
+ },
130
+ version: handover.version ?? packageVersion(),
131
+ };
132
+ outcome = await negotiateOwner(context);
133
+ }
24
134
  const result = action === "install"
25
135
  ? installWebAutostart({ ...options, start: true })
26
136
  : removeWebAutostart({ ...options, start: true });
27
137
  console.log(`OMMS login item: ${result.state}`);
28
- if (action === "install" && result.state === "installed")
29
- console.log(`OMMS web app: ${url}`);
138
+ if (context && result.state === "installed")
139
+ await announceHandover(context, outcome);
30
140
  return action === "install" && result.state !== "installed" ? 1 : 0;
31
141
  }
32
142
  if (action === "status") {
@@ -72,5 +182,23 @@ export async function runWebCommand(args, options = {}, online) {
72
182
  };
73
183
  process.once("SIGINT", stop);
74
184
  process.once("SIGTERM", stop);
185
+ // The page's Stop and Restart buttons reach the same process through this callback.
186
+ const { createPowerAction } = await import("./web-power.js");
187
+ server.setOnPowerAction(createPowerAction({
188
+ stopServer: async () => {
189
+ clearInterval(keeper);
190
+ process.off("SIGINT", stop);
191
+ process.off("SIGTERM", stop);
192
+ await server.stop();
193
+ },
194
+ loginItem: action === "--login-item",
195
+ autostart: options,
196
+ }));
197
+ // A newer `web install` asked this web app to give up the port.
198
+ server.setOnStepAside(async () => {
199
+ clearInterval(keeper);
200
+ await server.stop();
201
+ process.exit(0);
202
+ });
75
203
  return 0;
76
204
  }
@@ -0,0 +1,40 @@
1
+ import type { WebAutostartOptions } from "../services/web-autostart.js";
2
+ export type PowerAction = "stop" | "restart";
3
+ interface SpawnedChild {
4
+ pid?: number;
5
+ unref(): void;
6
+ on(event: "error", listener: (error: Error) => void): unknown;
7
+ }
8
+ export interface PowerDeps {
9
+ spawn: (command: string, args: string[], options: {
10
+ detached: true;
11
+ stdio: "ignore";
12
+ cwd: string;
13
+ windowsHide: true;
14
+ }) => SpawnedChild;
15
+ /** Restart the login item through the service manager. False when it cannot. */
16
+ restartLoginItem: () => boolean | Promise<boolean>;
17
+ /** Write the start lock, naming `pid`, so a host that starts meanwhile waits. */
18
+ writeStartLock: (pid: number) => void | Promise<void>;
19
+ exit: (code: number) => void;
20
+ /** The runtime and arguments of this process, for a detached copy. */
21
+ execPath: string;
22
+ args: string[];
23
+ cwd: string;
24
+ pid: number;
25
+ log: (message: string, data: Record<string, unknown>) => void | Promise<void>;
26
+ }
27
+ /**
28
+ * The callback the web server runs after it replies to Stop or Restart.
29
+ * `stopServer` stops serving and any keep-alive timer. Restart starts a fresh
30
+ * copy on the same port: the service manager for the login item, and a detached
31
+ * copy otherwise. A detached child of the login item would die with its
32
+ * launchd job or systemd control group.
33
+ */
34
+ export declare function createPowerAction(options: {
35
+ stopServer: () => Promise<void>;
36
+ loginItem: boolean;
37
+ autostart?: WebAutostartOptions;
38
+ deps?: Partial<PowerDeps>;
39
+ }): (action: PowerAction) => Promise<void>;
40
+ export {};
@@ -0,0 +1,61 @@
1
+ import { spawn as nodeSpawn } from "node:child_process";
2
+ import { homedir } from "node:os";
3
+ function productionDeps(options) {
4
+ return {
5
+ spawn: nodeSpawn,
6
+ restartLoginItem: async () => (await import("../services/web-autostart.js")).restartWebAutostart(options),
7
+ writeStartLock: async (pid) => {
8
+ const { nodeLockFs, startLockPath } = await import("../services/web-ensure.js");
9
+ const path = startLockPath();
10
+ nodeLockFs.remove(path);
11
+ nodeLockFs.createExclusive(path, JSON.stringify({ pid, at: Date.now() }));
12
+ },
13
+ exit: (code) => process.exit(code),
14
+ execPath: process.execPath,
15
+ args: process.argv.slice(1),
16
+ cwd: homedir(),
17
+ pid: process.pid,
18
+ log: async (message, data) => (await import("../services/logger.js")).log(message, data),
19
+ };
20
+ }
21
+ /**
22
+ * The callback the web server runs after it replies to Stop or Restart.
23
+ * `stopServer` stops serving and any keep-alive timer. Restart starts a fresh
24
+ * copy on the same port: the service manager for the login item, and a detached
25
+ * copy otherwise. A detached child of the login item would die with its
26
+ * launchd job or systemd control group.
27
+ */
28
+ export function createPowerAction(options) {
29
+ const deps = { ...productionDeps(options.autostart ?? {}), ...options.deps };
30
+ return async (action) => {
31
+ if (action === "stop") {
32
+ await options.stopServer();
33
+ deps.exit(0);
34
+ return;
35
+ }
36
+ try {
37
+ // Hold other hosts off until the copy owns the port.
38
+ if (!options.loginItem)
39
+ await deps.writeStartLock(deps.pid);
40
+ await options.stopServer();
41
+ if (options.loginItem && (await deps.restartLoginItem()))
42
+ return deps.exit(0);
43
+ const child = deps.spawn(deps.execPath, deps.args, {
44
+ detached: true,
45
+ stdio: "ignore",
46
+ cwd: deps.cwd,
47
+ windowsHide: true,
48
+ });
49
+ child.on("error", () => undefined);
50
+ child.unref();
51
+ if (child.pid)
52
+ await deps.writeStartLock(child.pid);
53
+ }
54
+ catch (error) {
55
+ await Promise.resolve(deps.log("Web app restart failed", {
56
+ code: error instanceof Error ? error.name : "unknown",
57
+ })).catch(() => undefined);
58
+ }
59
+ deps.exit(0);
60
+ };
61
+ }
@@ -1,6 +1,6 @@
1
1
  import type { ImportHost } from "./import-args.js";
2
2
  /**
3
- * What a real web import could use right now, checked inside the OpenCode
3
+ * What a real web import could use right now, checked inside the web app
4
4
  * process that runs it. `env://` and `file://` keys were resolved when the
5
5
  * config loaded, so a variable set only in the user's shell shows as missing.
6
6
  * "Ready" means configured, not tested: the Health section runs test calls.
@@ -70,5 +70,10 @@ export function importBlockedReason(readiness, request) {
70
70
  return state === "ready" ? null : EXTERNAL_REASONS[state];
71
71
  }
72
72
  const connected = readiness.opencode.models.some((model) => `${model.provider}/${model.model}` === request.modelChoice);
73
- return connected ? null : "The chosen OpenCode model is not connected";
73
+ if (connected)
74
+ return null;
75
+ // The web app has no OpenCode session, so no host model is ever connected here.
76
+ return readiness.opencode.available
77
+ ? "The chosen OpenCode model is not connected"
78
+ : "The chosen OpenCode model is not connected here. An import with an OpenCode signed-in model runs from the terminal or with /import in OpenCode. Choose the external API for a web import.";
74
79
  }
@@ -94,22 +94,31 @@ export async function runSettingsHealth(input) {
94
94
  });
95
95
  }
96
96
  if (input.testModels) {
97
- await check("OpenCode model test", async () => {
98
- const { resolveOpencodeHostModel } = await import("../services/ai/live-model-choice.js");
99
- const ref = resolveOpencodeHostModel(CONFIG);
100
- if (ref?.modelID === "inherit")
101
- throw new Error("A session model needs an active session to test");
102
- const capture = ref
103
- ? (await createOpencodeImportModels(ref, input.directory)).capture
104
- : (await import("./model-selection.js")).selectImportModel({}).capture;
105
- await capture.summarize({
106
- userPrompt: PROBE,
107
- context: PROBE,
108
- sessionId: "health",
109
- projectDirectory: input.directory,
97
+ const { resolveOpencodeHostModel } = await import("../services/ai/live-model-choice.js");
98
+ const { getOpencodeHostModels } = await import("./backfill-controls.js");
99
+ const opencodeRef = resolveOpencodeHostModel(CONFIG);
100
+ if (opencodeRef && !getOpencodeHostModels()) {
101
+ // The web app runs outside OpenCode, so an OpenCode signed-in model cannot be called here.
102
+ checks.push({
103
+ check: "OpenCode model test",
104
+ status: "warn",
105
+ reason: "Skipped: an OpenCode signed-in model can be tested only inside OpenCode. Run a capture in OpenCode, or set the external API as the capture model.",
110
106
  });
111
- return "Fixed prompt completed";
112
- });
107
+ }
108
+ else {
109
+ await check("OpenCode model test", async () => {
110
+ const capture = opencodeRef
111
+ ? (await createOpencodeImportModels(opencodeRef, input.directory)).capture
112
+ : (await import("./model-selection.js")).selectImportModel({}).capture;
113
+ await capture.summarize({
114
+ userPrompt: PROBE,
115
+ context: PROBE,
116
+ sessionId: "health",
117
+ projectDirectory: input.directory,
118
+ });
119
+ return "Fixed prompt completed";
120
+ });
121
+ }
113
122
  if (pi.kind === "manual") {
114
123
  await check("Pi model test", async () => {
115
124
  const { selectImportModel } = await import("./model-selection.js");