dsh-ntr 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +9 -6
- package/README.md +9 -6
- package/cordis.patch.yml +12 -0
- package/lib/bridge.js +654 -85
- package/lib/client/panel.js +224 -78
- package/lib/client/store.js +133 -1
- package/lib/client.js +702 -304
- package/lib/client.js.map +1 -1
- package/lib/impl.js +47 -0
- package/lib/index.js +15 -1
- package/lib/types/bridge.d.ts +208 -6
- package/lib/types/client/panel.d.ts +0 -1
- package/lib/types/client/store.d.ts +54 -1
- package/lib/types/impl.d.ts +26 -1
- package/lib/types/protocol.d.ts +73 -0
- package/lib/types/update.d.ts +183 -0
- package/lib/update.js +681 -0
- package/package.json +5 -1
- package/src/bridge.ts +700 -83
- package/src/client/panel.tsx +406 -205
- package/src/client/store.ts +146 -2
- package/src/impl.ts +66 -1
- package/src/index.ts +16 -1
- package/src/protocol.ts +76 -0
- package/src/update.ts +699 -0
package/lib/client/store.js
CHANGED
|
@@ -44,6 +44,12 @@ const MAX_BLOCKS = 400;
|
|
|
44
44
|
const MAX_WATCHED = 6;
|
|
45
45
|
/** Raw JSON budget for one event block; the cut is marked. */
|
|
46
46
|
const RAW_MAX = 256_000;
|
|
47
|
+
/**
|
|
48
|
+
* How long a fetched update state stays fresh. The settings panel asks on every
|
|
49
|
+
* mount; without this, every open would hit the registry. A forced check (the
|
|
50
|
+
* card's own button) always goes out.
|
|
51
|
+
*/
|
|
52
|
+
const PLUGIN_UPDATE_TTL_MS = 10 * 60 * 1000;
|
|
47
53
|
/** Pretty-printed JSON of an agent message (never throws, cuts only at {@link RAW_MAX} with a marker). */
|
|
48
54
|
export function jsonText(value) {
|
|
49
55
|
let text;
|
|
@@ -279,6 +285,14 @@ export const DEFAULT_KNOWN_PRESETS = [
|
|
|
279
285
|
defaultCommand: 'opencode',
|
|
280
286
|
defaultArgs: ['acp'],
|
|
281
287
|
},
|
|
288
|
+
{
|
|
289
|
+
id: 'cursor',
|
|
290
|
+
name: 'Cursor CLI',
|
|
291
|
+
description: 'Cursor CLI — run as `agent acp` over stdio (legacy alias `cursor-agent acp`)',
|
|
292
|
+
detected: false,
|
|
293
|
+
defaultCommand: 'agent',
|
|
294
|
+
defaultArgs: ['acp'],
|
|
295
|
+
},
|
|
282
296
|
{
|
|
283
297
|
id: 'qwen',
|
|
284
298
|
name: 'Qwen Code',
|
|
@@ -394,6 +408,22 @@ export class AcpStore {
|
|
|
394
408
|
cachedModelReads = new Set();
|
|
395
409
|
/** Counts behind the last successful explicit re-detect, so a locale switch can re-render its summary. */
|
|
396
410
|
detectCounts;
|
|
411
|
+
/** When {@link UiState.pluginUpdate} was last read from the host (epoch ms). */
|
|
412
|
+
pluginUpdateFetchedAt = 0;
|
|
413
|
+
/**
|
|
414
|
+
* The shared in-flight `plugin/update-check`. The settings section mounts
|
|
415
|
+
* twice (both entries) and the update popover asks too; without this, two
|
|
416
|
+
* mounts in the same tick each fire their own registry read.
|
|
417
|
+
*/
|
|
418
|
+
pluginUpdateInflight;
|
|
419
|
+
/** Re-entry guard for `plugin/update-apply` (a double click is one install). */
|
|
420
|
+
pluginUpdateApplying = false;
|
|
421
|
+
/**
|
|
422
|
+
* Update read generation. An apply bumps it so a check that was already in
|
|
423
|
+
* flight (started before the install) cannot land afterwards and overwrite
|
|
424
|
+
* the install's verdict with the pre-install `available`.
|
|
425
|
+
*/
|
|
426
|
+
pluginUpdateGeneration = 0;
|
|
397
427
|
constructor(rpc, locale) {
|
|
398
428
|
this.rpc = rpc;
|
|
399
429
|
this.locale = locale;
|
|
@@ -533,7 +563,7 @@ export class AcpStore {
|
|
|
533
563
|
/** Snapshot refresh used on panel open and after a stream outage. */
|
|
534
564
|
async resync() {
|
|
535
565
|
try {
|
|
536
|
-
const [{ agents }, { sessions }, defaultAgentInfo, detectResult, presetResult, tagHaloResult, deadlinesResult] = await Promise.all([
|
|
566
|
+
const [{ agents }, { sessions }, defaultAgentInfo, detectResult, presetResult, tagHaloResult, deadlinesResult, versionResult] = await Promise.all([
|
|
537
567
|
this.call('agents/list'),
|
|
538
568
|
this.call('sessions/list'),
|
|
539
569
|
// The plugin's OWN default ACP agent — a plugin-private id, not a DSH
|
|
@@ -547,6 +577,9 @@ export class AcpStore {
|
|
|
547
577
|
// `undefined` (older half) hides the deadline card instead of showing a
|
|
548
578
|
// number this browser made up.
|
|
549
579
|
this.call('ui/approval-deadlines').catch(() => ({ deadlines: undefined })),
|
|
580
|
+
// The settings badge's data source; older halves answer `unknown
|
|
581
|
+
// endpoint`, which hides the badge.
|
|
582
|
+
this.call('plugin/version').catch(() => null),
|
|
550
583
|
]);
|
|
551
584
|
const detectedAgents = (detectResult && detectResult.agents.length > 0)
|
|
552
585
|
? detectResult.agents
|
|
@@ -561,6 +594,7 @@ export class AcpStore {
|
|
|
561
594
|
preset: presetResult?.preset ?? undefined,
|
|
562
595
|
tagHalo: tagHaloResult?.halo ?? { ...DEFAULT_TAG_HALO },
|
|
563
596
|
approvalDeadlines: deadlinesResult.deadlines,
|
|
597
|
+
pluginVersion: versionResult?.version !== undefined && versionResult.version !== '' ? versionResult.version : undefined,
|
|
564
598
|
});
|
|
565
599
|
const active = this.state.activeSessionId ?? sessions[0]?.sessionId;
|
|
566
600
|
if (active !== undefined)
|
|
@@ -570,6 +604,104 @@ export class AcpStore {
|
|
|
570
604
|
this.mutate({ notice: String(error instanceof Error ? error.message : error) });
|
|
571
605
|
}
|
|
572
606
|
}
|
|
607
|
+
/**
|
|
608
|
+
* Read the plugin's update state (`plugin/update-check`). Throttled to ten
|
|
609
|
+
* minutes unless `force`: the settings panel asks on mount, and every
|
|
610
|
+
* remount must not hit the registry.
|
|
611
|
+
*
|
|
612
|
+
* An older host serves no such endpoint (`unknown endpoint`). That is a
|
|
613
|
+
* normal degradation, not an error worth a notice: the answer is `undefined`
|
|
614
|
+
* and the panel simply keeps its update entry hidden.
|
|
615
|
+
*/
|
|
616
|
+
async checkPluginUpdate(force = false) {
|
|
617
|
+
// One read for every caller while it is in flight, force included: the
|
|
618
|
+
// settings page and the popover mounting together must not hit the
|
|
619
|
+
// registry twice.
|
|
620
|
+
if (this.pluginUpdateInflight !== undefined)
|
|
621
|
+
return this.pluginUpdateInflight;
|
|
622
|
+
const current = this.state.pluginUpdate;
|
|
623
|
+
if (!force && current !== undefined && Date.now() - this.pluginUpdateFetchedAt < PLUGIN_UPDATE_TTL_MS) {
|
|
624
|
+
return current;
|
|
625
|
+
}
|
|
626
|
+
const generation = this.pluginUpdateGeneration;
|
|
627
|
+
const inflight = this.readPluginUpdate(generation);
|
|
628
|
+
this.pluginUpdateInflight = inflight;
|
|
629
|
+
try {
|
|
630
|
+
return await inflight;
|
|
631
|
+
}
|
|
632
|
+
finally {
|
|
633
|
+
this.pluginUpdateInflight = undefined;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
async readPluginUpdate(generation) {
|
|
637
|
+
try {
|
|
638
|
+
const pluginUpdate = await this.call('plugin/update-check');
|
|
639
|
+
// An install started while this read was in flight: its answer is
|
|
640
|
+
// already stale, and the install's verdict owns the state now.
|
|
641
|
+
if (generation !== this.pluginUpdateGeneration)
|
|
642
|
+
return undefined;
|
|
643
|
+
this.pluginUpdateFetchedAt = Date.now();
|
|
644
|
+
this.mutate({ pluginUpdate });
|
|
645
|
+
return pluginUpdate;
|
|
646
|
+
}
|
|
647
|
+
catch {
|
|
648
|
+
return undefined;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Install one exact version (`plugin/update-apply`). The status flips to
|
|
653
|
+
* `applying` first so the badge answers the click; the host's answer then
|
|
654
|
+
* becomes the state.
|
|
655
|
+
*
|
|
656
|
+
* A failed call does NOT prove the install did not happen: on an HMR host
|
|
657
|
+
* the update uninstalls the plugin before reinstalling it, which can cut the
|
|
658
|
+
* response. Whatever the error, the disk may already carry the new version,
|
|
659
|
+
* so the state becomes `restart-required` (the one neutral verdict for "the
|
|
660
|
+
* running code is not the disk's") with `installed` dropped (unknown, not a
|
|
661
|
+
* fact to display) and the notice asks for a reload rather than restoring
|
|
662
|
+
* `available` and pretending nothing changed.
|
|
663
|
+
*/
|
|
664
|
+
async applyPluginUpdate(version) {
|
|
665
|
+
if (this.pluginUpdateApplying)
|
|
666
|
+
return undefined;
|
|
667
|
+
this.pluginUpdateApplying = true;
|
|
668
|
+
// Any check already in flight predates the install; its answer must not
|
|
669
|
+
// land on top of the install's verdict.
|
|
670
|
+
this.pluginUpdateGeneration += 1;
|
|
671
|
+
const previous = this.state.pluginUpdate;
|
|
672
|
+
if (previous !== undefined) {
|
|
673
|
+
this.mutate({ pluginUpdate: { ...previous, status: 'applying' } });
|
|
674
|
+
}
|
|
675
|
+
try {
|
|
676
|
+
const pluginUpdate = await this.call('plugin/update-apply', { version });
|
|
677
|
+
this.pluginUpdateFetchedAt = Date.now();
|
|
678
|
+
this.mutate({ pluginUpdate });
|
|
679
|
+
return pluginUpdate;
|
|
680
|
+
}
|
|
681
|
+
catch (error) {
|
|
682
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
683
|
+
// The disk state is UNKNOWN here: the install may have completed before
|
|
684
|
+
// the response was cut. Keep `latest` for display, drop `installed` so no
|
|
685
|
+
// surface can claim a disk version we never read, and reset the throttle
|
|
686
|
+
// so the next check actually asks the host instead of serving this
|
|
687
|
+
// unknown verdict for ten minutes.
|
|
688
|
+
const interrupted = previous !== undefined
|
|
689
|
+
? { ...previous, status: 'restart-required', installed: undefined, message }
|
|
690
|
+
: { status: 'restart-required', current: this.state.pluginVersion ?? '?', message };
|
|
691
|
+
this.pluginUpdateFetchedAt = 0;
|
|
692
|
+
this.mutate({
|
|
693
|
+
pluginUpdate: interrupted,
|
|
694
|
+
notice: this.locale('更新请求已中断;请刷新页面确认当前版本。', 'The update request was interrupted. Reload the page to confirm the installed version.'),
|
|
695
|
+
});
|
|
696
|
+
return undefined;
|
|
697
|
+
}
|
|
698
|
+
finally {
|
|
699
|
+
// Invalidate reads that STARTED during the install: their "applying"
|
|
700
|
+
// answer would otherwise land on top of this call's final verdict.
|
|
701
|
+
this.pluginUpdateGeneration += 1;
|
|
702
|
+
this.pluginUpdateApplying = false;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
573
705
|
async detectAgents() {
|
|
574
706
|
this.mutate({ detecting: true });
|
|
575
707
|
try {
|