@mem9/opencode 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,82 +2,40 @@
2
2
 
3
3
  Persistent memory for [OpenCode](https://opencode.ai).
4
4
 
5
- The package ships two plugin entrypoints:
5
+ This package has two entrypoints:
6
6
 
7
7
  - a server plugin for recall, auto-ingest, and memory tools
8
8
  - a TUI plugin for interactive setup inside OpenCode
9
9
 
10
10
  ## Quick Start
11
11
 
12
- ### 1. Install the server plugin
12
+ ### 1. Install mem9 once at user scope
13
13
 
14
- Install the server plugin in one scope only.
15
-
16
- User scope:
17
-
18
- File: `~/.config/opencode/opencode.json`
19
-
20
- ```json
21
- {
22
- "plugin": ["@mem9/opencode"]
23
- }
24
- ```
25
-
26
- Project scope:
27
-
28
- File: `<project>/.opencode/opencode.json`
29
-
30
- ```json
31
- {
32
- "plugin": ["@mem9/opencode"]
33
- }
34
- ```
35
-
36
- Recommended pattern:
37
-
38
- - install the server plugin once at user scope
39
- - keep project-specific behavior in `<project>/.opencode/mem9.json`
40
-
41
- `@mem9/opencode` resolves npm `latest`.
42
- If you publish a prerelease channel, install it with an explicit npm specifier instead:
43
-
44
- ```json
45
- {
46
- "plugin": ["@mem9/opencode@rc"]
47
- }
48
- ```
49
-
50
- You can also pin an exact prerelease version:
51
-
52
- ```json
53
- {
54
- "plugin": ["@mem9/opencode@0.1.0-rc.0"]
55
- }
14
+ ```bash
15
+ opencode plugin --global @mem9/opencode
56
16
  ```
57
17
 
58
- Avoid duplicate plugin loading, such as:
18
+ That adds `@mem9/opencode` to these files:
59
19
 
60
- - user scope plugin plus project scope plugin
61
- - npm plugin plus local file plugin
20
+ macOS/Linux:
62
21
 
63
- ### 2. Install the TUI plugin for `/mem9-setup`
22
+ - `~/.config/opencode/opencode.json`
23
+ - `~/.config/opencode/tui.json`
64
24
 
65
- File: `~/.config/opencode/tui.json`
25
+ Windows:
66
26
 
67
- ```json
68
- {
69
- "plugin": ["@mem9/opencode"]
70
- }
71
- ```
27
+ - `%APPDATA%\\opencode\\opencode.json`
28
+ - `%APPDATA%\\opencode\\tui.json`
72
29
 
73
- That enables the `/mem9-setup` command inside OpenCode.
30
+ mem9 works best with one global plugin install plus project `mem9.json` overrides.
31
+ OpenCode merges plugin lists across scopes, so one install keeps recall, ingest, and tools predictable.
74
32
 
75
- ### 3. Restart OpenCode and run `/mem9-setup`
33
+ ### 2. Restart OpenCode and run `/mem9-setup`
76
34
 
77
- `/mem9-setup` is the single entrypoint for both:
35
+ `/mem9-setup` is the main entrypoint for:
78
36
 
79
37
  - shared mem9 credentials
80
- - OpenCode user/project mem9 settings
38
+ - OpenCode mem9 settings
81
39
 
82
40
  When no usable profile exists, it shows two actions:
83
41
 
@@ -89,32 +47,45 @@ When usable profiles already exist, it shows four actions:
89
47
  - `Get a mem9 API key automatically`
90
48
  - `Add an existing mem9 API key`
91
49
  - `Use an existing mem9 profile in a scope`
92
- - `Configure user/project settings`
50
+ - `Adjust scope settings`
93
51
 
94
- Profile creation ends after the profile is saved. Scope configuration is a separate action.
52
+ The last two actions do different things:
95
53
 
96
- ## File Layout
54
+ - `Use an existing mem9 profile in a scope` changes which profile a user or project scope uses
55
+ - `Adjust scope settings` changes `debug`, `defaultTimeoutMs`, and `searchTimeoutMs` for a user or project scope
97
56
 
98
- OpenCode config directory:
57
+ ### 3. Add project overrides only when you need them
99
58
 
100
- - macOS/Linux: usually `~/.config/opencode`
101
- - Windows: usually `%APPDATA%\\opencode`
59
+ Keep the plugin install global.
60
+ Use `<project>/.opencode/mem9.json` when one repository needs a different profile, debug flag, or timeout.
102
61
 
103
- OpenCode state directory:
62
+ Example:
104
63
 
105
- - macOS/Linux: usually `~/.local/share/opencode`
106
- - Windows: usually `%LOCALAPPDATA%\\opencode`
64
+ ```json
65
+ {
66
+ "schemaVersion": 1,
67
+ "profileId": "default",
68
+ "debug": false,
69
+ "defaultTimeoutMs": 8000,
70
+ "searchTimeoutMs": 15000
71
+ }
72
+ ```
107
73
 
108
- mem9 uses these files:
74
+ ## Where mem9 stores data
109
75
 
110
- - server plugin list: `~/.config/opencode/opencode.json` or `<project>/.opencode/opencode.json`
111
- - TUI plugin list: `~/.config/opencode/tui.json`
112
- - shared credentials: `$MEM9_HOME/.credentials.json`
113
- - user mem9 config: `<OpenCode config dir>/mem9.json`
114
- - project mem9 config: `<project>/.opencode/mem9.json`
115
- - debug logs: `<OpenCode state dir>/plugins/mem9/log/YYYY-MM-DD.jsonl`
76
+ - Shared credentials:
77
+ macOS/Linux: `$HOME/.mem9/.credentials.json`
78
+ Windows: `%USERPROFILE%\\.mem9\\.credentials.json`
79
+ - Global mem9 config:
80
+ macOS/Linux: `~/.config/opencode/mem9.json`
81
+ Windows: `%APPDATA%\\opencode\\mem9.json`
82
+ - Project mem9 override:
83
+ all platforms: `<project>/.opencode/mem9.json`
84
+ - Debug logs:
85
+ macOS/Linux: `~/.local/share/opencode/plugins/mem9/log/YYYY-MM-DD.jsonl`
86
+ Windows: `%LOCALAPPDATA%\\opencode\\plugins\\mem9\\log\\YYYY-MM-DD.jsonl`
116
87
 
117
- `MEM9_HOME` defaults to `$HOME/.mem9`.
88
+ `MEM9_HOME` defaults to `$HOME/.mem9` on macOS/Linux and `%USERPROFILE%\\.mem9` on Windows.
118
89
 
119
90
  ## Credentials File
120
91
 
@@ -173,33 +144,33 @@ These environment variables still override disk config at runtime:
173
144
  - `MEM9_DEBUG`
174
145
  - `MEM9_HOME`
175
146
 
176
- Legacy compatibility remains:
147
+ Legacy compatibility:
177
148
 
178
149
  - `MEM9_TENANT_ID`
179
150
 
180
151
  `MEM9_TENANT_ID` is treated as the API key source for older setups.
181
152
 
182
- ## Local Development Install
153
+ ## Upgrading
183
154
 
184
- You can also point OpenCode at a local checkout.
155
+ OpenCode caches npm plugins by package specifier.
156
+ When config points at `@mem9/opencode`, OpenCode resolves it as `@mem9/opencode@latest`.
185
157
 
186
- Server plugin example:
158
+ Upgrade flow:
187
159
 
188
- ```json
189
- {
190
- "plugin": ["./.opencode/plugins/mem9/src/index.ts"]
191
- }
192
- ```
160
+ 1. Quit OpenCode.
161
+ 2. Delete the cached folder that matches the installed specifier.
162
+ macOS/Linux default: `~/.cache/opencode/packages/@mem9/opencode@latest`
163
+ Windows default: `%LOCALAPPDATA%\\opencode\\packages\\@mem9\\opencode@latest`
164
+ If you pinned an exact version such as `@mem9/opencode@0.1.1`, delete that exact folder name instead.
165
+ 3. Run:
193
166
 
194
- TUI plugin example:
195
-
196
- ```json
197
- {
198
- "plugin": ["./.opencode/plugins/mem9/src/tui/index.ts"]
199
- }
167
+ ```bash
168
+ opencode plugin --force --global @mem9/opencode
200
169
  ```
201
170
 
202
- Keep the same one-scope rule for the server plugin even when using local paths.
171
+ 4. Restart OpenCode.
172
+
173
+ For prerelease testing, install an explicit npm specifier such as `@mem9/opencode@rc` or an exact version.
203
174
 
204
175
  ## What the Plugin Does
205
176
 
@@ -211,7 +182,7 @@ The server plugin does three things:
211
182
 
212
183
  ### Hook Flow
213
184
 
214
- OpenCode integration currently uses four runtime hooks:
185
+ OpenCode integration uses four runtime hooks:
215
186
 
216
187
  | Hook | What mem9 does |
217
188
  | --- | --- |
@@ -253,16 +224,8 @@ The plugin registers these tools:
253
224
  ## Troubleshooting
254
225
 
255
226
  - `Setup pending` means the plugin could not find a usable runtime identity. Run `/mem9-setup`, add `MEM9_API_KEY`, or point the active `mem9.json` scope at a profile with a non-empty `apiKey`.
256
- - If `/mem9-setup` is missing, confirm `@mem9/opencode` is listed in `~/.config/opencode/tui.json`.
227
+ - If `/mem9-setup` is missing, confirm `@mem9/opencode` is listed in your global `tui.json`.
257
228
  - If recall or tools work in one project and not another, check whether the project has its own `.opencode/mem9.json` override.
258
229
  - If recall, auto-ingest, or debug logs appear to run twice, check for duplicate plugin registration across user scope, project scope, npm, or local file paths.
259
230
  - If the selected profile exists but has no `apiKey`, update that profile in `$MEM9_HOME/.credentials.json`.
260
231
  - If debug logging is enabled and no file appears, confirm OpenCode can write to its state directory.
261
-
262
- ## Local Verification
263
-
264
- ```bash
265
- pnpm test
266
- pnpm run typecheck
267
- pnpm run pack:check
268
- ```
package/package.json CHANGED
@@ -1,15 +1,12 @@
1
1
  {
2
2
  "name": "@mem9/opencode",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "OpenCode memory plugin — cloud-persistent AI agent memory with hybrid vector + keyword search via mem9",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
7
- "oc-plugin": [
8
- "server",
9
- "tui"
10
- ],
11
7
  "exports": {
12
8
  ".": "./src/index.ts",
9
+ "./server": "./src/index.ts",
13
10
  "./tui": "./src/tui/index.ts"
14
11
  },
15
12
  "license": "Apache-2.0",
@@ -17,6 +17,7 @@ export interface SetupProfileSummary {
17
17
  label: string;
18
18
  baseUrl: string;
19
19
  hasApiKey: boolean;
20
+ apiKeyPreview: string;
20
21
  }
21
22
 
22
23
  export interface ScopeConfigState {
@@ -112,6 +113,23 @@ function hasApiKey(profile: unknown): boolean {
112
113
  return Boolean(normalizeOptionalString(profile.apiKey));
113
114
  }
114
115
 
116
+ function summarizeApiKeyPreview(apiKey: string): string {
117
+ const normalized = apiKey.trim();
118
+ if (normalized.length === 0) {
119
+ return "";
120
+ }
121
+
122
+ if (normalized.length <= 4) {
123
+ return `${normalized.slice(0, 1)}...`;
124
+ }
125
+
126
+ if (normalized.length <= 8) {
127
+ return `${normalized.slice(0, 2)}...${normalized.slice(-2)}`;
128
+ }
129
+
130
+ return `${normalized.slice(0, 4)}...${normalized.slice(-4)}`;
131
+ }
132
+
115
133
  function normalizeProfileRecord(
116
134
  profileId: string,
117
135
  profile: Mem9Profile | undefined,
@@ -188,6 +206,7 @@ function buildProfiles(
188
206
  label: normalized.label,
189
207
  baseUrl: normalized.baseUrl,
190
208
  hasApiKey: normalized.apiKey.trim().length > 0,
209
+ apiKeyPreview: summarizeApiKeyPreview(normalized.apiKey),
191
210
  };
192
211
  })
193
212
  .sort(sortProfileSummaries);
package/src/tui/index.ts CHANGED
@@ -3,6 +3,7 @@ import type {
3
3
  TuiCommand,
4
4
  TuiPlugin,
5
5
  TuiPluginApi,
6
+ TuiToast,
6
7
  } from "@opencode-ai/plugin/tui";
7
8
  import {
8
9
  resolveMem9Home,
@@ -27,7 +28,7 @@ type SetupAction =
27
28
  | "use-profile-in-scope"
28
29
  | "configure-scope";
29
30
 
30
- type ScopeFlowMode = "profile-only" | "full-config";
31
+ type ScopeFlowMode = "profile-only" | "settings-only";
31
32
 
32
33
  interface ProfileDraft {
33
34
  profileId: string;
@@ -50,6 +51,33 @@ export interface SetupActionOption {
50
51
  description: string;
51
52
  }
52
53
 
54
+ export interface SetupProfileOption {
55
+ title: string;
56
+ value: string;
57
+ description: string;
58
+ disabled: boolean;
59
+ }
60
+
61
+ interface SetupProfileOptionState {
62
+ currentProfileId?: string;
63
+ }
64
+
65
+ let hasShownVisibleApiKeyWarning = false;
66
+ const DEFAULT_TOAST_DURATION_MS = 5000;
67
+
68
+ function scheduleDialogTransition(next: () => void): void {
69
+ // Delay prompt-to-prompt transitions so the next dialog does not reuse
70
+ // the same Enter keypress that confirmed the current prompt.
71
+ setTimeout(next, 0);
72
+ }
73
+
74
+ function showToast(api: TuiPluginApi, toast: TuiToast): void {
75
+ api.ui.toast({
76
+ duration: toast.duration ?? DEFAULT_TOAST_DURATION_MS,
77
+ ...toast,
78
+ });
79
+ }
80
+
53
81
  function getProjectDir(api: TuiPluginApi): string {
54
82
  const worktree = api.state.path.worktree.trim();
55
83
  if (worktree.length > 0) {
@@ -102,7 +130,7 @@ function showError(
102
130
  retryCommand = false,
103
131
  ): void {
104
132
  const suffix = retryCommand ? " Run /mem9-setup again when you are ready to retry." : "";
105
- api.ui.toast({
133
+ showToast(api, {
106
134
  variant: "error",
107
135
  title: "mem9 setup failed",
108
136
  message: `${error instanceof Error ? error.message : String(error)}${suffix}`,
@@ -110,7 +138,7 @@ function showError(
110
138
  }
111
139
 
112
140
  function showProfileSavedSuccess(api: TuiPluginApi, profileId: string): void {
113
- api.ui.toast({
141
+ showToast(api, {
114
142
  variant: "success",
115
143
  title: "mem9 configured",
116
144
  message: `Saved profile ${profileId} and set it as the default user profile. Restart OpenCode to reload mem9.`,
@@ -123,7 +151,7 @@ function showScopeSavedSuccess(
123
151
  profileId: string,
124
152
  ): void {
125
153
  const scopeLabel = scope === "user" ? "user settings" : "project settings";
126
- api.ui.toast({
154
+ showToast(api, {
127
155
  variant: "success",
128
156
  title: "mem9 configured",
129
157
  message: `Saved ${scopeLabel} with profile ${profileId}. Restart OpenCode to reload mem9.`,
@@ -155,12 +183,12 @@ export function buildSetupActionOptions(
155
183
  {
156
184
  title: "Use an existing mem9 profile in a scope",
157
185
  value: "use-profile-in-scope",
158
- description: "Choose a saved profile and apply it to user or project settings.",
186
+ description: "Choose which saved profile user or project settings should use.",
159
187
  },
160
188
  {
161
- title: "Configure user/project settings",
189
+ title: "Adjust scope settings",
162
190
  value: "configure-scope",
163
- description: "Change scope profile, debug logging, and request timeouts.",
191
+ description: "Change debug logging, request timeouts, and other mem9 settings for a user or project scope.",
164
192
  },
165
193
  );
166
194
  }
@@ -168,6 +196,30 @@ export function buildSetupActionOptions(
168
196
  return options;
169
197
  }
170
198
 
199
+ function formatProfileTitle(profile: SetupProfileSummary): string {
200
+ return profile.label === profile.profileId
201
+ ? profile.label
202
+ : `${profile.label} (${profile.profileId})`;
203
+ }
204
+
205
+ export function buildScopeProfileOptions(
206
+ state: Pick<SetupState, "profiles">,
207
+ optionState: SetupProfileOptionState = {},
208
+ ): SetupProfileOption[] {
209
+ return state.profiles.map((profile) => ({
210
+ title: formatProfileTitle(profile),
211
+ value: profile.profileId,
212
+ description: [
213
+ profile.apiKeyPreview || "API key missing",
214
+ profile.baseUrl,
215
+ profile.profileId === optionState.currentProfileId ? "Current in this scope" : undefined,
216
+ ]
217
+ .filter((value): value is string => Boolean(value))
218
+ .join(" | "),
219
+ disabled: !profile.hasApiKey,
220
+ }));
221
+ }
222
+
171
223
  function parsePositiveInteger(value: string, field: string): number | null {
172
224
  const trimmed = value.trim();
173
225
  if (trimmed.length === 0) {
@@ -279,7 +331,7 @@ function showActionDialog(
279
331
  return;
280
332
  }
281
333
 
282
- showScopeDialog(api, paths, state, "full-config");
334
+ showScopeDialog(api, paths, state, "settings-only");
283
335
  },
284
336
  }),
285
337
  );
@@ -300,7 +352,7 @@ function showProfileIdDialog(
300
352
  onConfirm: (value) => {
301
353
  const next = value.trim();
302
354
  if (next.length === 0) {
303
- api.ui.toast({
355
+ showToast(api, {
304
356
  variant: "warning",
305
357
  message: "Profile ID is required.",
306
358
  });
@@ -308,7 +360,7 @@ function showProfileIdDialog(
308
360
  }
309
361
 
310
362
  if (isReusableProfileID(state, next)) {
311
- api.ui.toast({
363
+ showToast(api, {
312
364
  variant: "warning",
313
365
  message: "That profile already has credentials. Pick a new profile ID or use the existing profile in scope settings.",
314
366
  });
@@ -319,7 +371,9 @@ function showProfileIdDialog(
319
371
  if (draft.label.trim().length === 0) {
320
372
  draft.label = next === "default" ? "Personal" : next;
321
373
  }
322
- showProfileLabelDialog(api, paths, state, action, draft);
374
+ scheduleDialogTransition(() => {
375
+ showProfileLabelDialog(api, paths, state, action, draft);
376
+ });
323
377
  },
324
378
  onCancel: () => {
325
379
  showActionDialog(api, paths, state);
@@ -343,7 +397,7 @@ function showProfileLabelDialog(
343
397
  onConfirm: (value) => {
344
398
  const next = value.trim();
345
399
  if (next.length === 0) {
346
- api.ui.toast({
400
+ showToast(api, {
347
401
  variant: "warning",
348
402
  message: "Profile label is required.",
349
403
  });
@@ -351,10 +405,14 @@ function showProfileLabelDialog(
351
405
  }
352
406
 
353
407
  draft.label = next;
354
- showProfileBaseUrlDialog(api, paths, state, action, draft);
408
+ scheduleDialogTransition(() => {
409
+ showProfileBaseUrlDialog(api, paths, state, action, draft);
410
+ });
355
411
  },
356
412
  onCancel: () => {
357
- showProfileIdDialog(api, paths, state, action, draft);
413
+ scheduleDialogTransition(() => {
414
+ showProfileIdDialog(api, paths, state, action, draft);
415
+ });
358
416
  },
359
417
  }),
360
418
  );
@@ -375,7 +433,7 @@ function showProfileBaseUrlDialog(
375
433
  onConfirm: (value) => {
376
434
  const next = value.trim();
377
435
  if (next.length === 0) {
378
- api.ui.toast({
436
+ showToast(api, {
379
437
  variant: "warning",
380
438
  message: "mem9 API URL is required.",
381
439
  });
@@ -388,10 +446,14 @@ function showProfileBaseUrlDialog(
388
446
  return;
389
447
  }
390
448
 
391
- showProfileApiKeyDialog(api, paths, state, draft);
449
+ scheduleDialogTransition(() => {
450
+ showProfileApiKeyDialog(api, paths, state, draft);
451
+ });
392
452
  },
393
453
  onCancel: () => {
394
- showProfileLabelDialog(api, paths, state, action, draft);
454
+ scheduleDialogTransition(() => {
455
+ showProfileLabelDialog(api, paths, state, action, draft);
456
+ });
395
457
  },
396
458
  }),
397
459
  );
@@ -403,11 +465,14 @@ function showProfileApiKeyDialog(
403
465
  state: SetupState,
404
466
  draft: ProfileDraft,
405
467
  ): void {
406
- api.ui.toast({
407
- variant: "warning",
408
- message: "The current OpenCode prompt is plain text. Your API key stays visible while typing.",
409
- duration: 4000,
410
- });
468
+ if (!hasShownVisibleApiKeyWarning) {
469
+ hasShownVisibleApiKeyWarning = true;
470
+ showToast(api, {
471
+ variant: "warning",
472
+ message: "API key input is visible while typing.",
473
+ duration: 4000,
474
+ });
475
+ }
411
476
 
412
477
  api.ui.dialog.replace(() =>
413
478
  api.ui.DialogPrompt({
@@ -416,7 +481,7 @@ function showProfileApiKeyDialog(
416
481
  onConfirm: (value) => {
417
482
  const next = value.trim();
418
483
  if (next.length === 0) {
419
- api.ui.toast({
484
+ showToast(api, {
420
485
  variant: "warning",
421
486
  message: "mem9 API key is required.",
422
487
  });
@@ -427,7 +492,9 @@ function showProfileApiKeyDialog(
427
492
  void submitManualProfile(api, paths, draft);
428
493
  },
429
494
  onCancel: () => {
430
- showProfileBaseUrlDialog(api, paths, state, "manual-api-key", draft);
495
+ scheduleDialogTransition(() => {
496
+ showProfileBaseUrlDialog(api, paths, state, "manual-api-key", draft);
497
+ });
431
498
  },
432
499
  }),
433
500
  );
@@ -459,7 +526,12 @@ function showScopeDialog(
459
526
  ],
460
527
  onSelect: (option) => {
461
528
  const draft = createScopeDraft(state, option.value);
462
- showScopeProfileDialog(api, paths, state, mode, draft);
529
+ if (mode === "profile-only") {
530
+ showScopeProfileDialog(api, paths, state, draft);
531
+ return;
532
+ }
533
+
534
+ showScopeDebugDialog(api, paths, state, draft);
463
535
  },
464
536
  }),
465
537
  );
@@ -469,27 +541,18 @@ function showScopeProfileDialog(
469
541
  api: TuiPluginApi,
470
542
  paths: Mem9ResolvedPaths,
471
543
  state: SetupState,
472
- mode: ScopeFlowMode,
473
544
  draft: ScopeDraft,
474
545
  ): void {
475
546
  api.ui.dialog.replace(() =>
476
547
  api.ui.DialogSelect<string>({
477
548
  title: "Which mem9 profile should this scope use?",
478
549
  current: draft.profileId,
479
- options: state.profiles.map((profile) => ({
480
- title: `${profile.label} (${profile.profileId})`,
481
- value: profile.profileId,
482
- description: `${profile.baseUrl} • ${profile.hasApiKey ? "API key configured" : "API key missing"}`,
483
- disabled: !profile.hasApiKey,
484
- })),
550
+ options: buildScopeProfileOptions(state, {
551
+ currentProfileId: draft.profileId,
552
+ }),
485
553
  onSelect: (option) => {
486
554
  draft.profileId = option.value;
487
- if (mode === "profile-only") {
488
- void submitScopeConfigDraft(api, paths, draft);
489
- return;
490
- }
491
-
492
- showScopeDebugDialog(api, paths, state, draft);
555
+ void submitScopeConfigDraft(api, paths, draft);
493
556
  },
494
557
  }),
495
558
  );
@@ -539,7 +602,7 @@ function showDefaultTimeoutDialog(
539
602
  onConfirm: (value) => {
540
603
  const parsed = parsePositiveInteger(value, "defaultTimeoutMs");
541
604
  if (parsed === null) {
542
- api.ui.toast({
605
+ showToast(api, {
543
606
  variant: "warning",
544
607
  message: "Default timeout must be a positive integer.",
545
608
  });
@@ -547,10 +610,12 @@ function showDefaultTimeoutDialog(
547
610
  }
548
611
 
549
612
  draft.defaultTimeoutMs = parsed;
550
- showSearchTimeoutDialog(api, paths, state, draft);
613
+ scheduleDialogTransition(() => {
614
+ showSearchTimeoutDialog(api, paths, state, draft);
615
+ });
551
616
  },
552
617
  onCancel: () => {
553
- showScopeProfileDialog(api, paths, state, "full-config", draft);
618
+ showScopeDebugDialog(api, paths, state, draft);
554
619
  },
555
620
  }),
556
621
  );
@@ -570,7 +635,7 @@ function showSearchTimeoutDialog(
570
635
  onConfirm: (value) => {
571
636
  const parsed = parsePositiveInteger(value, "searchTimeoutMs");
572
637
  if (parsed === null) {
573
- api.ui.toast({
638
+ showToast(api, {
574
639
  variant: "warning",
575
640
  message: "Search timeout must be a positive integer.",
576
641
  });
@@ -581,7 +646,9 @@ function showSearchTimeoutDialog(
581
646
  void submitScopeConfigDraft(api, paths, draft);
582
647
  },
583
648
  onCancel: () => {
584
- showDefaultTimeoutDialog(api, paths, state, draft);
649
+ scheduleDialogTransition(() => {
650
+ showDefaultTimeoutDialog(api, paths, state, draft);
651
+ });
585
652
  },
586
653
  }),
587
654
  );