@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.
Files changed (77) hide show
  1. package/PLUGIN.mdl +64 -16
  2. package/dist/lib/NativeCapabilities.mjs +1 -1
  3. package/dist/lib/NativeCapabilities.mjs.map +1 -1
  4. package/dist/lib/Update.mjs.map +1 -1
  5. package/dist/lib/capabilities.browser.mjs +42 -0
  6. package/dist/lib/capabilities.browser.mjs.map +1 -0
  7. package/dist/lib/capabilities.mjs +1 -3
  8. package/dist/lib/capabilities.mjs.map +1 -1
  9. package/dist/lib/capabilities.node.mjs.map +1 -1
  10. package/dist/lib/capabilities.tauri.mjs +42 -0
  11. package/dist/lib/capabilities.tauri.mjs.map +1 -0
  12. package/dist/lib/capabilities.workerd.mjs.map +1 -1
  13. package/dist/lib/chunk-NativeSettings.mjs +15 -77
  14. package/dist/lib/chunk-NativeSettings.mjs.map +1 -1
  15. package/dist/lib/chunk-PLUGIN.mjs +6 -0
  16. package/dist/lib/chunk-PLUGIN.mjs.map +1 -0
  17. package/dist/lib/chunk-ollama.mjs +75 -41
  18. package/dist/lib/chunk-ollama.mjs.map +1 -1
  19. package/dist/lib/chunk-react-surface.mjs +2 -2
  20. package/dist/lib/chunk-react-surface.mjs.map +1 -1
  21. package/dist/lib/chunk-settings.mjs +2 -2
  22. package/dist/lib/chunk-settings.mjs.map +1 -1
  23. package/dist/lib/chunk-spotlight-listener.mjs +6 -5
  24. package/dist/lib/chunk-spotlight-listener.mjs.map +1 -1
  25. package/dist/lib/chunk-updater.mjs +18 -15
  26. package/dist/lib/chunk-updater.mjs.map +1 -1
  27. package/dist/lib/containers.mjs.map +1 -1
  28. package/dist/lib/index.mjs +0 -1
  29. package/dist/lib/meta.mjs +1 -1
  30. package/dist/lib/meta.mjs.map +1 -1
  31. package/dist/lib/translations.mjs.map +1 -1
  32. package/dist/types/src/capabilities/gen/browser.d.ts +9 -0
  33. package/dist/types/src/capabilities/gen/browser.d.ts.map +1 -0
  34. package/dist/types/src/capabilities/gen/tauri.d.ts +9 -0
  35. package/dist/types/src/capabilities/gen/tauri.d.ts.map +1 -0
  36. package/dist/types/src/capabilities/ollama.d.ts.map +1 -1
  37. package/dist/types/src/capabilities/spotlight-listener.d.ts.map +1 -1
  38. package/dist/types/src/capabilities/updater.d.ts +1 -1
  39. package/dist/types/src/constants.d.ts +2 -1
  40. package/dist/types/src/constants.d.ts.map +1 -1
  41. package/dist/types/src/containers/NativeSettings/NativeSettings.d.ts +1 -1
  42. package/dist/types/src/containers/NativeSettings/NativeSettings.d.ts.map +1 -1
  43. package/dist/types/src/containers/NativeSettings/index.d.ts +1 -1
  44. package/dist/types/src/containers/NativeSettings/index.d.ts.map +1 -1
  45. package/dist/types/src/containers/index.d.ts.map +1 -1
  46. package/dist/types/src/index.d.ts +1 -2
  47. package/dist/types/src/index.d.ts.map +1 -1
  48. package/dist/types/src/translations.d.ts.map +1 -1
  49. package/dist/types/src/types/NativeCapabilities.d.ts +3 -3
  50. package/dist/types/src/types/NativeCapabilities.d.ts.map +1 -1
  51. package/dist/types/src/types/Update.d.ts +1 -31
  52. package/dist/types/src/types/Update.d.ts.map +1 -1
  53. package/dist/types/src/types/index.d.ts +4 -4
  54. package/dist/types/src/types/index.d.ts.map +1 -1
  55. package/dist/types/tsconfig.tsbuildinfo +1 -1
  56. package/dx.config.ts +1 -1
  57. package/package.json +23 -22
  58. package/src/capabilities/gen/browser.ts +66 -0
  59. package/src/capabilities/gen/node.ts +4 -4
  60. package/src/capabilities/gen/tauri.ts +66 -0
  61. package/src/capabilities/gen/workerd.ts +4 -4
  62. package/src/capabilities/index.ts +5 -5
  63. package/src/capabilities/ollama.ts +65 -35
  64. package/src/capabilities/react-surface.ts +2 -2
  65. package/src/capabilities/settings.ts +2 -2
  66. package/src/capabilities/spotlight-listener.ts +8 -4
  67. package/src/capabilities/updater.ts +12 -6
  68. package/src/constants.ts +2 -1
  69. package/src/containers/NativeSettings/NativeSettings.tsx +17 -120
  70. package/src/containers/NativeSettings/index.ts +1 -1
  71. package/src/containers/index.ts +1 -1
  72. package/src/index.ts +1 -2
  73. package/src/meta.ts +1 -1
  74. package/src/translations.ts +2 -2
  75. package/src/types/NativeCapabilities.ts +5 -5
  76. package/src/types/Update.ts +4 -21
  77. 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(OllamaSidecar.layerLive);
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) => updateState((state) => ({
41
- ...state,
42
- kind: "failed",
43
- error
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: 114,
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: 134,
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: 145,
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: 175,
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: 178,
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: 183,
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: 197,
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: 212,
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: 215,
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: 228,
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: 265,
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 OllamaSidecar extends Context.Service()("@dxos/plugin-native/OllamaSidecar") {
256
- static layerLive = Layer.effect(OllamaSidecar, Effect.gen(function* () {
257
- const command = Command.sidecar("sidecar/ollama", ["serve"], { env: {
258
- OLLAMA_HOST,
259
- OLLAMA_ORIGINS: "*"
260
- } });
261
- command.stdout.on("data", (data) => logSidecar(data.toString()));
262
- command.stderr.on("data", (data) => logSidecar(data.toString()));
263
- command.on("close", (code) => log.info("Ollama process exited", { code }, {
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: 302,
280
+ L: 322,
267
281
  S: this
268
- }));
269
- command.on("error", (error) => log.error("Ollama error", { error }, {
282
+ });
283
+ else log.warn("Ollama process exited", {
284
+ code,
285
+ signal
286
+ }, {
270
287
  "~LogMeta": "~LogMeta",
271
288
  F: __dxlog_file,
272
- L: 303,
289
+ L: 324,
273
290
  S: this
274
- }));
275
- const child = yield* Effect.promise(() => command.spawn());
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
- yield* Effect.promise(() => child.kill());
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: 310,
316
+ L: 340,
283
317
  S: this
284
318
  });
285
- return { endpoint: OLLAMA_HOST };
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":";;;;;;;;;;;;;;;;;;;;;;;;AA8BA,IAAM,cAAc;AAMpB,IAAA,iBAAe,WAAW,WACxB,OAAO,WAAW,aAAa;CAC7B,MAAM,WAAW,OAAO,aAAa;CAErC,MAAM,UAAU,eAAe,KAAK,cAAc,SAAS;CAG3D,MAAM,eAAe,MAAM,cAAc,QAAQ,cAAc,KAAK,OAAO,IAAI,QAAQ,KAAK,aAAa,CAAC,CAAC,CAAC;CAE5G,MAAM,QAAQ,YAAY,KAAK,EAAE,UAAU,YAAY,CAAC;CACxD,MAAM,YAAY,KAAK,KAAyB;EAC9C,MAAM;EACN,QAAQ,CAAC;EACT,QAAQ,CAAC;EACT,OAAO,CAAC;EACR,QAAQ,CAAC;CACX,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS;CAGtB,MAAM,WAAW,OAAO,WAAW,SAAS,IAAI,SAAS,CAAC;CAC1D,MAAM,eAAe,MACnB,OAAO,WAAW,SAAS,IAAI,WAAW,EAAE,SAAS,IAAI,SAAS,CAAC,CAAC,CAAC;CAGvE,MAAM,QAAQ,UAAuC,aAAa,WAAW;EAAE,GAAG;EAAO,MAAM;EAAU;CAAM,EAAE;CAIjH,MAAM,YAAY,MAAc,UAC9B,aAAa,UAAU;EACrB,MAAM,SAAS,EAAE,GAAG,MAAM,OAAO;EACjC,IAAI,OACF,OAAO,QAAQ;OAEf,OAAO,OAAO;EAEhB,OAAO;GAAE,GAAG;GAAO;EAAO;CAC5B,CAAC;CAGH,MAAM,eAAe,MAAc,aACjC,aAAa,UAAU;EACrB,MAAM,QAAQ,EAAE,GAAG,MAAM,MAAM;EAC/B,IAAI,UACF,MAAM,QAAQ;OAEd,OAAO,MAAM;EAEf,OAAO;GAAE,GAAG;GAAO;EAAM;CAC3B,CAAC;CAMH,MAAM,gBAAqC,OAAO,OAAO,aAAa,CAAC,CAAC,KAAK,OAAO,QAAQ,YAAY,CAAC;CAIzG,MAAM,YACJ,WAEA,OAAO,KAAK,OAAO,QAAQ,WAAW,GAAG,OAAO,QAAQ,OAAO,IAAI,OAAO,UAAU,UAAU,MAAM,OAAO,CAAC,CAAC;CAG/G,MAAM,6BAAa,IAAI,IAA+B;CAItD,MAAM,gBAAqC,OAAO,IAAI,aAAa;EACjE,MAAM,SAAS,OAAO,SAAS,MAAM,EAAE;EACvC,IAAI,OAAO,UAAU,MAAM,GACzB;EAEF,MAAM,UAAU,OAAO,SAAA,CAAU,OAAO,KAAK,UAAU,MAAM,IAAI;EACjE,MAAM,QAAQ,OAAO,QAAQ,KAAK,UAAU,MAAM,IAAI;EACtD,IAAI,OAAO,KAAK,MAAM,MAAM,KAAK,GAC/B,OAAO,OAAO,WAAW,IAAI,KAAK,gCAAgC,EAAE,QAAQ,MAAM,GAAA;GAAA,YAAA;GAAA,GAAA;GAAA,GAAA;GAAA,GAAA;EAAA,CAAC,CAAC;EAEtF,OAAO,aAAa,WAAW;GAAE,GAAG;GAAO,QAAQ,OAAO;EAAQ,EAAE;CACtE,CAAC;CAED,MAAM,UAA+B,OAAO,IAAI,aAAa;EAC3D,OAAO,aAAa,WAAW;GAAE,GAAG;GAAO,MAAM;GAAW,OAAO,KAAA;EAAU,EAAE;EAC/E,MAAM,UAAU,OAAO,OAAO,KAAK,aAAa;EAChD,IAAI,KAAK,UAAU,OAAO,GACxB,OAAO,OAAO,KAAK,YAAY,MAAM,OAAO,QAAQ,KAAK,CAAC,CAAC;EAI7D,MAAM,SAAS,OAAO,SACpB,MAAM,KAAK,KAAK,OAAO,MAAM;GAAE,UAAU,SAAS,OAAO,SAAS,OAAO,GAAG,CAAC;GAAG,OAAO;EAAG,CAAC,CAAC,CAC9F;EACA,IAAI,OAAO,UAAU,MAAM,GACzB,OAAO,OAAO,KAAK,OAAO,OAAO;EAEnC,OAAO,aAAa,WAAW;GAAE,GAAG;GAAO,MAAM;GAAS,QAAQ,OAAO;GAAS,OAAO,KAAA;EAAU,EAAE;EACrG,OAAO,OAAO,WAAW,IAAI,KAAK,iBAAiB,EAAE,WAAW,OAAO,QAAQ,KAAK,UAAU,MAAM,IAAI,EAAE,GAAA;GAAA,YAAA;GAAA,GAAA;GAAA,GAAA;GAAA,GAAA;EAAA,CAAC,CAAC;EAC5G,OAAO;CACT,CAAC;CAED,MAAM,QAAQ,SACZ,OAAO,IAAI,aAAa;EACtB,MAAM,UAAU,OAAO,OAAO,KAAK,aAAa;EAChD,IAAI,KAAK,UAAU,OAAO,GACxB,OAAO,OAAO,KAAK,YAAY,MAAM,OAAO,QAAQ,KAAK,CAAC,CAAC;EAE7D,OAAO,SAAS,MAAM,KAAA,CAAS;EAC/B,OAAO,OAAO,WAAW,IAAI,KAAK,uBAAuB,EAAE,KAAK,GAAA;GAAA,YAAA;GAAA,GAAA;GAAA,GAAA;GAAA,GAAA;EAAA,CAAC,CAAC;EAClE,OAAO,YAAY,MAAM,EAAE,QAAQ,WAAW,CAAC;EAI/C,MAAM,6BAAa,IAAI,IAAkD;EACzE,MAAM,cAAc,aAClB,OAAO,cAAc;GACnB,IAAI,SAAS,UAAU,SAAS,OAAO;IACrC,WAAW,IAAI,SAAS,QAAQ;KAAE,WAAW,SAAS,aAAa;KAAG,OAAO,SAAS;IAAM,CAAC;IAC7F,IAAI,YAAY;IAChB,IAAI,QAAQ;IACZ,KAAK,MAAM,SAAS,WAAW,OAAO,GAAG;KACvC,aAAa,MAAM;KACnB,SAAS,MAAM;IACjB;IACA,OAAO,YAAY,MAAM;KAAE,QAAQ,SAAS;KAAQ;KAAW;IAAM,CAAC;GACxE;GAEA,OAAO,YAAY,MAAM,EAAE,QAAQ,SAAS,OAAO,CAAC;EACtD,CAAC;EAIH,MAAM,OAAO,MAAM,KAAK,IAAI,CAAC,CAAC,KAC5B,OAAO,WAAW,UAAU,GAC5B,OAAO,QAAQ,WAAW,GAC1B,OAAO,iBAAiB;GACtB,YAAY,UACV,MAAM,kBAAkB,KAAK,IACzB,OAAO,WAAW,IAAI,KAAK,wBAAwB;IAAE;IAAM,WAAW;GAAK,GAAA;IAAA,YAAA;IAAA,GAAA;IAAA,GAAA;IAAA,GAAA;GAAA,CAAC,CAAC,IAC7E,OAAO,IAAI,aAAa;IACtB,MAAM,UAAU,YAAY,MAAM,OAAO,KAAK,CAAC;IAC/C,OAAO,OAAO,WAAW,IAAI,KAAK,sBAAsB;KAAE;KAAM,OAAO;IAAQ,GAAA;KAAA,YAAA;KAAA,GAAA;KAAA,GAAA;KAAA,GAAA;IAAA,CAAC,CAAC;IACjF,OAAO,SAAS,MAAM,OAAO;GAC/B,CAAC;GACP,iBACE,OAAO,IAAI,aAAa;IACtB,OAAO,OAAO,WAAW,IAAI,KAAK,wBAAwB;KAAE;KAAM,WAAW;IAAM,GAAA;KAAA,YAAA;KAAA,GAAA;KAAA,GAAA;KAAA,GAAA;IAAA,CAAC,CAAC;IACrF,OAAO;GACT,CAAC;EACL,CAAC,GACD,OAAO,SAAS,YAAY,MAAM,KAAA,CAAS,CAAC,GAC5C,OAAO,SAAS,OAAO,WAAW,WAAW,OAAO,IAAI,CAAC,CAAC,CAC5D;EAEA,MAAM,QAAQ,OAAO,OAAO,WAAW,IAAI;EAC3C,OAAO,OAAO,WAAW,WAAW,IAAI,MAAM,KAAK,CAAC;CACtD,CAAC;CAEH,MAAM,UAAU,SACd,OAAO,IAAI,aAAa;EACtB,OAAO,OAAO,WAAW,IAAI,KAAK,yBAAyB,EAAE,KAAK,GAAA;GAAA,YAAA;GAAA,GAAA;GAAA,GAAA;GAAA,GAAA;EAAA,CAAC,CAAC;EACpE,MAAM,QAAQ,WAAW,IAAI,IAAI;EACjC,IAAI,OAEF,OAAO,MAAM,UAAU,KAAK;CAEhC,CAAC;CAEH,MAAM,QAAQ,SACZ,OAAO,IAAI,aAAa;EACtB,OAAO,SAAS,MAAM,KAAA,CAAS;EAC/B,MAAM,UAAU,OAAO,OAAO,KAAK,aAAa;EAChD,IAAI,KAAK,UAAU,OAAO,GACxB,OAAO,OAAO,SAAS,MAAM,YAAY,MAAM,OAAO,QAAQ,KAAK,CAAC,CAAC;EAEvE,OAAO,OAAO,WAAW,IAAI,KAAK,eAAe,EAAE,KAAK,GAAA;GAAA,YAAA;GAAA,GAAA;GAAA,GAAA;GAAA,GAAA;EAAA,CAAC,CAAC;EAC1D,MAAM,SAAS,OAAO,SAAS,MAAM,KAAK,IAAI,CAAC;EAC/C,IAAI,OAAO,UAAU,MAAM,GAAG;GAC5B,OAAO,OAAO,WAAW,IAAI,KAAK,sBAAsB;IAAE;IAAM,OAAO,OAAO;GAAQ,GAAA;IAAA,YAAA;IAAA,GAAA;IAAA,GAAA;IAAA,GAAA;GAAA,CAAC,CAAC;GACxF,OAAO,OAAO,SAAS,MAAM,OAAO,OAAO;EAC7C;EACA,OAAO;CACT,CAAC;CAEH,MAAM,UAAU,SACd,OAAO,IAAI,aAAa;EACtB,OAAO,SAAS,MAAM,KAAA,CAAS;EAC/B,MAAM,UAAU,OAAO,OAAO,KAAK,aAAa;EAChD,IAAI,KAAK,UAAU,OAAO,GACxB,OAAO,OAAO,SAAS,MAAM,YAAY,MAAM,OAAO,QAAQ,KAAK,CAAC,CAAC;EAEvE,OAAO,OAAO,WAAW,IAAI,KAAK,iBAAiB,EAAE,KAAK,GAAA;GAAA,YAAA;GAAA,GAAA;GAAA,GAAA;GAAA,GAAA;EAAA,CAAC,CAAC;EAC5D,MAAM,SAAS,OAAO,SAAS,MAAM,OAAO,IAAI,CAAC;EACjD,IAAI,OAAO,UAAU,MAAM,GACzB,OAAO,OAAO,SAAS,MAAM,OAAO,OAAO;EAE7C,OAAO;CACT,CAAC;CAEH,MAAM,UAAU,SACd,OAAO,IAAI,aAAa;EACtB,OAAO,SAAS,MAAM,KAAA,CAAS;EAC/B,MAAM,UAAU,OAAO,OAAO,KAAK,aAAa;EAChD,IAAI,KAAK,UAAU,OAAO,GACxB,OAAO,OAAO,SAAS,MAAM,YAAY,MAAM,OAAO,QAAQ,KAAK,CAAC,CAAC;EAEvE,MAAM,SAAS,OAAO,SAAS,MAAM,OAAO,IAAI,CAAC;EACjD,IAAI,OAAO,UAAU,MAAM,GACzB,OAAO,OAAO,SAAS,MAAM,OAAO,OAAO;EAE7C,OAAO;CACT,CAAC;CAEH,MAAM,UAA0B;EAC9B,UAAU;EACV,OAAO;EACP;EACA;EACA;EACA;EACA;EACA;EACA;CACF;CAGA,OAAO,OAAO,mBACZ,OAAO,iBAAiB,QAAQ,QAAQ,CAAC,CAAC,CAAC,KACzC,OAAO,OAAO,UAAU,OAAO,WAAW,IAAI,KAAK,iCAAiC,EAAE,MAAM,GAAA;EAAA,YAAA;EAAA,GAAA;EAAA,GAAA;EAAA,GAAA;CAAA,CAAC,CAAC,CAAC,CACjG,CACF;CACA,OAAO,CACL,WAAW,WACT,gBAAgB,iBAChB,2BAA2B,KAAK,MAAM,QAAQ,YAAY,CAAC,CAC7D,GACA,WAAW,WAAW,sBAAsB,eAAe,OAAO,CACpE;AACF,CAAC,CACH;AAEA,IAAM,gBAAN,MAAM,sBAAsB,QAAQ,QAKlC,CAAC,CAAC,mCAAmC,CAAC,CAAC;CACvC,OAAO,YAAY,MAAM,OACvB,eACA,OAAO,IAAI,aAAa;EAItB,MAAM,UAAU,QAAQ,QAAQ,kBAAkB,CAAC,OAAO,GAAG,EAC3D,KAAK;GACH;GACA,gBAAgB;EAClB,EACF,CAAC;EAKD,QAAQ,OAAO,GAAG,SAAS,SAAS,WAAW,KAAK,SAAS,CAAC,CAAC;EAC/D,QAAQ,OAAO,GAAG,SAAS,SAAS,WAAW,KAAK,SAAS,CAAC,CAAC;EAC/D,QAAQ,GAAG,UAAU,SAAS,IAAI,KAAK,yBAAyB,EAAE,KAAK,GAAA;GAAA,YAAA;GAAA,GAAA;GAAA,GAAA;GAAA,GAAA;EAAA,CAAC,CAAC;EACzE,QAAQ,GAAG,UAAU,UAAU,IAAI,MAAM,gBAAgB,EAAE,MAAM,GAAA;GAAA,YAAA;GAAA,GAAA;GAAA,GAAA;GAAA,GAAA;EAAA,CAAC,CAAC;EACnE,MAAM,QAAQ,OAAO,OAAO,cAAc,QAAQ,MAAM,CAAC;EACzD,OAAO,OAAO,aACZ,OAAO,GAAG,aAAa;GACrB,OAAO,OAAO,cAAc,MAAM,KAAK,CAAC;EAC1C,CAAC,CACH;EACA,IAAI,KAAK,kBAAkB,EAAE,KAAK,MAAM,IAAI,GAAA;GAAA,YAAA;GAAA,GAAA;GAAA,GAAA;GAAA,GAAA;EAAA,CAAC;EAE7C,OAAO,EACL,UAAU,YACZ;CACF,CAAC,CACH;AACF;AAEA,IAAM,6BAAiG,MAAM,OAC3G,OAAO,IAAI,aAAa;CACtB,MAAM,EAAE,aAAa,OAAO;CAC5B,OAAO,eAAe,KAAK;EACzB;EACA,UAAU,SAAS,QAAQ;EAC3B,iBAAiB,WAAW,kBAAkB,OAAO,eAAe,WAAW,0BAA0B,KAAK,CAAC;CACjH,CAAC;AACH,CAAC,CACH,CAAC,CAAC,KAAK,MAAM,QAAQ,gBAAgB,KAAK,CAAC;AAE3C,IAAM,eAAe,UACnB,OAAO,UAAU,WAAW,QAAQ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;;AAG3F,IAAM,cAAc,gBAAgB;;;;;AAMpC,IAAM,cAAc,UAAwB;CAC1C,KAAK,MAAM,QAAQ,MAAM,MAAM,IAAI,GAAG;EACpC,IAAI,KAAK,KAAK,CAAC,CAAC,WAAW,GACzB;EAGF,IAAI,KAAK,SAAS,aAAa,GAC7B,QAAQ,MAAM,YAAY,IAAI;OACzB,IAAI,KAAK,SAAS,YAAY,GACnC,QAAQ,KAAK,YAAY,IAAI;OAE7B,QAAQ,IAAI,YAAY,IAAI;CAGhC;AACF"}
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 { Surface } from "@dxos/app-framework/ui";
6
- import { AppSurface } from "@dxos/app-toolkit/ui";
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 { Surface } from '@dxos/app-framework/ui';\nimport { AppSurface } from '@dxos/app-toolkit/ui';\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"}
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 { createKvsStore } from "@dxos/effect";
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 = createKvsStore({
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 { createKvsStore } from '@dxos/effect';\n\nimport { meta } from '#meta';\nimport { NativeCapabilities, Settings } from '#types';\n\nexport default Capability.makeModule(() =>\n Effect.sync(() => {\n const settingsAtom = createKvsStore({\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,eAAe;EAClC,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
+ {"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: 31,
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: 41,
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: 48,
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// TODO(wittjosiah): Formalize with a stricter schema if we evolve this protocol.\ntype SpotlightInvokePayload = {\n operation: string;\n payload?: Record<string, any>;\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, payload as any);\n break;\n case 'switch-workspace':\n await invokePromise(LayoutOperation.SwitchWorkspace, payload as any);\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":";;;;;;;;;;AAoBA,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,OAAc;MACxD;KACF,KAAK;MACH,MAAM,cAAc,gBAAgB,iBAAiB,OAAc;MACnE;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"}
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
- downloaded,
144
- contentLength
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
- downloaded,
151
- contentLength
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: 122,
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
- relaunch: async () => {
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: 157,
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: 189,
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,6BAAmB"}
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"}
@@ -1,4 +1,3 @@
1
1
  import { t as NativePlugin_exports } from "./NativePlugin.mjs";
2
2
  export * from "#types";
3
- export * from "#meta";
4
3
  export { NativePlugin_exports as NativePlugin };
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 an Ollama sidecar process on localhost for local AI model inference, registering
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
  `,