@magnitudedev/pi-extension 0.0.1-alpha.0 → 0.0.1-alpha.2

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/README.md CHANGED
@@ -3,15 +3,42 @@
3
3
  This Pi package adds Magnitude model management commands and live local-inference progress to Pi's
4
4
  built-in working row above the editor.
5
5
 
6
- Requires Pi 0.83.0 or newer and a matching Magnitude CLI. Install a local model, then run:
6
+ Requires Pi 0.83.0 or newer. Install from your terminal:
7
+
8
+ ```sh
9
+ pi install npm:@magnitudedev/pi-extension
10
+ ```
11
+
12
+ On the next fresh interactive Pi launch, Magnitude asks **“Set up local models with Magnitude now?”**
13
+ Yes opens Magnitude's actual graphical setup in the same terminal, including its model rankings,
14
+ radar, downloads, and loading progress. It connects Pi automatically at the final step, then returns
15
+ to the same Pi conversation with the selected model active. No agent prompt or cloud credentials
16
+ are needed. If Magnitude is missing, accepting setup installs the CLI with
17
+ `npm install --global @magnitudedev/cli` before opening onboarding. Pi stays visible with its native
18
+ spinner: “Installing Magnitude…”. Installer output is suppressed; failures
19
+ show a concise diagnostic. Escape cancels preparation, and `/magnitude-setup` retries.
20
+ An existing CLI is preserved and must support hosted setup; an incompatible version reports an
21
+ update requirement rather than being silently replaced.
22
+
23
+ No or Escape leaves “You can set up local models anytime with `/magnitude-setup`.” in the conversation.
24
+ The offer is remembered per Pi profile across restarts, reloads, and package updates. Existing
25
+ Magnitude model configurations, non-interactive modes, conversations, and startup prompts are left
26
+ alone. Run `/magnitude-setup` whenever you want to start onboarding yourself.
27
+
28
+ The package includes the Magnitude usage skill; an already-loaded skill of that name takes
29
+ precedence without a collision warning. `--no-skills` disables this fallback too. No separate CLI
30
+ installation is needed. npm must be available with a writable global prefix, as for a normal npm
31
+ CLI installation. Merely loading the extension does not install software.
32
+
33
+ If you already have Magnitude and a local model, connect directly:
7
34
 
8
35
  ```sh
9
36
  magnitude connections add pi
10
37
  ```
11
38
 
12
39
  Restart Pi or run `/reload` after connecting. `PI_CODING_AGENT_DIR`, if set, is honored.
13
- Installing the package directly with `pi install` does not configure provider models,
14
- install the Magnitude CLI, start its service, or install its agent skill; the connection command
40
+ Installing the package directly does not configure provider models,
41
+ install the Magnitude CLI, or start its service; the connection command
15
42
  does all connection configuration and can be run safely after a standalone package install.
16
43
 
17
44
  The extension bundles Magnitude's private SDK. Model status, loading, and stopping use the existing
@@ -28,6 +55,7 @@ latency, and token-weighted generation throughput. Pi extensions execute with yo
28
55
 
29
56
  Commands:
30
57
 
58
+ - `/magnitude-setup` — open Magnitude's graphical model setup inside Pi
31
59
  - `/load-model [model-id]` — load an installed model
32
60
  - `/stop-model` — stop the active model
33
61
 
@@ -52,3 +80,16 @@ builds and runs the checkout's inference runtime,
52
80
  and keeps the current source CLI available until Pi exits. Exiting Pi stops the development runtime
53
81
  and restores the service state that existed before launch. The launcher inherits the current
54
82
  environment; it does not start or configure tracing.
83
+
84
+ To test the first-run setup offer with no preconfigured Magnitude provider:
85
+
86
+ ```sh
87
+ bun run dev:pi --setup
88
+ ```
89
+
90
+ This installs only the checkout's package into the temporary Pi profile. Accept the setup offer,
91
+ choose a model, and return to Pi to chat. Both development modes keep connection files and skills
92
+ inside the temporary profile, and do not change login-startup registration. They share the machine's
93
+ real model storage and inference service: downloads consume disk space and selected models use
94
+ memory. The launcher restores the prior managed service after Pi exits. Ordinary installed setup
95
+ does enable login startup.
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@magnitudedev/pi-extension",
3
- "version": "0.0.1-alpha.0",
3
+ "version": "0.0.1-alpha.2",
4
4
  "rpcVersion": 1,
5
- "contentFingerprint": "0cae890db353d9f063a1298bd0f770259d18a27692c4c392fd7561c031e5792c",
5
+ "contentFingerprint": "5661646da7080511725cdef888b7ce04148c99b56fbe6454619754ebc5235718",
6
6
  "files": {
7
- "README.md": "345dccd4bee3706cb2647d10185acd0a79de61baf002f9cfce587b35376d5b19",
8
- "dist/magnitude.js": "0d59abfd9a959d5ea4a066a39bcb1cc072c957c778edcd7bd702cc5878a8403f"
7
+ "README.md": "0334364928717efd1b146486d6dc2d9ff0f28b77ff28b162d04351738170b581",
8
+ "dist/magnitude.js": "915f1d6bb080180a774b1bd009b3d7bc6c5cbab94fd7ab067dd3345d638538ad",
9
+ "dist/skills/magnitude/SKILL.md": "37de67f0c7b831415f6298c8785b7ae97d5cfe454dc778651d96114840d2ace2"
9
10
  }
10
11
  }
package/dist/magnitude.js CHANGED
@@ -3364,13 +3364,14 @@ var makeClient = (options) => Effect6.gen(function* () {
3364
3364
  yield* Deferred2.fail(active, new ConnectionClosed({}));
3365
3365
  })).pipe(Effect6.zipRight(Scope2.close(scope, Exit.void))));
3366
3366
  const unavailable = (message) => new ServiceUnavailable({ origin, message });
3367
+ const decodeHealth = Schema47.decodeUnknown(Schema47.Union(MagnitudeHealthResponseSchema, AcnHealthResponseSchema));
3367
3368
  const probe = http.get(`${origin}/health`).pipe(Effect6.timeoutFail({
3368
3369
  duration: "2 seconds",
3369
3370
  onTimeout: () => unavailable("Magnitude service health timed out")
3370
3371
  }), Effect6.mapError((error) => error instanceof ServiceUnavailable ? error : unavailable("Magnitude service is unavailable")), Effect6.flatMap((response) => response.status !== 200 && response.status !== 503 ? Effect6.fail(new InvalidServiceResponse({
3371
3372
  origin,
3372
3373
  message: `Health returned HTTP ${response.status}`
3373
- })) : response.json.pipe(Effect6.flatMap(Schema47.decodeUnknown(MagnitudeHealthResponseSchema)), Effect6.mapError(() => new InvalidServiceResponse({
3374
+ })) : response.json.pipe(Effect6.flatMap(decodeHealth), Effect6.map((health) => ("rpcVersion" in health) ? health : { ...health, rpcVersion: 0 }), Effect6.mapError(() => new InvalidServiceResponse({
3374
3375
  origin,
3375
3376
  message: "Invalid Magnitude health response"
3376
3377
  })))));
@@ -3391,36 +3392,35 @@ var makeClient = (options) => Effect6.gen(function* () {
3391
3392
  version: health.version,
3392
3393
  rpcVersion: health.rpcVersion
3393
3394
  });
3395
+ const startService = (start2) => Effect6.scoped(Effect6.gen(function* () {
3396
+ yield* report({ _tag: "Starting", phase: "PreparingAcn" });
3397
+ yield* probe.pipe(Effect6.flatMap((health) => Option13.match(serviceProgressFromHealth(health.state), {
3398
+ onNone: () => Effect6.void,
3399
+ onSome: report
3400
+ })), Effect6.ignore, Effect6.zipRight(Effect6.sleep("250 millis")), Effect6.forever, Effect6.forkScoped);
3401
+ yield* start2.pipe(Stream4.runForEach(report));
3402
+ }));
3394
3403
  const acquire = Effect6.gen(function* () {
3395
3404
  let started = false;
3396
3405
  while (true) {
3397
- const observed = yield* Effect6.either(probe);
3398
- if (observed._tag === "Left") {
3399
- if (observed.left._tag !== "ServiceUnavailable")
3400
- return yield* observed.left;
3401
- if (!started && Option13.isSome(starter)) {
3402
- started = true;
3403
- yield* report({ _tag: "Starting", phase: "PreparingAcn" });
3404
- yield* Effect6.scoped(Effect6.gen(function* () {
3405
- yield* probe.pipe(Effect6.flatMap((health) => Option13.match(serviceProgressFromHealth(health.state), {
3406
- onNone: () => Effect6.void,
3407
- onSome: report
3408
- })), Effect6.ignore, Effect6.zipRight(Effect6.sleep("250 millis")), Effect6.forever, Effect6.forkScoped);
3409
- yield* starter.value.start.pipe(Stream4.runForEach(report));
3410
- }));
3411
- continue;
3412
- }
3413
- if (!started)
3414
- return yield* observed.left;
3415
- } else {
3416
- const health = observed.right;
3417
- const service = yield* validate(health);
3406
+ const observed = yield* Effect6.either(probe.pipe(Effect6.flatMap((health) => validate(health).pipe(Effect6.map((service) => ({ health, service }))))));
3407
+ if (observed._tag === "Right") {
3408
+ const { health, service } = observed.right;
3418
3409
  if (health.state._tag === "Ready")
3419
3410
  return service;
3420
3411
  yield* Option13.match(serviceProgressFromHealth(health.state), {
3421
3412
  onNone: () => Effect6.void,
3422
3413
  onSome: report
3423
3414
  });
3415
+ } else if (started) {
3416
+ if (observed.left._tag !== "ServiceUnavailable")
3417
+ return yield* observed.left;
3418
+ } else if (Option13.isSome(starter)) {
3419
+ started = true;
3420
+ yield* startService(starter.value.start);
3421
+ continue;
3422
+ } else {
3423
+ return yield* observed.left;
3424
3424
  }
3425
3425
  yield* Effect6.sleep("250 millis");
3426
3426
  }
@@ -5841,16 +5841,372 @@ var registerMagnitudeCommands = (pi, sdk = clientLayer2()) => {
5841
5841
  };
5842
5842
  };
5843
5843
 
5844
+ // extensions/onboarding.ts
5845
+ import * as FileSystem3 from "@effect/platform/FileSystem";
5846
+ import * as NodeFileSystem2 from "@effect/platform-node/NodeFileSystem";
5847
+ import { getAgentDir, parseArgs } from "@earendil-works/pi-coding-agent";
5848
+ import { Effect as Effect13, Exit as Exit4, Scope as Scope5 } from "effect";
5849
+ import { fileURLToPath } from "node:url";
5850
+
5851
+ // extensions/setup.ts
5852
+ import * as Command2 from "@effect/platform/Command";
5853
+ import * as CommandExecutor3 from "@effect/platform/CommandExecutor";
5854
+ import * as FileSystem from "@effect/platform/FileSystem";
5855
+ import * as NodeContext from "@effect/platform-node/NodeContext";
5856
+ import { delimiter, join } from "node:path";
5857
+ import { stripVTControlCharacters } from "node:util";
5858
+ import { Context as Context7, Effect as Effect12, Exit as Exit3, Fiber as Fiber2, Layer as Layer6, ManagedRuntime as ManagedRuntime2, Schema as Schema53, Scope as Scope4, Stream as Stream7 } from "effect";
5859
+ import { CancellableLoader } from "@earendil-works/pi-tui";
5860
+
5861
+ // ../../packages/utils/src/process/interactive-process.ts
5862
+ import { spawn } from "node:child_process";
5863
+ import { Deferred as Deferred3, Duration as Duration2, Effect as Effect11, Option as Option17, Schema as Schema51 } from "effect";
5864
+
5865
+ class InteractiveProcessFailed extends Schema51.TaggedError()("InteractiveProcessFailed", {
5866
+ operation: Schema51.Literal("spawn", "run"),
5867
+ message: Schema51.String
5868
+ }) {
5869
+ }
5870
+ var messageFrom = (cause) => cause instanceof Error ? cause.message : String(cause);
5871
+ var definedEnvironment = (environment) => Object.fromEntries(Object.entries(environment).filter((entry) => entry[1] !== undefined));
5872
+ var acquireInteractiveProcess = (input) => Effect11.gen(function* () {
5873
+ const termination = yield* Deferred3.make();
5874
+ const child = yield* Effect11.async((resume) => {
5875
+ let acquired = false;
5876
+ let handle;
5877
+ try {
5878
+ handle = spawn(input.executable, [...input.args], {
5879
+ cwd: input.workingDirectory,
5880
+ detached: false,
5881
+ env: definedEnvironment(input.environment),
5882
+ shell: false,
5883
+ stdio: "inherit"
5884
+ });
5885
+ } catch (cause) {
5886
+ resume(Effect11.fail(new InteractiveProcessFailed({
5887
+ operation: "spawn",
5888
+ message: messageFrom(cause)
5889
+ })));
5890
+ return;
5891
+ }
5892
+ const onError = (cause) => {
5893
+ const failure = new InteractiveProcessFailed({
5894
+ operation: acquired ? "run" : "spawn",
5895
+ message: messageFrom(cause)
5896
+ });
5897
+ if (acquired) {
5898
+ Deferred3.unsafeDone(termination, Effect11.fail(failure));
5899
+ } else {
5900
+ resume(Effect11.fail(failure));
5901
+ }
5902
+ };
5903
+ const onExit = (code, signal) => {
5904
+ Deferred3.unsafeDone(termination, Effect11.succeed(code === null ? { _tag: "Signaled", signal } : { _tag: "Exited", code }));
5905
+ };
5906
+ const onSpawn = () => {
5907
+ acquired = true;
5908
+ resume(Effect11.succeed(handle));
5909
+ };
5910
+ handle.once("error", onError);
5911
+ handle.once("exit", onExit);
5912
+ handle.once("spawn", onSpawn);
5913
+ return Effect11.sync(() => {
5914
+ if (!acquired) {
5915
+ handle.removeListener("error", onError);
5916
+ handle.removeListener("exit", onExit);
5917
+ handle.removeListener("spawn", onSpawn);
5918
+ if (handle.exitCode === null && handle.signalCode === null) {
5919
+ try {
5920
+ handle.kill("SIGTERM");
5921
+ } catch {}
5922
+ }
5923
+ }
5924
+ });
5925
+ });
5926
+ return { child, termination };
5927
+ });
5928
+ var FORWARDED_SIGNALS = process.platform === "win32" ? ["SIGINT", "SIGTERM", "SIGBREAK"] : ["SIGINT", "SIGTERM", "SIGHUP"];
5929
+ var awaitWithSignalForwarding = ({ child, termination }) => Effect11.acquireUseRelease(Effect11.sync(() => {
5930
+ const listeners = new Map;
5931
+ for (const signal of FORWARDED_SIGNALS) {
5932
+ const listener = () => {
5933
+ try {
5934
+ child.kill(signal);
5935
+ } catch {}
5936
+ };
5937
+ listeners.set(signal, listener);
5938
+ process.on(signal, listener);
5939
+ }
5940
+ return listeners;
5941
+ }), () => Deferred3.await(termination), (listeners) => Effect11.sync(() => {
5942
+ for (const [signal, listener] of listeners) {
5943
+ process.removeListener(signal, listener);
5944
+ }
5945
+ }));
5946
+ var terminateAndReap = ({ child, termination }) => Effect11.gen(function* () {
5947
+ if (yield* Deferred3.isDone(termination))
5948
+ return;
5949
+ yield* Effect11.sync(() => {
5950
+ try {
5951
+ child.kill("SIGTERM");
5952
+ } catch {}
5953
+ });
5954
+ const graceful = yield* Deferred3.await(termination).pipe(Effect11.ignore, Effect11.timeoutOption(Duration2.seconds(2)));
5955
+ if (Option17.isSome(graceful))
5956
+ return;
5957
+ yield* Effect11.sync(() => {
5958
+ try {
5959
+ child.kill("SIGKILL");
5960
+ } catch {}
5961
+ });
5962
+ yield* Deferred3.await(termination).pipe(Effect11.ignore, Effect11.timeout(Duration2.seconds(2)), Effect11.ignore);
5963
+ });
5964
+ var runInteractiveProcess = (input) => Effect11.acquireUseRelease(acquireInteractiveProcess(input), awaitWithSignalForwarding, terminateAndReap);
5965
+ // ../../packages/client-common/src/harness-connections/hosted-setup.ts
5966
+ import { Schema as Schema52 } from "effect";
5967
+ var HOSTED_SETUP_PROTOCOL_VERSION = 1;
5968
+ var HOSTED_SETUP_MAX_RESULT_BYTES = 16 * 1024;
5969
+ var HostedSetupCapability = Schema52.Struct({
5970
+ protocolVersion: Schema52.Literal(HOSTED_SETUP_PROTOCOL_VERSION)
5971
+ });
5972
+ var version = Schema52.Literal(HOSTED_SETUP_PROTOCOL_VERSION);
5973
+ var HostedSetupResult = Schema52.Union(Schema52.TaggedStruct("Completed", { protocolVersion: version, modelId: ModelIdSchema }), Schema52.TaggedStruct("Cancelled", { protocolVersion: version }), Schema52.TaggedStruct("Failed", {
5974
+ protocolVersion: version,
5975
+ message: Schema52.String.pipe(Schema52.filter((message) => new TextEncoder().encode(message).length <= 4096))
5976
+ }));
5977
+
5978
+ // extensions/setup.ts
5979
+ class PiSetupFailed extends Schema53.TaggedError()("PiSetupFailed", {
5980
+ message: Schema53.String
5981
+ }) {
5982
+ }
5983
+ var failure = (message) => new PiSetupFailed({ message });
5984
+ var prepareMagnitudeCli = (cwd, setMessage = () => Effect12.void) => Effect12.gen(function* () {
5985
+ const fs = yield* FileSystem.FileSystem;
5986
+ const override = process.env.MAGNITUDE_CLI?.trim();
5987
+ const executable = override || "magnitude";
5988
+ const probe = Effect12.scoped(Effect12.gen(function* () {
5989
+ const child = yield* Command2.make(executable, "setup", "--host-protocol").pipe(Command2.start);
5990
+ const [code, output, stderr] = yield* Effect12.all([
5991
+ child.exitCode,
5992
+ child.stdout.pipe(Stream7.decodeText(), Stream7.runFold("", (text2, part) => (text2 + part).slice(0, 4097))),
5993
+ child.stderr.pipe(Stream7.decodeText(), Stream7.runFold("", (text2, part) => (text2 + part).slice(-4096)))
5994
+ ], { concurrency: "unbounded" });
5995
+ if (code !== 0 || output.length > 4096)
5996
+ return yield* failure(`Magnitude's setup capability check failed at ${executable} (exit ${code}). ${stripVTControlCharacters(stderr).trim().slice(-600)}${override ? " Check MAGNITUDE_CLI." : ""}`);
5997
+ return output;
5998
+ }));
5999
+ const capability = yield* probe.pipe(Effect12.catchIf((error) => !override && error._tag === "SystemError" && error.reason === "NotFound", (error) => Effect12.gen(function* () {
6000
+ for (const directory of (process.env.PATH ?? "").split(delimiter)) {
6001
+ if (yield* fs.exists(join(directory || cwd, executable)))
6002
+ return yield* error;
6003
+ }
6004
+ yield* setMessage("Installing Magnitude…");
6005
+ yield* Effect12.scoped(Effect12.gen(function* () {
6006
+ const child = yield* Command2.make("npm", "install", "--global", "@magnitudedev/cli").pipe(Command2.workingDirectory(cwd), Command2.start);
6007
+ const [code, , stderr] = yield* Effect12.all([
6008
+ child.exitCode,
6009
+ Stream7.runDrain(child.stdout),
6010
+ child.stderr.pipe(Stream7.decodeText(), Stream7.runFold("", (text2, part) => (text2 + part).slice(-4096)))
6011
+ ], { concurrency: "unbounded" });
6012
+ if (code !== 0)
6013
+ return yield* failure(`Magnitude installation failed (exit ${code}). ${stripVTControlCharacters(stderr).trim().slice(-600)} Run /magnitude-setup to retry.`);
6014
+ }));
6015
+ return yield* probe;
6016
+ })), Effect12.timeout("10 minutes"), Effect12.mapError((error) => error instanceof PiSetupFailed ? error : failure(`Could not prepare Magnitude: ${String(error)}. Run /magnitude-setup to retry${override ? ", or check MAGNITUDE_CLI" : ""}.`)));
6017
+ yield* Schema53.decodeUnknown(Schema53.parseJson(HostedSetupCapability))(capability).pipe(Effect12.mapError(() => failure(`The Magnitude CLI at ${executable} does not support hosted setup. Update @magnitudedev/cli${override ? ", or check MAGNITUDE_CLI" : ""}.`)));
6018
+ return executable;
6019
+ });
6020
+ var withPiPreparation = (ctx, work) => Effect12.acquireUseRelease(Effect12.async((resume, signal) => {
6021
+ let closed;
6022
+ closed = ctx.ui.custom((tui, theme, _keys, done) => {
6023
+ const loader = new CancellableLoader(tui, (text2) => theme.fg("accent", text2), (text2) => theme.fg("muted", text2), "Installing Magnitude…");
6024
+ if (signal.aborted) {
6025
+ loader.dispose();
6026
+ done();
6027
+ } else
6028
+ resume(Effect12.succeed({ loader, finish: () => done(), closed: () => closed }));
6029
+ return loader;
6030
+ });
6031
+ closed.catch((error) => resume(Effect12.fail(failure(`Could not open Pi setup: ${String(error)}`))));
6032
+ }), ({ loader }) => work((message) => Effect12.sync(() => loader.setMessage(message))).pipe(Effect12.raceFirst(Effect12.async((resume) => {
6033
+ loader.onAbort = () => resume(Effect12.fail(failure("Magnitude setup cancelled. Run /magnitude-setup to retry.")));
6034
+ if (loader.signal.aborted)
6035
+ loader.onAbort();
6036
+ return Effect12.sync(() => {
6037
+ loader.onAbort = undefined;
6038
+ });
6039
+ }))), ({ loader, finish, closed }) => Effect12.sync(() => {
6040
+ loader.dispose();
6041
+ finish();
6042
+ }).pipe(Effect12.zipRight(Effect12.promise(closed))));
6043
+ var restoreHostTerminal = (tui) => tui.terminal.write("\x1B[?2026l\x1B[?1003l\x1B[?1002l\x1B[?1000l\x1B[?1006l\x1B[?1004l" + "\x1B[?2004l\x1B[>4;0m\x1B[=0u\x1B[?1049l\x1B[0m\x1B[0 q");
6044
+ var withPiTerminal = (ctx, work) => Effect12.acquireUseRelease(Effect12.async((resume, signal) => {
6045
+ let closed;
6046
+ closed = ctx.ui.custom((tui, _theme, _keys, done) => {
6047
+ if (signal.aborted)
6048
+ done();
6049
+ else
6050
+ resume(Effect12.succeed({ tui, finish: () => done(), closed: () => closed }));
6051
+ return { render: () => [], invalidate: () => {} };
6052
+ });
6053
+ closed.catch((error) => resume(Effect12.fail(failure(`Could not open Pi setup: ${String(error)}`))));
6054
+ }), ({ tui }) => Effect12.sync(() => tui.stop()).pipe(Effect12.zipRight(work)), ({ tui, finish, closed }) => Effect12.sync(() => {
6055
+ try {
6056
+ restoreHostTerminal(tui);
6057
+ tui.start();
6058
+ tui.requestRender(true);
6059
+ } finally {
6060
+ finish();
6061
+ }
6062
+ }).pipe(Effect12.zipRight(Effect12.promise(closed))));
6063
+ var validateSetupTermination = (result, termination) => {
6064
+ if (termination._tag !== "Exited" || termination.code === 0 !== (result._tag !== "Failed")) {
6065
+ return Effect12.fail(failure("Magnitude setup exited without a consistent completion result. Run /magnitude-setup to retry."));
6066
+ }
6067
+ return result._tag === "Failed" ? Effect12.fail(failure(result.message)) : Effect12.succeed(result);
6068
+ };
6069
+ var PiSetup = Context7.GenericTag("pi/PiSetup");
6070
+ var PiSetupLive = Layer6.effect(PiSetup, Effect12.gen(function* () {
6071
+ const fs = yield* FileSystem.FileSystem;
6072
+ const executor = yield* CommandExecutor3.CommandExecutor;
6073
+ return {
6074
+ run: (ctx) => Effect12.scoped(Effect12.gen(function* () {
6075
+ const directory = yield* fs.makeTempDirectoryScoped({ prefix: "magnitude-pi-setup-" });
6076
+ const resultPath = `${directory}/result.json`;
6077
+ const executable = yield* withPiPreparation(ctx, (setMessage) => prepareMagnitudeCli(ctx.cwd, setMessage));
6078
+ const termination = yield* withPiTerminal(ctx, runInteractiveProcess({
6079
+ executable,
6080
+ args: ["setup", "--host", "pi", "--result-file", resultPath],
6081
+ environment: process.env,
6082
+ workingDirectory: ctx.cwd
6083
+ }));
6084
+ if (termination._tag === "Signaled" && termination.signal === "SIGINT") {
6085
+ return { protocolVersion: HOSTED_SETUP_PROTOCOL_VERSION, _tag: "Cancelled" };
6086
+ }
6087
+ if (termination._tag === "Signaled") {
6088
+ return yield* failure(`Magnitude setup stopped unexpectedly (${termination.signal}). Run /magnitude-setup to retry.`);
6089
+ }
6090
+ const stat = yield* fs.stat(resultPath).pipe(Effect12.mapError(() => failure("Magnitude setup exited without a completion result. Run /magnitude-setup to retry.")));
6091
+ if (stat.type !== "File" || stat.size > BigInt(HOSTED_SETUP_MAX_RESULT_BYTES)) {
6092
+ return yield* failure("Magnitude setup returned an invalid or oversized result");
6093
+ }
6094
+ const result = yield* fs.readFileString(resultPath).pipe(Effect12.flatMap(Schema53.decodeUnknown(Schema53.parseJson(HostedSetupResult))), Effect12.mapError(() => failure("Magnitude setup did not return a valid completion result. Run /magnitude-setup to retry.")));
6095
+ return yield* validateSetupTermination(result, termination);
6096
+ })).pipe(Effect12.provideService(CommandExecutor3.CommandExecutor, executor), Effect12.provideService(FileSystem.FileSystem, fs), Effect12.mapError((error) => error instanceof PiSetupFailed ? error : failure(`Magnitude setup could not finish: ${String(error)}`)))
6097
+ };
6098
+ }));
6099
+ var registerMagnitudeSetup = (pi, layer4 = PiSetupLive.pipe(Layer6.provide(NodeContext.layer))) => {
6100
+ const runtime = ManagedRuntime2.make(layer4);
6101
+ const scope = Effect12.runSync(Scope4.make());
6102
+ const gate = Effect12.runSync(Effect12.makeSemaphore(1));
6103
+ const action = (ctx) => Effect12.gen(function* () {
6104
+ if (ctx.mode !== "tui")
6105
+ return yield* failure("Run /magnitude-setup in Pi's interactive terminal.");
6106
+ if (!ctx.isIdle() || ctx.hasPendingMessages())
6107
+ return yield* failure("Wait for the current task to finish, then run /magnitude-setup.");
6108
+ const setup = yield* PiSetup;
6109
+ const result = yield* setup.run(ctx);
6110
+ if (result._tag !== "Completed")
6111
+ return false;
6112
+ yield* Effect12.tryPromise({
6113
+ try: async () => {
6114
+ await ctx.modelRegistry.refresh();
6115
+ const model2 = ctx.modelRegistry.find("magnitude", result.modelId);
6116
+ if (!model2 || !await pi.setModel(model2))
6117
+ throw new Error("The selected model is not available in Pi");
6118
+ },
6119
+ catch: (error) => failure(`Magnitude setup completed, but Pi could not activate the model: ${String(error)}. Run /reload and select it with /model.`)
6120
+ });
6121
+ return true;
6122
+ });
6123
+ const run = (ctx) => runtime.runPromise(Effect12.forkIn(gate.withPermitsIfAvailable(1)(action(ctx)).pipe(Effect12.flatMap((result) => result._tag === "Some" ? Effect12.succeed(result.value) : Effect12.fail(failure("Magnitude setup is already open."))), Effect12.catchAll((error) => Effect12.sync(() => {
6124
+ ctx.ui.notify(error.message, "error");
6125
+ return false;
6126
+ }))), scope).pipe(Effect12.flatMap(Fiber2.join)));
6127
+ pi.registerCommand("magnitude-setup", {
6128
+ description: "Choose and set up a local Magnitude model",
6129
+ handler: async (_args, ctx) => {
6130
+ const reload = await run(ctx);
6131
+ if (reload) {
6132
+ try {
6133
+ await ctx.reload();
6134
+ } catch (error) {
6135
+ throw failure(`Magnitude setup completed and selected the model, but Pi could not reload its resources: ${String(error)}. Run /reload to retry.`);
6136
+ }
6137
+ }
6138
+ }
6139
+ });
6140
+ return {
6141
+ run,
6142
+ dispose: async () => {
6143
+ await Effect12.runPromise(Scope4.close(scope, Exit3.void));
6144
+ await runtime.dispose();
6145
+ }
6146
+ };
6147
+ };
6148
+
6149
+ // extensions/onboarding.ts
6150
+ var SETUP_QUESTION = "Set up local models with Magnitude now?";
6151
+ var SETUP_REMINDER = "You can set up local models anytime with `/magnitude-setup`.";
6152
+ var claimSetupOffer = (agentDir) => Effect13.gen(function* () {
6153
+ const fs = yield* FileSystem3.FileSystem;
6154
+ const directory = `${agentDir}/magnitude`;
6155
+ yield* fs.makeDirectory(directory, { recursive: true });
6156
+ return yield* fs.writeFileString(`${directory}/setup-offered`, "", { flag: "wx" }).pipe(Effect13.as(true), Effect13.catchTag("SystemError", (error) => error.reason === "AlreadyExists" ? Effect13.succeed(false) : Effect13.fail(error)));
6157
+ });
6158
+ function canOfferSetup(ctx, argv) {
6159
+ if (ctx.mode !== "tui" || !ctx.isIdle() || ctx.hasPendingMessages() || ctx.ui.getEditorText().trim())
6160
+ return false;
6161
+ if (ctx.sessionManager.getEntries().some((entry) => entry.type === "message"))
6162
+ return false;
6163
+ if (ctx.modelRegistry.getAll().some((model2) => model2.provider === "magnitude"))
6164
+ return false;
6165
+ const args = parseArgs([...argv]);
6166
+ return args.messages.length === 0 && args.fileArgs.length === 0;
6167
+ }
6168
+ var offerSetup = (pi, ctx, agentDir, signal, runSetup) => Effect13.gen(function* () {
6169
+ if (!(yield* claimSetupOffer(agentDir)) || signal.aborted)
6170
+ return;
6171
+ const accepted = yield* Effect13.tryPromise(() => ctx.ui.confirm(SETUP_QUESTION, "", { signal }));
6172
+ if (signal.aborted)
6173
+ return;
6174
+ if (accepted && ctx.isIdle() && !ctx.hasPendingMessages()) {
6175
+ yield* Effect13.tryPromise(() => runSetup(ctx));
6176
+ } else
6177
+ pi.sendMessage({ customType: "magnitude-setup", content: SETUP_REMINDER, display: true });
6178
+ });
6179
+ function registerMagnitudeOnboarding(pi) {
6180
+ const abort = new AbortController;
6181
+ const scope = Effect13.runSync(Scope5.make());
6182
+ const setup = registerMagnitudeSetup(pi);
6183
+ pi.on("resources_discover", () => {
6184
+ if (parseArgs(process.argv.slice(2)).noSkills || pi.getCommands().some((command) => command.source === "skill" && command.name === "skill:magnitude"))
6185
+ return;
6186
+ return { skillPaths: [fileURLToPath(new URL("./skills/magnitude/SKILL.md", import.meta.url))] };
6187
+ });
6188
+ pi.on("session_start", (event, ctx) => {
6189
+ if (event.reason !== "startup" || !canOfferSetup(ctx, process.argv.slice(2)))
6190
+ return;
6191
+ Effect13.runFork(Effect13.forkIn(offerSetup(pi, ctx, getAgentDir(), abort.signal, setup.run).pipe(Effect13.provide(NodeFileSystem2.layer), Effect13.catchAll(() => Effect13.sync(() => ctx.ui.notify(`Magnitude setup could not open. ${SETUP_REMINDER}`, "warning")))), scope));
6192
+ });
6193
+ return async () => {
6194
+ abort.abort();
6195
+ await Effect13.runPromise(Scope5.close(scope, Exit4.void));
6196
+ await setup.dispose();
6197
+ };
6198
+ }
6199
+
5844
6200
  // extensions/observing-fetch.ts
5845
- import { Effect as Effect11, Either as Either3, Schema as Schema52 } from "effect";
6201
+ import { Effect as Effect14, Either as Either3, Schema as Schema55 } from "effect";
5846
6202
 
5847
6203
  // extensions/protocol.ts
5848
- import { Either as Either2, Schema as Schema51 } from "effect";
5849
- var ProgressChunkSchema = Schema51.Struct({ progress: MagnitudeProgressSchema });
5850
- var TimingsChunkSchema = Schema51.Struct({ timings: MagnitudeTimingsSchema });
6204
+ import { Either as Either2, Schema as Schema54 } from "effect";
6205
+ var ProgressChunkSchema = Schema54.Struct({ progress: MagnitudeProgressSchema });
6206
+ var TimingsChunkSchema = Schema54.Struct({ timings: MagnitudeTimingsSchema });
5851
6207
  var decodeMagnitudeObservation = (value) => {
5852
- const progress = Schema51.decodeUnknownEither(ProgressChunkSchema)(value);
5853
- const timings = Schema51.decodeUnknownEither(TimingsChunkSchema)(value);
6208
+ const progress = Schema54.decodeUnknownEither(ProgressChunkSchema)(value);
6209
+ const timings = Schema54.decodeUnknownEither(TimingsChunkSchema)(value);
5854
6210
  if (Either2.isLeft(progress) && Either2.isLeft(timings))
5855
6211
  return;
5856
6212
  return {
@@ -5861,7 +6217,7 @@ var decodeMagnitudeObservation = (value) => {
5861
6217
 
5862
6218
  // extensions/observing-fetch.ts
5863
6219
  var MAGNITUDE_PROGRESS_HEADER = "Magnitude-Include-Progress";
5864
- var JsonDocumentSchema = Schema52.parseJson(Schema52.Unknown);
6220
+ var JsonDocumentSchema = Schema55.parseJson(Schema55.Unknown);
5865
6221
 
5866
6222
  class SseDataParser {
5867
6223
  #buffer = "";
@@ -5908,26 +6264,26 @@ class SseDataParser {
5908
6264
  this.#dataLines = [];
5909
6265
  if (data === "[DONE]")
5910
6266
  return;
5911
- const decoded = Schema52.decodeUnknownEither(JsonDocumentSchema)(data);
6267
+ const decoded = Schema55.decodeUnknownEither(JsonDocumentSchema)(data);
5912
6268
  if (Either3.isRight(decoded))
5913
6269
  observe(decoded.right);
5914
6270
  }
5915
6271
  }
5916
- var observeResponse = (response, request, signal) => Effect11.scoped(Effect11.gen(function* () {
6272
+ var observeResponse = (response, request, signal) => Effect14.scoped(Effect14.gen(function* () {
5917
6273
  if (!response.ok || response.body === null) {
5918
6274
  yield* request.fail;
5919
6275
  return;
5920
6276
  }
5921
- const reader = yield* Effect11.acquireRelease(Effect11.sync(() => response.body.getReader()), (reader2) => Effect11.promise(() => reader2.cancel()).pipe(Effect11.interruptible, Effect11.timeout("100 millis"), Effect11.ignore, Effect11.ensuring(Effect11.sync(() => reader2.releaseLock()))));
6277
+ const reader = yield* Effect14.acquireRelease(Effect14.sync(() => response.body.getReader()), (reader2) => Effect14.promise(() => reader2.cancel()).pipe(Effect14.interruptible, Effect14.timeout("100 millis"), Effect14.ignore, Effect14.ensuring(Effect14.sync(() => reader2.releaseLock()))));
5922
6278
  if (signal.aborted) {
5923
6279
  yield* request.fail;
5924
6280
  return;
5925
6281
  }
5926
6282
  const decoder = new TextDecoder;
5927
6283
  const parser = new SseDataParser;
5928
- const drain = Effect11.gen(function* () {
6284
+ const drain = Effect14.gen(function* () {
5929
6285
  while (true) {
5930
- const result = yield* Effect11.tryPromise(() => reader.read());
6286
+ const result = yield* Effect14.tryPromise(() => reader.read());
5931
6287
  if (result.done)
5932
6288
  break;
5933
6289
  const values = [];
@@ -5940,35 +6296,35 @@ var observeResponse = (response, request, signal) => Effect11.scoped(Effect11.ge
5940
6296
  }
5941
6297
  yield* request.finish;
5942
6298
  });
5943
- const aborted = Effect11.async((resume) => {
5944
- const abort = () => resume(Effect11.interrupt);
6299
+ const aborted = Effect14.async((resume) => {
6300
+ const abort = () => resume(Effect14.interrupt);
5945
6301
  signal.addEventListener("abort", abort, { once: true });
5946
6302
  if (signal.aborted)
5947
6303
  abort();
5948
- return Effect11.sync(() => signal.removeEventListener("abort", abort));
6304
+ return Effect14.sync(() => signal.removeEventListener("abort", abort));
5949
6305
  });
5950
- yield* Effect11.raceFirst(drain, aborted).pipe(Effect11.onError(() => request.fail));
5951
- })).pipe(Effect11.catchAllCause(() => Effect11.void));
6306
+ yield* Effect14.raceFirst(drain, aborted).pipe(Effect14.onError(() => request.fail));
6307
+ })).pipe(Effect14.catchAllCause(() => Effect14.void));
5952
6308
  var makeObservingFetch = (fetchImplementation, begin, scope) => {
5953
6309
  const observingFetch = async (input, init) => {
5954
6310
  const request = new Request(input, init);
5955
6311
  request.headers.set(MAGNITUDE_PROGRESS_HEADER, "true");
5956
- const progress = Effect11.runSync(Effect11.suspend(begin).pipe(Effect11.catchAllCause(() => Effect11.succeed({
5957
- observe: () => Effect11.void,
5958
- finish: Effect11.void,
5959
- fail: Effect11.void
6312
+ const progress = Effect14.runSync(Effect14.suspend(begin).pipe(Effect14.catchAllCause(() => Effect14.succeed({
6313
+ observe: () => Effect14.void,
6314
+ finish: Effect14.void,
6315
+ fail: Effect14.void
5960
6316
  }))));
5961
6317
  try {
5962
6318
  const response = await fetchImplementation(request);
5963
6319
  try {
5964
6320
  const observation = observeResponse(response.clone(), progress, request.signal);
5965
- Effect11.runFork(Effect11.forkIn(observation, scope));
6321
+ Effect14.runFork(Effect14.forkIn(observation, scope));
5966
6322
  } catch {
5967
- Effect11.runSync(progress.fail.pipe(Effect11.catchAllCause(() => Effect11.void)));
6323
+ Effect14.runSync(progress.fail.pipe(Effect14.catchAllCause(() => Effect14.void)));
5968
6324
  }
5969
6325
  return response;
5970
6326
  } catch (error) {
5971
- Effect11.runSync(progress.fail.pipe(Effect11.catchAllCause(() => Effect11.void)));
6327
+ Effect14.runSync(progress.fail.pipe(Effect14.catchAllCause(() => Effect14.void)));
5972
6328
  throw error;
5973
6329
  }
5974
6330
  };
@@ -5979,9 +6335,9 @@ var makeObservingFetch = (fetchImplementation, begin, scope) => {
5979
6335
 
5980
6336
  // extensions/progress.ts
5981
6337
  import { Text } from "@earendil-works/pi-tui";
5982
- import { Data as Data3, Effect as Effect12, Fiber as Fiber2, Option as Option17, Queue, Schema as Schema53 } from "effect";
6338
+ import { Data as Data3, Effect as Effect15, Fiber as Fiber3, Option as Option18, Queue, Schema as Schema56 } from "effect";
5983
6339
  var MAGNITUDE_SUMMARY_WIDGET_KEY = "magnitude-inference-summary";
5984
- var RequestId = Schema53.Number.pipe(Schema53.int(), Schema53.brand("ProgressRequestId"));
6340
+ var RequestId = Schema56.Number.pipe(Schema56.int(), Schema56.brand("ProgressRequestId"));
5985
6341
 
5986
6342
  class Observing extends Data3.TaggedClass("Observing") {
5987
6343
  }
@@ -6036,31 +6392,31 @@ var formatLiveProgress = ({ progress, modelName, startedAt }, now) => {
6036
6392
  }
6037
6393
  }
6038
6394
  };
6039
- var makeProgressTracker = (ui, now = () => performance.now()) => Effect12.gen(function* () {
6395
+ var makeProgressTracker = (ui, now = () => performance.now()) => Effect15.gen(function* () {
6040
6396
  let state = new Idle2;
6041
6397
  let nextId = 0;
6042
6398
  let active;
6043
6399
  let latest;
6044
6400
  let nextResponseId = 0;
6045
6401
  const wake = yield* Queue.sliding(1);
6046
- const present = (f) => Effect12.sync(f).pipe(Effect12.catchAllCause(() => Effect12.void));
6402
+ const present = (f) => Effect15.sync(f).pipe(Effect15.catchAllCause(() => Effect15.void));
6047
6403
  const clearSummary = present(() => ui.setWidget(MAGNITUDE_SUMMARY_WIDGET_KEY, undefined));
6048
6404
  const resetRow = present(() => ui.setWorkingMessage());
6049
6405
  const render2 = present(() => {
6050
6406
  if (active)
6051
6407
  ui.setWorkingMessage(formatLiveProgress(active.phase, now()));
6052
6408
  });
6053
- const finalize = Effect12.gen(function* () {
6409
+ const finalize = Effect15.gen(function* () {
6054
6410
  if (state._tag !== "Settled")
6055
6411
  return;
6056
- if ([...state.run.responses.values()].some(Option17.isNone))
6412
+ if ([...state.run.responses.values()].some(Option18.isNone))
6057
6413
  return;
6058
6414
  if ([...state.run.requests.values()].some((r) => r._tag === "Observing"))
6059
6415
  return;
6060
6416
  const settled = state;
6061
6417
  state = runMachine.transition(settled, "Idle", {});
6062
6418
  const timings = [...settled.run.completed.entries()].filter(([id]) => settled.run.accepted.has(id)).sort(([a], [b]) => a - b).map(([, value]) => value);
6063
- if ([...settled.run.responses.values()].some((outcome) => !Option17.getOrElse(outcome, () => false)) || timings.length === 0) {
6419
+ if ([...settled.run.responses.values()].some((outcome) => !Option18.getOrElse(outcome, () => false)) || timings.length === 0) {
6064
6420
  yield* clearSummary;
6065
6421
  return;
6066
6422
  }
@@ -6069,7 +6425,7 @@ var makeProgressTracker = (ui, now = () => performance.now()) => Effect12.gen(fu
6069
6425
  const summary = `● ${settled.run.modelName} worked for ${duration(settled.settledAt - settled.run.startedAt)}` + ` · ${seconds(timings[0].time_to_first_token_ms)} TTFT` + (decodeMs > 0 && tokens > 0 ? ` · ${(tokens * 1000 / decodeMs).toFixed(1)} tok/s` : "");
6070
6426
  yield* present(() => ui.setWidget(MAGNITUDE_SUMMARY_WIDGET_KEY, (_tui, theme) => new Text(theme.fg("muted", summary), 0, 0)));
6071
6427
  });
6072
- const clear = Effect12.gen(function* () {
6428
+ const clear = Effect15.gen(function* () {
6073
6429
  if (state._tag === "Disposed")
6074
6430
  return;
6075
6431
  if (state._tag !== "Idle")
@@ -6079,7 +6435,7 @@ var makeProgressTracker = (ui, now = () => performance.now()) => Effect12.gen(fu
6079
6435
  yield* resetRow;
6080
6436
  yield* clearSummary;
6081
6437
  });
6082
- const startRun = (modelName) => Effect12.gen(function* () {
6438
+ const startRun = (modelName) => Effect15.gen(function* () {
6083
6439
  if (state._tag === "Disposed")
6084
6440
  return;
6085
6441
  if (state._tag === "Settled")
@@ -6089,38 +6445,38 @@ var makeProgressTracker = (ui, now = () => performance.now()) => Effect12.gen(fu
6089
6445
  state = runMachine.transition(state, "Working", { run: { startedAt: now(), modelName, requests: new Map, completed: new Map, responses: new Map, accepted: new Set } });
6090
6446
  yield* clearSummary;
6091
6447
  });
6092
- const timer = yield* Effect12.forever(Effect12.gen(function* () {
6448
+ const timer = yield* Effect15.forever(Effect15.gen(function* () {
6093
6449
  yield* Queue.take(wake);
6094
6450
  while (active) {
6095
- yield* Effect12.sleep("100 millis");
6451
+ yield* Effect15.sleep("100 millis");
6096
6452
  yield* render2;
6097
6453
  }
6098
- })).pipe(Effect12.forkScoped);
6099
- yield* Effect12.addFinalizer(() => Effect12.gen(function* () {
6454
+ })).pipe(Effect15.forkScoped);
6455
+ yield* Effect15.addFinalizer(() => Effect15.gen(function* () {
6100
6456
  yield* clear;
6101
6457
  if (state._tag !== "Disposed")
6102
6458
  state = runMachine.transition(state, "Disposed", {});
6103
- yield* Fiber2.interrupt(timer);
6459
+ yield* Fiber3.interrupt(timer);
6104
6460
  }));
6105
6461
  return {
6106
6462
  startRun,
6107
6463
  clear,
6108
- settleRun: Effect12.gen(function* () {
6464
+ settleRun: Effect15.gen(function* () {
6109
6465
  if (state._tag === "Working")
6110
6466
  state = runMachine.transition(state, "Settled", { settledAt: now() });
6111
6467
  yield* finalize;
6112
6468
  }),
6113
- beginResponse: (modelName) => Effect12.gen(function* () {
6469
+ beginResponse: (modelName) => Effect15.gen(function* () {
6114
6470
  yield* startRun(modelName);
6115
6471
  const run = state._tag === "Working" ? state.run : undefined;
6116
6472
  const responseId = ++nextResponseId;
6117
6473
  const requests = [];
6118
- run?.responses.set(responseId, Option17.none());
6474
+ run?.responses.set(responseId, Option18.none());
6119
6475
  const belongs = () => run !== undefined && (state._tag === "Working" || state._tag === "Settled") && state.run === run;
6120
- const end = (successful) => Effect12.gen(function* () {
6121
- if (!belongs() || Option17.isSome(run.responses.get(responseId)))
6476
+ const end = (successful) => Effect15.gen(function* () {
6477
+ if (!belongs() || Option18.isSome(run.responses.get(responseId)))
6122
6478
  return;
6123
- run.responses.set(responseId, Option17.some(successful));
6479
+ run.responses.set(responseId, Option18.some(successful));
6124
6480
  const last = requests.at(-1);
6125
6481
  if (successful && last !== undefined)
6126
6482
  run.accepted.add(last);
@@ -6139,16 +6495,16 @@ var makeProgressTracker = (ui, now = () => performance.now()) => Effect12.gen(fu
6139
6495
  }
6140
6496
  yield* finalize;
6141
6497
  });
6142
- const begin = Effect12.gen(function* () {
6143
- if (!belongs() || Option17.isSome(run.responses.get(responseId)))
6144
- return { observe: () => Effect12.void, finish: Effect12.void, fail: Effect12.void };
6498
+ const begin = Effect15.gen(function* () {
6499
+ if (!belongs() || Option18.isSome(run.responses.get(responseId)))
6500
+ return { observe: () => Effect15.void, finish: Effect15.void, fail: Effect15.void };
6145
6501
  const id = RequestId.make(++nextId);
6146
6502
  requests.push(id);
6147
6503
  latest = id;
6148
6504
  active = undefined;
6149
- run?.requests.set(id, new Observing({ timings: Option17.none() }));
6505
+ run?.requests.set(id, new Observing({ timings: Option18.none() }));
6150
6506
  yield* resetRow;
6151
- const close = (failed) => Effect12.gen(function* () {
6507
+ const close = (failed) => Effect15.gen(function* () {
6152
6508
  if (!belongs())
6153
6509
  return;
6154
6510
  const request = run.requests.get(id);
@@ -6158,7 +6514,7 @@ var makeProgressTracker = (ui, now = () => performance.now()) => Effect12.gen(fu
6158
6514
  run.requests.set(id, requestMachine.transition(request, "Closed", {}));
6159
6515
  else {
6160
6516
  run.requests.set(id, requestMachine.transition(request, "Observed", {}));
6161
- if (Option17.isSome(request.timings))
6517
+ if (Option18.isSome(request.timings))
6162
6518
  run.completed.set(id, request.timings.value);
6163
6519
  }
6164
6520
  if (latest === id) {
@@ -6168,14 +6524,14 @@ var makeProgressTracker = (ui, now = () => performance.now()) => Effect12.gen(fu
6168
6524
  yield* finalize;
6169
6525
  });
6170
6526
  return {
6171
- observe: (observation) => Effect12.gen(function* () {
6527
+ observe: (observation) => Effect15.gen(function* () {
6172
6528
  if (!belongs())
6173
6529
  return;
6174
6530
  const request = run.requests.get(id);
6175
6531
  if (request._tag !== "Observing")
6176
6532
  return;
6177
6533
  if (observation.timings)
6178
- run.requests.set(id, requestMachine.hold(request, { timings: Option17.some(observation.timings) }));
6534
+ run.requests.set(id, requestMachine.hold(request, { timings: Option18.some(observation.timings) }));
6179
6535
  if (latest !== id || !observation.progress)
6180
6536
  return;
6181
6537
  const progress = observation.progress;
@@ -6199,34 +6555,35 @@ var makeProgressTracker = (ui, now = () => performance.now()) => Effect12.gen(fu
6199
6555
  });
6200
6556
 
6201
6557
  // extensions/magnitude.ts
6202
- import { Effect as Effect13, Exit as Exit3, Scope as Scope5 } from "effect";
6558
+ import { Effect as Effect16, Exit as Exit5, Scope as Scope7 } from "effect";
6203
6559
  function magnitudeExtension(pi) {
6204
6560
  const completions = openAICompletionsApi();
6205
6561
  let tracker;
6206
6562
  let scope;
6207
6563
  const disposeCommands = registerMagnitudeCommands(pi);
6208
- const perform = (effect) => Effect13.runSync((effect ?? Effect13.void).pipe(Effect13.catchAllCause(() => Effect13.void)));
6564
+ const disposeOnboarding = registerMagnitudeOnboarding(pi);
6565
+ const perform = (effect) => Effect16.runSync((effect ?? Effect16.void).pipe(Effect16.catchAllCause(() => Effect16.void)));
6209
6566
  pi.registerProvider("magnitude", {
6210
6567
  api: "openai-completions",
6211
6568
  streamSimple: (model2, context, options) => {
6212
6569
  if (model2.api !== "openai-completions") {
6213
6570
  throw new Error(`Magnitude for Pi requires openai-completions, received ${model2.api}`);
6214
6571
  }
6215
- const response = tracker && Effect13.runSync(tracker.beginResponse(model2.name));
6572
+ const response = tracker && Effect16.runSync(tracker.beginResponse(model2.name));
6216
6573
  const stream = completions.streamSimple(model2, context, {
6217
6574
  ...options,
6218
- fetch: scope ? makeObservingFetch(options?.fetch ?? globalThis.fetch, () => response?.begin ?? Effect13.succeed({ observe: () => Effect13.void, finish: Effect13.void, fail: Effect13.void }), scope) : options?.fetch ?? globalThis.fetch
6575
+ fetch: scope ? makeObservingFetch(options?.fetch ?? globalThis.fetch, () => response?.begin ?? Effect16.succeed({ observe: () => Effect16.void, finish: Effect16.void, fail: Effect16.void }), scope) : options?.fetch ?? globalThis.fetch
6219
6576
  });
6220
6577
  if (response && scope)
6221
- Effect13.runFork(Effect13.forkIn(Effect13.tryPromise(() => stream.result()).pipe(Effect13.flatMap((message) => response.end(message.stopReason !== "error" && message.stopReason !== "aborted")), Effect13.catchAllCause(() => response.end(false))), scope));
6578
+ Effect16.runFork(Effect16.forkIn(Effect16.tryPromise(() => stream.result()).pipe(Effect16.flatMap((message) => response.end(message.stopReason !== "error" && message.stopReason !== "aborted")), Effect16.catchAllCause(() => response.end(false))), scope));
6222
6579
  return stream;
6223
6580
  }
6224
6581
  });
6225
6582
  pi.on("session_start", async (_event, ctx) => {
6226
6583
  if (scope)
6227
- await Effect13.runPromise(Scope5.close(scope, Exit3.void));
6228
- scope = Effect13.runSync(Scope5.make());
6229
- tracker = await Effect13.runPromise(makeProgressTracker(ctx.ui).pipe(Scope5.extend(scope)));
6584
+ await Effect16.runPromise(Scope7.close(scope, Exit5.void));
6585
+ scope = Effect16.runSync(Scope7.make());
6586
+ tracker = await Effect16.runPromise(makeProgressTracker(ctx.ui).pipe(Scope7.extend(scope)));
6230
6587
  });
6231
6588
  pi.on("model_select", (event) => {
6232
6589
  if (event.model.provider !== "magnitude")
@@ -6238,8 +6595,9 @@ function magnitudeExtension(pi) {
6238
6595
  });
6239
6596
  pi.on("agent_settled", () => perform(tracker?.settleRun));
6240
6597
  pi.on("session_shutdown", async () => {
6598
+ await disposeOnboarding();
6241
6599
  if (scope)
6242
- await Effect13.runPromise(Scope5.close(scope, Exit3.void));
6600
+ await Effect16.runPromise(Scope7.close(scope, Exit5.void));
6243
6601
  tracker = undefined;
6244
6602
  scope = undefined;
6245
6603
  await disposeCommands();
@@ -0,0 +1,16 @@
1
+ ---
2
+ name: magnitude
3
+ description: Set up and operate Magnitude local inference through its CLI, recommend hardware-fit local models, monitor acquisition and loading, and connect an agent harness. Use for Magnitude service, model, catalog, setup, or harness requests.
4
+ ---
5
+
6
+ # Magnitude
7
+
8
+ Magnitude profiles the local machine, assesses compatible model configurations, acquires and runs
9
+ selected models, and connects them to supported agent harnesses.
10
+
11
+ For agent-guided setup, read `magnitude docs onboarding` completely and follow it. It contains the
12
+ current CLI-only workflow, model-selection evidence, progress semantics, and harness connection
13
+ rules.
14
+
15
+ For the general non-interactive command contract, read `magnitude docs cli`. Command output is
16
+ designed to be read directly by both agents and people.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magnitudedev/pi-extension",
3
- "version": "0.0.1-alpha.0",
3
+ "version": "0.0.1-alpha.2",
4
4
  "description": "Magnitude model controls and inference progress for Pi",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -28,6 +28,7 @@
28
28
  "@earendil-works/pi-tui": ">=0.83.0"
29
29
  },
30
30
  "devDependencies": {
31
+ "@magnitudedev/client-common": "workspace:*",
31
32
  "@effect/platform-bun": "^0.90.0",
32
33
  "@magnitudedev/release": "workspace:*",
33
34
  "@magnitudedev/utils": "workspace:*",