dsh-ntr 0.3.0 → 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/lib/bridge.js +7 -7
- package/lib/client/panel.js +33 -3
- package/lib/client/store.js +55 -3
- package/lib/client.js +129 -3
- package/lib/client.js.map +1 -1
- package/lib/types/bridge.d.ts +2 -2
- package/lib/types/client/store.d.ts +33 -0
- package/lib/types/protocol.d.ts +7 -0
- package/lib/types/update.d.ts +7 -4
- package/lib/update.js +25 -10
- package/package.json +1 -1
- package/src/bridge.ts +8 -8
- package/src/client/panel.tsx +73 -2
- package/src/client/store.ts +88 -6
- package/src/protocol.ts +8 -0
- package/src/update.ts +21 -8
package/lib/bridge.js
CHANGED
|
@@ -329,7 +329,7 @@ export class AgentBridge {
|
|
|
329
329
|
this.touch();
|
|
330
330
|
return;
|
|
331
331
|
}
|
|
332
|
-
this.releaseAll(`agent "${this.def.id}" was released after ${String(Math.round(this.currentIdleTimeoutMs / 1000))}s idle
|
|
332
|
+
this.releaseAll(`agent "${this.def.id}" was released after ${String(Math.round(this.currentIdleTimeoutMs / 1000))}s idle`, 'idle');
|
|
333
333
|
}
|
|
334
334
|
/**
|
|
335
335
|
* Claim this bridge's single prompt slot for one turn, waiting (bounded) for
|
|
@@ -532,7 +532,7 @@ export class AgentBridge {
|
|
|
532
532
|
if (this.state === 'ready' && this.conn !== undefined && this.spawnCwd !== spawnCwd && this.isQuiet()) {
|
|
533
533
|
const reason = `agent "${this.def.id}" restarts in ${spawnCwd} (the workspace of the session using it)`;
|
|
534
534
|
this.hub.broadcast({ type: 'agent/notice', agentId: this.def.id, level: 'info', text: `acp: ${reason}` });
|
|
535
|
-
this.releaseAll(reason);
|
|
535
|
+
this.releaseAll(reason, 'workspace');
|
|
536
536
|
}
|
|
537
537
|
}
|
|
538
538
|
if (this.state === 'ready' && this.conn !== undefined)
|
|
@@ -979,12 +979,12 @@ export class AgentBridge {
|
|
|
979
979
|
this.sessions.delete(session.sessionId);
|
|
980
980
|
session.released = true;
|
|
981
981
|
this.released.set(session.sessionId, session);
|
|
982
|
-
this.hub.broadcast({ type: 'session/released', sessionId: session.sessionId, session: snapshot(session), reason: 'resume failed' });
|
|
982
|
+
this.hub.broadcast({ type: 'session/released', sessionId: session.sessionId, session: snapshot(session), reason: 'resume failed', cause: 'resume-failed' });
|
|
983
983
|
throw error;
|
|
984
984
|
}
|
|
985
985
|
}
|
|
986
986
|
/** Release all live sessions (process died or went idle): keep them resumable, settle permissions, drop the connection. */
|
|
987
|
-
releaseAll(reason) {
|
|
987
|
+
releaseAll(reason, cause) {
|
|
988
988
|
this.clearIdleTimer();
|
|
989
989
|
for (const session of [...this.sessions.values()]) {
|
|
990
990
|
// A session already being closed is `closeSession`'s to delete. Marking
|
|
@@ -1005,7 +1005,7 @@ export class AgentBridge {
|
|
|
1005
1005
|
session.released = true;
|
|
1006
1006
|
this.settlePermissions(session);
|
|
1007
1007
|
this.released.set(session.sessionId, session);
|
|
1008
|
-
this.hub.broadcast({ type: 'session/released', sessionId: session.sessionId, session: snapshot(session), reason });
|
|
1008
|
+
this.hub.broadcast({ type: 'session/released', sessionId: session.sessionId, session: snapshot(session), reason, cause });
|
|
1009
1009
|
}
|
|
1010
1010
|
this.sessions.clear();
|
|
1011
1011
|
this.acpIndex.clear();
|
|
@@ -4654,7 +4654,7 @@ export class BridgeManager {
|
|
|
4654
4654
|
// here (their `runPrompt` promise also rejects into settle, but this also
|
|
4655
4655
|
// covers a promise the connection teardown leaves pending).
|
|
4656
4656
|
this.disposeTurnStreamsOf(controller => controller.session.agentId === bridge.def.id);
|
|
4657
|
-
bridge.releaseAll(reason);
|
|
4657
|
+
bridge.releaseAll(reason, 'died');
|
|
4658
4658
|
}
|
|
4659
4659
|
agentViews() {
|
|
4660
4660
|
return [...this.defsMap.values()].map(def => (this.bridges.get(def.id) ?? this.makeBridge(def, () => { })).view());
|
|
@@ -5370,7 +5370,7 @@ export class BridgeManager {
|
|
|
5370
5370
|
// rejection does not broadcast a second `turn/end` for a turn already
|
|
5371
5371
|
// finalized by the watchdog (`LiveSession.reclaimedTurnSeq`).
|
|
5372
5372
|
session.reclaimedTurnSeq = session.turnSeq;
|
|
5373
|
-
bridge.releaseAll('agent did not acknowledge the stalled turn');
|
|
5373
|
+
bridge.releaseAll('agent did not acknowledge the stalled turn', 'stalled');
|
|
5374
5374
|
session.orphanPromptTurnSeq = undefined;
|
|
5375
5375
|
return { ok: true };
|
|
5376
5376
|
}
|
package/lib/client/panel.js
CHANGED
|
@@ -17,7 +17,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
17
17
|
*/
|
|
18
18
|
import { useEffect, useRef, useState, useSyncExternalStore } from 'react';
|
|
19
19
|
import { formatCommandLine, isAbsolutePathLike, splitCommandLine } from "./acp-ui.js";
|
|
20
|
-
import { DEFAULT_KNOWN_PRESETS } from "./store.js";
|
|
20
|
+
import { DEFAULT_KNOWN_PRESETS, LOG_MAX_ENTRIES } from "./store.js";
|
|
21
21
|
/**
|
|
22
22
|
* Mirrors `AGENT_ID_PATTERN` in impl.ts — the client cannot import host-side
|
|
23
23
|
* modules, so this copy must stay in sync (same deal as the preset copies,
|
|
@@ -242,6 +242,17 @@ const STYLE = `
|
|
|
242
242
|
.dwa-deadline-flag { margin-top: 4px; font-size: var(--ntr-font-xs); font-weight: 550; color: var(--ntr-warn); }
|
|
243
243
|
.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; }
|
|
244
244
|
.dwa-deadline-reset { display: flex; justify-content: flex-end; margin-top: 8px; }
|
|
245
|
+
/* Runtime log: one bounded list, newest first. The time column never wraps;
|
|
246
|
+
the message is the only cell that may break anywhere. */
|
|
247
|
+
.dwa-log-list { list-style: none; margin: 0; padding: 0; border: var(--ntr-line); border-radius: var(--ntr-radius); overflow: hidden; }
|
|
248
|
+
.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; }
|
|
249
|
+
.dwa-log-row:first-child { border-top: 0; }
|
|
250
|
+
.dwa-log-row.is-error { background: var(--dsw-alias-state-error-tertiary, #fef2f2); }
|
|
251
|
+
.dwa-log-time { color: var(--ntr-muted); white-space: nowrap; }
|
|
252
|
+
.dwa-log-text { flex: 1; min-width: 0; overflow-wrap: anywhere; }
|
|
253
|
+
.dwa-log-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--ntr-danger); display: inline-block; margin-left: 6px; }
|
|
254
|
+
.dwa-log-level.is-info { background: var(--dsw-alias-state-business-tertiary, #dbeafe); color: var(--dsw-alias-state-business-primary, #2563eb); }
|
|
255
|
+
.dwa-log-level.is-error { background: var(--dsw-alias-state-error-tertiary, #fef2f2); color: var(--ntr-danger); }
|
|
245
256
|
@media (max-width: 640px) {
|
|
246
257
|
.dwa-root { padding: 20px 16px; }
|
|
247
258
|
}
|
|
@@ -726,6 +737,17 @@ function ApprovalDeadlinesCard(props) {
|
|
|
726
737
|
const waitMode = deadlines.mode === 'wait';
|
|
727
738
|
return (_jsxs("section", { className: "dwa-section", children: [_jsx("div", { className: "dwa-section-heading", children: _jsxs("div", { children: [_jsx("h3", { className: "dwa-section-title", children: acp.label('审批时限', 'Approval deadlines') }), _jsx("p", { className: "dwa-section-copy", children: acp.label('权限请求的等待时长与超时处理方式。', 'How long to wait for a permission request, and what happens when nobody answers.') })] }) }), _jsxs("div", { className: "dwa-preferences", children: [_jsx(DeadlineModeBlock, { acp: acp, mode: deadlines.mode, override: modeNote }), _jsx(DeadlineSliderBlock, { acp: acp, value: waitMode ? undefined : deadlines.permissionTimeoutMs, mode: deadlines.mode, override: baselineNote }), facts.overridden && (_jsx("div", { className: "dwa-deadline-reset", children: _jsx("button", { type: "button", className: "dwa-btn dwa-btn-secondary", onClick: () => void acp.resetApprovalDeadlines(), children: acp.label('恢复配置文件的值', 'Use config file value') }) }))] })] }));
|
|
728
739
|
}
|
|
740
|
+
/**
|
|
741
|
+
* Local wall-clock `HH:MM:SS` for one runtime-log line. Hand-built on purpose:
|
|
742
|
+
* `toLocaleTimeString` may drop seconds or pick a 12-hour clock depending on
|
|
743
|
+
* the browser locale, which makes the column ragged and ambiguous.
|
|
744
|
+
*/
|
|
745
|
+
function logClock(at) {
|
|
746
|
+
const date = new Date(at);
|
|
747
|
+
return [date.getHours(), date.getMinutes(), date.getSeconds()]
|
|
748
|
+
.map(value => String(value).padStart(2, '0'))
|
|
749
|
+
.join(':');
|
|
750
|
+
}
|
|
729
751
|
/** The states that carry an update badge next to the version badge. */
|
|
730
752
|
function updateBadgeText(update, acp) {
|
|
731
753
|
if (update.status === 'available') {
|
|
@@ -835,6 +857,12 @@ export function AcpSettingsSection(props) {
|
|
|
835
857
|
useEffect(() => {
|
|
836
858
|
void acp.checkPluginUpdate();
|
|
837
859
|
}, [acp]);
|
|
860
|
+
// Opening the log is the read receipt. The store does not mutate at zero,
|
|
861
|
+
// so this cannot loop.
|
|
862
|
+
useEffect(() => {
|
|
863
|
+
if (page === 'logs' && ui.logUnread > 0)
|
|
864
|
+
acp.markLogsRead();
|
|
865
|
+
}, [acp, page, ui.logUnread]);
|
|
838
866
|
const { configured, available } = partitionAgents(ui.detectedAgents, ui.agents);
|
|
839
867
|
const selected = addTarget ?? available[0] ?? NEW_AGENT_SEED;
|
|
840
868
|
const openAdd = () => {
|
|
@@ -855,7 +883,7 @@ export function AcpSettingsSection(props) {
|
|
|
855
883
|
&& update.status !== 'not-installed';
|
|
856
884
|
return (_jsxs("div", { className: "dwa-root", children: [_jsxs("header", { className: "dwa-page-header", children: [_jsxs("div", { className: "dwa-page-heading", children: [_jsxs("h2", { className: "dwa-page-title", "aria-label": acp.label('NTR 设置', 'NTR settings'), children: [acp.label('NTR 设置', 'NTR settings'), ui.pluginVersion !== undefined && (versionOpensUpdate
|
|
857
885
|
? (_jsx("button", { type: "button", className: "dwa-badge dwa-version dwa-version-btn", title: versionTitle, "aria-expanded": updateOpen, "aria-controls": "ntr-plugin-update-card", onClick: () => setUpdateOpen(open => !open), children: `v${ui.pluginVersion}` }))
|
|
858
|
-
: _jsx("span", { className: "dwa-badge dwa-version", title: versionTitle, children: `v${ui.pluginVersion}` })), update !== undefined && (_jsx(PluginUpdateBadge, { acp: acp, update: update, open: updateOpen, onToggle: () => setUpdateOpen(open => !open) }))] }), _jsx("p", { className: "dwa-page-description", children: acp.label('连接智能体,设置你的对话偏好。', 'Connect agents and set your conversation preferences.') })] }), page === 'agents' && configured.length > 0 && (_jsx("button", { type: "button", className: "dwa-btn dwa-btn-primary", onClick: openAdd, children: acp.label('添加智能体', 'Add agent') }))] }), update !== undefined && updateCardVisible(update, updateOpen) && (_jsx(PluginUpdateCard, { acp: acp, update: update, onClose: () => setUpdateOpen(false) })), ledger.visible && (_jsxs("div", { className: "dwa-card dwa-card-warn", style: { marginBottom: 16 }, children: [_jsx("div", { style: { fontWeight: 600, marginBottom: 4 }, children: acp.label('待处理权限请求', 'Pending Permission Requests') }), _jsx("div", { className: "dwa-hint", style: { marginBottom: 8 }, children: acp.label('对话打开时在那边的卡片上回答;这里兜底显示其他会话的询问。', 'Answer in the conversation card while that conversation is open; this list covers the asks whose conversation is not.') }), ui.permissions.map(permission => (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6, flexWrap: 'wrap' }, children: [_jsxs("span", { style: { flex: 1, minWidth: 160 }, children: [_jsx("span", { style: { fontWeight: 600 }, children: permission.toolName }), permission.reason !== undefined && (_jsxs("span", { style: { color: 'var(--ntr-muted)' }, children: [" \u00B7 ", permission.reason] }))] }), permission.options.map(option => (_jsx("button", { type: "button", className: "dwa-btn", onClick: () => { void acp.respondPermission(permission.requestId, option.optionId).catch(() => undefined); }, children: option.name }, option.optionId))), _jsx("button", { type: "button", className: "dwa-btn dwa-btn-quiet", onClick: () => { void acp.respondPermission(permission.requestId).catch(() => undefined); }, children: acp.label('取消', 'Cancel') })] }, permission.requestId)))] })), ui.notice !== undefined && (_jsxs("div", { className: "dwa-card dwa-card-warn", style: { marginBottom: 16, display: 'flex', alignItems: 'center', gap: 8 }, children: [_jsx("span", { style: { flex: 1 }, children: ui.notice }), _jsx("button", { type: "button", className: "dwa-btn dwa-btn-quiet", "aria-label": acp.label('关闭通知', 'Dismiss notice'), onClick: () => acp.clearNotice(), children: "\u2715" })] })), presetProblem && (_jsxs("div", { className: "dwa-card dwa-card-warn dwa-notice", role: "status", children: [_jsx("span", { children: acp.label('NTR 模式需要检查,当前可能无法开始对话。', 'NTR mode needs attention; new conversations may be unavailable.') }), _jsx("button", { type: "button", className: "dwa-btn", onClick: () => setPage('diagnostics'), children: acp.label('查看诊断', 'View diagnostics') })] })), _jsxs("nav", { className: "dwa-tabs", "aria-label": acp.label('NTR 设置分类', 'NTR settings categories'), children: [_jsx("button", { type: "button", "aria-pressed": page === 'agents', onClick: () => setPage('agents'), children: acp.label('智能体', 'Agents') }), ui.approvalDeadlines !== undefined && _jsx("button", { type: "button", "aria-pressed": page === 'approvals', onClick: () => setPage('approvals'), children: acp.label('审批时限', 'Approval deadlines') }), _jsx("button", { type: "button", "aria-pressed": page === 'appearance', onClick: () => setPage('appearance'), children: acp.label('对话外观', 'Appearance') })] }), page === 'diagnostics' && (_jsxs("section", { className: "dwa-section", children: [_jsx("h3", { className: "dwa-section-title", children: acp.label('连接诊断', 'Connection diagnostics') }), _jsxs("div", { className: "dwa-preference-row", children: [_jsx("span", { children: acp.label('宿主事件流', 'Host event stream') }), _jsx("span", { className: "dwa-hint", children: streamText(ui.stream, acp) })] }), _jsxs("div", { className: "dwa-row-actions", children: [_jsx("button", { type: "button", className: "dwa-btn", onClick: () => { void acp.resync(); void acp.detectAgents(); }, disabled: ui.detecting, children: ui.detecting ? acp.label('检测中...', 'Detecting...') : acp.label('重新检测与刷新', 'Re-detect & Refresh') }), ui.detectSummary !== undefined && _jsx("span", { className: "dwa-hint", role: "status", children: ui.detectSummary })] })] })), page === 'diagnostics' && ui.preset !== undefined && (_jsxs("div", { className: "dwa-card dwa-mode-banner", children: [_jsxs("div", { className: "dwa-mode-title", children: [acp.label('NTR 模式', 'NTR mode'), _jsx("span", { className: "dwa-badge", children: ui.preset.id })] }), _jsx("span", { className: `dwa-badge ${ui.preset.installed ? 'dwa-badge-success' : 'dwa-badge-warn'}`, children: ui.preset.installed ? acp.label('已就绪', 'Ready') : acp.label('需检查', 'Needs attention') }), _jsxs("div", { className: "dwa-mode-copy", children: [ui.preset.installed
|
|
886
|
+
: _jsx("span", { className: "dwa-badge dwa-version", title: versionTitle, children: `v${ui.pluginVersion}` })), update !== undefined && (_jsx(PluginUpdateBadge, { acp: acp, update: update, open: updateOpen, onToggle: () => setUpdateOpen(open => !open) }))] }), _jsx("p", { className: "dwa-page-description", children: acp.label('连接智能体,设置你的对话偏好。', 'Connect agents and set your conversation preferences.') })] }), page === 'agents' && configured.length > 0 && (_jsx("button", { type: "button", className: "dwa-btn dwa-btn-primary", onClick: openAdd, children: acp.label('添加智能体', 'Add agent') }))] }), update !== undefined && updateCardVisible(update, updateOpen) && (_jsx(PluginUpdateCard, { acp: acp, update: update, onClose: () => setUpdateOpen(false) })), ledger.visible && (_jsxs("div", { className: "dwa-card dwa-card-warn", style: { marginBottom: 16 }, children: [_jsx("div", { style: { fontWeight: 600, marginBottom: 4 }, children: acp.label('待处理权限请求', 'Pending Permission Requests') }), _jsx("div", { className: "dwa-hint", style: { marginBottom: 8 }, children: acp.label('对话打开时在那边的卡片上回答;这里兜底显示其他会话的询问。', 'Answer in the conversation card while that conversation is open; this list covers the asks whose conversation is not.') }), ui.permissions.map(permission => (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6, flexWrap: 'wrap' }, children: [_jsxs("span", { style: { flex: 1, minWidth: 160 }, children: [_jsx("span", { style: { fontWeight: 600 }, children: permission.toolName }), permission.reason !== undefined && (_jsxs("span", { style: { color: 'var(--ntr-muted)' }, children: [" \u00B7 ", permission.reason] }))] }), permission.options.map(option => (_jsx("button", { type: "button", className: "dwa-btn", onClick: () => { void acp.respondPermission(permission.requestId, option.optionId).catch(() => undefined); }, children: option.name }, option.optionId))), _jsx("button", { type: "button", className: "dwa-btn dwa-btn-quiet", onClick: () => { void acp.respondPermission(permission.requestId).catch(() => undefined); }, children: acp.label('取消', 'Cancel') })] }, permission.requestId)))] })), ui.notice !== undefined && (_jsxs("div", { className: "dwa-card dwa-card-warn", style: { marginBottom: 16, display: 'flex', alignItems: 'center', gap: 8 }, children: [_jsx("span", { style: { flex: 1 }, children: ui.notice }), _jsx("button", { type: "button", className: "dwa-btn dwa-btn-quiet", "aria-label": acp.label('关闭通知', 'Dismiss notice'), onClick: () => acp.clearNotice(), children: "\u2715" })] })), presetProblem && (_jsxs("div", { className: "dwa-card dwa-card-warn dwa-notice", role: "status", children: [_jsx("span", { children: acp.label('NTR 模式需要检查,当前可能无法开始对话。', 'NTR mode needs attention; new conversations may be unavailable.') }), _jsx("button", { type: "button", className: "dwa-btn", onClick: () => setPage('diagnostics'), children: acp.label('查看诊断', 'View diagnostics') })] })), _jsxs("nav", { className: "dwa-tabs", "aria-label": acp.label('NTR 设置分类', 'NTR settings categories'), children: [_jsx("button", { type: "button", "aria-pressed": page === 'agents', onClick: () => setPage('agents'), children: acp.label('智能体', 'Agents') }), ui.approvalDeadlines !== undefined && _jsx("button", { type: "button", "aria-pressed": page === 'approvals', onClick: () => setPage('approvals'), children: acp.label('审批时限', 'Approval deadlines') }), _jsx("button", { type: "button", "aria-pressed": page === 'appearance', onClick: () => setPage('appearance'), children: acp.label('对话外观', 'Appearance') }), _jsxs("button", { type: "button", "aria-pressed": page === 'logs', onClick: () => setPage('logs'), children: [acp.label('日志', 'Logs'), ui.logUnread > 0 && _jsx("span", { className: "dwa-log-dot", "aria-hidden": "true" })] })] }), page === 'diagnostics' && (_jsxs("section", { className: "dwa-section", children: [_jsx("h3", { className: "dwa-section-title", children: acp.label('连接诊断', 'Connection diagnostics') }), _jsxs("div", { className: "dwa-preference-row", children: [_jsx("span", { children: acp.label('宿主事件流', 'Host event stream') }), _jsx("span", { className: "dwa-hint", children: streamText(ui.stream, acp) })] }), _jsxs("div", { className: "dwa-row-actions", children: [_jsx("button", { type: "button", className: "dwa-btn", onClick: () => { void acp.resync(); void acp.detectAgents(); }, disabled: ui.detecting, children: ui.detecting ? acp.label('检测中...', 'Detecting...') : acp.label('重新检测与刷新', 'Re-detect & Refresh') }), ui.detectSummary !== undefined && _jsx("span", { className: "dwa-hint", role: "status", children: ui.detectSummary })] })] })), page === 'diagnostics' && ui.preset !== undefined && (_jsxs("div", { className: "dwa-card dwa-mode-banner", children: [_jsxs("div", { className: "dwa-mode-title", children: [acp.label('NTR 模式', 'NTR mode'), _jsx("span", { className: "dwa-badge", children: ui.preset.id })] }), _jsx("span", { className: `dwa-badge ${ui.preset.installed ? 'dwa-badge-success' : 'dwa-badge-warn'}`, children: ui.preset.installed ? acp.label('已就绪', 'Ready') : acp.label('需检查', 'Needs attention') }), _jsxs("div", { className: "dwa-mode-copy", children: [ui.preset.installed
|
|
859
887
|
? acp.label('已加入宿主模式列表,新建会话时选择「NTR 模式」即可使用。', 'Added to the harness roster. Pick “NTR mode” when creating a session.')
|
|
860
888
|
: acp.label('宿主模式列表中没有可用的「NTR 模式」,请检查预设声明。', 'The harness roster has no usable “NTR mode”. Check the preset declaration.'), _jsx("code", { className: "dwa-mode-source dwa-mono", children: ui.preset.source })] }), ui.preset.problem !== undefined && (_jsx("div", { className: "dwa-hint", style: { gridColumn: '1 / -1', color: 'var(--ntr-warn)' }, children: acp.label(`宿主报告:${ui.preset.problem}`, `Harness reported: ${ui.preset.problem}`) }))] })), _jsxs("section", { className: "dwa-section", hidden: page !== 'agents', "aria-label": acp.label('已接入的智能体', 'Connected agents'), children: [_jsxs("div", { className: "dwa-section-heading", children: [_jsx("div", { children: _jsxs("h3", { className: "dwa-section-title", children: [acp.label('已接入的智能体', 'Connected agents'), ui.agentsLoaded && _jsx("span", { className: "dwa-badge", children: configured.length })] }) }), configured.length > 0 && _jsx("span", { className: "dwa-hint", children: acp.label('默认项用于新的 NTR 会话', 'The default serves new NTR conversations') })] }), !ui.agentsLoaded && _jsx("div", { className: "dwa-empty", role: "status", children: acp.label('正在读取智能体…', 'Loading agents…') }), ui.agentsLoaded && configured.length === 0 && (_jsxs("div", { className: "dwa-empty", children: [_jsx("h3", { children: acp.label('接入第一个智能体', 'Connect your first agent') }), _jsx("p", { className: "dwa-section-copy", children: acp.label('选择已有预设,或填写自己的 ACP 启动命令。', 'Choose a preset or enter your own ACP launch command.') }), _jsx("button", { type: "button", className: "dwa-btn dwa-btn-primary", onClick: openAdd, children: acp.label('添加智能体', 'Add agent') })] })), _jsx("div", { className: "dwa-agent-list", children: configured.map(({ info, synthesized }) => {
|
|
861
889
|
const activeAgent = ui.agents.find(a => a.id === info.id);
|
|
@@ -864,7 +892,9 @@ export function AcpSettingsSection(props) {
|
|
|
864
892
|
setAddTarget(undefined);
|
|
865
893
|
setAddOpen(false);
|
|
866
894
|
setPage('agents');
|
|
867
|
-
} }, selected.id)] }))] })] }), _jsx("div", { hidden: page !== 'approvals', children: _jsx(ApprovalDeadlinesCard, { acp: acp, ui: ui }) }), _jsxs("section", { className: "dwa-section", hidden: page !== 'appearance', "aria-label": acp.label('对话外观', 'Appearance'), children: [_jsx("div", { className: "dwa-section-heading", children: _jsxs("div", { children: [_jsx("h3", { className: "dwa-section-title", children: acp.label('对话外观', 'Conversation appearance') }), _jsx("p", { className: "dwa-section-copy", children: acp.label('设置对话中 NTR 挂牌的进程存活提示。', 'Choose how the NTR tag indicates a running agent process.') })] }) }), _jsxs("div", { className: "dwa-preferences", children: [_jsxs("div", { className: "dwa-preference-row", children: [_jsxs("div", { children: [_jsx("div", { className: "dwa-preference-label", children: acp.label('挂牌光晕', 'Tag halo') }), _jsx("div", { className: "dwa-preference-copy", children: acp.label('呼吸为脉动光,常亮为恒定光。', 'Breathe pulses gently; steady keeps a constant glow.') })] }), _jsx("div", { className: "dwa-seg", children: ['breath', 'steady'].map(mode => (_jsx("button", { type: "button", className: ui.tagHalo.mode === mode ? 'is-on' : '', onClick: () => void acp.setTagHalo({ ...ui.tagHalo, mode }), children: mode === 'breath' ? acp.label('呼吸', 'Breathe') : acp.label('常亮', 'Steady') }, mode))) })] }), ui.tagHalo.mode === 'breath' && (_jsxs("div", { className: "dwa-preference-row", children: [_jsxs("div", { children: [_jsx("div", { className: "dwa-preference-label", children: acp.label('呼吸频率', 'Breathing speed') }), _jsx("div", { className: "dwa-preference-copy", children: acp.label('控制光晕的脉动速度。', 'Controls how quickly the halo pulses.') })] }), _jsx("div", { className: "dwa-seg", children: ['slow', 'normal', 'fast'].map(speed => (_jsx("button", { type: "button", className: ui.tagHalo.speed === speed ? 'is-on' : '', onClick: () => void acp.setTagHalo({ ...ui.tagHalo, speed }), children: speed === 'slow' ? acp.label('慢速', 'Slow') : speed === 'normal' ? acp.label('正常', 'Normal') : acp.label('快速', 'Fast') }, speed))) })] }))] })] }), _jsxs("
|
|
895
|
+
} }, selected.id)] }))] })] }), _jsx("div", { hidden: page !== 'approvals', children: _jsx(ApprovalDeadlinesCard, { acp: acp, ui: ui }) }), _jsxs("section", { className: "dwa-section", hidden: page !== 'appearance', "aria-label": acp.label('对话外观', 'Appearance'), children: [_jsx("div", { className: "dwa-section-heading", children: _jsxs("div", { children: [_jsx("h3", { className: "dwa-section-title", children: acp.label('对话外观', 'Conversation appearance') }), _jsx("p", { className: "dwa-section-copy", children: acp.label('设置对话中 NTR 挂牌的进程存活提示。', 'Choose how the NTR tag indicates a running agent process.') })] }) }), _jsxs("div", { className: "dwa-preferences", children: [_jsxs("div", { className: "dwa-preference-row", children: [_jsxs("div", { children: [_jsx("div", { className: "dwa-preference-label", children: acp.label('挂牌光晕', 'Tag halo') }), _jsx("div", { className: "dwa-preference-copy", children: acp.label('呼吸为脉动光,常亮为恒定光。', 'Breathe pulses gently; steady keeps a constant glow.') })] }), _jsx("div", { className: "dwa-seg", children: ['breath', 'steady'].map(mode => (_jsx("button", { type: "button", className: ui.tagHalo.mode === mode ? 'is-on' : '', onClick: () => void acp.setTagHalo({ ...ui.tagHalo, mode }), children: mode === 'breath' ? acp.label('呼吸', 'Breathe') : acp.label('常亮', 'Steady') }, mode))) })] }), ui.tagHalo.mode === 'breath' && (_jsxs("div", { className: "dwa-preference-row", children: [_jsxs("div", { children: [_jsx("div", { className: "dwa-preference-label", children: acp.label('呼吸频率', 'Breathing speed') }), _jsx("div", { className: "dwa-preference-copy", children: acp.label('控制光晕的脉动速度。', 'Controls how quickly the halo pulses.') })] }), _jsx("div", { className: "dwa-seg", children: ['slow', 'normal', 'fast'].map(speed => (_jsx("button", { type: "button", className: ui.tagHalo.speed === speed ? 'is-on' : '', onClick: () => void acp.setTagHalo({ ...ui.tagHalo, speed }), children: speed === 'slow' ? acp.label('慢速', 'Slow') : speed === 'normal' ? acp.label('正常', 'Normal') : acp.label('快速', 'Fast') }, speed))) })] }))] })] }), page === 'logs' && (_jsxs("section", { className: "dwa-section", "aria-label": acp.label('运行日志', 'Runtime log'), children: [_jsxs("div", { className: "dwa-section-heading", children: [_jsxs("div", { children: [_jsx("h3", { className: "dwa-section-title", children: acp.label('运行日志', 'Runtime log') }), _jsx("p", { className: "dwa-section-copy", children: acp.label(`agent 运行消息,最多 ${String(LOG_MAX_ENTRIES)} 条。`, `Agent runtime messages, up to ${String(LOG_MAX_ENTRIES)}.`) })] }), ui.logs.length > 0 && (_jsx("button", { type: "button", className: "dwa-btn dwa-btn-quiet", onClick: () => acp.clearLogs(), children: acp.label('清空', 'Clear') }))] }), ui.logs.length === 0
|
|
896
|
+
? _jsx("div", { className: "dwa-empty", role: "status", children: acp.label('暂无运行日志。', 'No runtime messages yet.') })
|
|
897
|
+
: (_jsx("ul", { className: "dwa-log-list", children: [...ui.logs].reverse().map(entry => (_jsxs("li", { className: entry.level === 'error' ? 'dwa-log-row is-error' : 'dwa-log-row', children: [_jsx("span", { className: "dwa-log-time dwa-mono", children: logClock(entry.at) }), _jsx("span", { className: `dwa-badge dwa-log-level ${entry.level === 'error' ? 'is-error' : 'is-info'}`, children: entry.level === 'error' ? acp.label('错误', 'Error') : acp.label('信息', 'Info') }), entry.agentId !== undefined && _jsx("span", { className: "dwa-badge", children: entry.agentId }), _jsx("span", { className: "dwa-log-text", children: entry.text })] }, entry.id))) }))] })), _jsxs("footer", { className: "dwa-health", children: [_jsxs("div", { className: "dwa-health-status", children: [_jsx("span", { className: "dwa-dot", style: { background: ui.stream === 'live' ? 'var(--ntr-success)' : ui.stream === 'down' ? 'var(--ntr-danger)' : 'var(--ntr-muted)' } }), _jsx("span", { children: streamText(ui.stream, acp) }), ui.preset !== undefined && _jsx("span", { children: ui.preset.installed && !presetProblem ? acp.label('NTR 模式已就绪', 'NTR mode ready') : acp.label('NTR 模式需检查', 'NTR mode needs attention') })] }), page !== 'diagnostics' && _jsx("button", { type: "button", className: "dwa-btn dwa-btn-quiet", onClick: () => setPage('diagnostics'), children: acp.label('查看诊断', 'View diagnostics') })] })] }));
|
|
868
898
|
}
|
|
869
899
|
/** One row per agent: compact when collapsed, the full form when expanded. */
|
|
870
900
|
function AgentRow({ detected, activeAgent, acp, isDefault, synthesized, active, newCustom = false, onCreated, }) {
|
package/lib/client/store.js
CHANGED
|
@@ -39,6 +39,8 @@ export function deadlineRemainingLabel(deadlineAt, now, mode) {
|
|
|
39
39
|
const rest = seconds % 60;
|
|
40
40
|
return rest === 0 ? `${String(minutes)}m` : `${String(minutes)}m ${String(rest)}s`;
|
|
41
41
|
}
|
|
42
|
+
/** Cap on retained runtime-log lines; older entries fall off the front. */
|
|
43
|
+
export const LOG_MAX_ENTRIES = 200;
|
|
42
44
|
const MAX_BLOCKS = 400;
|
|
43
45
|
/** How many observed ACP transcripts stay live at once (insertion-ordered eviction). */
|
|
44
46
|
const MAX_WATCHED = 6;
|
|
@@ -339,6 +341,8 @@ export class AcpStore {
|
|
|
339
341
|
blocks: [],
|
|
340
342
|
permissions: [],
|
|
341
343
|
busy: false,
|
|
344
|
+
logs: [],
|
|
345
|
+
logUnread: 0,
|
|
342
346
|
detectedAgents: [...DEFAULT_KNOWN_PRESETS],
|
|
343
347
|
detecting: false,
|
|
344
348
|
modelCatalogs: {},
|
|
@@ -360,6 +364,8 @@ export class AcpStore {
|
|
|
360
364
|
};
|
|
361
365
|
listeners = new Set();
|
|
362
366
|
source;
|
|
367
|
+
/** Monotonic id source for {@link AcpLogEntry}s (never reset by `clearLogs`). */
|
|
368
|
+
logSeq = 0;
|
|
363
369
|
lastSeq = new Map();
|
|
364
370
|
/** Sessions with a finalized turn (dedups the cancel-fallback + late real turn/end pair). */
|
|
365
371
|
openTurns = new Set();
|
|
@@ -445,6 +451,23 @@ export class AcpStore {
|
|
|
445
451
|
for (const listener of [...this.listeners])
|
|
446
452
|
listener();
|
|
447
453
|
}
|
|
454
|
+
/**
|
|
455
|
+
* Build the log patch for one line. Does NOT mutate: the caller merges it
|
|
456
|
+
* into the single `mutate` call it was already making for the same event.
|
|
457
|
+
*/
|
|
458
|
+
logPatch(level, text, agentId) {
|
|
459
|
+
const entry = {
|
|
460
|
+
id: ++this.logSeq,
|
|
461
|
+
at: Date.now(),
|
|
462
|
+
level,
|
|
463
|
+
text,
|
|
464
|
+
...(agentId === undefined ? {} : { agentId }),
|
|
465
|
+
};
|
|
466
|
+
return {
|
|
467
|
+
logs: [...this.state.logs, entry].slice(-LOG_MAX_ENTRIES),
|
|
468
|
+
logUnread: this.state.logUnread + 1,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
448
471
|
// ---- Streaming update coalescing ----
|
|
449
472
|
/**
|
|
450
473
|
* Live `session/update` frames waiting to be folded. Agents stream at token
|
|
@@ -1628,6 +1651,16 @@ export class AcpStore {
|
|
|
1628
1651
|
clearNotice() {
|
|
1629
1652
|
this.mutate({ notice: undefined });
|
|
1630
1653
|
}
|
|
1654
|
+
/** Drop every retained runtime-log line (the unread counter restarts too). */
|
|
1655
|
+
clearLogs() {
|
|
1656
|
+
this.mutate({ logs: [], logUnread: 0 });
|
|
1657
|
+
}
|
|
1658
|
+
/** The user looked at the log: clear the unread badge. */
|
|
1659
|
+
markLogsRead() {
|
|
1660
|
+
if (this.state.logUnread === 0)
|
|
1661
|
+
return;
|
|
1662
|
+
this.mutate({ logUnread: 0 });
|
|
1663
|
+
}
|
|
1631
1664
|
flashError(error) {
|
|
1632
1665
|
this.mutate({ notice: error instanceof Error ? error.message : String(error) });
|
|
1633
1666
|
}
|
|
@@ -1635,7 +1668,7 @@ export class AcpStore {
|
|
|
1635
1668
|
applyEvent(event) {
|
|
1636
1669
|
switch (event.type) {
|
|
1637
1670
|
case 'agent/notice': {
|
|
1638
|
-
this.mutate(
|
|
1671
|
+
this.mutate(this.logPatch(event.level, event.text, event.agentId));
|
|
1639
1672
|
return Promise.resolve();
|
|
1640
1673
|
}
|
|
1641
1674
|
case 'agent/status': {
|
|
@@ -1666,6 +1699,7 @@ export class AcpStore {
|
|
|
1666
1699
|
return Promise.resolve();
|
|
1667
1700
|
}
|
|
1668
1701
|
case 'session/closed': {
|
|
1702
|
+
const closingSession = this.state.sessions.find(s => s.sessionId === event.sessionId);
|
|
1669
1703
|
const sessions = this.state.sessions.filter(s => s.sessionId !== event.sessionId);
|
|
1670
1704
|
const active = this.state.activeSessionId === event.sessionId ? sessions[0]?.sessionId : this.state.activeSessionId;
|
|
1671
1705
|
this.openTurns.delete(event.sessionId);
|
|
@@ -1681,7 +1715,9 @@ export class AcpStore {
|
|
|
1681
1715
|
chatTranscripts: closedTranscripts,
|
|
1682
1716
|
...(chatSessions.length !== this.state.chatSessions.length ? { chatSessions } : {}),
|
|
1683
1717
|
permissions: this.state.permissions.filter(p => p.sessionId !== event.sessionId),
|
|
1684
|
-
...(event.reason === 'agent-died'
|
|
1718
|
+
...(event.reason === 'agent-died'
|
|
1719
|
+
? this.logPatch('error', this.locale('会话因 agent 进程退出而终止', 'session ended: agent process died'), closingSession?.agentId)
|
|
1720
|
+
: {}),
|
|
1685
1721
|
});
|
|
1686
1722
|
return Promise.resolve();
|
|
1687
1723
|
}
|
|
@@ -1698,6 +1734,22 @@ export class AcpStore {
|
|
|
1698
1734
|
const chatSessions = this.state.chatSessions.some(s => s.sessionId === event.sessionId)
|
|
1699
1735
|
? this.state.chatSessions.map(s => (s.sessionId === event.sessionId ? { ...s, released: true } : s))
|
|
1700
1736
|
: this.state.chatSessions;
|
|
1737
|
+
// One runtime-log line per release cause; `idle` stays silent on
|
|
1738
|
+
// purpose (an expected lifecycle transition, not a fault).
|
|
1739
|
+
const cause = event.cause ?? 'died';
|
|
1740
|
+
let log = {};
|
|
1741
|
+
if (cause === 'workspace') {
|
|
1742
|
+
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);
|
|
1743
|
+
}
|
|
1744
|
+
else if (cause === 'resume-failed') {
|
|
1745
|
+
log = this.logPatch('error', this.locale('会话恢复失败;再次发送消息将重试恢复。', 'session resume failed; your next message will retry it.'), event.session.agentId);
|
|
1746
|
+
}
|
|
1747
|
+
else if (cause === 'stalled') {
|
|
1748
|
+
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);
|
|
1749
|
+
}
|
|
1750
|
+
else if (cause === 'died') {
|
|
1751
|
+
log = this.logPatch('error', this.locale('agent 进程已退出,会话已挂起;再次发送消息将自动恢复。', 'agent process died; the session is paused and will resume on your next message.'), event.session.agentId);
|
|
1752
|
+
}
|
|
1701
1753
|
this.mutate({
|
|
1702
1754
|
sessions,
|
|
1703
1755
|
...(chatSessions !== this.state.chatSessions ? { chatSessions } : {}),
|
|
@@ -1705,7 +1757,7 @@ export class AcpStore {
|
|
|
1705
1757
|
// An observed transcript keeps its buffer but flips to released, so
|
|
1706
1758
|
// the ACP view can say "会恢复" instead of silently looking dead.
|
|
1707
1759
|
...this.watchedSessionPatch(event.sessionId, event.session),
|
|
1708
|
-
|
|
1760
|
+
...log,
|
|
1709
1761
|
});
|
|
1710
1762
|
return Promise.resolve();
|
|
1711
1763
|
}
|
package/lib/client.js
CHANGED
|
@@ -322,6 +322,8 @@ window.__ModuleLoader__.load({
|
|
|
322
322
|
blocks: [],
|
|
323
323
|
permissions: [],
|
|
324
324
|
busy: false,
|
|
325
|
+
logs: [],
|
|
326
|
+
logUnread: 0,
|
|
325
327
|
detectedAgents: [...DEFAULT_KNOWN_PRESETS],
|
|
326
328
|
detecting: false,
|
|
327
329
|
modelCatalogs: {},
|
|
@@ -343,6 +345,8 @@ window.__ModuleLoader__.load({
|
|
|
343
345
|
};
|
|
344
346
|
listeners = /* @__PURE__ */ new Set();
|
|
345
347
|
source;
|
|
348
|
+
/** Monotonic id source for {@link AcpLogEntry}s (never reset by `clearLogs`). */
|
|
349
|
+
logSeq = 0;
|
|
346
350
|
lastSeq = /* @__PURE__ */ new Map();
|
|
347
351
|
/** Sessions with a finalized turn (dedups the cancel-fallback + late real turn/end pair). */
|
|
348
352
|
openTurns = /* @__PURE__ */ new Set();
|
|
@@ -429,6 +433,23 @@ window.__ModuleLoader__.load({
|
|
|
429
433
|
for (const listener of [...this.listeners]) listener();
|
|
430
434
|
}
|
|
431
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
|
+
/**
|
|
432
453
|
* Live `session/update` frames waiting to be folded. Agents stream at token
|
|
433
454
|
* granularity — hundreds of frames a second with usage ticks interleaved —
|
|
434
455
|
* and folding plus notifying listeners per frame re-rendered every store
|
|
@@ -1479,13 +1500,25 @@ window.__ModuleLoader__.load({
|
|
|
1479
1500
|
clearNotice() {
|
|
1480
1501
|
this.mutate({ notice: void 0 });
|
|
1481
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
|
+
}
|
|
1482
1515
|
flashError(error) {
|
|
1483
1516
|
this.mutate({ notice: error instanceof Error ? error.message : String(error) });
|
|
1484
1517
|
}
|
|
1485
1518
|
applyEvent(event) {
|
|
1486
1519
|
switch (event.type) {
|
|
1487
1520
|
case "agent/notice":
|
|
1488
|
-
this.mutate(
|
|
1521
|
+
this.mutate(this.logPatch(event.level, event.text, event.agentId));
|
|
1489
1522
|
return Promise.resolve();
|
|
1490
1523
|
case "agent/status":
|
|
1491
1524
|
if (event.agent !== void 0) {
|
|
@@ -1510,6 +1543,7 @@ window.__ModuleLoader__.load({
|
|
|
1510
1543
|
return Promise.resolve();
|
|
1511
1544
|
}
|
|
1512
1545
|
case "session/closed": {
|
|
1546
|
+
const closingSession = this.state.sessions.find((s) => s.sessionId === event.sessionId);
|
|
1513
1547
|
const sessions = this.state.sessions.filter((s) => s.sessionId !== event.sessionId);
|
|
1514
1548
|
const active = this.state.activeSessionId === event.sessionId ? sessions[0]?.sessionId : this.state.activeSessionId;
|
|
1515
1549
|
this.openTurns.delete(event.sessionId);
|
|
@@ -1522,7 +1556,7 @@ window.__ModuleLoader__.load({
|
|
|
1522
1556
|
chatTranscripts: closedTranscripts,
|
|
1523
1557
|
...chatSessions.length !== this.state.chatSessions.length ? { chatSessions } : {},
|
|
1524
1558
|
permissions: this.state.permissions.filter((p) => p.sessionId !== event.sessionId),
|
|
1525
|
-
...event.reason === "agent-died" ?
|
|
1559
|
+
...event.reason === "agent-died" ? this.logPatch("error", this.locale("会话因 agent 进程退出而终止", "session ended: agent process died"), closingSession?.agentId) : {}
|
|
1526
1560
|
});
|
|
1527
1561
|
return Promise.resolve();
|
|
1528
1562
|
}
|
|
@@ -1533,12 +1567,18 @@ window.__ModuleLoader__.load({
|
|
|
1533
1567
|
...s,
|
|
1534
1568
|
released: true
|
|
1535
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);
|
|
1536
1576
|
this.mutate({
|
|
1537
1577
|
sessions,
|
|
1538
1578
|
...chatSessions !== this.state.chatSessions ? { chatSessions } : {},
|
|
1539
1579
|
permissions: this.state.permissions.filter((p) => p.sessionId !== event.sessionId),
|
|
1540
1580
|
...this.watchedSessionPatch(event.sessionId, event.session),
|
|
1541
|
-
|
|
1581
|
+
...log
|
|
1542
1582
|
});
|
|
1543
1583
|
return Promise.resolve();
|
|
1544
1584
|
}
|
|
@@ -2921,6 +2961,17 @@ window.__ModuleLoader__.load({
|
|
|
2921
2961
|
.dwa-deadline-flag { margin-top: 4px; font-size: var(--ntr-font-xs); font-weight: 550; color: var(--ntr-warn); }
|
|
2922
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; }
|
|
2923
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); }
|
|
2924
2975
|
@media (max-width: 640px) {
|
|
2925
2976
|
.dwa-root { padding: 20px 16px; }
|
|
2926
2977
|
}
|
|
@@ -3449,6 +3500,19 @@ window.__ModuleLoader__.load({
|
|
|
3449
3500
|
})]
|
|
3450
3501
|
});
|
|
3451
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
|
+
}
|
|
3452
3516
|
/** The states that carry an update badge next to the version badge. */
|
|
3453
3517
|
function updateBadgeText(update, acp) {
|
|
3454
3518
|
if (update.status === "available") return acp.label(`可更新 v${update.latest ?? update.current}`, `Update v${update.latest ?? update.current}`);
|
|
@@ -3594,6 +3658,13 @@ window.__ModuleLoader__.load({
|
|
|
3594
3658
|
(0, react.useEffect)(() => {
|
|
3595
3659
|
acp.checkPluginUpdate();
|
|
3596
3660
|
}, [acp]);
|
|
3661
|
+
(0, react.useEffect)(() => {
|
|
3662
|
+
if (page === "logs" && ui.logUnread > 0) acp.markLogsRead();
|
|
3663
|
+
}, [
|
|
3664
|
+
acp,
|
|
3665
|
+
page,
|
|
3666
|
+
ui.logUnread
|
|
3667
|
+
]);
|
|
3597
3668
|
const { configured, available } = partitionAgents(ui.detectedAgents, ui.agents);
|
|
3598
3669
|
const selected = addTarget ?? available[0] ?? NEW_AGENT_SEED;
|
|
3599
3670
|
const openAdd = () => {
|
|
@@ -3760,6 +3831,15 @@ window.__ModuleLoader__.load({
|
|
|
3760
3831
|
"aria-pressed": page === "appearance",
|
|
3761
3832
|
onClick: () => setPage("appearance"),
|
|
3762
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
|
+
})]
|
|
3763
3843
|
})
|
|
3764
3844
|
]
|
|
3765
3845
|
}),
|
|
@@ -4011,6 +4091,52 @@ window.__ModuleLoader__.load({
|
|
|
4011
4091
|
})]
|
|
4012
4092
|
})]
|
|
4013
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
|
+
}),
|
|
4014
4140
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
|
|
4015
4141
|
className: "dwa-health",
|
|
4016
4142
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|