@shendeguize/dsh-agent-sidecar 0.1.1 → 0.2.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 (53) hide show
  1. package/README.md +78 -11
  2. package/lib/client.js +8659 -7589
  3. package/lib/client.js.map +1 -1
  4. package/package.json +3 -1
  5. package/src/client/analysis/AnalysisPanel.tsx +19 -27
  6. package/src/client/analysis/analysis.module.css +36 -97
  7. package/src/client/board/Board.tsx +115 -61
  8. package/src/client/board/board.module.css +72 -151
  9. package/src/client/board/project-view-logic.ts +21 -34
  10. package/src/client/board/project-view.module.css +41 -96
  11. package/src/client/board/project-view.tsx +29 -13
  12. package/src/client/board/strings.ts +68 -107
  13. package/src/client/commands.ts +30 -15
  14. package/src/client/detail/SessionDetail.tsx +92 -58
  15. package/src/client/detail/detail.module.css +62 -218
  16. package/src/client/detail/strings.ts +64 -88
  17. package/src/client/detail-view.module.css +30 -55
  18. package/src/client/detail-view.tsx +80 -108
  19. package/src/client/dsh-tools/LineageTree.tsx +22 -13
  20. package/src/client/dsh-tools/SearchPanel.tsx +18 -11
  21. package/src/client/dsh-tools/dsh-tools.module.css +50 -139
  22. package/src/client/dsh-tools/strings.ts +42 -77
  23. package/src/client/index.ts +285 -124
  24. package/src/client/inject/InjectPanel.tsx +31 -64
  25. package/src/client/inject/inject.module.css +97 -198
  26. package/src/client/lifecycle/handoff.ts +83 -0
  27. package/src/client/locales/en.ts +74 -2
  28. package/src/client/locales/host.ts +131 -0
  29. package/src/client/locales/index.ts +196 -8
  30. package/src/client/locales/react.ts +10 -0
  31. package/src/client/locales/view.ts +38 -0
  32. package/src/client/locales/zh.ts +194 -134
  33. package/src/client/mount.tsx +72 -38
  34. package/src/client/navigation/CenterOverlay.tsx +40 -0
  35. package/src/client/navigation/center-overlay.module.css +51 -0
  36. package/src/client/navigation/center.ts +45 -0
  37. package/src/client/navigation/modal-isolation.ts +152 -0
  38. package/src/client/navigation/modal-surface-anchor.ts +72 -0
  39. package/src/client/navigation/sidebar-entry.module.css +73 -0
  40. package/src/client/navigation/sidebar-entry.ts +309 -0
  41. package/src/client/primitives/StaticPill.tsx +21 -0
  42. package/src/client/settings-card.module.css +35 -119
  43. package/src/client/settings-card.tsx +31 -153
  44. package/src/client/settings-fields.tsx +131 -0
  45. package/src/client/sidebar/SidebarTab.tsx +156 -0
  46. package/src/client/sidebar/model.ts +65 -0
  47. package/src/client/sidebar/sidebar-tab.module.css +118 -0
  48. package/src/client/sidebar-tab.tsx +46 -320
  49. package/src/client/theme/agsc.module.css +31 -0
  50. package/src/client/theme/parts.ts +56 -0
  51. package/src/client/ui-integration.ts +86 -0
  52. package/src/client/widget.tsx +9 -8
  53. package/src/client/inject/overlay.module.css +0 -22
@@ -1,41 +1,30 @@
1
- /* Inject panel (design §5.1 view 3). Every color rides a dsh --dsw-alias-*
2
- * design token WITH a neutral fallback (UX-16: same token+fallback
3
- * discipline as every other CSS module), and error tones ride the
4
- * state-error-* family the board uses. The confirm-injection button is
5
- * the one danger affordance: eye-catching (error-toned border/text,
6
- * filled on hover) but restrained (no permanent solid red block). */
7
-
8
1
  .panel {
9
- border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12));
10
- border-radius: 12px;
11
- background: var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, 0.04));
12
- max-width: 560px;
13
2
  display: flex;
14
3
  flex-direction: column;
4
+ max-width: 560px;
5
+ border: 1px solid var(--agsc-border-strong);
6
+ border-radius: var(--agsc-radius-card);
7
+ background: var(--agsc-bg);
15
8
  }
16
9
 
17
- /* ── header ────────────────────────────────────────────────────────────── */
18
-
19
10
  .header {
20
11
  display: flex;
21
12
  align-items: center;
22
13
  gap: 12px;
23
14
  padding: 14px 16px 10px;
24
- border-bottom: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12));
15
+ border-bottom: 1px solid var(--agsc-border-strong);
25
16
  }
26
17
 
27
18
  .title {
28
19
  flex: 1;
29
20
  min-width: 0;
30
21
  margin: 0;
22
+ color: var(--agsc-fg);
31
23
  font-size: 15px;
32
24
  font-weight: 600;
33
25
  line-height: 1.4;
34
- color: var(--dsw-alias-label-primary, #1f2328);
35
26
  }
36
27
 
37
- /* ── body ──────────────────────────────────────────────────────────────── */
38
-
39
28
  .body {
40
29
  display: flex;
41
30
  flex-direction: column;
@@ -43,117 +32,120 @@
43
32
  padding: 12px 16px 14px;
44
33
  }
45
34
 
46
- .capabilityOff {
47
- margin: 0;
48
- border-radius: 8px;
49
- padding: 10px 12px;
50
- font-size: 13px;
51
- line-height: 1.6;
52
- background: var(--dsw-alias-bg-module-platform, rgba(0, 0, 0, 0.03));
53
- color: var(--dsw-alias-label-secondary, #57606a);
54
- }
55
-
56
- /* ── notices (editor banners after a failed/expired attempt) ───────────── */
57
-
35
+ .capabilityOff,
58
36
  .noticeWarn,
59
- .noticeError {
37
+ .noticeError,
38
+ .warnBar,
39
+ .auditNote,
40
+ .resultOk,
41
+ .resultFail,
42
+ .resultUnknown {
60
43
  margin: 0;
61
- border-radius: 8px;
62
44
  padding: 8px 10px;
45
+ border-radius: var(--agsc-radius-control);
46
+ background: var(--agsc-bg-raised);
47
+ color: var(--agsc-fg-secondary);
63
48
  font-size: 12px;
64
49
  line-height: 1.6;
65
- background: var(--dsw-alias-bg-module-platform, rgba(0, 0, 0, 0.03));
66
- color: var(--dsw-alias-label-secondary, #57606a);
67
50
  }
68
51
 
69
- .noticeError {
70
- border: 1px solid var(--dsw-alias-state-error-primary, #cf222e);
71
- color: var(--dsw-alias-state-error-primary, #cf222e);
52
+ .noticeWarn,
53
+ .warnBar {
54
+ border: 1px solid var(--agsc-warn);
55
+ color: var(--agsc-warn);
72
56
  }
73
57
 
74
- .noticeDetail {
75
- color: var(--dsw-alias-label-tertiary, #6e7781);
58
+ .noticeError,
59
+ .resultFail {
60
+ border: 1px solid var(--agsc-err);
61
+ color: var(--agsc-err);
76
62
  }
77
63
 
78
- /* ── target row ────────────────────────────────────────────────────────── */
64
+ .noticeDetail,
65
+ .noTarget,
66
+ .modeHint,
67
+ .planKey,
68
+ .observedNote,
69
+ .resultDetail {
70
+ color: var(--agsc-fg-dimmed);
71
+ }
79
72
 
80
- .targetRow {
73
+ .targetRow,
74
+ .planRow {
81
75
  display: flex;
82
76
  align-items: baseline;
83
77
  gap: 10px;
84
78
  min-width: 0;
85
79
  }
86
80
 
87
- .noTarget {
81
+ .noTarget,
82
+ .planKey,
83
+ .modeHint,
84
+ .resultDetail {
88
85
  font-size: 12px;
89
86
  line-height: 1.5;
90
- color: var(--dsw-alias-label-tertiary, #6e7781);
91
87
  }
92
88
 
93
89
  .agentTag {
94
90
  flex: none;
95
- border-radius: 999px;
96
- padding: 1px 8px;
97
- font-size: 11px;
98
- line-height: 17px;
99
- font-weight: 500;
100
- background: var(--dsw-alias-bg-module-platform, rgba(0, 0, 0, 0.03));
101
- color: var(--dsw-alias-label-secondary, #57606a);
102
91
  }
103
92
 
104
93
  .planTitle {
105
94
  min-width: 0;
106
95
  overflow: hidden;
107
- text-overflow: ellipsis;
108
- white-space: nowrap;
96
+ color: var(--agsc-fg);
109
97
  font-size: 13px;
110
98
  line-height: 1.5;
111
- color: var(--dsw-alias-label-primary, #1f2328);
99
+ text-overflow: ellipsis;
100
+ white-space: nowrap;
112
101
  }
113
102
 
114
- /* ── editor field ──────────────────────────────────────────────────────── */
115
-
116
- .field {
103
+ .field,
104
+ .modeText,
105
+ .planPreview {
117
106
  display: flex;
118
107
  flex-direction: column;
108
+ }
109
+
110
+ .field {
119
111
  gap: 6px;
120
112
  }
121
113
 
122
- .label {
114
+ .label,
115
+ .modeLabel {
116
+ color: var(--agsc-fg);
123
117
  font-size: 13px;
124
118
  line-height: 1.5;
125
- color: var(--dsw-alias-label-primary, #1f2328);
126
119
  }
127
120
 
128
121
  .textarea {
129
122
  appearance: none;
130
- resize: vertical;
131
123
  min-height: 96px;
132
- border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12));
133
- border-radius: 8px;
134
- background: var(--dsw-alias-bg-layer-3, #ffffff);
124
+ resize: vertical;
135
125
  padding: 8px 10px;
126
+ border: 1px solid var(--agsc-border-strong);
127
+ border-radius: var(--agsc-radius-control);
128
+ background: var(--agsc-bg-raised);
129
+ color: var(--agsc-fg);
136
130
  font: inherit;
137
131
  font-size: 13px;
138
132
  line-height: 1.6;
139
- color: var(--dsw-alias-label-primary, #1f2328);
140
133
  }
141
134
 
142
135
  .textarea:focus-visible {
136
+ border-color: var(--agsc-accent);
143
137
  outline: none;
144
- border-color: var(--dsw-alias-brand-primary, #4d6bfe);
145
138
  }
146
139
 
147
140
  .textarea::placeholder {
148
- color: var(--dsw-alias-label-tertiary, #6e7781);
141
+ color: var(--agsc-fg-dimmed);
149
142
  }
150
143
 
151
144
  .textarea:disabled {
152
- opacity: 0.5;
153
145
  cursor: default;
146
+ opacity: 0.5;
154
147
  }
155
148
 
156
- /* Byte counter: thin progress track + numeric readout. */
157
149
  .byteRow {
158
150
  display: flex;
159
151
  align-items: center;
@@ -163,52 +155,49 @@
163
155
  .byteBar {
164
156
  flex: 1;
165
157
  height: 3px;
166
- border-radius: 2px;
167
- background: var(--dsw-alias-bg-module-platform, rgba(0, 0, 0, 0.03));
168
158
  overflow: hidden;
159
+ border-radius: 2px;
160
+ background: var(--agsc-border);
169
161
  }
170
162
 
171
163
  .byteFill {
172
164
  height: 100%;
173
165
  border-radius: 2px;
174
- background: var(--dsw-alias-brand-primary, #4d6bfe);
166
+ background: var(--agsc-accent);
175
167
  transition: width .12s;
176
168
  }
177
169
 
178
- /* Modifier applied ALONGSIDE .byteFill (no `composes`; see settings card). */
179
170
  .byteFillOver {
180
- background: var(--dsw-alias-state-error-primary, #cf222e);
171
+ background: var(--agsc-err);
181
172
  }
182
173
 
183
174
  .byteText,
184
175
  .byteTextOver {
185
176
  flex: none;
177
+ color: var(--agsc-fg-dimmed);
186
178
  font-size: 11px;
187
- line-height: 1.5;
188
179
  font-variant-numeric: tabular-nums;
189
- color: var(--dsw-alias-label-tertiary, #6e7781);
180
+ line-height: 1.5;
190
181
  }
191
182
 
192
- .byteTextOver {
193
- color: var(--dsw-alias-state-error-primary, #cf222e);
183
+ .byteTextOver,
184
+ .invalid {
185
+ color: var(--agsc-err);
194
186
  }
195
187
 
196
188
  .invalid {
197
189
  margin: 0;
198
190
  font-size: 12px;
199
191
  line-height: 1.5;
200
- color: var(--dsw-alias-state-error-primary, #cf222e);
201
192
  }
202
193
 
203
- /* ── mode radios ───────────────────────────────────────────────────────── */
204
-
205
194
  .modes {
206
- margin: 0;
207
- border: 0;
208
- padding: 0;
209
195
  display: flex;
210
196
  flex-direction: column;
211
197
  gap: 8px;
198
+ margin: 0;
199
+ padding: 0;
200
+ border: 0;
212
201
  }
213
202
 
214
203
  .modeOption {
@@ -219,10 +208,10 @@
219
208
  }
220
209
 
221
210
  .modeOption input {
222
- accent-color: var(--dsw-alias-brand-primary, #4d6bfe);
223
211
  width: 14px;
224
212
  height: 14px;
225
213
  margin: 3px 0 0;
214
+ accent-color: var(--agsc-accent);
226
215
  cursor: pointer;
227
216
  }
228
217
 
@@ -231,71 +220,26 @@
231
220
  }
232
221
 
233
222
  .modeText {
234
- display: flex;
235
- flex-direction: column;
236
223
  gap: 2px;
237
224
  min-width: 0;
238
225
  }
239
226
 
240
- .modeLabel {
241
- font-size: 13px;
242
- line-height: 1.5;
243
- color: var(--dsw-alias-label-primary, #1f2328);
244
- }
245
-
246
- .modeHint {
247
- font-size: 12px;
248
- line-height: 1.5;
249
- color: var(--dsw-alias-label-tertiary, #6e7781);
250
- }
251
-
252
- /* ── warnings & audit note ─────────────────────────────────────────────── */
253
-
254
- .warnBar {
255
- margin: 0;
256
- border: 1px solid var(--dsw-alias-state-error-primary, #cf222e);
257
- border-radius: 8px;
258
- padding: 8px 10px;
259
- font-size: 12px;
260
- line-height: 1.6;
261
- background: var(--dsw-alias-bg-module-platform, rgba(0, 0, 0, 0.03));
262
- color: var(--dsw-alias-state-error-primary, #cf222e);
263
- }
264
-
265
227
  .auditNote {
266
- margin: 0;
267
- border-radius: 8px;
268
228
  padding: 8px 10px;
269
- font-size: 12px;
270
- line-height: 1.6;
271
- background: var(--dsw-alias-bg-module-platform, rgba(0, 0, 0, 0.03));
272
- color: var(--dsw-alias-label-secondary, #57606a);
273
229
  }
274
230
 
275
- /* ── confirm plan ──────────────────────────────────────────────────────── */
276
-
277
231
  .planBox {
278
- border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12));
279
- border-radius: 8px;
280
- background: var(--dsw-alias-bg-layer-3, #ffffff);
281
- padding: 10px 12px;
282
232
  display: flex;
283
233
  flex-direction: column;
284
234
  gap: 6px;
285
- }
286
-
287
- .planRow {
288
- display: flex;
289
- align-items: baseline;
290
- gap: 10px;
291
- min-width: 0;
235
+ padding: 10px 12px;
236
+ border: 1px solid var(--agsc-border-strong);
237
+ border-radius: var(--agsc-radius-control);
238
+ background: var(--agsc-bg-raised);
292
239
  }
293
240
 
294
241
  .planKey {
295
242
  flex: none;
296
- font-size: 12px;
297
- line-height: 1.5;
298
- color: var(--dsw-alias-label-tertiary, #6e7781);
299
243
  }
300
244
 
301
245
  .planValue {
@@ -303,85 +247,67 @@
303
247
  align-items: baseline;
304
248
  gap: 8px;
305
249
  min-width: 0;
250
+ color: var(--agsc-fg);
306
251
  font-size: 13px;
307
252
  line-height: 1.5;
308
- color: var(--dsw-alias-label-primary, #1f2328);
309
253
  }
310
254
 
311
255
  .observedNote {
312
256
  margin: 0;
313
257
  font-size: 11px;
314
258
  line-height: 1.5;
315
- color: var(--dsw-alias-label-tertiary, #6e7781);
316
259
  }
317
260
 
318
261
  .planPreview {
319
- display: flex;
320
- flex-direction: column;
321
262
  gap: 4px;
322
263
  min-width: 0;
323
264
  }
324
265
 
325
266
  .preview {
326
- margin: 0;
327
- border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12));
328
- border-radius: 6px;
329
- background: var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, 0.04));
330
- padding: 8px 10px;
331
267
  max-height: 120px;
332
268
  overflow: auto;
269
+ margin: 0;
270
+ padding: 8px 10px;
271
+ border: 1px solid var(--agsc-border-strong);
272
+ border-radius: 6px;
273
+ background: var(--agsc-bg);
274
+ color: var(--agsc-fg-secondary);
333
275
  font-size: 12px;
334
276
  line-height: 1.6;
335
277
  white-space: pre-wrap;
336
278
  word-break: break-word;
337
- color: var(--dsw-alias-label-secondary, #57606a);
338
279
  }
339
280
 
340
281
  .countdown {
341
282
  margin: 0;
283
+ color: var(--agsc-fg-secondary);
342
284
  font-size: 12px;
343
- line-height: 1.5;
344
285
  font-variant-numeric: tabular-nums;
345
- color: var(--dsw-alias-label-secondary, #57606a);
286
+ line-height: 1.5;
346
287
  }
347
288
 
348
- /* ── results ───────────────────────────────────────────────────────────── */
349
-
350
289
  .resultOk,
351
290
  .resultFail,
352
291
  .resultUnknown {
353
- margin: 0;
354
- border-radius: 8px;
355
292
  padding: 10px 12px;
293
+ color: var(--agsc-fg);
356
294
  font-size: 13px;
357
- line-height: 1.6;
358
- background: var(--dsw-alias-bg-module-platform, rgba(0, 0, 0, 0.03));
359
- color: var(--dsw-alias-label-primary, #1f2328);
360
295
  }
361
296
 
362
297
  .resultOk {
363
- border: 1px solid var(--dsw-alias-brand-primary, #4d6bfe);
364
- }
365
-
366
- .resultFail {
367
- border: 1px solid var(--dsw-alias-state-error-primary, #cf222e);
368
- color: var(--dsw-alias-state-error-primary, #cf222e);
298
+ border: 1px solid var(--agsc-ok);
369
299
  }
370
300
 
371
301
  .resultUnknown {
372
- border: 1px solid var(--dsw-alias-label-dimmed, #8c959f);
373
- color: var(--dsw-alias-label-secondary, #57606a);
302
+ border: 1px solid var(--agsc-fg-dimmed);
303
+ color: var(--agsc-fg-secondary);
374
304
  }
375
305
 
376
306
  .resultDetail {
377
307
  margin: 0;
378
- font-size: 12px;
379
308
  line-height: 1.6;
380
- color: var(--dsw-alias-label-tertiary, #6e7781);
381
309
  }
382
310
 
383
- /* ── footer buttons ────────────────────────────────────────────────────── */
384
-
385
311
  .footer {
386
312
  display: flex;
387
313
  align-items: center;
@@ -390,58 +316,31 @@
390
316
  padding-top: 4px;
391
317
  }
392
318
 
393
- .btn,
394
- .btnPrimary,
395
319
  .btnDanger {
396
320
  appearance: none;
397
- border: 1px solid transparent;
398
- border-radius: 8px;
399
321
  padding: 5px 14px;
322
+ border: 1px solid var(--agsc-err);
323
+ border-radius: var(--agsc-radius-control);
324
+ background: transparent;
325
+ color: var(--agsc-err);
400
326
  font: inherit;
401
327
  font-size: 13px;
328
+ font-weight: 600;
402
329
  line-height: 1.5;
403
330
  cursor: pointer;
404
331
  }
405
332
 
406
- .btn {
407
- border-color: var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12));
408
- background: none;
409
- color: var(--dsw-alias-label-secondary, #57606a);
410
- }
411
-
412
- .btn:hover:not(:disabled) {
413
- color: var(--dsw-alias-label-primary, #1f2328);
414
- border-color: var(--dsw-alias-label-dimmed, #8c959f);
415
- }
416
-
417
- .btnPrimary {
418
- background: var(--dsw-alias-label-primary, #1f2328);
419
- color: var(--dsw-alias-bg-layer-3, #ffffff);
420
- }
421
-
422
- /* The danger action: outlined error tone at rest, fills on hover. */
423
- .btnDanger {
424
- border-color: var(--dsw-alias-state-error-primary, #cf222e);
425
- background: none;
426
- color: var(--dsw-alias-state-error-primary, #cf222e);
427
- font-weight: 600;
428
- }
429
-
430
333
  .btnDanger:hover:not(:disabled) {
431
- background: var(--dsw-alias-state-error-primary, #cf222e);
432
- color: var(--dsw-alias-bg-layer-3, #ffffff);
334
+ background: var(--agsc-err);
335
+ color: var(--agsc-bg-raised);
433
336
  }
434
337
 
435
- .btn:disabled,
436
- .btnPrimary:disabled,
437
338
  .btnDanger:disabled {
438
- opacity: 0.4;
439
339
  cursor: default;
340
+ opacity: 0.4;
440
341
  }
441
342
 
442
- .btn:focus-visible,
443
- .btnPrimary:focus-visible,
444
343
  .btnDanger:focus-visible {
445
- outline: 2px solid var(--dsw-alias-brand-primary, #4d6bfe);
344
+ outline: 2px solid var(--agsc-accent);
446
345
  outline-offset: 1px;
447
346
  }
@@ -0,0 +1,83 @@
1
+ export type ResourceDisposer = () => void
2
+
3
+ /** Narrow clock port for deterministic handoff tests. */
4
+ export interface HandoffScheduler {
5
+ setTimeout(callback: () => void, delayMs: number): unknown
6
+ clearTimeout(handle: unknown): void
7
+ }
8
+
9
+ export interface HandoffOptions {
10
+ isCollision(error: unknown): boolean
11
+ onError(error: unknown): void
12
+ onTimeout(): void
13
+ scheduler?: HandoffScheduler
14
+ }
15
+
16
+ const DEFAULT_DELAYS_MS = [8, 16, 32, 64, 128, 256, 496] as const
17
+
18
+ const defaultScheduler: HandoffScheduler = {
19
+ setTimeout: (callback, delayMs) => globalThis.setTimeout(callback, delayMs),
20
+ clearTimeout: (handle) => globalThis.clearTimeout(handle as ReturnType<typeof setTimeout>),
21
+ }
22
+
23
+ /**
24
+ * Acquire a registry resource now, or briefly wait for an overlapping old
25
+ * fiber to release it. The returned lease owns both waiting and acquisition.
26
+ */
27
+ export function acquireWithHandoff(
28
+ register: () => ResourceDisposer | undefined,
29
+ options: HandoffOptions,
30
+ ): ResourceDisposer {
31
+ const scheduler = options.scheduler ?? defaultScheduler
32
+ let stopped = false
33
+ let attempts = 0
34
+ let delayIndex = 0
35
+ let timer: unknown
36
+ let acquired: ResourceDisposer | undefined
37
+
38
+ const finishError = (error: unknown): void => {
39
+ stopped = true
40
+ options.onError(error)
41
+ }
42
+
43
+ const attempt = (): void => {
44
+ if (stopped) return
45
+ timer = undefined
46
+ attempts += 1
47
+ try {
48
+ acquired = register()
49
+ } catch (error) {
50
+ if (!options.isCollision(error)) {
51
+ finishError(error)
52
+ return
53
+ }
54
+ }
55
+ if (acquired !== undefined) return
56
+ if (attempts >= 8 || delayIndex >= DEFAULT_DELAYS_MS.length) {
57
+ stopped = true
58
+ options.onTimeout()
59
+ return
60
+ }
61
+ const delay = DEFAULT_DELAYS_MS[delayIndex++]
62
+ try {
63
+ timer = scheduler.setTimeout(attempt, delay!)
64
+ } catch (error) {
65
+ finishError(error)
66
+ }
67
+ }
68
+
69
+ attempt()
70
+ return () => {
71
+ if (stopped) return
72
+ stopped = true
73
+ if (timer !== undefined) scheduler.clearTimeout(timer)
74
+ const dispose = acquired
75
+ acquired = undefined
76
+ dispose?.()
77
+ }
78
+ }
79
+
80
+ /** Only known duplicate-registry diagnostics are eligible for handoff. */
81
+ export function isRegistrationCollision(error: unknown): boolean {
82
+ return error instanceof Error && /\b(?:duplicate|already registered)\b/i.test(error.message)
83
+ }