@dxos/plugin-native 0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264 → 0.13.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.
- package/PLUGIN.mdl +64 -16
- package/dist/lib/NativeCapabilities.mjs +1 -1
- package/dist/lib/NativeCapabilities.mjs.map +1 -1
- package/dist/lib/Update.mjs.map +1 -1
- package/dist/lib/capabilities.browser.mjs +42 -0
- package/dist/lib/capabilities.browser.mjs.map +1 -0
- package/dist/lib/capabilities.mjs +1 -3
- package/dist/lib/capabilities.mjs.map +1 -1
- package/dist/lib/capabilities.node.mjs.map +1 -1
- package/dist/lib/capabilities.tauri.mjs +42 -0
- package/dist/lib/capabilities.tauri.mjs.map +1 -0
- package/dist/lib/capabilities.workerd.mjs.map +1 -1
- package/dist/lib/chunk-NativeSettings.mjs +15 -77
- package/dist/lib/chunk-NativeSettings.mjs.map +1 -1
- package/dist/lib/chunk-PLUGIN.mjs +6 -0
- package/dist/lib/chunk-PLUGIN.mjs.map +1 -0
- package/dist/lib/chunk-ollama.mjs +75 -41
- package/dist/lib/chunk-ollama.mjs.map +1 -1
- package/dist/lib/chunk-react-surface.mjs +2 -2
- package/dist/lib/chunk-react-surface.mjs.map +1 -1
- package/dist/lib/chunk-settings.mjs +2 -2
- package/dist/lib/chunk-settings.mjs.map +1 -1
- package/dist/lib/chunk-spotlight-listener.mjs +6 -5
- package/dist/lib/chunk-spotlight-listener.mjs.map +1 -1
- package/dist/lib/chunk-updater.mjs +18 -15
- package/dist/lib/chunk-updater.mjs.map +1 -1
- package/dist/lib/containers.mjs.map +1 -1
- package/dist/lib/index.mjs +0 -1
- package/dist/lib/meta.mjs +1 -1
- package/dist/lib/meta.mjs.map +1 -1
- package/dist/lib/translations.mjs.map +1 -1
- package/dist/types/src/capabilities/gen/browser.d.ts +9 -0
- package/dist/types/src/capabilities/gen/browser.d.ts.map +1 -0
- package/dist/types/src/capabilities/gen/tauri.d.ts +9 -0
- package/dist/types/src/capabilities/gen/tauri.d.ts.map +1 -0
- package/dist/types/src/capabilities/ollama.d.ts.map +1 -1
- package/dist/types/src/capabilities/spotlight-listener.d.ts.map +1 -1
- package/dist/types/src/capabilities/updater.d.ts +1 -1
- package/dist/types/src/constants.d.ts +2 -1
- package/dist/types/src/constants.d.ts.map +1 -1
- package/dist/types/src/containers/NativeSettings/NativeSettings.d.ts +1 -1
- package/dist/types/src/containers/NativeSettings/NativeSettings.d.ts.map +1 -1
- package/dist/types/src/containers/NativeSettings/index.d.ts +1 -1
- package/dist/types/src/containers/NativeSettings/index.d.ts.map +1 -1
- package/dist/types/src/containers/index.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/NativeCapabilities.d.ts +3 -3
- package/dist/types/src/types/NativeCapabilities.d.ts.map +1 -1
- package/dist/types/src/types/Update.d.ts +1 -31
- package/dist/types/src/types/Update.d.ts.map +1 -1
- package/dist/types/src/types/index.d.ts +4 -4
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/dx.config.ts +1 -1
- package/package.json +23 -22
- package/src/capabilities/gen/browser.ts +66 -0
- package/src/capabilities/gen/node.ts +4 -4
- package/src/capabilities/gen/tauri.ts +66 -0
- package/src/capabilities/gen/workerd.ts +4 -4
- package/src/capabilities/index.ts +5 -5
- package/src/capabilities/ollama.ts +65 -35
- package/src/capabilities/react-surface.ts +2 -2
- package/src/capabilities/settings.ts +2 -2
- package/src/capabilities/spotlight-listener.ts +8 -4
- package/src/capabilities/updater.ts +12 -6
- package/src/constants.ts +2 -1
- package/src/containers/NativeSettings/NativeSettings.tsx +17 -120
- package/src/containers/NativeSettings/index.ts +1 -1
- package/src/containers/index.ts +1 -1
- package/src/index.ts +1 -2
- package/src/meta.ts +1 -1
- package/src/translations.ts +2 -2
- package/src/types/NativeCapabilities.ts +5 -5
- package/src/types/Update.ts +4 -21
- package/src/types/index.ts +4 -4
|
@@ -9,14 +9,15 @@ import * as Duration from "effect/Duration";
|
|
|
9
9
|
import * as Effect from "effect/Effect";
|
|
10
10
|
import * as Exit from "effect/Exit";
|
|
11
11
|
import * as Fiber from "effect/Fiber";
|
|
12
|
+
import * as FetchHttpClient from "effect/http/FetchHttpClient";
|
|
13
|
+
import * as HttpClient from "effect/http/HttpClient";
|
|
12
14
|
import * as Layer from "effect/Layer";
|
|
13
15
|
import * as ManagedRuntime from "effect/ManagedRuntime";
|
|
16
|
+
import * as Option from "effect/Option";
|
|
17
|
+
import * as Atom from "effect/reactivity/Atom";
|
|
14
18
|
import * as Result from "effect/Result";
|
|
15
19
|
import * as Schedule from "effect/Schedule";
|
|
16
20
|
import * as Stream from "effect/Stream";
|
|
17
|
-
import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
|
|
18
|
-
import * as HttpClient from "effect/unstable/http/HttpClient";
|
|
19
|
-
import * as Atom from "effect/unstable/reactivity/Atom";
|
|
20
21
|
import { Provider } from "@dxos/ai";
|
|
21
22
|
import { OllamaAdmin, OllamaResolver } from "@dxos/ai/resolvers";
|
|
22
23
|
import { log } from "@dxos/log";
|
|
@@ -25,7 +26,7 @@ var __dxlog_file = "/__w/dxos/dxos/packages/plugins/plugin-native/src/capabiliti
|
|
|
25
26
|
var OLLAMA_HOST = "http://localhost:21434";
|
|
26
27
|
var ollama_default = Capability.makeModule(Effect.fnUntraced(function* () {
|
|
27
28
|
const registry = yield* Capabilities.AtomRegistry;
|
|
28
|
-
const runtime = ManagedRuntime.make(
|
|
29
|
+
const runtime = ManagedRuntime.make(OllamaSidecarLive);
|
|
29
30
|
const sidecarLayer = Layer.effectContext(runtime.contextEffect.pipe(Effect.map(Context.pick(OllamaSidecar))));
|
|
30
31
|
const admin = OllamaAdmin.make({ endpoint: OLLAMA_HOST });
|
|
31
32
|
const stateAtom = Atom.make({
|
|
@@ -37,11 +38,19 @@ var ollama_default = Capability.makeModule(Effect.fnUntraced(function* () {
|
|
|
37
38
|
}).pipe(Atom.keepAlive);
|
|
38
39
|
const getState = Effect.sync(() => registry.get(stateAtom));
|
|
39
40
|
const updateState = (f) => Effect.sync(() => registry.set(stateAtom, f(registry.get(stateAtom))));
|
|
40
|
-
const fail = (error) =>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
const fail = (error) => Effect.gen(function* () {
|
|
42
|
+
yield* Effect.sync(() => log.warn("ollama unreachable", { error }, {
|
|
43
|
+
"~LogMeta": "~LogMeta",
|
|
44
|
+
F: __dxlog_file,
|
|
45
|
+
L: 65,
|
|
46
|
+
S: this
|
|
47
|
+
}));
|
|
48
|
+
yield* updateState((state) => ({
|
|
49
|
+
...state,
|
|
50
|
+
kind: "failed",
|
|
51
|
+
error
|
|
52
|
+
}));
|
|
53
|
+
});
|
|
45
54
|
const setError = (name, error) => updateState((state) => {
|
|
46
55
|
const errors = { ...state.errors };
|
|
47
56
|
if (error) errors[name] = error;
|
|
@@ -71,7 +80,7 @@ var ollama_default = Capability.makeModule(Effect.fnUntraced(function* () {
|
|
|
71
80
|
if (before.join() !== after.join()) yield* Effect.sync(() => log.info("ollama loaded models changed", { loaded: after }, {
|
|
72
81
|
"~LogMeta": "~LogMeta",
|
|
73
82
|
F: __dxlog_file,
|
|
74
|
-
L:
|
|
83
|
+
L: 120,
|
|
75
84
|
S: this
|
|
76
85
|
}));
|
|
77
86
|
yield* updateState((state) => ({
|
|
@@ -101,7 +110,7 @@ var ollama_default = Capability.makeModule(Effect.fnUntraced(function* () {
|
|
|
101
110
|
yield* Effect.sync(() => log.info("ollama models", { installed: result.success.map((model) => model.name) }, {
|
|
102
111
|
"~LogMeta": "~LogMeta",
|
|
103
112
|
F: __dxlog_file,
|
|
104
|
-
L:
|
|
113
|
+
L: 140,
|
|
105
114
|
S: this
|
|
106
115
|
}));
|
|
107
116
|
yield* refreshLoaded;
|
|
@@ -113,7 +122,7 @@ var ollama_default = Capability.makeModule(Effect.fnUntraced(function* () {
|
|
|
113
122
|
yield* Effect.sync(() => log.info("ollama pull started", { name }, {
|
|
114
123
|
"~LogMeta": "~LogMeta",
|
|
115
124
|
F: __dxlog_file,
|
|
116
|
-
L:
|
|
125
|
+
L: 151,
|
|
117
126
|
S: this
|
|
118
127
|
}));
|
|
119
128
|
yield* setProgress(name, { status: "starting" });
|
|
@@ -145,7 +154,7 @@ var ollama_default = Capability.makeModule(Effect.fnUntraced(function* () {
|
|
|
145
154
|
}, {
|
|
146
155
|
"~LogMeta": "~LogMeta",
|
|
147
156
|
F: __dxlog_file,
|
|
148
|
-
L:
|
|
157
|
+
L: 181,
|
|
149
158
|
S: this
|
|
150
159
|
})) : Effect.gen(function* () {
|
|
151
160
|
const message = formatError(Cause.squash(cause));
|
|
@@ -155,7 +164,7 @@ var ollama_default = Capability.makeModule(Effect.fnUntraced(function* () {
|
|
|
155
164
|
}, {
|
|
156
165
|
"~LogMeta": "~LogMeta",
|
|
157
166
|
F: __dxlog_file,
|
|
158
|
-
L:
|
|
167
|
+
L: 184,
|
|
159
168
|
S: this
|
|
160
169
|
}));
|
|
161
170
|
yield* setError(name, message);
|
|
@@ -167,7 +176,7 @@ var ollama_default = Capability.makeModule(Effect.fnUntraced(function* () {
|
|
|
167
176
|
}, {
|
|
168
177
|
"~LogMeta": "~LogMeta",
|
|
169
178
|
F: __dxlog_file,
|
|
170
|
-
L:
|
|
179
|
+
L: 189,
|
|
171
180
|
S: this
|
|
172
181
|
}));
|
|
173
182
|
yield* refresh;
|
|
@@ -180,7 +189,7 @@ var ollama_default = Capability.makeModule(Effect.fnUntraced(function* () {
|
|
|
180
189
|
yield* Effect.sync(() => log.info("ollama pull cancelled", { name }, {
|
|
181
190
|
"~LogMeta": "~LogMeta",
|
|
182
191
|
F: __dxlog_file,
|
|
183
|
-
L:
|
|
192
|
+
L: 203,
|
|
184
193
|
S: this
|
|
185
194
|
}));
|
|
186
195
|
const fiber = pullFibers.get(name);
|
|
@@ -193,7 +202,7 @@ var ollama_default = Capability.makeModule(Effect.fnUntraced(function* () {
|
|
|
193
202
|
yield* Effect.sync(() => log.info("ollama load", { name }, {
|
|
194
203
|
"~LogMeta": "~LogMeta",
|
|
195
204
|
F: __dxlog_file,
|
|
196
|
-
L:
|
|
205
|
+
L: 218,
|
|
197
206
|
S: this
|
|
198
207
|
}));
|
|
199
208
|
const result = yield* runAdmin(admin.load(name));
|
|
@@ -204,7 +213,7 @@ var ollama_default = Capability.makeModule(Effect.fnUntraced(function* () {
|
|
|
204
213
|
}, {
|
|
205
214
|
"~LogMeta": "~LogMeta",
|
|
206
215
|
F: __dxlog_file,
|
|
207
|
-
L:
|
|
216
|
+
L: 221,
|
|
208
217
|
S: this
|
|
209
218
|
}));
|
|
210
219
|
return yield* setError(name, result.failure);
|
|
@@ -218,7 +227,7 @@ var ollama_default = Capability.makeModule(Effect.fnUntraced(function* () {
|
|
|
218
227
|
yield* Effect.sync(() => log.info("ollama unload", { name }, {
|
|
219
228
|
"~LogMeta": "~LogMeta",
|
|
220
229
|
F: __dxlog_file,
|
|
221
|
-
L:
|
|
230
|
+
L: 234,
|
|
222
231
|
S: this
|
|
223
232
|
}));
|
|
224
233
|
const result = yield* runAdmin(admin.unload(name));
|
|
@@ -247,44 +256,69 @@ var ollama_default = Capability.makeModule(Effect.fnUntraced(function* () {
|
|
|
247
256
|
yield* Effect.addFinalizer(() => Effect.tryPromise(() => runtime.dispose()).pipe(Effect.catch((error) => Effect.sync(() => log.warn("ollama runtime dispose failed", { error }, {
|
|
248
257
|
"~LogMeta": "~LogMeta",
|
|
249
258
|
F: __dxlog_file,
|
|
250
|
-
L:
|
|
259
|
+
L: 271,
|
|
251
260
|
S: this
|
|
252
261
|
})))));
|
|
253
262
|
return [Capability.contribute(AppCapabilities.AiModelResolver, OllamaSidecarModelResolver.pipe(Layer.provide(sidecarLayer))), Capability.contribute(AssistantCapabilities.OllamaManager, manager)];
|
|
254
263
|
}));
|
|
255
|
-
var OllamaSidecar = class
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
+
var OllamaSidecar = class extends Context.Service()("@dxos/plugin-native/OllamaSidecar") {};
|
|
265
|
+
var OllamaSidecarLive = Layer.effect(OllamaSidecar, Effect.gen(function* () {
|
|
266
|
+
const command = Command.create("ollama", ["serve"], { env: {
|
|
267
|
+
OLLAMA_HOST,
|
|
268
|
+
OLLAMA_ORIGINS: "*"
|
|
269
|
+
} });
|
|
270
|
+
command.stdout.on("data", (data) => logSidecar(data.toString()));
|
|
271
|
+
command.stderr.on("data", (data) => logSidecar(data.toString()));
|
|
272
|
+
let stopping = false;
|
|
273
|
+
command.on("close", ({ code, signal }) => {
|
|
274
|
+
if (stopping || code === 0) log.info("Ollama process exited", {
|
|
275
|
+
code,
|
|
276
|
+
signal
|
|
277
|
+
}, {
|
|
264
278
|
"~LogMeta": "~LogMeta",
|
|
265
279
|
F: __dxlog_file,
|
|
266
|
-
L:
|
|
280
|
+
L: 322,
|
|
267
281
|
S: this
|
|
268
|
-
})
|
|
269
|
-
|
|
282
|
+
});
|
|
283
|
+
else log.warn("Ollama process exited", {
|
|
284
|
+
code,
|
|
285
|
+
signal
|
|
286
|
+
}, {
|
|
270
287
|
"~LogMeta": "~LogMeta",
|
|
271
288
|
F: __dxlog_file,
|
|
272
|
-
L:
|
|
289
|
+
L: 324,
|
|
273
290
|
S: this
|
|
274
|
-
})
|
|
275
|
-
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
command.on("error", (error) => log.error("Ollama error", { error }, {
|
|
294
|
+
"~LogMeta": "~LogMeta",
|
|
295
|
+
F: __dxlog_file,
|
|
296
|
+
L: 327,
|
|
297
|
+
S: this
|
|
298
|
+
}));
|
|
299
|
+
const child = yield* Effect.tryPromise({
|
|
300
|
+
try: () => command.spawn(),
|
|
301
|
+
catch: formatError
|
|
302
|
+
}).pipe(Effect.tapError((error) => Effect.sync(() => log.warn("ollama not started", { error }, {
|
|
303
|
+
"~LogMeta": "~LogMeta",
|
|
304
|
+
F: __dxlog_file,
|
|
305
|
+
L: 330,
|
|
306
|
+
S: this
|
|
307
|
+
}))), Effect.option);
|
|
308
|
+
if (Option.isSome(child)) {
|
|
276
309
|
yield* Effect.addFinalizer(Effect.fn(function* () {
|
|
277
|
-
|
|
310
|
+
stopping = true;
|
|
311
|
+
yield* Effect.promise(() => child.value.kill());
|
|
278
312
|
}));
|
|
279
|
-
log.info("Running ollama", { pid: child.pid }, {
|
|
313
|
+
log.info("Running ollama", { pid: child.value.pid }, {
|
|
280
314
|
"~LogMeta": "~LogMeta",
|
|
281
315
|
F: __dxlog_file,
|
|
282
|
-
L:
|
|
316
|
+
L: 340,
|
|
283
317
|
S: this
|
|
284
318
|
});
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
};
|
|
319
|
+
}
|
|
320
|
+
return { endpoint: OLLAMA_HOST };
|
|
321
|
+
}));
|
|
288
322
|
var OllamaSidecarModelResolver = Layer.unwrap(Effect.gen(function* () {
|
|
289
323
|
const { endpoint } = yield* OllamaSidecar;
|
|
290
324
|
return OllamaResolver.make({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chunk-ollama.mjs","names":[],"sources":["../../src/capabilities/ollama.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport { Command } from '@tauri-apps/plugin-shell';\nimport * as Cause from 'effect/Cause';\nimport * as Context from 'effect/Context';\nimport * as Duration from 'effect/Duration';\nimport * as Effect from 'effect/Effect';\nimport * as Exit from 'effect/Exit';\nimport * as Fiber from 'effect/Fiber';\nimport * as Layer from 'effect/Layer';\nimport * as ManagedRuntime from 'effect/ManagedRuntime';\nimport * as Result from 'effect/Result';\nimport * as Schedule from 'effect/Schedule';\nimport * as Stream from 'effect/Stream';\nimport * as FetchHttpClient from 'effect/unstable/http/FetchHttpClient';\nimport * as HttpClient from 'effect/unstable/http/HttpClient';\nimport * as Atom from 'effect/unstable/reactivity/Atom';\n\nimport { type AiModelResolver, Provider } from '@dxos/ai';\nimport { OllamaAdmin, OllamaResolver } from '@dxos/ai/resolvers';\nimport * as Capabilities from '@dxos/app-framework/Capabilities';\nimport * as Capability from '@dxos/app-framework/Capability';\nimport * as AppCapabilities from '@dxos/app-toolkit/AppCapabilities';\nimport { log } from '@dxos/log';\nimport * as AssistantCapabilities from '@dxos/plugin-assistant/AssistantCapabilities';\nimport type * as Ollama from '@dxos/plugin-assistant/Ollama';\n\n// NOTE: Running ollama on non-standard port (config Tauri).\nconst OLLAMA_HOST = 'http://localhost:21434';\n\nexport type OllamaCapabilities =\n | Capability.Capability<typeof AppCapabilities.AiModelResolver>\n | Capability.Capability<typeof AssistantCapabilities.OllamaManager>;\n\nexport default Capability.makeModule(\n Effect.fnUntraced(function* () {\n const registry = yield* Capabilities.AtomRegistry;\n\n const runtime = ManagedRuntime.make(OllamaSidecar.layerLive);\n\n // Layer for the sidecar but the lifecycle is managed by the runtime.\n const sidecarLayer = Layer.effectContext(runtime.contextEffect.pipe(Effect.map(Context.pick(OllamaSidecar))));\n\n const admin = OllamaAdmin.make({ endpoint: OLLAMA_HOST });\n const stateAtom = Atom.make<Ollama.ModelsState>({\n kind: 'idle',\n models: [],\n loaded: [],\n pulls: {},\n errors: {},\n }).pipe(Atom.keepAlive);\n\n // Read/update the reactive state atom as Effects.\n const getState = Effect.sync(() => registry.get(stateAtom));\n const updateState = (f: (state: Ollama.ModelsState) => Ollama.ModelsState): Effect.Effect<void> =>\n Effect.sync(() => registry.set(stateAtom, f(registry.get(stateAtom))));\n\n // Connection-level failure (reaching the service); shown at the section, not tied to a model.\n const fail = (error: string): Effect.Effect<void> => updateState((state) => ({ ...state, kind: 'failed', error }));\n\n // Per-model action error (load/unload/remove/pull); shown inline on that model's row. Pass\n // `undefined` to clear.\n const setError = (name: string, error: string | undefined): Effect.Effect<void> =>\n updateState((state) => {\n const errors = { ...state.errors };\n if (error) {\n errors[name] = error;\n } else {\n delete errors[name];\n }\n return { ...state, errors };\n });\n\n // Write (or clear, when `progress` is undefined) the in-flight progress for `name`.\n const setProgress = (name: string, progress: OllamaAdmin.PullProgress | undefined): Effect.Effect<void> =>\n updateState((state) => {\n const pulls = { ...state.pulls };\n if (progress) {\n pulls[name] = progress;\n } else {\n delete pulls[name];\n }\n return { ...state, pulls };\n });\n\n // Force the scoped sidecar layer, spawning the process idempotently — the runtime is lazy and\n // only spawns on first force, so opening settings starts Ollama rather than hitting a refused\n // connection, and a chat already using the resolver does not spawn a second process. The endpoint\n // is unused; forcing `OllamaSidecar` is what triggers the spawn. Failures surface as defects.\n const ensureRunning: Effect.Effect<void> = Effect.asVoid(OllamaSidecar).pipe(Effect.provide(sidecarLayer));\n\n // Run an admin effect with the HttpClient provided, folding its typed failure into an Either so\n // callers branch on the result without a typed error channel.\n const runAdmin = <A, E extends { readonly message: string }>(\n effect: Effect.Effect<A, E, HttpClient.HttpClient>,\n ): Effect.Effect<Result.Result<A, string>> =>\n effect.pipe(Effect.provide(clientLayer), Effect.result, Effect.map(Result.mapError((error) => error.message)));\n\n // In-flight pull fibers, so a pull can be cancelled via interruption.\n const pullFibers = new Map<string, Fiber.Fiber<void>>();\n\n // Refresh only the loaded-into-memory set (cheap; no spawn). Logs load/unload transitions so the\n // console reflects which model is resident when a chat request triggers a load.\n const refreshLoaded: Effect.Effect<void> = Effect.gen(function* () {\n const result = yield* runAdmin(admin.ps);\n if (Result.isFailure(result)) {\n return;\n }\n const before = (yield* getState).loaded.map((model) => model.name);\n const after = result.success.map((model) => model.name);\n if (before.join() !== after.join()) {\n yield* Effect.sync(() => log.info('ollama loaded models changed', { loaded: after }));\n }\n yield* updateState((state) => ({ ...state, loaded: result.success }));\n });\n\n const refresh: Effect.Effect<void> = Effect.gen(function* () {\n yield* updateState((state) => ({ ...state, kind: 'loading', error: undefined }));\n const started = yield* Effect.exit(ensureRunning);\n if (Exit.isFailure(started)) {\n return yield* fail(formatError(Cause.squash(started.cause)));\n }\n // The sidecar process spawns before its HTTP server is listening, so retry until the first\n // `list` succeeds rather than reporting a spurious failure on startup.\n const result = yield* runAdmin(\n admin.list.pipe(Effect.retry({ schedule: Schedule.spaced(Duration.millis(300)), times: 29 })),\n );\n if (Result.isFailure(result)) {\n return yield* fail(result.failure);\n }\n yield* updateState((state) => ({ ...state, kind: 'ready', models: result.success, error: undefined }));\n yield* Effect.sync(() => log.info('ollama models', { installed: result.success.map((model) => model.name) }));\n yield* refreshLoaded;\n });\n\n const pull = (name: string): Effect.Effect<void> =>\n Effect.gen(function* () {\n const started = yield* Effect.exit(ensureRunning);\n if (Exit.isFailure(started)) {\n return yield* fail(formatError(Cause.squash(started.cause)));\n }\n yield* setError(name, undefined);\n yield* Effect.sync(() => log.info('ollama pull started', { name }));\n yield* setProgress(name, { status: 'starting' });\n\n // Ollama reports `completed`/`total` per layer (digest); aggregate across layers so the\n // overall percent is monotonic rather than resetting to 0 as each layer starts.\n const downloaded = new Map<string, { completed: number; total: number }>();\n const onProgress = (progress: OllamaAdmin.PullProgress): Effect.Effect<void> =>\n Effect.suspend(() => {\n if (progress.digest && progress.total) {\n downloaded.set(progress.digest, { completed: progress.completed ?? 0, total: progress.total });\n let completed = 0;\n let total = 0;\n for (const layer of downloaded.values()) {\n completed += layer.completed;\n total += layer.total;\n }\n return setProgress(name, { status: progress.status, completed, total });\n }\n // Non-download phase (manifest, verifying, writing): keep the status label, drop totals.\n return setProgress(name, { status: progress.status });\n });\n\n // Run the pull on a daemon fiber so `cancel` can interrupt it; clear progress and drop the\n // fiber on every exit path. A cancelled pull interrupts the fiber — treated as a no-op.\n const work = admin.pull(name).pipe(\n Stream.runForEach(onProgress),\n Effect.provide(clientLayer),\n Effect.matchCauseEffect({\n onFailure: (cause) =>\n Cause.hasInterruptsOnly(cause)\n ? Effect.sync(() => log.info('ollama pull finished', { name, cancelled: true }))\n : Effect.gen(function* () {\n const message = formatError(Cause.squash(cause));\n yield* Effect.sync(() => log.warn('ollama pull failed', { name, error: message }));\n yield* setError(name, message);\n }),\n onSuccess: () =>\n Effect.gen(function* () {\n yield* Effect.sync(() => log.info('ollama pull finished', { name, cancelled: false }));\n yield* refresh;\n }),\n }),\n Effect.ensuring(setProgress(name, undefined)),\n Effect.ensuring(Effect.sync(() => pullFibers.delete(name))),\n );\n\n const fiber = yield* Effect.forkDetach(work);\n yield* Effect.sync(() => pullFibers.set(name, fiber));\n });\n\n const cancel = (name: string): Effect.Effect<void> =>\n Effect.gen(function* () {\n yield* Effect.sync(() => log.info('ollama pull cancelled', { name }));\n const fiber = pullFibers.get(name);\n if (fiber) {\n // The pull's `ensuring` clears progress and drops the fiber on interruption.\n yield* Fiber.interrupt(fiber);\n }\n });\n\n const load = (name: string): Effect.Effect<void> =>\n Effect.gen(function* () {\n yield* setError(name, undefined);\n const started = yield* Effect.exit(ensureRunning);\n if (Exit.isFailure(started)) {\n return yield* setError(name, formatError(Cause.squash(started.cause)));\n }\n yield* Effect.sync(() => log.info('ollama load', { name }));\n const result = yield* runAdmin(admin.load(name));\n if (Result.isFailure(result)) {\n yield* Effect.sync(() => log.warn('ollama load failed', { name, error: result.failure }));\n return yield* setError(name, result.failure);\n }\n yield* refreshLoaded;\n });\n\n const unload = (name: string): Effect.Effect<void> =>\n Effect.gen(function* () {\n yield* setError(name, undefined);\n const started = yield* Effect.exit(ensureRunning);\n if (Exit.isFailure(started)) {\n return yield* setError(name, formatError(Cause.squash(started.cause)));\n }\n yield* Effect.sync(() => log.info('ollama unload', { name }));\n const result = yield* runAdmin(admin.unload(name));\n if (Result.isFailure(result)) {\n return yield* setError(name, result.failure);\n }\n yield* refreshLoaded;\n });\n\n const remove = (name: string): Effect.Effect<void> =>\n Effect.gen(function* () {\n yield* setError(name, undefined);\n const started = yield* Effect.exit(ensureRunning);\n if (Exit.isFailure(started)) {\n return yield* setError(name, formatError(Cause.squash(started.cause)));\n }\n const result = yield* runAdmin(admin.remove(name));\n if (Result.isFailure(result)) {\n return yield* setError(name, result.failure);\n }\n yield* refresh;\n });\n\n const manager: Ollama.Manager = {\n endpoint: OLLAMA_HOST,\n state: stateAtom,\n refresh,\n refreshLoaded,\n pull,\n cancel,\n load,\n unload,\n remove,\n };\n\n // One disposal path: the resolver and the manager close over the same runtime.\n yield* Effect.addFinalizer(() =>\n Effect.tryPromise(() => runtime.dispose()).pipe(\n Effect.catch((error) => Effect.sync(() => log.warn('ollama runtime dispose failed', { error }))),\n ),\n );\n return [\n Capability.contribute(\n AppCapabilities.AiModelResolver,\n OllamaSidecarModelResolver.pipe(Layer.provide(sidecarLayer)),\n ),\n Capability.contribute(AssistantCapabilities.OllamaManager, manager),\n ];\n }),\n);\n\nclass OllamaSidecar extends Context.Service<\n OllamaSidecar,\n {\n endpoint: string;\n }\n>()('@dxos/plugin-native/OllamaSidecar') {\n static layerLive = Layer.effect(\n OllamaSidecar,\n Effect.gen(function* () {\n // The `ollama` launcher discovers `llama-server` + its libraries relative to its own\n // executable (`<exe>/lib/ollama/`), ignoring OLLAMA_LIBRARY_PATH, so the runtime ships into\n // `Contents/MacOS/lib/ollama` next to the signed sidecar (see tauri.conf bundle.macOS.files).\n const command = Command.sidecar('sidecar/ollama', ['serve'], {\n env: {\n OLLAMA_HOST,\n OLLAMA_ORIGINS: '*', // CORS\n },\n });\n\n // Ollama writes nearly all of its output (including normal startup/inference logs) to stderr,\n // so route by the structured `level=` field rather than the stream to avoid flooding the\n // console with red errors. Kept on `console.*` for Ollama's own line formatting.\n command.stdout.on('data', (data) => logSidecar(data.toString()));\n command.stderr.on('data', (data) => logSidecar(data.toString()));\n command.on('close', (code) => log.info('Ollama process exited', { code }));\n command.on('error', (error) => log.error('Ollama error', { error }));\n const child = yield* Effect.promise(() => command.spawn());\n yield* Effect.addFinalizer(\n Effect.fn(function* () {\n yield* Effect.promise(() => child.kill());\n }),\n );\n log.info('Running ollama', { pid: child.pid });\n\n return {\n endpoint: OLLAMA_HOST,\n };\n }),\n );\n}\n\nconst OllamaSidecarModelResolver: Layer.Layer<AiModelResolver.AiModelResolver, never, OllamaSidecar> = Layer.unwrap(\n Effect.gen(function* () {\n const { endpoint } = yield* OllamaSidecar;\n return OllamaResolver.make({\n endpoint,\n provider: Provider.builtIn.id,\n transformClient: HttpClient.transformResponse(Effect.provideService(HttpClient.TracerPropagationEnabled, false)),\n });\n }),\n).pipe(Layer.provide(FetchHttpClient.layer));\n\nconst formatError = (error: unknown): string =>\n typeof error === 'string' ? error : error instanceof Error ? error.message : String(error);\n\n/** Effect HttpClient layer shared by every admin call. */\nconst clientLayer = FetchHttpClient.layer;\n\n/**\n * Route a chunk of Ollama process output to the console by its structured log level. Ollama emits\n * `INFO`/`WARN` to stderr alongside genuine errors, so only `level=ERROR` lines surface as errors.\n */\nconst logSidecar = (chunk: string): void => {\n for (const line of chunk.split('\\n')) {\n if (line.trim().length === 0) {\n continue;\n }\n /* eslint-disable no-console */\n if (line.includes('level=ERROR')) {\n console.error('[ollama]', line);\n } else if (line.includes('level=WARN')) {\n console.warn('[ollama]', line);\n } else {\n console.log('[ollama]', line);\n }\n /* eslint-enable no-console */\n }\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chunk-ollama.mjs","names":[],"sources":["../../src/capabilities/ollama.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport { Command } from '@tauri-apps/plugin-shell';\nimport * as Cause from 'effect/Cause';\nimport * as Context from 'effect/Context';\nimport * as Duration from 'effect/Duration';\nimport * as Effect from 'effect/Effect';\nimport * as Exit from 'effect/Exit';\nimport * as Fiber from 'effect/Fiber';\nimport * as FetchHttpClient from 'effect/http/FetchHttpClient';\nimport * as HttpClient from 'effect/http/HttpClient';\nimport * as Layer from 'effect/Layer';\nimport * as ManagedRuntime from 'effect/ManagedRuntime';\nimport * as Option from 'effect/Option';\nimport * as Atom from 'effect/reactivity/Atom';\nimport * as Result from 'effect/Result';\nimport * as Schedule from 'effect/Schedule';\nimport * as Stream from 'effect/Stream';\n\nimport { type AiModelResolver, Provider } from '@dxos/ai';\nimport { OllamaAdmin, OllamaResolver } from '@dxos/ai/resolvers';\nimport * as Capabilities from '@dxos/app-framework/Capabilities';\nimport * as Capability from '@dxos/app-framework/Capability';\nimport * as AppCapabilities from '@dxos/app-toolkit/AppCapabilities';\nimport { log } from '@dxos/log';\nimport * as AssistantCapabilities from '@dxos/plugin-assistant/AssistantCapabilities';\nimport type * as Ollama from '@dxos/plugin-assistant/Ollama';\n\n// NOTE: Running ollama on non-standard port (config Tauri).\nconst OLLAMA_HOST = 'http://localhost:21434';\n\nexport type OllamaCapabilities =\n | Capability.Capability<typeof AppCapabilities.AiModelResolver>\n | Capability.Capability<typeof AssistantCapabilities.OllamaManager>;\n\nexport default Capability.makeModule(\n Effect.fnUntraced(function* () {\n const registry = yield* Capabilities.AtomRegistry;\n\n const runtime = ManagedRuntime.make(OllamaSidecarLive);\n\n // Layer for the sidecar but the lifecycle is managed by the runtime.\n const sidecarLayer = Layer.effectContext(runtime.contextEffect.pipe(Effect.map(Context.pick(OllamaSidecar))));\n\n const admin = OllamaAdmin.make({ endpoint: OLLAMA_HOST });\n const stateAtom = Atom.make<Ollama.ModelsState>({\n kind: 'idle',\n models: [],\n loaded: [],\n pulls: {},\n errors: {},\n }).pipe(Atom.keepAlive);\n\n // Read/update the reactive state atom as Effects.\n const getState = Effect.sync(() => registry.get(stateAtom));\n const updateState = (f: (state: Ollama.ModelsState) => Ollama.ModelsState): Effect.Effect<void> =>\n Effect.sync(() => registry.set(stateAtom, f(registry.get(stateAtom))));\n\n // Connection-level failure (reaching the service); shown at the section, not tied to a model.\n // Logged as well as rendered, so a bug report's log bundle carries it.\n const fail = (error: string): Effect.Effect<void> =>\n Effect.gen(function* () {\n yield* Effect.sync(() => log.warn('ollama unreachable', { error }));\n yield* updateState((state) => ({ ...state, kind: 'failed', error }));\n });\n\n // Per-model action error (load/unload/remove/pull); shown inline on that model's row. Pass\n // `undefined` to clear.\n const setError = (name: string, error: string | undefined): Effect.Effect<void> =>\n updateState((state) => {\n const errors = { ...state.errors };\n if (error) {\n errors[name] = error;\n } else {\n delete errors[name];\n }\n return { ...state, errors };\n });\n\n // Write (or clear, when `progress` is undefined) the in-flight progress for `name`.\n const setProgress = (name: string, progress: OllamaAdmin.PullProgress | undefined): Effect.Effect<void> =>\n updateState((state) => {\n const pulls = { ...state.pulls };\n if (progress) {\n pulls[name] = progress;\n } else {\n delete pulls[name];\n }\n return { ...state, pulls };\n });\n\n // Force the scoped sidecar layer, spawning the process idempotently — the runtime is lazy and\n // only spawns on first force, so opening settings starts Ollama rather than hitting a refused\n // connection, and a chat already using the resolver does not spawn a second process. The endpoint\n // is unused; forcing `OllamaSidecar` is what triggers the spawn. Failures surface as defects.\n const ensureRunning: Effect.Effect<void> = Effect.asVoid(OllamaSidecar).pipe(Effect.provide(sidecarLayer));\n\n // Run an admin effect with the HttpClient provided, folding its typed failure into an Either so\n // callers branch on the result without a typed error channel.\n const runAdmin = <A, E extends { readonly message: string }>(\n effect: Effect.Effect<A, E, HttpClient.HttpClient>,\n ): Effect.Effect<Result.Result<A, string>> =>\n effect.pipe(Effect.provide(clientLayer), Effect.result, Effect.map(Result.mapError((error) => error.message)));\n\n // In-flight pull fibers, so a pull can be cancelled via interruption.\n const pullFibers = new Map<string, Fiber.Fiber<void>>();\n\n // Refresh only the loaded-into-memory set (cheap; no spawn). Logs load/unload transitions so the\n // console reflects which model is resident when a chat request triggers a load.\n const refreshLoaded: Effect.Effect<void> = Effect.gen(function* () {\n const result = yield* runAdmin(admin.ps);\n if (Result.isFailure(result)) {\n return;\n }\n const before = (yield* getState).loaded.map((model) => model.name);\n const after = result.success.map((model) => model.name);\n if (before.join() !== after.join()) {\n yield* Effect.sync(() => log.info('ollama loaded models changed', { loaded: after }));\n }\n yield* updateState((state) => ({ ...state, loaded: result.success }));\n });\n\n const refresh: Effect.Effect<void> = Effect.gen(function* () {\n yield* updateState((state) => ({ ...state, kind: 'loading', error: undefined }));\n const started = yield* Effect.exit(ensureRunning);\n if (Exit.isFailure(started)) {\n return yield* fail(formatError(Cause.squash(started.cause)));\n }\n // The sidecar process spawns before its HTTP server is listening, so retry until the first\n // `list` succeeds rather than reporting a spurious failure on startup.\n const result = yield* runAdmin(\n admin.list.pipe(Effect.retry({ schedule: Schedule.spaced(Duration.millis(300)), times: 29 })),\n );\n if (Result.isFailure(result)) {\n return yield* fail(result.failure);\n }\n yield* updateState((state) => ({ ...state, kind: 'ready', models: result.success, error: undefined }));\n yield* Effect.sync(() => log.info('ollama models', { installed: result.success.map((model) => model.name) }));\n yield* refreshLoaded;\n });\n\n const pull = (name: string): Effect.Effect<void> =>\n Effect.gen(function* () {\n const started = yield* Effect.exit(ensureRunning);\n if (Exit.isFailure(started)) {\n return yield* fail(formatError(Cause.squash(started.cause)));\n }\n yield* setError(name, undefined);\n yield* Effect.sync(() => log.info('ollama pull started', { name }));\n yield* setProgress(name, { status: 'starting' });\n\n // Ollama reports `completed`/`total` per layer (digest); aggregate across layers so the\n // overall percent is monotonic rather than resetting to 0 as each layer starts.\n const downloaded = new Map<string, { completed: number; total: number }>();\n const onProgress = (progress: OllamaAdmin.PullProgress): Effect.Effect<void> =>\n Effect.suspend(() => {\n if (progress.digest && progress.total) {\n downloaded.set(progress.digest, { completed: progress.completed ?? 0, total: progress.total });\n let completed = 0;\n let total = 0;\n for (const layer of downloaded.values()) {\n completed += layer.completed;\n total += layer.total;\n }\n return setProgress(name, { status: progress.status, completed, total });\n }\n // Non-download phase (manifest, verifying, writing): keep the status label, drop totals.\n return setProgress(name, { status: progress.status });\n });\n\n // Run the pull on a daemon fiber so `cancel` can interrupt it; clear progress and drop the\n // fiber on every exit path. A cancelled pull interrupts the fiber — treated as a no-op.\n const work = admin.pull(name).pipe(\n Stream.runForEach(onProgress),\n Effect.provide(clientLayer),\n Effect.matchCauseEffect({\n onFailure: (cause) =>\n Cause.hasInterruptsOnly(cause)\n ? Effect.sync(() => log.info('ollama pull finished', { name, cancelled: true }))\n : Effect.gen(function* () {\n const message = formatError(Cause.squash(cause));\n yield* Effect.sync(() => log.warn('ollama pull failed', { name, error: message }));\n yield* setError(name, message);\n }),\n onSuccess: () =>\n Effect.gen(function* () {\n yield* Effect.sync(() => log.info('ollama pull finished', { name, cancelled: false }));\n yield* refresh;\n }),\n }),\n Effect.ensuring(setProgress(name, undefined)),\n Effect.ensuring(Effect.sync(() => pullFibers.delete(name))),\n );\n\n const fiber = yield* Effect.forkDetach(work);\n yield* Effect.sync(() => pullFibers.set(name, fiber));\n });\n\n const cancel = (name: string): Effect.Effect<void> =>\n Effect.gen(function* () {\n yield* Effect.sync(() => log.info('ollama pull cancelled', { name }));\n const fiber = pullFibers.get(name);\n if (fiber) {\n // The pull's `ensuring` clears progress and drops the fiber on interruption.\n yield* Fiber.interrupt(fiber);\n }\n });\n\n const load = (name: string): Effect.Effect<void> =>\n Effect.gen(function* () {\n yield* setError(name, undefined);\n const started = yield* Effect.exit(ensureRunning);\n if (Exit.isFailure(started)) {\n return yield* setError(name, formatError(Cause.squash(started.cause)));\n }\n yield* Effect.sync(() => log.info('ollama load', { name }));\n const result = yield* runAdmin(admin.load(name));\n if (Result.isFailure(result)) {\n yield* Effect.sync(() => log.warn('ollama load failed', { name, error: result.failure }));\n return yield* setError(name, result.failure);\n }\n yield* refreshLoaded;\n });\n\n const unload = (name: string): Effect.Effect<void> =>\n Effect.gen(function* () {\n yield* setError(name, undefined);\n const started = yield* Effect.exit(ensureRunning);\n if (Exit.isFailure(started)) {\n return yield* setError(name, formatError(Cause.squash(started.cause)));\n }\n yield* Effect.sync(() => log.info('ollama unload', { name }));\n const result = yield* runAdmin(admin.unload(name));\n if (Result.isFailure(result)) {\n return yield* setError(name, result.failure);\n }\n yield* refreshLoaded;\n });\n\n const remove = (name: string): Effect.Effect<void> =>\n Effect.gen(function* () {\n yield* setError(name, undefined);\n const started = yield* Effect.exit(ensureRunning);\n if (Exit.isFailure(started)) {\n return yield* setError(name, formatError(Cause.squash(started.cause)));\n }\n const result = yield* runAdmin(admin.remove(name));\n if (Result.isFailure(result)) {\n return yield* setError(name, result.failure);\n }\n yield* refresh;\n });\n\n const manager: Ollama.Manager = {\n endpoint: OLLAMA_HOST,\n state: stateAtom,\n refresh,\n refreshLoaded,\n pull,\n cancel,\n load,\n unload,\n remove,\n };\n\n // One disposal path: the resolver and the manager close over the same runtime.\n yield* Effect.addFinalizer(() =>\n Effect.tryPromise(() => runtime.dispose()).pipe(\n Effect.catch((error) => Effect.sync(() => log.warn('ollama runtime dispose failed', { error }))),\n ),\n );\n return [\n Capability.contribute(\n AppCapabilities.AiModelResolver,\n OllamaSidecarModelResolver.pipe(Layer.provide(sidecarLayer)),\n ),\n Capability.contribute(AssistantCapabilities.OllamaManager, manager),\n ];\n }),\n);\n\nclass OllamaSidecar extends Context.Service<\n OllamaSidecar,\n {\n endpoint: string;\n }\n>()('@dxos/plugin-native/OllamaSidecar') {}\n\nconst OllamaSidecarLive = Layer.effect(\n OllamaSidecar,\n Effect.gen(function* () {\n // Ollama is spawned as a scoped shell command, not a Tauri sidecar. Tauri signs every\n // `externalBin` with the app's single entitlements file, which claims restricted entitlements\n // (application-identifier, associated-domains) for passkeys. macOS honours those only for a\n // binary covered by the app's provisioning profile, and a separate executable is not, so it\n // kills the sidecar at exec (AMFI error -413). Tauri has no per-binary entitlements, and the\n // shell plugin spawns only sidecars listed in `externalBin`. So the launcher ships as a plain\n // bundled file at `$RESOURCE/ollama` (bundle.macOS.files, which Tauri copies but never signs),\n // and CI signs it with no entitlements. It needs none: it is a local HTTP server.\n //\n // The launcher finds `llama-server` and its libraries at `<exe>/lib/ollama/`, ignoring\n // OLLAMA_LIBRARY_PATH, so the runtime ships at `Contents/Resources/lib/ollama` beside it.\n const command = Command.create('ollama', ['serve'], {\n env: {\n OLLAMA_HOST,\n OLLAMA_ORIGINS: '*', // CORS\n },\n });\n\n // Ollama writes nearly all of its output (including normal startup/inference logs) to stderr,\n // so route by the structured `level=` field rather than the stream to avoid flooding the\n // console with red errors. Kept on `console.*` for Ollama's own line formatting.\n command.stdout.on('data', (data) => logSidecar(data.toString()));\n command.stderr.on('data', (data) => logSidecar(data.toString()));\n // Set before the finalizer's kill, so a warning below means the process died on its own — the\n // only other trace of that is the connection error the settings panel shows nine seconds later.\n let stopping = false;\n command.on('close', ({ code, signal }) => {\n if (stopping || code === 0) {\n log.info('Ollama process exited', { code, signal });\n } else {\n log.warn('Ollama process exited', { code, signal });\n }\n });\n command.on('error', (error) => log.error('Ollama error', { error }));\n // Only macOS builds bundle the launcher, and a defect here fails every model resolver materialized beside it.\n const child = yield* Effect.tryPromise({ try: () => command.spawn(), catch: formatError }).pipe(\n Effect.tapError((error) => Effect.sync(() => log.warn('ollama not started', { error }))),\n Effect.option,\n );\n if (Option.isSome(child)) {\n yield* Effect.addFinalizer(\n Effect.fn(function* () {\n stopping = true;\n yield* Effect.promise(() => child.value.kill());\n }),\n );\n log.info('Running ollama', { pid: child.value.pid });\n }\n\n return {\n endpoint: OLLAMA_HOST,\n };\n }),\n);\n\nconst OllamaSidecarModelResolver: Layer.Layer<AiModelResolver.AiModelResolver, never, OllamaSidecar> = Layer.unwrap(\n Effect.gen(function* () {\n const { endpoint } = yield* OllamaSidecar;\n return OllamaResolver.make({\n endpoint,\n provider: Provider.builtIn.id,\n transformClient: HttpClient.transformResponse(Effect.provideService(HttpClient.TracerPropagationEnabled, false)),\n });\n }),\n).pipe(Layer.provide(FetchHttpClient.layer));\n\nconst formatError = (error: unknown): string =>\n typeof error === 'string' ? error : error instanceof Error ? error.message : String(error);\n\n/** Effect HttpClient layer shared by every admin call. */\nconst clientLayer = FetchHttpClient.layer;\n\n/**\n * Route a chunk of Ollama process output to the console by its structured log level. Ollama emits\n * `INFO`/`WARN` to stderr alongside genuine errors, so only `level=ERROR` lines surface as errors.\n */\nconst logSidecar = (chunk: string): void => {\n for (const line of chunk.split('\\n')) {\n if (line.trim().length === 0) {\n continue;\n }\n /* eslint-disable no-console */\n if (line.includes('level=ERROR')) {\n console.error('[ollama]', line);\n } else if (line.includes('level=WARN')) {\n console.warn('[ollama]', line);\n } else {\n console.log('[ollama]', line);\n }\n /* eslint-enable no-console */\n }\n};\n"],"mappings":"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"}
|
|
@@ -2,8 +2,8 @@ import * as Capabilities from "@dxos/app-framework/Capabilities";
|
|
|
2
2
|
import * as Capability from "@dxos/app-framework/Capability";
|
|
3
3
|
import { meta } from "#meta";
|
|
4
4
|
import * as Effect from "effect/Effect";
|
|
5
|
-
import
|
|
6
|
-
import
|
|
5
|
+
import * as Surface from "@dxos/app-framework/Surface";
|
|
6
|
+
import * as AppSurface from "@dxos/app-toolkit/AppSurface";
|
|
7
7
|
import { NativeSettings } from "#containers";
|
|
8
8
|
//#region src/capabilities/react-surface.ts
|
|
9
9
|
var react_surface_default = Capability.makeModule(() => Effect.succeed(Capability.contribute(Capabilities.ReactSurface, [Surface.create({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chunk-react-surface.mjs","names":[],"sources":["../../src/capabilities/react-surface.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport * as Capabilities from '@dxos/app-framework/Capabilities';\nimport * as Capability from '@dxos/app-framework/Capability';\nimport
|
|
1
|
+
{"version":3,"file":"chunk-react-surface.mjs","names":[],"sources":["../../src/capabilities/react-surface.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport * as Capabilities from '@dxos/app-framework/Capabilities';\nimport * as Capability from '@dxos/app-framework/Capability';\nimport * as Surface from '@dxos/app-framework/Surface';\nimport * as AppSurface from '@dxos/app-toolkit/AppSurface';\n\nimport { NativeSettings } from '#containers';\nimport { meta } from '#meta';\n\nexport default Capability.makeModule(() =>\n Effect.succeed(\n Capability.contribute(Capabilities.ReactSurface, [\n Surface.create({\n id: 'pluginSettings',\n filter: AppSurface.settings(AppSurface.Article, meta.profile.key),\n component: NativeSettings,\n }),\n ]),\n ),\n);\n"],"mappings":";;;;;;;;AAcA,IAAA,wBAAe,WAAW,iBACxB,OAAO,QACL,WAAW,WAAW,aAAa,cAAc,CAC/C,QAAQ,OAAO;CACb,IAAI;CACJ,QAAQ,WAAW,SAAS,WAAW,SAAS,KAAK,QAAQ,GAAG;CAChE,WAAW;AACb,CAAC,CACH,CAAC,CACH,CACF"}
|
|
@@ -3,10 +3,10 @@ import * as AppCapabilities from "@dxos/app-toolkit/AppCapabilities";
|
|
|
3
3
|
import { NativeCapabilities, Settings } from "#types";
|
|
4
4
|
import { meta } from "#meta";
|
|
5
5
|
import * as Effect from "effect/Effect";
|
|
6
|
-
import
|
|
6
|
+
import * as KvsStore from "@dxos/effect/KvsStore";
|
|
7
7
|
//#region src/capabilities/settings.ts
|
|
8
8
|
var settings_default = Capability.makeModule(() => Effect.sync(() => {
|
|
9
|
-
const settingsAtom =
|
|
9
|
+
const settingsAtom = KvsStore.make({
|
|
10
10
|
key: meta.profile.key,
|
|
11
11
|
schema: Settings.Settings,
|
|
12
12
|
defaultValue: () => ({})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chunk-settings.mjs","names":[],"sources":["../../src/capabilities/settings.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport * as Capability from '@dxos/app-framework/Capability';\nimport * as AppCapabilities from '@dxos/app-toolkit/AppCapabilities';\nimport
|
|
1
|
+
{"version":3,"file":"chunk-settings.mjs","names":[],"sources":["../../src/capabilities/settings.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport * as Capability from '@dxos/app-framework/Capability';\nimport * as AppCapabilities from '@dxos/app-toolkit/AppCapabilities';\nimport * as KvsStore from '@dxos/effect/KvsStore';\n\nimport { meta } from '#meta';\nimport { NativeCapabilities, Settings } from '#types';\n\nexport default Capability.makeModule(() =>\n Effect.sync(() => {\n const settingsAtom = KvsStore.make({\n key: meta.profile.key,\n schema: Settings.Settings,\n defaultValue: () => ({}),\n });\n\n return [\n Capability.contribute(NativeCapabilities.Settings, settingsAtom),\n Capability.contribute(AppCapabilities.Settings, {\n prefix: meta.profile.key,\n schema: Settings.Settings,\n atom: settingsAtom,\n }),\n ];\n }),\n);\n"],"mappings":";;;;;;;AAaA,IAAA,mBAAe,WAAW,iBACxB,OAAO,WAAW;CAChB,MAAM,eAAe,SAAS,KAAK;EACjC,KAAK,KAAK,QAAQ;EAClB,QAAQ,SAAS;EACjB,qBAAqB,CAAC;CACxB,CAAC;CAED,OAAO,CACL,WAAW,WAAW,mBAAmB,UAAU,YAAY,GAC/D,WAAW,WAAW,gBAAgB,UAAU;EAC9C,QAAQ,KAAK,QAAQ;EACrB,QAAQ,SAAS;EACjB,MAAM;CACR,CAAC,CACH;AACF,CAAC,CACH"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as Capabilities from "@dxos/app-framework/Capabilities";
|
|
2
2
|
import * as Capability from "@dxos/app-framework/Capability";
|
|
3
|
+
import * as Schema from "effect/Schema";
|
|
3
4
|
import * as Effect from "effect/Effect";
|
|
4
5
|
import { log } from "@dxos/log";
|
|
5
6
|
import * as LayoutOperation from "@dxos/app-toolkit/LayoutOperation";
|
|
@@ -21,21 +22,21 @@ var spotlight_listener_default = Capability.makeModule(Effect.fnUntraced(functio
|
|
|
21
22
|
}, {
|
|
22
23
|
"~LogMeta": "~LogMeta",
|
|
23
24
|
F: __dxlog_file,
|
|
24
|
-
L:
|
|
25
|
+
L: 32,
|
|
25
26
|
S: this
|
|
26
27
|
});
|
|
27
28
|
try {
|
|
28
29
|
switch (operation) {
|
|
29
30
|
case "open":
|
|
30
|
-
await invokePromise(LayoutOperation.Open, payload);
|
|
31
|
+
await invokePromise(LayoutOperation.Open, Schema.decodeUnknownSync(LayoutOperation.Open.input)(payload));
|
|
31
32
|
break;
|
|
32
33
|
case "switch-workspace":
|
|
33
|
-
await invokePromise(LayoutOperation.SwitchWorkspace, payload);
|
|
34
|
+
await invokePromise(LayoutOperation.SwitchWorkspace, Schema.decodeUnknownSync(LayoutOperation.SwitchWorkspace.input)(payload));
|
|
34
35
|
break;
|
|
35
36
|
default: log.warn("Unknown spotlight operation", { operation }, {
|
|
36
37
|
"~LogMeta": "~LogMeta",
|
|
37
38
|
F: __dxlog_file,
|
|
38
|
-
L:
|
|
39
|
+
L: 45,
|
|
39
40
|
S: this
|
|
40
41
|
});
|
|
41
42
|
}
|
|
@@ -46,7 +47,7 @@ var spotlight_listener_default = Capability.makeModule(Effect.fnUntraced(functio
|
|
|
46
47
|
log.catch(err, void 0, {
|
|
47
48
|
"~LogMeta": "~LogMeta",
|
|
48
49
|
F: __dxlog_file,
|
|
49
|
-
L:
|
|
50
|
+
L: 52,
|
|
50
51
|
S: this
|
|
51
52
|
});
|
|
52
53
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chunk-spotlight-listener.mjs","names":[],"sources":["../../src/capabilities/spotlight-listener.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport * as Capabilities from '@dxos/app-framework/Capabilities';\nimport * as Capability from '@dxos/app-framework/Capability';\nimport * as LayoutOperation from '@dxos/app-toolkit/LayoutOperation';\nimport { log } from '@dxos/log';\n\n//
|
|
1
|
+
{"version":3,"file":"chunk-spotlight-listener.mjs","names":[],"sources":["../../src/capabilities/spotlight-listener.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\nimport * as Schema from 'effect/Schema';\n\nimport * as Capabilities from '@dxos/app-framework/Capabilities';\nimport * as Capability from '@dxos/app-framework/Capability';\nimport * as LayoutOperation from '@dxos/app-toolkit/LayoutOperation';\nimport { log } from '@dxos/log';\n\n// The payload is the operation's input, decoded against its schema on arrival.\ntype SpotlightInvokePayload = {\n operation: string;\n payload?: unknown;\n};\n\n/**\n * Listens for spotlight:invoke events from the popover window and dispatches the corresponding operation.\n */\nexport default Capability.makeModule(\n Effect.fnUntraced(function* () {\n const { invokePromise } = yield* Capabilities.OperationInvoker;\n\n const unlisten = yield* Effect.promise(async () => {\n const { listen } = await import('@tauri-apps/api/event');\n const { getCurrentWindow } = await import('@tauri-apps/api/window');\n\n return listen<SpotlightInvokePayload>('spotlight:invoke', async (event) => {\n const { operation, payload } = event.payload;\n log.info('Received spotlight invoke event', { operation, payload });\n try {\n switch (operation) {\n case 'open':\n await invokePromise(LayoutOperation.Open, Schema.decodeUnknownSync(LayoutOperation.Open.input)(payload));\n break;\n case 'switch-workspace':\n await invokePromise(\n LayoutOperation.SwitchWorkspace,\n Schema.decodeUnknownSync(LayoutOperation.SwitchWorkspace.input)(payload),\n );\n break;\n default:\n log.warn('Unknown spotlight operation', { operation });\n }\n\n const mainWindow = getCurrentWindow();\n await mainWindow.show();\n await mainWindow.setFocus();\n } catch (err) {\n log.catch(err);\n }\n });\n });\n\n yield* Effect.addFinalizer(() =>\n Effect.sync(() => {\n unlisten();\n }),\n );\n return [];\n }),\n);\n"],"mappings":";;;;;;;;;;;AAqBA,IAAA,6BAAe,WAAW,WACxB,OAAO,WAAW,aAAa;CAC7B,MAAM,EAAE,kBAAkB,OAAO,aAAa;CAE9C,MAAM,WAAW,OAAO,OAAO,QAAQ,YAAY;EACjD,MAAM,EAAE,WAAW,MAAM,OAAO;EAChC,MAAM,EAAE,qBAAqB,MAAM,OAAO;EAE1C,OAAO,OAA+B,oBAAoB,OAAO,UAAU;GACzE,MAAM,EAAE,WAAW,YAAY,MAAM;GACrC,IAAI,KAAK,mCAAmC;IAAE;IAAW;GAAQ,GAAA;IAAA,YAAA;IAAA,GAAA;IAAA,GAAA;IAAA,GAAA;GAAA,CAAC;GAClE,IAAI;IACF,QAAQ,WAAR;KACE,KAAK;MACH,MAAM,cAAc,gBAAgB,MAAM,OAAO,kBAAkB,gBAAgB,KAAK,KAAK,CAAC,CAAC,OAAO,CAAC;MACvG;KACF,KAAK;MACH,MAAM,cACJ,gBAAgB,iBAChB,OAAO,kBAAkB,gBAAgB,gBAAgB,KAAK,CAAC,CAAC,OAAO,CACzE;MACA;KACF,SACE,IAAI,KAAK,+BAA+B,EAAE,UAAU,GAAA;MAAA,YAAA;MAAA,GAAA;MAAA,GAAA;MAAA,GAAA;KAAA,CAAC;IACzD;IAEA,MAAM,aAAa,iBAAiB;IACpC,MAAM,WAAW,KAAK;IACtB,MAAM,WAAW,SAAS;GAC5B,SAAS,KAAK;IACZ,IAAI,MAAM,KAAE,KAAA,GAAA;KAAA,YAAA;KAAA,GAAA;KAAA,GAAA;KAAA,GAAA;IAAA,CAAC;GACf;EACF,CAAC;CACH,CAAC;CAED,OAAO,OAAO,mBACZ,OAAO,WAAW;EAChB,SAAS;CACX,CAAC,CACH;CACA,OAAO,CAAC;AACV,CAAC,CACH"}
|
|
@@ -5,8 +5,8 @@ import { meta } from "#meta";
|
|
|
5
5
|
import * as Duration from "effect/Duration";
|
|
6
6
|
import * as Effect from "effect/Effect";
|
|
7
7
|
import * as Fiber from "effect/Fiber";
|
|
8
|
+
import * as Atom from "effect/reactivity/Atom";
|
|
8
9
|
import * as Schedule from "effect/Schedule";
|
|
9
|
-
import * as Atom from "effect/unstable/reactivity/Atom";
|
|
10
10
|
import { log } from "@dxos/log";
|
|
11
11
|
import * as LayoutOperation from "@dxos/app-toolkit/LayoutOperation";
|
|
12
12
|
import { type } from "@tauri-apps/plugin-os";
|
|
@@ -18,7 +18,8 @@ import * as Match from "effect/Match";
|
|
|
18
18
|
* Ports the Tauri localhost asset server binds, one per release channel — each channel installs as its
|
|
19
19
|
* own app and needs its own origin, since a shared port let whichever app bound it first serve its code
|
|
20
20
|
* to the others.
|
|
21
|
-
* Must match `ReleaseChannel::localhost_port` in `src-tauri/src/channel.rs
|
|
21
|
+
* Must match `ReleaseChannel::localhost_port` in `src-tauri/src/channel.rs`, except the test channel's 26781:
|
|
22
|
+
* an automation build must not update itself into a shipped one.
|
|
22
23
|
*/
|
|
23
24
|
var TAURI_LOCALHOST_PORTS = [
|
|
24
25
|
"26777",
|
|
@@ -131,30 +132,32 @@ var updater_default = Capability.makeModule(Effect.fnUntraced(function* () {
|
|
|
131
132
|
if (!update) return false;
|
|
132
133
|
let downloaded = 0;
|
|
133
134
|
let contentLength = 0;
|
|
134
|
-
registry.set(statusAtom, {
|
|
135
|
-
kind: "downloading",
|
|
136
|
-
downloaded: 0,
|
|
137
|
-
contentLength: 0
|
|
138
|
-
});
|
|
135
|
+
registry.set(statusAtom, { kind: "downloading" });
|
|
139
136
|
const result = await safeDownloadAndInstall(update, Match.type().pipe(Match.when({ event: "Started" }, (event) => {
|
|
140
137
|
contentLength = event.data.contentLength ?? 0;
|
|
141
138
|
registry.set(statusAtom, {
|
|
142
139
|
kind: "downloading",
|
|
143
|
-
|
|
144
|
-
|
|
140
|
+
progress: {
|
|
141
|
+
completed: downloaded,
|
|
142
|
+
total: contentLength,
|
|
143
|
+
unit: "bytes"
|
|
144
|
+
}
|
|
145
145
|
});
|
|
146
146
|
}), Match.when({ event: "Progress" }, (event) => {
|
|
147
147
|
downloaded += event.data.chunkLength;
|
|
148
148
|
registry.set(statusAtom, {
|
|
149
149
|
kind: "downloading",
|
|
150
|
-
|
|
151
|
-
|
|
150
|
+
progress: {
|
|
151
|
+
completed: downloaded,
|
|
152
|
+
total: contentLength,
|
|
153
|
+
unit: "bytes"
|
|
154
|
+
}
|
|
152
155
|
});
|
|
153
156
|
}), Match.when({ event: "Finished" }, () => {
|
|
154
157
|
log.info("download completed", void 0, {
|
|
155
158
|
"~LogMeta": "~LogMeta",
|
|
156
159
|
F: __dxlog_file,
|
|
157
|
-
L:
|
|
160
|
+
L: 128,
|
|
158
161
|
S: this
|
|
159
162
|
});
|
|
160
163
|
}), Match.exhaustive));
|
|
@@ -178,7 +181,7 @@ var updater_default = Capability.makeModule(Effect.fnUntraced(function* () {
|
|
|
178
181
|
if (!enabled) return;
|
|
179
182
|
await doInstall();
|
|
180
183
|
},
|
|
181
|
-
|
|
184
|
+
apply: async () => {
|
|
182
185
|
await relaunch();
|
|
183
186
|
}
|
|
184
187
|
};
|
|
@@ -190,7 +193,7 @@ var updater_default = Capability.makeModule(Effect.fnUntraced(function* () {
|
|
|
190
193
|
}, {
|
|
191
194
|
"~LogMeta": "~LogMeta",
|
|
192
195
|
F: __dxlog_file,
|
|
193
|
-
L:
|
|
196
|
+
L: 163,
|
|
194
197
|
S: this
|
|
195
198
|
});
|
|
196
199
|
return [managerContribution];
|
|
@@ -215,7 +218,7 @@ var updater_default = Capability.makeModule(Effect.fnUntraced(function* () {
|
|
|
215
218
|
log.info("updater module initialized, update check scheduled", void 0, {
|
|
216
219
|
"~LogMeta": "~LogMeta",
|
|
217
220
|
F: __dxlog_file,
|
|
218
|
-
L:
|
|
221
|
+
L: 195,
|
|
219
222
|
S: this
|
|
220
223
|
});
|
|
221
224
|
yield* Effect.addFinalizer(() => Fiber.interrupt(fiber));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chunk-updater.mjs","names":[],"sources":["../../src/constants.ts","../../src/capabilities/updater.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\n/**\n * Ports the Tauri localhost asset server binds, one per release channel — each channel installs as its\n * own app and needs its own origin, since a shared port let whichever app bound it first serve its code\n * to the others.\n * Must match `ReleaseChannel::localhost_port` in `src-tauri/src/channel.rs`.\n */\nexport const TAURI_LOCALHOST_PORTS = ['26777', '26778', '26779', '26780'];\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type } from '@tauri-apps/plugin-os';\nimport { relaunch } from '@tauri-apps/plugin-process';\nimport * as Updater from '@tauri-apps/plugin-updater';\nimport * as Duration from 'effect/Duration';\nimport * as Effect from 'effect/Effect';\nimport * as Fiber from 'effect/Fiber';\nimport * as Match from 'effect/Match';\nimport * as Schedule from 'effect/Schedule';\nimport * as Atom from 'effect/unstable/reactivity/Atom';\n\nimport * as Capabilities from '@dxos/app-framework/Capabilities';\nimport * as Capability from '@dxos/app-framework/Capability';\nimport * as LayoutOperation from '@dxos/app-toolkit/LayoutOperation';\nimport { log } from '@dxos/log';\n\nimport { meta } from '#meta';\nimport { NativeCapabilities, Update } from '#types';\n\nimport { TAURI_LOCALHOST_PORTS } from '../constants';\n\nconst SUPPORTS_OTA = ['linux', 'macos', 'windows'];\n\n/** Safe wrapper around Updater.check(). Distinguishes \"no update\" (ok with null update) from a thrown error. */\nconst safeCheck = async (): Promise<{ ok: true; update: Updater.Update | null } | { ok: false; error: string }> => {\n try {\n return { ok: true, update: await Updater.check() };\n } catch (error) {\n log.error('failed to check for updates', { error });\n return { ok: false, error: formatError(error) };\n }\n};\n\n/** Safe wrapper around update.downloadAndInstall(). */\nconst safeDownloadAndInstall = async (\n update: Updater.Update,\n onEvent: (event: Updater.DownloadEvent) => void,\n): Promise<{ ok: true } | { ok: false; error: string }> => {\n try {\n await update.downloadAndInstall(onEvent);\n return { ok: true };\n } catch (error) {\n log.error('failed to download and install update', { error });\n return { ok: false, error: formatError(error) };\n }\n};\n\n/** Extract a user-readable error string from whatever the Tauri updater threw. */\nconst formatError = (error: unknown): string => {\n if (typeof error === 'string') {\n return error;\n }\n if (error instanceof Error) {\n return error.message;\n }\n return String(error);\n};\n\nexport default Capability.makeModule(\n Effect.fnUntraced(function* () {\n const platform = type();\n const isDevServer = !TAURI_LOCALHOST_PORTS.includes(window.location.port);\n const enabled = SUPPORTS_OTA.includes(platform) && !isDevServer;\n\n const registry = yield* Capabilities.AtomRegistry;\n const { invoke } = yield* Capabilities.OperationInvoker;\n\n // The two disabled states are distinct to the reader: a dev server on macOS would update fine\n // once packaged, so reporting it as an unsupported platform is wrong.\n const disabledStatus: Update.Status = SUPPORTS_OTA.includes(platform) ? { kind: 'dev' } : { kind: 'unsupported' };\n\n const statusAtom = Atom.make<Update.Status>(enabled ? { kind: 'idle' } : disabledStatus).pipe(Atom.keepAlive);\n\n // Updater.Update is a class with instance methods (downloadAndInstall) and can't live in an\n // atom value; cache it here between check and install.\n let pendingUpdate: Updater.Update | null = null;\n\n // Core: check for an update and update the status atom. Returns the update if available.\n const doCheck = async (): Promise<Updater.Update | null> => {\n registry.set(statusAtom, { kind: 'checking' });\n log.info('checking for updates');\n const result = await safeCheck();\n if (!result.ok) {\n pendingUpdate = null;\n registry.set(statusAtom, { kind: 'failed', error: result.error });\n return null;\n }\n const update = result.update;\n if (!update?.available) {\n pendingUpdate = null;\n registry.set(statusAtom, { kind: 'up-to-date', checkedAt: Date.now() });\n return null;\n }\n log.info('update available', { version: update.version });\n pendingUpdate = update;\n registry.set(statusAtom, { kind: 'available', version: update.version });\n return update;\n };\n\n // Core: download + install the cached pending update, streaming progress to the status atom.\n const doInstall = async (): Promise<boolean> => {\n const update = pendingUpdate;\n if (!update) {\n return false;\n }\n let downloaded = 0;\n let contentLength = 0;\n registry.set(statusAtom, { kind: 'downloading', downloaded: 0, contentLength: 0 });\n const onEvent = Match.type<Updater.DownloadEvent>().pipe(\n Match.when({ event: 'Started' }, (event) => {\n contentLength = event.data.contentLength ?? 0;\n registry.set(statusAtom, { kind: 'downloading', downloaded, contentLength });\n }),\n Match.when({ event: 'Progress' }, (event) => {\n downloaded += event.data.chunkLength;\n registry.set(statusAtom, { kind: 'downloading', downloaded, contentLength });\n }),\n Match.when({ event: 'Finished' }, () => {\n log.info('download completed');\n }),\n Match.exhaustive,\n );\n const result = await safeDownloadAndInstall(update, onEvent);\n if (result.ok) {\n registry.set(statusAtom, { kind: 'ready' });\n return true;\n }\n registry.set(statusAtom, { kind: 'failed', error: result.error });\n return false;\n };\n\n const manager: Update.Manager = {\n status: statusAtom,\n check: async () => {\n if (!enabled) {\n return;\n }\n await doCheck();\n },\n install: async () => {\n if (!enabled) {\n return;\n }\n await doInstall();\n },\n relaunch: async () => {\n await relaunch();\n },\n };\n\n const managerContribution = Capability.contribute(NativeCapabilities.UpdateManager, manager);\n\n if (!enabled) {\n log.info('updater disabled', { platform, port: window.location.port });\n return [managerContribution];\n }\n\n // Background flow: periodic check + auto-install + toast when ready.\n // The toast is the entry point for users who weren't watching the settings panel.\n const backgroundAction = Effect.gen(function* () {\n const update = yield* Effect.promise(() => doCheck());\n if (!update) {\n return true;\n }\n const ok = yield* Effect.promise(() => doInstall());\n if (!ok) {\n return true;\n }\n yield* invoke(LayoutOperation.AddToast, {\n id: `${meta.profile.key}.update-ready`,\n title: ['update-ready.label', { ns: meta.profile.key }],\n description: ['update-ready.description', { ns: meta.profile.key }],\n duration: Infinity,\n actionLabel: ['update.label', { ns: meta.profile.key }],\n actionAlt: ['update.alt', { ns: meta.profile.key }],\n onAction: () => relaunch(),\n });\n return false;\n });\n\n // v4 moved the output predicate off the schedule and onto `repeat`.\n const fiber = yield* backgroundAction.pipe(\n Effect.repeat({ schedule: Schedule.fixed(Duration.hours(1)), while: (keepChecking) => keepChecking }),\n Effect.forkDetach,\n );\n log.info('updater module initialized, update check scheduled');\n\n // Fiber.interrupt is async and would throw AsyncFiberException if wrapped in Effect.runSync,\n // so the finalizer returns the interruption effect directly.\n yield* Effect.addFinalizer(() => Fiber.interrupt(fiber));\n\n return managerContribution;\n }),\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chunk-updater.mjs","names":[],"sources":["../../src/constants.ts","../../src/capabilities/updater.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\n/**\n * Ports the Tauri localhost asset server binds, one per release channel — each channel installs as its\n * own app and needs its own origin, since a shared port let whichever app bound it first serve its code\n * to the others.\n * Must match `ReleaseChannel::localhost_port` in `src-tauri/src/channel.rs`, except the test channel's 26781:\n * an automation build must not update itself into a shipped one.\n */\nexport const TAURI_LOCALHOST_PORTS = ['26777', '26778', '26779', '26780'];\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type } from '@tauri-apps/plugin-os';\nimport { relaunch } from '@tauri-apps/plugin-process';\nimport * as Updater from '@tauri-apps/plugin-updater';\nimport * as Duration from 'effect/Duration';\nimport * as Effect from 'effect/Effect';\nimport * as Fiber from 'effect/Fiber';\nimport * as Match from 'effect/Match';\nimport * as Atom from 'effect/reactivity/Atom';\nimport * as Schedule from 'effect/Schedule';\n\nimport * as Capabilities from '@dxos/app-framework/Capabilities';\nimport * as Capability from '@dxos/app-framework/Capability';\nimport * as LayoutOperation from '@dxos/app-toolkit/LayoutOperation';\nimport { log } from '@dxos/log';\n\nimport { meta } from '#meta';\nimport { NativeCapabilities, Update } from '#types';\n\nimport { TAURI_LOCALHOST_PORTS } from '../constants.ts';\n\nconst SUPPORTS_OTA = ['linux', 'macos', 'windows'];\n\n/** Safe wrapper around Updater.check(). Distinguishes \"no update\" (ok with null update) from a thrown error. */\nconst safeCheck = async (): Promise<{ ok: true; update: Updater.Update | null } | { ok: false; error: string }> => {\n try {\n return { ok: true, update: await Updater.check() };\n } catch (error) {\n log.error('failed to check for updates', { error });\n return { ok: false, error: formatError(error) };\n }\n};\n\n/** Safe wrapper around update.downloadAndInstall(). */\nconst safeDownloadAndInstall = async (\n update: Updater.Update,\n onEvent: (event: Updater.DownloadEvent) => void,\n): Promise<{ ok: true } | { ok: false; error: string }> => {\n try {\n await update.downloadAndInstall(onEvent);\n return { ok: true };\n } catch (error) {\n log.error('failed to download and install update', { error });\n return { ok: false, error: formatError(error) };\n }\n};\n\n/** Extract a user-readable error string from whatever the Tauri updater threw. */\nconst formatError = (error: unknown): string => {\n if (typeof error === 'string') {\n return error;\n }\n if (error instanceof Error) {\n return error.message;\n }\n return String(error);\n};\n\nexport default Capability.makeModule(\n Effect.fnUntraced(function* () {\n const platform = type();\n const isDevServer = !TAURI_LOCALHOST_PORTS.includes(window.location.port);\n const enabled = SUPPORTS_OTA.includes(platform) && !isDevServer;\n\n const registry = yield* Capabilities.AtomRegistry;\n const { invoke } = yield* Capabilities.OperationInvoker;\n\n // The two disabled states are distinct to the reader: a dev server on macOS would update fine\n // once packaged, so reporting it as an unsupported platform is wrong.\n const disabledStatus: Update.Status = SUPPORTS_OTA.includes(platform) ? { kind: 'dev' } : { kind: 'unsupported' };\n\n const statusAtom = Atom.make<Update.Status>(enabled ? { kind: 'idle' } : disabledStatus).pipe(Atom.keepAlive);\n\n // Updater.Update is a class with instance methods (downloadAndInstall) and can't live in an\n // atom value; cache it here between check and install.\n let pendingUpdate: Updater.Update | null = null;\n\n // Core: check for an update and update the status atom. Returns the update if available.\n const doCheck = async (): Promise<Updater.Update | null> => {\n registry.set(statusAtom, { kind: 'checking' });\n log.info('checking for updates');\n const result = await safeCheck();\n if (!result.ok) {\n pendingUpdate = null;\n registry.set(statusAtom, { kind: 'failed', error: result.error });\n return null;\n }\n const update = result.update;\n if (!update?.available) {\n pendingUpdate = null;\n registry.set(statusAtom, { kind: 'up-to-date', checkedAt: Date.now() });\n return null;\n }\n log.info('update available', { version: update.version });\n pendingUpdate = update;\n registry.set(statusAtom, { kind: 'available', version: update.version });\n return update;\n };\n\n // Core: download + install the cached pending update, streaming progress to the status atom.\n const doInstall = async (): Promise<boolean> => {\n const update = pendingUpdate;\n if (!update) {\n return false;\n }\n let downloaded = 0;\n let contentLength = 0;\n registry.set(statusAtom, { kind: 'downloading' });\n const onEvent = Match.type<Updater.DownloadEvent>().pipe(\n Match.when({ event: 'Started' }, (event) => {\n contentLength = event.data.contentLength ?? 0;\n registry.set(statusAtom, {\n kind: 'downloading',\n progress: { completed: downloaded, total: contentLength, unit: 'bytes' },\n });\n }),\n Match.when({ event: 'Progress' }, (event) => {\n downloaded += event.data.chunkLength;\n registry.set(statusAtom, {\n kind: 'downloading',\n progress: { completed: downloaded, total: contentLength, unit: 'bytes' },\n });\n }),\n Match.when({ event: 'Finished' }, () => {\n log.info('download completed');\n }),\n Match.exhaustive,\n );\n const result = await safeDownloadAndInstall(update, onEvent);\n if (result.ok) {\n registry.set(statusAtom, { kind: 'ready' });\n return true;\n }\n registry.set(statusAtom, { kind: 'failed', error: result.error });\n return false;\n };\n\n const manager: Update.Manager = {\n status: statusAtom,\n check: async () => {\n if (!enabled) {\n return;\n }\n await doCheck();\n },\n install: async () => {\n if (!enabled) {\n return;\n }\n await doInstall();\n },\n apply: async () => {\n await relaunch();\n },\n };\n\n const managerContribution = Capability.contribute(NativeCapabilities.UpdateManager, manager);\n\n if (!enabled) {\n log.info('updater disabled', { platform, port: window.location.port });\n return [managerContribution];\n }\n\n // Background flow: periodic check + auto-install + toast when ready.\n // The toast is the entry point for users who weren't watching the settings panel.\n const backgroundAction = Effect.gen(function* () {\n const update = yield* Effect.promise(() => doCheck());\n if (!update) {\n return true;\n }\n const ok = yield* Effect.promise(() => doInstall());\n if (!ok) {\n return true;\n }\n yield* invoke(LayoutOperation.AddToast, {\n id: `${meta.profile.key}.update-ready`,\n title: ['update-ready.label', { ns: meta.profile.key }],\n description: ['update-ready.description', { ns: meta.profile.key }],\n duration: Infinity,\n actionLabel: ['update.label', { ns: meta.profile.key }],\n actionAlt: ['update.alt', { ns: meta.profile.key }],\n onAction: () => relaunch(),\n });\n return false;\n });\n\n // v4 moved the output predicate off the schedule and onto `repeat`.\n const fiber = yield* backgroundAction.pipe(\n Effect.repeat({ schedule: Schedule.fixed(Duration.hours(1)), while: (keepChecking) => keepChecking }),\n Effect.forkDetach,\n );\n log.info('updater module initialized, update check scheduled');\n\n // Fiber.interrupt is async and would throw AsyncFiberException if wrapped in Effect.runSync,\n // so the finalizer returns the interruption effect directly.\n yield* Effect.addFinalizer(() => Fiber.interrupt(fiber));\n\n return managerContribution;\n }),\n);\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"containers.mjs","names":[],"sources":["../../src/containers/index.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport { type ComponentType, lazy } from 'react';\n\nexport const NativeSettings: ComponentType<any> = lazy(() => import('./NativeSettings'));\n"],"mappings":";;AAMA,IAAa,iBAAqC,WAAW,OAAO,
|
|
1
|
+
{"version":3,"file":"containers.mjs","names":[],"sources":["../../src/containers/index.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport { type ComponentType, lazy } from 'react';\n\nexport const NativeSettings: ComponentType<any> = lazy(() => import('./NativeSettings/index.ts'));\n"],"mappings":";;AAMA,IAAa,iBAAqC,WAAW,OAAO,6BAA4B"}
|
package/dist/lib/index.mjs
CHANGED
package/dist/lib/meta.mjs
CHANGED
|
@@ -17,7 +17,7 @@ var dx_config_default = Config2.make({ plugin: {
|
|
|
17
17
|
checking for new releases in the background, streaming download progress, and surfacing
|
|
18
18
|
a toast notification with a one-click Relaunch action when an update is ready.
|
|
19
19
|
|
|
20
|
-
Spawns
|
|
20
|
+
Spawns the bundled Ollama process on localhost for local AI model inference, registering
|
|
21
21
|
it as an AiModelResolver so Assistant features can resolve model requests without any
|
|
22
22
|
external server dependency.
|
|
23
23
|
`,
|