dsh-ntr 0.2.1 → 0.3.1
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 +4 -4
- package/README.md +4 -4
- package/cordis.patch.yml +12 -0
- package/lib/bridge.js +646 -68
- package/lib/client/panel.js +162 -4
- package/lib/client/store.js +188 -4
- package/lib/client.js +431 -9
- 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 +206 -4
- package/lib/types/client/store.d.ts +87 -1
- package/lib/types/impl.d.ts +26 -1
- package/lib/types/protocol.d.ts +80 -0
- package/lib/types/update.d.ts +186 -0
- package/lib/update.js +696 -0
- package/package.json +1 -1
- package/src/bridge.ts +691 -68
- package/src/client/panel.tsx +288 -5
- package/src/client/store.ts +234 -8
- package/src/impl.ts +66 -1
- package/src/index.ts +16 -1
- package/src/protocol.ts +84 -0
- package/src/update.ts +712 -0
package/lib/client.js
CHANGED
|
@@ -49,6 +49,12 @@ window.__ModuleLoader__.load({
|
|
|
49
49
|
const MAX_WATCHED = 6;
|
|
50
50
|
/** Raw JSON budget for one event block; the cut is marked. */
|
|
51
51
|
const RAW_MAX = 256e3;
|
|
52
|
+
/**
|
|
53
|
+
* How long a fetched update state stays fresh. The settings panel asks on every
|
|
54
|
+
* mount; without this, every open would hit the registry. A forced check (the
|
|
55
|
+
* card's own button) always goes out.
|
|
56
|
+
*/
|
|
57
|
+
const PLUGIN_UPDATE_TTL_MS = 6e5;
|
|
52
58
|
/** Pretty-printed JSON of an agent message (never throws, cuts only at {@link RAW_MAX} with a marker). */
|
|
53
59
|
function jsonText(value) {
|
|
54
60
|
let text;
|
|
@@ -262,6 +268,14 @@ window.__ModuleLoader__.load({
|
|
|
262
268
|
defaultCommand: "opencode",
|
|
263
269
|
defaultArgs: ["acp"]
|
|
264
270
|
},
|
|
271
|
+
{
|
|
272
|
+
id: "cursor",
|
|
273
|
+
name: "Cursor CLI",
|
|
274
|
+
description: "Cursor CLI — run as `agent acp` over stdio (legacy alias `cursor-agent acp`)",
|
|
275
|
+
detected: false,
|
|
276
|
+
defaultCommand: "agent",
|
|
277
|
+
defaultArgs: ["acp"]
|
|
278
|
+
},
|
|
265
279
|
{
|
|
266
280
|
id: "qwen",
|
|
267
281
|
name: "Qwen Code",
|
|
@@ -308,6 +322,8 @@ window.__ModuleLoader__.load({
|
|
|
308
322
|
blocks: [],
|
|
309
323
|
permissions: [],
|
|
310
324
|
busy: false,
|
|
325
|
+
logs: [],
|
|
326
|
+
logUnread: 0,
|
|
311
327
|
detectedAgents: [...DEFAULT_KNOWN_PRESETS],
|
|
312
328
|
detecting: false,
|
|
313
329
|
modelCatalogs: {},
|
|
@@ -329,6 +345,8 @@ window.__ModuleLoader__.load({
|
|
|
329
345
|
};
|
|
330
346
|
listeners = /* @__PURE__ */ new Set();
|
|
331
347
|
source;
|
|
348
|
+
/** Monotonic id source for {@link AcpLogEntry}s (never reset by `clearLogs`). */
|
|
349
|
+
logSeq = 0;
|
|
332
350
|
lastSeq = /* @__PURE__ */ new Map();
|
|
333
351
|
/** Sessions with a finalized turn (dedups the cancel-fallback + late real turn/end pair). */
|
|
334
352
|
openTurns = /* @__PURE__ */ new Set();
|
|
@@ -377,6 +395,22 @@ window.__ModuleLoader__.load({
|
|
|
377
395
|
cachedModelReads = /* @__PURE__ */ new Set();
|
|
378
396
|
/** Counts behind the last successful explicit re-detect, so a locale switch can re-render its summary. */
|
|
379
397
|
detectCounts;
|
|
398
|
+
/** When {@link UiState.pluginUpdate} was last read from the host (epoch ms). */
|
|
399
|
+
pluginUpdateFetchedAt = 0;
|
|
400
|
+
/**
|
|
401
|
+
* The shared in-flight `plugin/update-check`. The settings section mounts
|
|
402
|
+
* twice (both entries) and the update popover asks too; without this, two
|
|
403
|
+
* mounts in the same tick each fire their own registry read.
|
|
404
|
+
*/
|
|
405
|
+
pluginUpdateInflight;
|
|
406
|
+
/** Re-entry guard for `plugin/update-apply` (a double click is one install). */
|
|
407
|
+
pluginUpdateApplying = false;
|
|
408
|
+
/**
|
|
409
|
+
* Update read generation. An apply bumps it so a check that was already in
|
|
410
|
+
* flight (started before the install) cannot land afterwards and overwrite
|
|
411
|
+
* the install's verdict with the pre-install `available`.
|
|
412
|
+
*/
|
|
413
|
+
pluginUpdateGeneration = 0;
|
|
380
414
|
constructor(rpc, locale) {
|
|
381
415
|
this.rpc = rpc;
|
|
382
416
|
this.locale = locale;
|
|
@@ -399,6 +433,23 @@ window.__ModuleLoader__.load({
|
|
|
399
433
|
for (const listener of [...this.listeners]) listener();
|
|
400
434
|
}
|
|
401
435
|
/**
|
|
436
|
+
* Build the log patch for one line. Does NOT mutate: the caller merges it
|
|
437
|
+
* into the single `mutate` call it was already making for the same event.
|
|
438
|
+
*/
|
|
439
|
+
logPatch(level, text, agentId) {
|
|
440
|
+
const entry = {
|
|
441
|
+
id: ++this.logSeq,
|
|
442
|
+
at: Date.now(),
|
|
443
|
+
level,
|
|
444
|
+
text,
|
|
445
|
+
...agentId === void 0 ? {} : { agentId }
|
|
446
|
+
};
|
|
447
|
+
return {
|
|
448
|
+
logs: [...this.state.logs, entry].slice(-200),
|
|
449
|
+
logUnread: this.state.logUnread + 1
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
402
453
|
* Live `session/update` frames waiting to be folded. Agents stream at token
|
|
403
454
|
* granularity — hundreds of frames a second with usage ticks interleaved —
|
|
404
455
|
* and folding plus notifying listeners per frame re-rendered every store
|
|
@@ -499,14 +550,15 @@ window.__ModuleLoader__.load({
|
|
|
499
550
|
/** Snapshot refresh used on panel open and after a stream outage. */
|
|
500
551
|
async resync() {
|
|
501
552
|
try {
|
|
502
|
-
const [{ agents }, { sessions }, defaultAgentInfo, detectResult, presetResult, tagHaloResult, deadlinesResult] = await Promise.all([
|
|
553
|
+
const [{ agents }, { sessions }, defaultAgentInfo, detectResult, presetResult, tagHaloResult, deadlinesResult, versionResult] = await Promise.all([
|
|
503
554
|
this.call("agents/list"),
|
|
504
555
|
this.call("sessions/list"),
|
|
505
556
|
this.call("default/agent").catch(() => ({})),
|
|
506
557
|
this.call("agents/detect").catch(() => null),
|
|
507
558
|
this.call("preset/status").catch(() => null),
|
|
508
559
|
this.call("ui/tag-halo").catch(() => ({ halo: { ...DEFAULT_TAG_HALO } })),
|
|
509
|
-
this.call("ui/approval-deadlines").catch(() => ({ deadlines: void 0 }))
|
|
560
|
+
this.call("ui/approval-deadlines").catch(() => ({ deadlines: void 0 })),
|
|
561
|
+
this.call("plugin/version").catch(() => null)
|
|
510
562
|
]);
|
|
511
563
|
const detectedAgents = detectResult && detectResult.agents.length > 0 ? detectResult.agents : this.state.detectedAgents.length > 0 ? this.state.detectedAgents : [...DEFAULT_KNOWN_PRESETS];
|
|
512
564
|
this.mutate({
|
|
@@ -517,7 +569,8 @@ window.__ModuleLoader__.load({
|
|
|
517
569
|
detectedAgents,
|
|
518
570
|
preset: presetResult?.preset ?? void 0,
|
|
519
571
|
tagHalo: tagHaloResult?.halo ?? { ...DEFAULT_TAG_HALO },
|
|
520
|
-
approvalDeadlines: deadlinesResult.deadlines
|
|
572
|
+
approvalDeadlines: deadlinesResult.deadlines,
|
|
573
|
+
pluginVersion: versionResult?.version !== void 0 && versionResult.version !== "" ? versionResult.version : void 0
|
|
521
574
|
});
|
|
522
575
|
const active = this.state.activeSessionId ?? sessions[0]?.sessionId;
|
|
523
576
|
if (active !== void 0) await this.loadTranscript(active);
|
|
@@ -525,6 +578,89 @@ window.__ModuleLoader__.load({
|
|
|
525
578
|
this.mutate({ notice: String(error instanceof Error ? error.message : error) });
|
|
526
579
|
}
|
|
527
580
|
}
|
|
581
|
+
/**
|
|
582
|
+
* Read the plugin's update state (`plugin/update-check`). Throttled to ten
|
|
583
|
+
* minutes unless `force`: the settings panel asks on mount, and every
|
|
584
|
+
* remount must not hit the registry.
|
|
585
|
+
*
|
|
586
|
+
* An older host serves no such endpoint (`unknown endpoint`). That is a
|
|
587
|
+
* normal degradation, not an error worth a notice: the answer is `undefined`
|
|
588
|
+
* and the panel simply keeps its update entry hidden.
|
|
589
|
+
*/
|
|
590
|
+
async checkPluginUpdate(force = false) {
|
|
591
|
+
if (this.pluginUpdateInflight !== void 0) return this.pluginUpdateInflight;
|
|
592
|
+
const current = this.state.pluginUpdate;
|
|
593
|
+
if (!force && current !== void 0 && Date.now() - this.pluginUpdateFetchedAt < PLUGIN_UPDATE_TTL_MS) return current;
|
|
594
|
+
const generation = this.pluginUpdateGeneration;
|
|
595
|
+
const inflight = this.readPluginUpdate(generation);
|
|
596
|
+
this.pluginUpdateInflight = inflight;
|
|
597
|
+
try {
|
|
598
|
+
return await inflight;
|
|
599
|
+
} finally {
|
|
600
|
+
this.pluginUpdateInflight = void 0;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
async readPluginUpdate(generation) {
|
|
604
|
+
try {
|
|
605
|
+
const pluginUpdate = await this.call("plugin/update-check");
|
|
606
|
+
if (generation !== this.pluginUpdateGeneration) return void 0;
|
|
607
|
+
this.pluginUpdateFetchedAt = Date.now();
|
|
608
|
+
this.mutate({ pluginUpdate });
|
|
609
|
+
return pluginUpdate;
|
|
610
|
+
} catch {
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Install one exact version (`plugin/update-apply`). The status flips to
|
|
616
|
+
* `applying` first so the badge answers the click; the host's answer then
|
|
617
|
+
* becomes the state.
|
|
618
|
+
*
|
|
619
|
+
* A failed call does NOT prove the install did not happen: on an HMR host
|
|
620
|
+
* the update uninstalls the plugin before reinstalling it, which can cut the
|
|
621
|
+
* response. Whatever the error, the disk may already carry the new version,
|
|
622
|
+
* so the state becomes `restart-required` (the one neutral verdict for "the
|
|
623
|
+
* running code is not the disk's") with `installed` dropped (unknown, not a
|
|
624
|
+
* fact to display) and the notice asks for a reload rather than restoring
|
|
625
|
+
* `available` and pretending nothing changed.
|
|
626
|
+
*/
|
|
627
|
+
async applyPluginUpdate(version) {
|
|
628
|
+
if (this.pluginUpdateApplying) return void 0;
|
|
629
|
+
this.pluginUpdateApplying = true;
|
|
630
|
+
this.pluginUpdateGeneration += 1;
|
|
631
|
+
const previous = this.state.pluginUpdate;
|
|
632
|
+
if (previous !== void 0) this.mutate({ pluginUpdate: {
|
|
633
|
+
...previous,
|
|
634
|
+
status: "applying"
|
|
635
|
+
} });
|
|
636
|
+
try {
|
|
637
|
+
const pluginUpdate = await this.call("plugin/update-apply", { version });
|
|
638
|
+
this.pluginUpdateFetchedAt = Date.now();
|
|
639
|
+
this.mutate({ pluginUpdate });
|
|
640
|
+
return pluginUpdate;
|
|
641
|
+
} catch (error) {
|
|
642
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
643
|
+
const interrupted = previous !== void 0 ? {
|
|
644
|
+
...previous,
|
|
645
|
+
status: "restart-required",
|
|
646
|
+
installed: void 0,
|
|
647
|
+
message
|
|
648
|
+
} : {
|
|
649
|
+
status: "restart-required",
|
|
650
|
+
current: this.state.pluginVersion ?? "?",
|
|
651
|
+
message
|
|
652
|
+
};
|
|
653
|
+
this.pluginUpdateFetchedAt = 0;
|
|
654
|
+
this.mutate({
|
|
655
|
+
pluginUpdate: interrupted,
|
|
656
|
+
notice: this.locale("更新请求已中断;请刷新页面确认当前版本。", "The update request was interrupted. Reload the page to confirm the installed version.")
|
|
657
|
+
});
|
|
658
|
+
return;
|
|
659
|
+
} finally {
|
|
660
|
+
this.pluginUpdateGeneration += 1;
|
|
661
|
+
this.pluginUpdateApplying = false;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
528
664
|
async detectAgents() {
|
|
529
665
|
this.mutate({ detecting: true });
|
|
530
666
|
try {
|
|
@@ -1364,13 +1500,25 @@ window.__ModuleLoader__.load({
|
|
|
1364
1500
|
clearNotice() {
|
|
1365
1501
|
this.mutate({ notice: void 0 });
|
|
1366
1502
|
}
|
|
1503
|
+
/** Drop every retained runtime-log line (the unread counter restarts too). */
|
|
1504
|
+
clearLogs() {
|
|
1505
|
+
this.mutate({
|
|
1506
|
+
logs: [],
|
|
1507
|
+
logUnread: 0
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
/** The user looked at the log: clear the unread badge. */
|
|
1511
|
+
markLogsRead() {
|
|
1512
|
+
if (this.state.logUnread === 0) return;
|
|
1513
|
+
this.mutate({ logUnread: 0 });
|
|
1514
|
+
}
|
|
1367
1515
|
flashError(error) {
|
|
1368
1516
|
this.mutate({ notice: error instanceof Error ? error.message : String(error) });
|
|
1369
1517
|
}
|
|
1370
1518
|
applyEvent(event) {
|
|
1371
1519
|
switch (event.type) {
|
|
1372
1520
|
case "agent/notice":
|
|
1373
|
-
this.mutate(
|
|
1521
|
+
this.mutate(this.logPatch(event.level, event.text, event.agentId));
|
|
1374
1522
|
return Promise.resolve();
|
|
1375
1523
|
case "agent/status":
|
|
1376
1524
|
if (event.agent !== void 0) {
|
|
@@ -1395,6 +1543,7 @@ window.__ModuleLoader__.load({
|
|
|
1395
1543
|
return Promise.resolve();
|
|
1396
1544
|
}
|
|
1397
1545
|
case "session/closed": {
|
|
1546
|
+
const closingSession = this.state.sessions.find((s) => s.sessionId === event.sessionId);
|
|
1398
1547
|
const sessions = this.state.sessions.filter((s) => s.sessionId !== event.sessionId);
|
|
1399
1548
|
const active = this.state.activeSessionId === event.sessionId ? sessions[0]?.sessionId : this.state.activeSessionId;
|
|
1400
1549
|
this.openTurns.delete(event.sessionId);
|
|
@@ -1407,7 +1556,7 @@ window.__ModuleLoader__.load({
|
|
|
1407
1556
|
chatTranscripts: closedTranscripts,
|
|
1408
1557
|
...chatSessions.length !== this.state.chatSessions.length ? { chatSessions } : {},
|
|
1409
1558
|
permissions: this.state.permissions.filter((p) => p.sessionId !== event.sessionId),
|
|
1410
|
-
...event.reason === "agent-died" ?
|
|
1559
|
+
...event.reason === "agent-died" ? this.logPatch("error", this.locale("会话因 agent 进程退出而终止", "session ended: agent process died"), closingSession?.agentId) : {}
|
|
1411
1560
|
});
|
|
1412
1561
|
return Promise.resolve();
|
|
1413
1562
|
}
|
|
@@ -1418,12 +1567,18 @@ window.__ModuleLoader__.load({
|
|
|
1418
1567
|
...s,
|
|
1419
1568
|
released: true
|
|
1420
1569
|
} : s) : this.state.chatSessions;
|
|
1570
|
+
const cause = event.cause ?? "died";
|
|
1571
|
+
let log = {};
|
|
1572
|
+
if (cause === "workspace") log = this.logPatch("info", this.locale("agent 为切换工作区重启,会话已挂起;再次发送消息将自动恢复。", "agent restarted for its workspace; the session is paused and will resume on your next message."), event.session.agentId);
|
|
1573
|
+
else if (cause === "resume-failed") log = this.logPatch("error", this.locale("会话恢复失败;再次发送消息将重试恢复。", "session resume failed; your next message will retry it."), event.session.agentId);
|
|
1574
|
+
else if (cause === "stalled") log = this.logPatch("error", this.locale("agent 未响应卡死的回合,会话已挂起;再次发送消息将自动恢复。", "agent did not acknowledge the stalled turn; the session is paused and will resume on your next message."), event.session.agentId);
|
|
1575
|
+
else if (cause === "died") log = this.logPatch("error", this.locale("agent 进程已退出,会话已挂起;再次发送消息将自动恢复。", "agent process died; the session is paused and will resume on your next message."), event.session.agentId);
|
|
1421
1576
|
this.mutate({
|
|
1422
1577
|
sessions,
|
|
1423
1578
|
...chatSessions !== this.state.chatSessions ? { chatSessions } : {},
|
|
1424
1579
|
permissions: this.state.permissions.filter((p) => p.sessionId !== event.sessionId),
|
|
1425
1580
|
...this.watchedSessionPatch(event.sessionId, event.session),
|
|
1426
|
-
|
|
1581
|
+
...log
|
|
1427
1582
|
});
|
|
1428
1583
|
return Promise.resolve();
|
|
1429
1584
|
}
|
|
@@ -2638,7 +2793,7 @@ window.__ModuleLoader__.load({
|
|
|
2638
2793
|
}
|
|
2639
2794
|
.dwa-page-header { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 28px; }
|
|
2640
2795
|
.dwa-page-heading { min-width: 0; }
|
|
2641
|
-
.dwa-page-title { margin: 0; font-size: 20px; line-height: 1.35; font-weight: 550; letter-spacing: -0.02em; }
|
|
2796
|
+
.dwa-page-title { margin: 0; font-size: 20px; line-height: 1.35; font-weight: 550; letter-spacing: -0.02em; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
2642
2797
|
.dwa-page-description { margin: 5px 0 0; color: var(--ntr-muted); font-size: var(--ntr-font-sm); }
|
|
2643
2798
|
.dwa-btn {
|
|
2644
2799
|
min-height: 32px; box-sizing: border-box; font: inherit; border-radius: var(--ntr-radius-sm);
|
|
@@ -2670,6 +2825,20 @@ window.__ModuleLoader__.load({
|
|
|
2670
2825
|
.dwa-badge-success { background: var(--dsw-alias-state-success-tertiary, #dcfce7); color: var(--ntr-success); }
|
|
2671
2826
|
.dwa-badge-warn { background: var(--dsw-alias-state-warn-tertiary, #fef3c7); color: var(--ntr-warn); }
|
|
2672
2827
|
.dwa-badge-info { background: var(--dsw-alias-state-business-tertiary, #dbeafe); color: var(--dsw-alias-state-business-primary, #2563eb); }
|
|
2828
|
+
/* The update badge is a button (it expands the update card): strip the
|
|
2829
|
+
browser's default button chrome, keep the badge look. */
|
|
2830
|
+
.dwa-update-badge { border: 0; font-family: inherit; cursor: pointer; }
|
|
2831
|
+
.dwa-update-badge:hover { opacity: 0.85; }
|
|
2832
|
+
/* The version badge doubles as the update entry: same badge look, no chrome. */
|
|
2833
|
+
.dwa-version-btn { border: 0; font-family: inherit; cursor: pointer; }
|
|
2834
|
+
.dwa-version-btn:hover { opacity: 0.85; }
|
|
2835
|
+
.dwa-version { font-family: var(--ds-font-family-code, monospace); }
|
|
2836
|
+
/* The update card sits between the page header and the permission mirror. */
|
|
2837
|
+
.dwa-update-card { margin-bottom: 16px; }
|
|
2838
|
+
.dwa-update-summary { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; font-weight: 550; }
|
|
2839
|
+
.dwa-update-copy { margin: 6px 0 0; color: var(--ntr-muted); font-size: var(--ntr-font-xs); }
|
|
2840
|
+
.dwa-update-message { margin: 6px 0 0; color: var(--ntr-muted); font-size: var(--ntr-font-xs); white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
2841
|
+
.dwa-update-actions { margin-top: 10px; }
|
|
2673
2842
|
.dwa-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; flex-shrink: 0; }
|
|
2674
2843
|
.dwa-agent-list { display: flex; flex-direction: column; border-top: var(--ntr-line); }
|
|
2675
2844
|
.dwa-agent-card { border-bottom: var(--ntr-line); min-width: 0; }
|
|
@@ -2792,6 +2961,17 @@ window.__ModuleLoader__.load({
|
|
|
2792
2961
|
.dwa-deadline-flag { margin-top: 4px; font-size: var(--ntr-font-xs); font-weight: 550; color: var(--ntr-warn); }
|
|
2793
2962
|
.dwa-deadline-warn { margin-top: 10px; padding: 9px 12px; border-left: 3px solid var(--dsw-alias-state-warn-primary, #d97706); background: var(--dsw-alias-state-warn-secondary, #fffbeb); border-radius: var(--ntr-radius-sm); font-size: var(--ntr-font-xs); line-height: 1.55; }
|
|
2794
2963
|
.dwa-deadline-reset { display: flex; justify-content: flex-end; margin-top: 8px; }
|
|
2964
|
+
/* Runtime log: one bounded list, newest first. The time column never wraps;
|
|
2965
|
+
the message is the only cell that may break anywhere. */
|
|
2966
|
+
.dwa-log-list { list-style: none; margin: 0; padding: 0; border: var(--ntr-line); border-radius: var(--ntr-radius); overflow: hidden; }
|
|
2967
|
+
.dwa-log-row { display: flex; align-items: baseline; gap: 8px; padding: 6px 10px; font-size: var(--ntr-font-sm); border-top: var(--ntr-line); min-width: 0; }
|
|
2968
|
+
.dwa-log-row:first-child { border-top: 0; }
|
|
2969
|
+
.dwa-log-row.is-error { background: var(--dsw-alias-state-error-tertiary, #fef2f2); }
|
|
2970
|
+
.dwa-log-time { color: var(--ntr-muted); white-space: nowrap; }
|
|
2971
|
+
.dwa-log-text { flex: 1; min-width: 0; overflow-wrap: anywhere; }
|
|
2972
|
+
.dwa-log-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--ntr-danger); display: inline-block; margin-left: 6px; }
|
|
2973
|
+
.dwa-log-level.is-info { background: var(--dsw-alias-state-business-tertiary, #dbeafe); color: var(--dsw-alias-state-business-primary, #2563eb); }
|
|
2974
|
+
.dwa-log-level.is-error { background: var(--dsw-alias-state-error-tertiary, #fef2f2); color: var(--ntr-danger); }
|
|
2795
2975
|
@media (max-width: 640px) {
|
|
2796
2976
|
.dwa-root { padding: 20px 16px; }
|
|
2797
2977
|
}
|
|
@@ -3320,6 +3500,152 @@ window.__ModuleLoader__.load({
|
|
|
3320
3500
|
})]
|
|
3321
3501
|
});
|
|
3322
3502
|
}
|
|
3503
|
+
/**
|
|
3504
|
+
* Local wall-clock `HH:MM:SS` for one runtime-log line. Hand-built on purpose:
|
|
3505
|
+
* `toLocaleTimeString` may drop seconds or pick a 12-hour clock depending on
|
|
3506
|
+
* the browser locale, which makes the column ragged and ambiguous.
|
|
3507
|
+
*/
|
|
3508
|
+
function logClock(at) {
|
|
3509
|
+
const date = new Date(at);
|
|
3510
|
+
return [
|
|
3511
|
+
date.getHours(),
|
|
3512
|
+
date.getMinutes(),
|
|
3513
|
+
date.getSeconds()
|
|
3514
|
+
].map((value) => String(value).padStart(2, "0")).join(":");
|
|
3515
|
+
}
|
|
3516
|
+
/** The states that carry an update badge next to the version badge. */
|
|
3517
|
+
function updateBadgeText(update, acp) {
|
|
3518
|
+
if (update.status === "available") return acp.label(`可更新 v${update.latest ?? update.current}`, `Update v${update.latest ?? update.current}`);
|
|
3519
|
+
if (update.status === "applying") return acp.label("更新中…", "Updating…");
|
|
3520
|
+
if (update.status === "applied") {
|
|
3521
|
+
const installed = update.installed ?? update.latest ?? update.current;
|
|
3522
|
+
return acp.label(`已更新 v${installed}`, `Updated v${installed}`);
|
|
3523
|
+
}
|
|
3524
|
+
if (update.status === "restart-required") {
|
|
3525
|
+
if (update.installed === void 0) return void 0;
|
|
3526
|
+
return acp.label(`待重启 v${update.installed}`, `Restart for v${update.installed}`);
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
3529
|
+
/**
|
|
3530
|
+
* The version badge's update companion. Every badge is a button, `applying`
|
|
3531
|
+
* included: the card is where the install's outcome is read, so the running
|
|
3532
|
+
* install must be able to open it too. States without anything to act on get
|
|
3533
|
+
* no badge at all — the version badge itself still opens the card.
|
|
3534
|
+
*/
|
|
3535
|
+
function PluginUpdateBadge(props) {
|
|
3536
|
+
const { acp, update, open, onToggle } = props;
|
|
3537
|
+
const text = updateBadgeText(update, acp);
|
|
3538
|
+
if (text === void 0) return null;
|
|
3539
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3540
|
+
type: "button",
|
|
3541
|
+
className: "dwa-badge dwa-badge-info dwa-update-badge",
|
|
3542
|
+
"aria-expanded": open,
|
|
3543
|
+
"aria-controls": "ntr-plugin-update-card",
|
|
3544
|
+
onClick: onToggle,
|
|
3545
|
+
children: text
|
|
3546
|
+
});
|
|
3547
|
+
}
|
|
3548
|
+
/**
|
|
3549
|
+
* The update card. In-page, no dialog: the settings page already owns the one
|
|
3550
|
+
* action (install the exact version the host reported), and a modal on top of
|
|
3551
|
+
* a settings section would hide the version it is updating.
|
|
3552
|
+
*
|
|
3553
|
+
* The copy stays to one line per state (constraint 33); the host's raw error
|
|
3554
|
+
* (`message`) is the only long text, and it wraps on its own line so a stack
|
|
3555
|
+
* trace or a registry error is readable without stretching the card.
|
|
3556
|
+
*/
|
|
3557
|
+
function PluginUpdateCard(props) {
|
|
3558
|
+
const { acp, update, onClose } = props;
|
|
3559
|
+
const applying = update.status === "applying";
|
|
3560
|
+
const latest = update.latest ?? update.current;
|
|
3561
|
+
const onDisk = update.installed !== void 0 && update.installed !== update.current ? update.installed : void 0;
|
|
3562
|
+
const published = update.publishedAt !== void 0 && !Number.isNaN(new Date(update.publishedAt).getTime()) ? new Date(update.publishedAt).toLocaleDateString() : void 0;
|
|
3563
|
+
const copy = update.status === "available" ? acp.label("更新会卸载并重装插件,之后可能需要重启 DSH。", "Updating uninstalls and reinstalls the plugin, and may require restarting DSH.") : update.status === "applied" ? acp.label("已应用,刷新页面即可使用新版本。", "Applied. Reload the page to use the new version.") : update.status === "restart-required" ? update.installed !== void 0 ? acp.label(`磁盘已是 v${update.installed},重启 DSH 后生效(桌面 App 退出重开;命令行重启 dsh web)。`, `On disk v${update.installed}. Restart DSH to apply (quit and reopen the app, or restart dsh web).`) : acp.label("更新可能已完成,请刷新页面确认当前版本。", "The update may already be complete. Refresh the page to confirm the current version.") : update.status === "busy" ? acp.label("有进行中的对话,结束后才能更新。", "Finish the running conversation before updating.") : update.status === "failed" ? acp.label("更新失败。", "Update failed.") : update.status === "current" ? acp.label("已是最新版本。", "Up to date.") : update.status === "source-install" ? acp.label("源码或本地安装,无法通过 registry 更新。", "Source or local install; update it through its own checkout.") : update.status === "managed" ? acp.label("此安装由宿主管理,不能在这里更新。", "This install is managed by the host and cannot be updated here.") : void 0;
|
|
3564
|
+
const showMessage = (update.status === "failed" || update.status === "busy" || update.status === "managed") && update.message !== void 0;
|
|
3565
|
+
const apply = async () => {
|
|
3566
|
+
if (update.latest === void 0) return;
|
|
3567
|
+
try {
|
|
3568
|
+
await acp.applyPluginUpdate(update.latest);
|
|
3569
|
+
} catch {}
|
|
3570
|
+
};
|
|
3571
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3572
|
+
id: "ntr-plugin-update-card",
|
|
3573
|
+
className: "dwa-card dwa-update-card",
|
|
3574
|
+
role: "group",
|
|
3575
|
+
"aria-label": acp.label("插件更新", "Plugin update"),
|
|
3576
|
+
children: [
|
|
3577
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3578
|
+
className: "dwa-update-summary",
|
|
3579
|
+
children: [
|
|
3580
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: acp.label(`当前 v${update.current}`, `Current v${update.current}`) }),
|
|
3581
|
+
onDisk !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3582
|
+
className: "dwa-hint",
|
|
3583
|
+
children: acp.label(`磁盘 v${onDisk}`, `On disk v${onDisk}`)
|
|
3584
|
+
}),
|
|
3585
|
+
update.latest !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
3586
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3587
|
+
"aria-hidden": "true",
|
|
3588
|
+
children: "→"
|
|
3589
|
+
}),
|
|
3590
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: acp.label(`最新 v${latest}`, `Latest v${latest}`) }),
|
|
3591
|
+
published !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3592
|
+
className: "dwa-hint",
|
|
3593
|
+
children: published
|
|
3594
|
+
})
|
|
3595
|
+
] })
|
|
3596
|
+
]
|
|
3597
|
+
}),
|
|
3598
|
+
copy !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3599
|
+
className: "dwa-update-copy",
|
|
3600
|
+
children: copy
|
|
3601
|
+
}),
|
|
3602
|
+
showMessage && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3603
|
+
className: "dwa-update-message",
|
|
3604
|
+
children: update.message
|
|
3605
|
+
}),
|
|
3606
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3607
|
+
className: "dwa-row-actions dwa-update-actions",
|
|
3608
|
+
children: [
|
|
3609
|
+
update.status === "available" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3610
|
+
type: "button",
|
|
3611
|
+
className: "dwa-btn dwa-btn-primary",
|
|
3612
|
+
onClick: () => {
|
|
3613
|
+
apply();
|
|
3614
|
+
},
|
|
3615
|
+
children: acp.label("更新", "Update")
|
|
3616
|
+
}),
|
|
3617
|
+
applying && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3618
|
+
type: "button",
|
|
3619
|
+
className: "dwa-btn dwa-btn-primary",
|
|
3620
|
+
disabled: true,
|
|
3621
|
+
children: acp.label("正在更新…", "Updating…")
|
|
3622
|
+
}),
|
|
3623
|
+
update.status !== "applying" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3624
|
+
type: "button",
|
|
3625
|
+
className: "dwa-btn",
|
|
3626
|
+
onClick: onClose,
|
|
3627
|
+
children: acp.label("关闭", "Close")
|
|
3628
|
+
}),
|
|
3629
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3630
|
+
type: "button",
|
|
3631
|
+
className: "dwa-btn dwa-btn-quiet",
|
|
3632
|
+
disabled: applying,
|
|
3633
|
+
onClick: () => void acp.checkPluginUpdate(true),
|
|
3634
|
+
children: acp.label("重新检查", "Check again")
|
|
3635
|
+
})
|
|
3636
|
+
]
|
|
3637
|
+
})
|
|
3638
|
+
]
|
|
3639
|
+
});
|
|
3640
|
+
}
|
|
3641
|
+
/**
|
|
3642
|
+
* The card is drawn for every state a host can report about this install,
|
|
3643
|
+
* except the two where there is no install to talk about at all.
|
|
3644
|
+
*/
|
|
3645
|
+
function updateCardVisible(update, open) {
|
|
3646
|
+
if (update === void 0 || !open) return false;
|
|
3647
|
+
return update.status !== "unsupported" && update.status !== "not-installed";
|
|
3648
|
+
}
|
|
3323
3649
|
function AcpSettingsSection(props) {
|
|
3324
3650
|
const acp = props.acp;
|
|
3325
3651
|
ensureStyle();
|
|
@@ -3328,6 +3654,17 @@ window.__ModuleLoader__.load({
|
|
|
3328
3654
|
const [page, setPage] = (0, react.useState)("agents");
|
|
3329
3655
|
const [addOpen, setAddOpen] = (0, react.useState)(false);
|
|
3330
3656
|
const [addTarget, setAddTarget] = (0, react.useState)(void 0);
|
|
3657
|
+
const [updateOpen, setUpdateOpen] = (0, react.useState)(false);
|
|
3658
|
+
(0, react.useEffect)(() => {
|
|
3659
|
+
acp.checkPluginUpdate();
|
|
3660
|
+
}, [acp]);
|
|
3661
|
+
(0, react.useEffect)(() => {
|
|
3662
|
+
if (page === "logs" && ui.logUnread > 0) acp.markLogsRead();
|
|
3663
|
+
}, [
|
|
3664
|
+
acp,
|
|
3665
|
+
page,
|
|
3666
|
+
ui.logUnread
|
|
3667
|
+
]);
|
|
3331
3668
|
const { configured, available } = partitionAgents(ui.detectedAgents, ui.agents);
|
|
3332
3669
|
const selected = addTarget ?? available[0] ?? NEW_AGENT_SEED;
|
|
3333
3670
|
const openAdd = () => {
|
|
@@ -3335,6 +3672,9 @@ window.__ModuleLoader__.load({
|
|
|
3335
3672
|
setPage("add");
|
|
3336
3673
|
};
|
|
3337
3674
|
const presetProblem = ui.preset !== void 0 && (!ui.preset.installed || ui.preset.problem !== void 0);
|
|
3675
|
+
const update = ui.pluginUpdate;
|
|
3676
|
+
const versionTitle = update?.status === "source-install" ? acp.label("源码或本地安装,无法通过 registry 更新。", "Source or local install; update it through its own checkout.") : void 0;
|
|
3677
|
+
const versionOpensUpdate = update !== void 0 && update.status !== "unsupported" && update.status !== "not-installed";
|
|
3338
3678
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3339
3679
|
className: "dwa-root",
|
|
3340
3680
|
children: [
|
|
@@ -3342,9 +3682,31 @@ window.__ModuleLoader__.load({
|
|
|
3342
3682
|
className: "dwa-page-header",
|
|
3343
3683
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3344
3684
|
className: "dwa-page-heading",
|
|
3345
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.
|
|
3685
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h2", {
|
|
3346
3686
|
className: "dwa-page-title",
|
|
3347
|
-
|
|
3687
|
+
"aria-label": acp.label("NTR 设置", "NTR settings"),
|
|
3688
|
+
children: [
|
|
3689
|
+
acp.label("NTR 设置", "NTR settings"),
|
|
3690
|
+
ui.pluginVersion !== void 0 && (versionOpensUpdate ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3691
|
+
type: "button",
|
|
3692
|
+
className: "dwa-badge dwa-version dwa-version-btn",
|
|
3693
|
+
title: versionTitle,
|
|
3694
|
+
"aria-expanded": updateOpen,
|
|
3695
|
+
"aria-controls": "ntr-plugin-update-card",
|
|
3696
|
+
onClick: () => setUpdateOpen((open) => !open),
|
|
3697
|
+
children: `v${ui.pluginVersion}`
|
|
3698
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3699
|
+
className: "dwa-badge dwa-version",
|
|
3700
|
+
title: versionTitle,
|
|
3701
|
+
children: `v${ui.pluginVersion}`
|
|
3702
|
+
})),
|
|
3703
|
+
update !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PluginUpdateBadge, {
|
|
3704
|
+
acp,
|
|
3705
|
+
update,
|
|
3706
|
+
open: updateOpen,
|
|
3707
|
+
onToggle: () => setUpdateOpen((open) => !open)
|
|
3708
|
+
})
|
|
3709
|
+
]
|
|
3348
3710
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3349
3711
|
className: "dwa-page-description",
|
|
3350
3712
|
children: acp.label("连接智能体,设置你的对话偏好。", "Connect agents and set your conversation preferences.")
|
|
@@ -3356,6 +3718,11 @@ window.__ModuleLoader__.load({
|
|
|
3356
3718
|
children: acp.label("添加智能体", "Add agent")
|
|
3357
3719
|
})]
|
|
3358
3720
|
}),
|
|
3721
|
+
update !== void 0 && updateCardVisible(update, updateOpen) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PluginUpdateCard, {
|
|
3722
|
+
acp,
|
|
3723
|
+
update,
|
|
3724
|
+
onClose: () => setUpdateOpen(false)
|
|
3725
|
+
}),
|
|
3359
3726
|
ledger.visible && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3360
3727
|
className: "dwa-card dwa-card-warn",
|
|
3361
3728
|
style: { marginBottom: 16 },
|
|
@@ -3464,6 +3831,15 @@ window.__ModuleLoader__.load({
|
|
|
3464
3831
|
"aria-pressed": page === "appearance",
|
|
3465
3832
|
onClick: () => setPage("appearance"),
|
|
3466
3833
|
children: acp.label("对话外观", "Appearance")
|
|
3834
|
+
}),
|
|
3835
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3836
|
+
type: "button",
|
|
3837
|
+
"aria-pressed": page === "logs",
|
|
3838
|
+
onClick: () => setPage("logs"),
|
|
3839
|
+
children: [acp.label("日志", "Logs"), ui.logUnread > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3840
|
+
className: "dwa-log-dot",
|
|
3841
|
+
"aria-hidden": "true"
|
|
3842
|
+
})]
|
|
3467
3843
|
})
|
|
3468
3844
|
]
|
|
3469
3845
|
}),
|
|
@@ -3715,6 +4091,52 @@ window.__ModuleLoader__.load({
|
|
|
3715
4091
|
})]
|
|
3716
4092
|
})]
|
|
3717
4093
|
}),
|
|
4094
|
+
page === "logs" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4095
|
+
className: "dwa-section",
|
|
4096
|
+
"aria-label": acp.label("运行日志", "Runtime log"),
|
|
4097
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4098
|
+
className: "dwa-section-heading",
|
|
4099
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
4100
|
+
className: "dwa-section-title",
|
|
4101
|
+
children: acp.label("运行日志", "Runtime log")
|
|
4102
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4103
|
+
className: "dwa-section-copy",
|
|
4104
|
+
children: acp.label(`agent 运行消息,最多 ${String(200)} 条。`, `Agent runtime messages, up to ${String(200)}.`)
|
|
4105
|
+
})] }), ui.logs.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4106
|
+
type: "button",
|
|
4107
|
+
className: "dwa-btn dwa-btn-quiet",
|
|
4108
|
+
onClick: () => acp.clearLogs(),
|
|
4109
|
+
children: acp.label("清空", "Clear")
|
|
4110
|
+
})]
|
|
4111
|
+
}), ui.logs.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4112
|
+
className: "dwa-empty",
|
|
4113
|
+
role: "status",
|
|
4114
|
+
children: acp.label("暂无运行日志。", "No runtime messages yet.")
|
|
4115
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
4116
|
+
className: "dwa-log-list",
|
|
4117
|
+
children: [...ui.logs].reverse().map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
4118
|
+
className: entry.level === "error" ? "dwa-log-row is-error" : "dwa-log-row",
|
|
4119
|
+
children: [
|
|
4120
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4121
|
+
className: "dwa-log-time dwa-mono",
|
|
4122
|
+
children: logClock(entry.at)
|
|
4123
|
+
}),
|
|
4124
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4125
|
+
className: `dwa-badge dwa-log-level ${entry.level === "error" ? "is-error" : "is-info"}`,
|
|
4126
|
+
children: entry.level === "error" ? acp.label("错误", "Error") : acp.label("信息", "Info")
|
|
4127
|
+
}),
|
|
4128
|
+
entry.agentId !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4129
|
+
className: "dwa-badge",
|
|
4130
|
+
children: entry.agentId
|
|
4131
|
+
}),
|
|
4132
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4133
|
+
className: "dwa-log-text",
|
|
4134
|
+
children: entry.text
|
|
4135
|
+
})
|
|
4136
|
+
]
|
|
4137
|
+
}, entry.id))
|
|
4138
|
+
})]
|
|
4139
|
+
}),
|
|
3718
4140
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
|
|
3719
4141
|
className: "dwa-health",
|
|
3720
4142
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|