nexus-agentd 0.2.6 → 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.md +103 -14
- package/dist/a2a/runtime.d.ts +4 -1
- package/dist/a2a/runtime.js +213 -10
- package/dist/acp/runtime.js +13 -6
- package/dist/artifact-store.d.ts +168 -0
- package/dist/artifact-store.js +1111 -0
- package/dist/cli.js +13 -14
- package/dist/config.js +45 -5
- package/dist/control-plane-validation.d.ts +54 -0
- package/dist/control-plane-validation.js +152 -0
- package/dist/control-plane.d.ts +9 -32
- package/dist/control-plane.js +41 -157
- package/dist/drivers/index.js +12 -0
- package/dist/drivers/pi.js +1 -1
- package/dist/drivers/stdio.d.ts +4 -1
- package/dist/drivers/stdio.js +135 -19
- package/dist/events.d.ts +9 -0
- package/dist/events.js +17 -4
- package/dist/index.js +10 -1
- package/dist/process-tree.js +15 -6
- package/dist/run-store.d.ts +90 -7
- package/dist/run-store.js +254 -22
- package/dist/server/auth-policy.d.ts +27 -0
- package/dist/server/auth-policy.js +143 -0
- package/dist/server/connection.d.ts +8 -0
- package/dist/server/connection.js +67 -0
- package/dist/server/diagnostics.d.ts +40 -0
- package/dist/server/diagnostics.js +130 -0
- package/dist/server/metrics.d.ts +51 -0
- package/dist/server/metrics.js +56 -0
- package/dist/server/quota.d.ts +59 -0
- package/dist/server/quota.js +176 -0
- package/dist/server/validation.d.ts +29 -0
- package/dist/server/validation.js +221 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +737 -419
- package/dist/session/admin-runs.d.ts +73 -0
- package/dist/session/admin-runs.js +298 -0
- package/dist/session/admission.d.ts +32 -0
- package/dist/session/admission.js +62 -0
- package/dist/session/readiness.d.ts +33 -0
- package/dist/session/readiness.js +174 -0
- package/dist/session.d.ts +65 -7
- package/dist/session.js +256 -109
- package/dist/sse-writer.d.ts +7 -0
- package/dist/sse-writer.js +60 -0
- package/dist/types.d.ts +46 -2
- package/dist/types.js +1 -0
- package/dist/webui/app/sources.js +18 -9
- package/dist/webui/icons.d.ts +1 -0
- package/dist/webui/icons.js +1 -0
- package/dist/webui/markup.js +3 -0
- package/dist/webui/styles.js +105 -15
- package/examples/client.mjs +102 -0
- package/nexus-agentd.example.json +19 -0
- package/package.json +3 -2
package/dist/webui/icons.d.ts
CHANGED
package/dist/webui/icons.js
CHANGED
|
@@ -2,6 +2,7 @@ const base = (paths) => `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="
|
|
|
2
2
|
export const icons = {
|
|
3
3
|
overview: base('<rect x="3" y="3" width="7" height="9" rx="1.5"/><rect x="14" y="3" width="7" height="5" rx="1.5"/><rect x="14" y="12" width="7" height="9" rx="1.5"/><rect x="3" y="16" width="7" height="5" rx="1.5"/>'),
|
|
4
4
|
runs: base('<path d="M22 12h-2.5a2 2 0 0 0-1.93 1.46l-2.1 7.4a.25.25 0 0 1-.48 0L9.24 3.14a.25.25 0 0 0-.48 0l-2.1 7.4A2 2 0 0 1 4.73 12H2"/>'),
|
|
5
|
+
artifacts: base('<path d="M21 8v8a2 2 0 0 1-1 1.73l-7 4a2 2 0 0 1-2 0l-7-4A2 2 0 0 1 3 16V8a2 2 0 0 1 1-1.73l7-4a2 2 0 0 1 2 0l7 4A2 2 0 0 1 21 8Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/>'),
|
|
5
6
|
agents: base('<path d="M12 8V4.5"/><circle cx="12" cy="3" r="1.1"/><rect x="4" y="8" width="16" height="12" rx="3"/><path d="M2 14h2"/><path d="M20 14h2"/><path d="M9.5 13v1.6"/><path d="M14.5 13v1.6"/><path d="M9.5 17.2h5"/>'),
|
|
6
7
|
workspaces: base('<path d="M20 20a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-7.1a2 2 0 0 1-1.66-.89l-.75-1.12A2 2 0 0 0 8.83 4H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2Z"/>'),
|
|
7
8
|
keys: base('<circle cx="8" cy="15" r="4.2"/><path d="m11.1 12 8-8"/><path d="m15.4 7.7 2.9 2.9"/><path d="m17.6 5.5 2.9 2.9"/>'),
|
package/dist/webui/markup.js
CHANGED
|
@@ -5,6 +5,7 @@ export const markup = String.raw `
|
|
|
5
5
|
<form id="setup-form" class="auth-card">
|
|
6
6
|
<header><span class="brand-mark">N</span><h1>初始化 Agent Nexus</h1></header>
|
|
7
7
|
<p>此密码仅用于登录控制台;客户端 API 密钥在登录后单独创建。</p>
|
|
8
|
+
<div class="field"><label for="setup-token">初始化令牌</label><input id="setup-token" name="setupToken" type="password" autocomplete="off" required><small class="field-help">从网关启动终端复制 Setup token。重启后令牌会更新;远程初始化请使用可信网络或 HTTPS。</small></div>
|
|
8
9
|
<div class="field"><label for="setup-password">控制台密码</label><input id="setup-password" name="password" type="password" minlength="12" autocomplete="new-password" required></div>
|
|
9
10
|
<div class="field"><label for="setup-confirm">确认密码</label><input id="setup-confirm" name="confirmPassword" type="password" minlength="12" autocomplete="new-password" required></div>
|
|
10
11
|
<button class="button primary auth-submit" type="submit">完成初始化</button>
|
|
@@ -29,6 +30,7 @@ export const markup = String.raw `
|
|
|
29
30
|
<p class="nav-group-label" id="nav-group-run">运行</p>
|
|
30
31
|
<button class="nav-item active" data-page="overview">${icons.overview}<span>总览</span></button>
|
|
31
32
|
<button class="nav-item" data-page="runs">${icons.runs}<span>运行记录</span></button>
|
|
33
|
+
<button class="nav-item" data-page="artifacts">${icons.artifacts}<span>产物</span></button>
|
|
32
34
|
</div>
|
|
33
35
|
<div class="nav-group" role="group" aria-labelledby="nav-group-config">
|
|
34
36
|
<p class="nav-group-label" id="nav-group-config">网关配置</p>
|
|
@@ -48,6 +50,7 @@ export const markup = String.raw `
|
|
|
48
50
|
<div id="page-actions" class="page-actions"></div>
|
|
49
51
|
</header>
|
|
50
52
|
<section id="page-content" class="page-content">
|
|
53
|
+
<p id="connection-status" class="connection-status hidden" role="status"></p>
|
|
51
54
|
<div id="page-stats"></div>
|
|
52
55
|
<div id="page-toolbar"></div>
|
|
53
56
|
<div id="page-results"></div>
|
package/dist/webui/styles.js
CHANGED
|
@@ -39,11 +39,11 @@ export const styles = String.raw `
|
|
|
39
39
|
--sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;
|
|
40
40
|
--sp-5: 20px; --sp-6: 24px; --sp-8: 32px;
|
|
41
41
|
|
|
42
|
-
/*
|
|
42
|
+
/* 字号阶梯,全站只用这六级(面向中文正文做了整体上调):
|
|
43
43
|
2xs 大写微标签(表头 / badge)· xs 辅助说明 · sm 正文与表格
|
|
44
44
|
md 面板与 drawer 标题 · lg 页面标题 · stat 统计数字 */
|
|
45
|
-
--fs-2xs:
|
|
46
|
-
--fs-
|
|
45
|
+
--fs-2xs: 12px; --fs-xs: 13px;
|
|
46
|
+
--fs-sm: 14px; --fs-md: 15px; --fs-lg: 17px; --fs-stat: 28px;
|
|
47
47
|
|
|
48
48
|
--radius: 6px;
|
|
49
49
|
--radius-lg: calc(var(--radius) + 2px);
|
|
@@ -117,7 +117,7 @@ body {
|
|
|
117
117
|
margin: 0;
|
|
118
118
|
background: hsl(var(--background));
|
|
119
119
|
color: hsl(var(--foreground));
|
|
120
|
-
font: var(--fs-sm)/1.
|
|
120
|
+
font: var(--fs-sm)/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans",
|
|
121
121
|
"PingFang SC", "Microsoft YaHei", Helvetica, Arial, sans-serif;
|
|
122
122
|
-webkit-font-smoothing: antialiased;
|
|
123
123
|
text-rendering: optimizeLegibility;
|
|
@@ -126,7 +126,7 @@ h1, h2, h3, p { margin: 0; }
|
|
|
126
126
|
button, input, select, textarea { font: inherit; color: inherit; }
|
|
127
127
|
button { background: none; border: 0; cursor: pointer; padding: 0; }
|
|
128
128
|
code, .mono { font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; }
|
|
129
|
-
.hidden { display: none !important; }
|
|
129
|
+
.hidden, [hidden] { display: none !important; }
|
|
130
130
|
.muted { color: hsl(var(--muted-foreground)); font-size: var(--fs-xs); }
|
|
131
131
|
|
|
132
132
|
:focus-visible {
|
|
@@ -220,6 +220,7 @@ input[type="checkbox"] { width: 14px; height: 14px; padding: 0; accent-color: hs
|
|
|
220
220
|
.button.solid-danger:hover:not(:disabled) { background: hsl(var(--destructive) / .9); color: hsl(var(--destructive-foreground)); }
|
|
221
221
|
.button.small { height: 26px; padding: 0 var(--sp-2); font-size: var(--fs-xs); }
|
|
222
222
|
.button svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; flex-shrink: 0; }
|
|
223
|
+
a.button { color: hsl(var(--foreground)); text-decoration: none; }
|
|
223
224
|
|
|
224
225
|
.icon-button {
|
|
225
226
|
display: inline-flex; align-items: center; justify-content: center;
|
|
@@ -327,7 +328,7 @@ button[aria-busy="true"]::after { content: '…'; margin-left: var(--sp-1); }
|
|
|
327
328
|
.empty-state .button { margin-top: var(--sp-3); }
|
|
328
329
|
|
|
329
330
|
/* Stats */
|
|
330
|
-
.stats { display: grid; grid-template-columns: repeat(
|
|
331
|
+
.stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--sp-4); }
|
|
331
332
|
.stat {
|
|
332
333
|
padding: var(--sp-4) var(--sp-5);
|
|
333
334
|
background: hsl(var(--card));
|
|
@@ -337,6 +338,8 @@ button[aria-busy="true"]::after { content: '…'; margin-left: var(--sp-1); }
|
|
|
337
338
|
}
|
|
338
339
|
.stat span { color: hsl(var(--muted-foreground)); font-size: var(--fs-xs); font-weight: 500; }
|
|
339
340
|
.stat strong { font-size: var(--fs-stat); font-weight: 600; letter-spacing: -.02em; line-height: 1.2; font-variant-numeric: tabular-nums; }
|
|
341
|
+
a.stat { color: inherit; text-decoration: none; }
|
|
342
|
+
a.stat:hover { border-color: hsl(var(--muted-foreground) / .4); box-shadow: var(--shadow-sm); }
|
|
340
343
|
|
|
341
344
|
/* ── Toolbar ──────────────────────────────────────────────────────── */
|
|
342
345
|
.toolbar { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
|
|
@@ -359,12 +362,14 @@ tbody tr:last-child td { border-bottom: 0; }
|
|
|
359
362
|
tbody tr:hover { background: hsl(var(--muted) / .5); }
|
|
360
363
|
tr[data-run-detail] { cursor: pointer; }
|
|
361
364
|
|
|
362
|
-
.agent-name { display: grid; gap:
|
|
365
|
+
.agent-name { display: grid; gap: var(--sp-1); min-width: 0; }
|
|
363
366
|
.agent-name strong { font-size: var(--fs-sm); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
364
367
|
.agent-name small { color: hsl(var(--muted-foreground)); font-size: var(--fs-xs); font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
|
365
368
|
.error-detail { color: hsl(var(--destructive-text)); font-size: var(--fs-sm); overflow-wrap: anywhere; line-height: 1.4; }
|
|
366
369
|
.row-actions { display: flex; justify-content: flex-end; gap: var(--sp-2); }
|
|
367
370
|
.agent-table { min-width: 1050px; table-layout: fixed; }
|
|
371
|
+
.connection-status { padding: var(--sp-3) var(--sp-4); border: 1px solid currentColor; border-radius: var(--radius-lg); color: hsl(var(--destructive-text)); margin-bottom: var(--sp-4); overflow-wrap: anywhere; }
|
|
372
|
+
.run-pagination { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; margin-bottom: 16px; }
|
|
368
373
|
.agent-table th:nth-child(1) { width: 22%; }
|
|
369
374
|
.agent-table th:nth-child(2) { width: 8%; }
|
|
370
375
|
.agent-table th:nth-child(3), .agent-table th:nth-child(4), .agent-table th:nth-child(6) { width: 10%; }
|
|
@@ -375,7 +380,7 @@ tr[data-run-detail] { cursor: pointer; }
|
|
|
375
380
|
/* ── Badges & status ──────────────────────────────────────────────── */
|
|
376
381
|
.badge {
|
|
377
382
|
display: inline-flex; align-items: center;
|
|
378
|
-
height:
|
|
383
|
+
height: 22px; padding: 0 var(--sp-2);
|
|
379
384
|
border: 1px solid hsl(var(--border));
|
|
380
385
|
border-radius: 4px;
|
|
381
386
|
background: hsl(var(--secondary));
|
|
@@ -384,7 +389,7 @@ tr[data-run-detail] { cursor: pointer; }
|
|
|
384
389
|
}
|
|
385
390
|
.status {
|
|
386
391
|
display: inline-flex; align-items: center; gap: var(--sp-2);
|
|
387
|
-
height:
|
|
392
|
+
height: 24px; padding: 0 var(--sp-2);
|
|
388
393
|
border-radius: 9999px;
|
|
389
394
|
background: hsl(var(--secondary));
|
|
390
395
|
color: hsl(var(--muted-foreground));
|
|
@@ -431,11 +436,11 @@ tr[data-run-detail] { cursor: pointer; }
|
|
|
431
436
|
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
|
|
432
437
|
overflow: hidden;
|
|
433
438
|
color: hsl(var(--foreground));
|
|
434
|
-
font-size: var(--fs-sm); line-height: 1.
|
|
439
|
+
font-size: var(--fs-sm); line-height: 1.6;
|
|
435
440
|
white-space: pre-wrap;
|
|
436
441
|
overflow-wrap: anywhere;
|
|
437
442
|
}
|
|
438
|
-
.run-progress { display: grid; gap:
|
|
443
|
+
.run-progress { display: grid; gap: var(--sp-1); }
|
|
439
444
|
.run-progress strong { font-size: var(--fs-xs); font-weight: 600; }
|
|
440
445
|
.run-progress small { color: hsl(var(--muted-foreground)); font-size: var(--fs-xs); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
441
446
|
.run-meta {
|
|
@@ -456,6 +461,13 @@ tr[data-run-detail] { cursor: pointer; }
|
|
|
456
461
|
.run-task-cell small { display: block; margin-top: 2px; color: hsl(var(--muted-foreground)); font-size: var(--fs-2xs); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
457
462
|
|
|
458
463
|
/* Run detail (drawer) */
|
|
464
|
+
.run-detail-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-bottom: var(--sp-3); }
|
|
465
|
+
.run-output-heading { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: var(--sp-2); margin-bottom: var(--sp-2); }
|
|
466
|
+
.run-output-heading h3 { margin: 0; }
|
|
467
|
+
.run-pending { padding: var(--sp-3); margin-bottom: var(--sp-4); border: 1px solid hsl(var(--border)); border-radius: var(--radius); }
|
|
468
|
+
.run-pending-prompt { white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
469
|
+
.run-pending h3 { font-size: var(--fs-md); }
|
|
470
|
+
.run-pending .row-actions { flex-wrap: wrap; }
|
|
459
471
|
.run-detail-grid {
|
|
460
472
|
display: grid; grid-template-columns: 1fr 1fr; gap: 1px;
|
|
461
473
|
margin-bottom: var(--sp-4);
|
|
@@ -464,6 +476,11 @@ tr[data-run-detail] { cursor: pointer; }
|
|
|
464
476
|
overflow: hidden;
|
|
465
477
|
background: hsl(var(--border));
|
|
466
478
|
}
|
|
479
|
+
/* 紧凑指标网格(总览系统状态等):复用 run-detail-item 的视觉,密度更高 */
|
|
480
|
+
.run-detail-grid.compact { grid-template-columns: repeat(4, minmax(0, 1fr)); margin-bottom: 0; }
|
|
481
|
+
.run-detail-grid.compact .run-detail-item { padding: var(--sp-2) var(--sp-3); }
|
|
482
|
+
/* 嵌在 panel 里时通到边框,避免双层描边 */
|
|
483
|
+
.panel > .run-detail-grid.compact { border: 0; border-radius: 0; }
|
|
467
484
|
.run-detail-item { padding: var(--sp-3); background: hsl(var(--card)); display: grid; gap: var(--sp-1); align-content: start; }
|
|
468
485
|
.run-detail-item span { color: hsl(var(--muted-foreground)); font-size: var(--fs-2xs); font-weight: 600; letter-spacing: .05em; text-transform: uppercase; }
|
|
469
486
|
.run-detail-item strong { font-size: var(--fs-sm); font-weight: 500; overflow-wrap: anywhere; }
|
|
@@ -493,12 +510,19 @@ tr[data-run-detail] { cursor: pointer; }
|
|
|
493
510
|
transition: border-color var(--duration-fast) var(--ease-standard);
|
|
494
511
|
}
|
|
495
512
|
.list-row:hover { border-color: hsl(var(--muted-foreground) / .4); }
|
|
496
|
-
.list-row-main { display: grid; gap:
|
|
513
|
+
.list-row-main { display: grid; gap: var(--sp-1); min-width: 0; }
|
|
497
514
|
.list-row-main > * { min-width: 0; }
|
|
498
515
|
.list-row-main strong, .path { font-size: var(--fs-sm); font-weight: 600; overflow-wrap: anywhere; }
|
|
499
516
|
.list-row-main small { color: hsl(var(--muted-foreground)); font-size: var(--fs-xs); overflow-wrap: anywhere; }
|
|
500
|
-
|
|
501
|
-
.panel-body .list-
|
|
517
|
+
/* panel 里嵌套的列表去掉卡片套卡片:压平成分隔线行 */
|
|
518
|
+
.panel-body .overview-list, .panel-body .workspace-list { gap: 0; }
|
|
519
|
+
.panel-body .list-row {
|
|
520
|
+
border: 0; border-radius: 0; background: transparent;
|
|
521
|
+
border-bottom: 1px solid hsl(var(--border));
|
|
522
|
+
padding: var(--sp-3) 0;
|
|
523
|
+
min-height: 0;
|
|
524
|
+
}
|
|
525
|
+
.panel-body .list-row:last-child { border-bottom: 0; }
|
|
502
526
|
|
|
503
527
|
/* ── API keys ─────────────────────────────────────────────────────── */
|
|
504
528
|
.key-table { min-width: 760px; width: 100%; table-layout: fixed; }
|
|
@@ -580,6 +604,35 @@ tr[data-run-detail] { cursor: pointer; }
|
|
|
580
604
|
font-size: var(--fs-xs);
|
|
581
605
|
}
|
|
582
606
|
|
|
607
|
+
/* ── Artifacts page ───────────────────────────────────────────────── */
|
|
608
|
+
.artifact-table { min-width: 760px; table-layout: fixed; }
|
|
609
|
+
.artifact-table th:nth-child(1) { width: 64px; }
|
|
610
|
+
.artifact-table th:nth-child(3) { width: 18%; }
|
|
611
|
+
.artifact-table th:nth-child(4), .artifact-table th:nth-child(5) { width: 120px; }
|
|
612
|
+
.artifact-table th:nth-child(6) { width: 180px; }
|
|
613
|
+
.artifact-table td { height: 56px; }
|
|
614
|
+
.artifact-thumb-button {
|
|
615
|
+
display: block; width: 44px; height: 44px;
|
|
616
|
+
padding: 0; border-radius: var(--radius);
|
|
617
|
+
overflow: hidden;
|
|
618
|
+
}
|
|
619
|
+
.artifact-thumb { display: block; width: 44px; height: 44px; object-fit: cover; border: 1px solid hsl(var(--border)); border-radius: var(--radius); background: hsl(var(--muted) / .4); }
|
|
620
|
+
/* 非图片产物的类型图标瓦片 */
|
|
621
|
+
.artifact-tile {
|
|
622
|
+
display: grid; place-items: center;
|
|
623
|
+
width: 44px; height: 44px;
|
|
624
|
+
border: 1px solid hsl(var(--border));
|
|
625
|
+
border-radius: var(--radius);
|
|
626
|
+
background: hsl(var(--muted) / .4);
|
|
627
|
+
color: hsl(var(--muted-foreground));
|
|
628
|
+
transition: background-color var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard);
|
|
629
|
+
}
|
|
630
|
+
.artifact-tile:hover { background: hsl(var(--accent)); color: hsl(var(--accent-foreground)); }
|
|
631
|
+
.artifact-tile svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
|
|
632
|
+
.artifact-preview-wrap { display: grid; justify-items: center; gap: var(--sp-2); margin-bottom: var(--sp-4); }
|
|
633
|
+
.artifact-preview { max-width: 100%; max-height: 320px; border: 1px solid hsl(var(--border)); border-radius: var(--radius); background: hsl(var(--muted) / .4); }
|
|
634
|
+
.artifact-preview-wrap audio { width: 100%; }
|
|
635
|
+
|
|
583
636
|
/* ── Drawer ───────────────────────────────────────────────────────── */
|
|
584
637
|
.drawer-backdrop { position: fixed; inset: 0; background: rgb(0 0 0 / .4); z-index: var(--z-backdrop); animation: fadeIn var(--duration-fade) var(--ease-out); }
|
|
585
638
|
.drawer {
|
|
@@ -723,7 +776,8 @@ tr[data-run-detail] { cursor: pointer; }
|
|
|
723
776
|
.settings-section-heading { margin-bottom: 24px; }
|
|
724
777
|
.settings-account-row { grid-template-columns: 1fr; gap: var(--sp-4); }
|
|
725
778
|
.search-box { max-width: none; }
|
|
726
|
-
.stats { grid-template-columns: 1fr; }
|
|
779
|
+
.stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
780
|
+
.run-detail-grid.compact { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
727
781
|
.run-summary { grid-template-columns: repeat(2, 1fr); }
|
|
728
782
|
.run-live-grid { grid-template-columns: minmax(0, 1fr); }
|
|
729
783
|
.toolbar input[type="search"], .toolbar select { max-width: none; width: 100%; }
|
|
@@ -734,6 +788,42 @@ tr[data-run-detail] { cursor: pointer; }
|
|
|
734
788
|
.toast-region { right: var(--sp-4); bottom: var(--sp-4); left: var(--sp-4); max-width: none; }
|
|
735
789
|
}
|
|
736
790
|
|
|
791
|
+
/* Keep row actions and task context in the viewport on laptops and phones. */
|
|
792
|
+
@media (max-width: 1400px) {
|
|
793
|
+
.agent-table, .run-table { min-width: 0; }
|
|
794
|
+
.agent-table thead, .run-table thead {
|
|
795
|
+
position: absolute; width: 1px; height: 1px; padding: 0;
|
|
796
|
+
overflow: hidden; clip-path: inset(50%); white-space: nowrap;
|
|
797
|
+
}
|
|
798
|
+
.agent-table tbody, .run-table tbody { display: grid; }
|
|
799
|
+
.agent-table tbody tr, .run-table tbody tr {
|
|
800
|
+
display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
|
801
|
+
gap: var(--sp-3) var(--sp-6); padding: var(--sp-4); border-bottom: 1px solid hsl(var(--border));
|
|
802
|
+
}
|
|
803
|
+
.agent-table td, .run-table td {
|
|
804
|
+
min-width: 0; max-width: none !important; padding: 0; height: auto;
|
|
805
|
+
border: 0; white-space: normal !important; overflow-wrap: anywhere; text-align: left !important;
|
|
806
|
+
}
|
|
807
|
+
.agent-table td::before, .run-table td::before { display: block; font-size: var(--fs-xs); color: hsl(var(--muted-foreground)); margin-bottom: var(--sp-1); }
|
|
808
|
+
.agent-table td:nth-child(2)::before { content: '协议'; }
|
|
809
|
+
.agent-table td:nth-child(3)::before { content: '驱动'; }
|
|
810
|
+
.agent-table td:nth-child(4)::before { content: '探测状态'; }
|
|
811
|
+
.agent-table td:nth-child(5)::before { content: '默认工作区'; }
|
|
812
|
+
.agent-table td:nth-child(6)::before { content: '权限策略'; }
|
|
813
|
+
.run-table td:nth-child(2)::before { content: '任务'; }
|
|
814
|
+
.run-table td:nth-child(3)::before { content: '结果'; }
|
|
815
|
+
.run-table td:nth-child(4)::before { content: '状态'; }
|
|
816
|
+
.run-table td:nth-child(5)::before { content: '开始时间'; }
|
|
817
|
+
.run-table td:nth-child(6)::before { content: '耗时'; }
|
|
818
|
+
.agent-table td:last-child, .run-table td:last-child, td[colspan] { grid-column: 1 / -1; }
|
|
819
|
+
.agent-table .row-actions { justify-content: flex-start; }
|
|
820
|
+
.agent-table .button.small, .run-table .button.small { min-height: 36px; }
|
|
821
|
+
}
|
|
822
|
+
@media (max-width: 560px) {
|
|
823
|
+
.agent-table tbody tr, .run-table tbody tr { grid-template-columns: minmax(0, 1fr); }
|
|
824
|
+
.run-pagination { align-items: flex-start; }
|
|
825
|
+
}
|
|
826
|
+
|
|
737
827
|
/* ── Reduced motion ───────────────────────────────────────────────── */
|
|
738
828
|
/* 统一压制而不是逐个列举消费者,这样以后新增的 transition 不会漏掉。
|
|
739
829
|
running/waiting 的 pulse 停下后,色点和文字标签仍然表达状态。 */
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { setTimeout as delay } from 'node:timers/promises'
|
|
2
|
+
|
|
3
|
+
/** Minimal Node 20+ client. Keep API Keys out of URLs and browser storage. */
|
|
4
|
+
export function createGatewayClient(baseUrl, apiKey) {
|
|
5
|
+
const base = new URL(baseUrl)
|
|
6
|
+
const headers = { Authorization: `Bearer ${apiKey}` }
|
|
7
|
+
const route = (sessionId) => `/v1/sessions/${encodeURIComponent(sessionId)}`
|
|
8
|
+
async function request(path, method = 'GET', body, signal) {
|
|
9
|
+
const response = await fetch(new URL(path, base), {
|
|
10
|
+
method, headers: { ...headers, ...(body === undefined ? {} : { 'Content-Type': 'application/json' }) },
|
|
11
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
12
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(30_000)]) : AbortSignal.timeout(30_000),
|
|
13
|
+
redirect: 'error'
|
|
14
|
+
})
|
|
15
|
+
if (!response.ok) {
|
|
16
|
+
const error = new Error(`Gateway request failed (${response.status})`)
|
|
17
|
+
error.status = response.status
|
|
18
|
+
await response.body?.cancel()
|
|
19
|
+
throw error
|
|
20
|
+
}
|
|
21
|
+
return response.json()
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
createSession: (agentId, workspace) => request('/v1/sessions', 'POST', { agentId, workspace }),
|
|
25
|
+
snapshot: (id) => request(route(id)),
|
|
26
|
+
message: (id, message) => request(`${route(id)}/message`, 'POST', { message }),
|
|
27
|
+
resolve: (id, requestId, answer) => request(`${route(id)}/requests/${encodeURIComponent(requestId)}/resolve`, 'POST', answer),
|
|
28
|
+
cancel: (id) => request(`${route(id)}/cancel`, 'POST', {}),
|
|
29
|
+
close: (id) => request(route(id), 'DELETE'),
|
|
30
|
+
/** A reset yields a replacement snapshot; old discarded events are not replayed. */
|
|
31
|
+
async *events(id, { after, signal = new AbortController().signal } = {}) {
|
|
32
|
+
let cursor = after
|
|
33
|
+
let retries = 0
|
|
34
|
+
while (!signal.aborted) {
|
|
35
|
+
const connection = new AbortController()
|
|
36
|
+
const combined = AbortSignal.any([signal, connection.signal])
|
|
37
|
+
let timer = setTimeout(() => connection.abort(new Error('SSE connection timeout')), 15_000)
|
|
38
|
+
let reader
|
|
39
|
+
let reset = false
|
|
40
|
+
try {
|
|
41
|
+
const response = await fetch(new URL(`${route(id)}/events`, base), {
|
|
42
|
+
headers: { ...headers, ...(cursor === undefined ? {} : { 'Last-Event-ID': cursor }) },
|
|
43
|
+
signal: combined, redirect: 'error'
|
|
44
|
+
})
|
|
45
|
+
clearTimeout(timer)
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
const error = new Error(`SSE request failed (${response.status})`)
|
|
48
|
+
error.status = response.status
|
|
49
|
+
await response.body?.cancel()
|
|
50
|
+
throw error
|
|
51
|
+
}
|
|
52
|
+
reader = response.body.getReader()
|
|
53
|
+
const decoder = new TextDecoder()
|
|
54
|
+
let buffer = ''
|
|
55
|
+
while (!signal.aborted && !reset) {
|
|
56
|
+
timer = setTimeout(() => connection.abort(new Error('SSE heartbeat timeout')), 45_000)
|
|
57
|
+
const { value, done } = await reader.read()
|
|
58
|
+
clearTimeout(timer)
|
|
59
|
+
if (done) break
|
|
60
|
+
buffer += decoder.decode(value, { stream: true }).replace(/\r/g, '')
|
|
61
|
+
if (buffer.length > 2 * 1024 * 1024) throw new Error('SSE frame limit exceeded')
|
|
62
|
+
let end
|
|
63
|
+
while ((end = buffer.indexOf('\n\n')) >= 0) {
|
|
64
|
+
const frame = buffer.slice(0, end)
|
|
65
|
+
buffer = buffer.slice(end + 2)
|
|
66
|
+
const lines = frame.split('\n')
|
|
67
|
+
const data = lines.filter((line) => line.startsWith('data:')).map((line) => line.slice(5).trimStart()).join('\n')
|
|
68
|
+
if (!data) continue // Heartbeat comments carry no event.
|
|
69
|
+
const type = lines.find((line) => line.startsWith('event:'))?.slice(6).trim()
|
|
70
|
+
const eventId = lines.find((line) => line.startsWith('id:'))?.slice(3).trim()
|
|
71
|
+
if (type === 'reset') {
|
|
72
|
+
// Use our fixed same-origin Session route, never a remote recovery URL.
|
|
73
|
+
const snapshot = await request(route(id), 'GET', undefined, signal)
|
|
74
|
+
cursor = snapshot.lastEventId || '0'
|
|
75
|
+
yield { type: 'reset', data: snapshot, recovery: JSON.parse(data) }
|
|
76
|
+
reset = true
|
|
77
|
+
break
|
|
78
|
+
}
|
|
79
|
+
if (!eventId || !/^\d+$/.test(eventId)) continue
|
|
80
|
+
if (cursor !== undefined && BigInt(eventId) <= BigInt(cursor)) continue
|
|
81
|
+
cursor = eventId
|
|
82
|
+
retries = 0
|
|
83
|
+
yield JSON.parse(data)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (signal.aborted) return
|
|
88
|
+
// A revoked Key, narrowed scope or deleted/restarted Session needs user action.
|
|
89
|
+
if ([401, 403, 404].includes(error.status)) throw error
|
|
90
|
+
} finally {
|
|
91
|
+
clearTimeout(timer)
|
|
92
|
+
connection.abort()
|
|
93
|
+
await reader?.cancel().catch(() => {})
|
|
94
|
+
}
|
|
95
|
+
if (!reset && !signal.aborted) {
|
|
96
|
+
await delay(Math.min(30_000, 500 * 2 ** Math.min(retries++, 6)) + Math.random() * 250,
|
|
97
|
+
undefined, { signal }).catch((error) => { if (!signal.aborted) throw error })
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -17,7 +17,26 @@
|
|
|
17
17
|
"maxSseConnections": 128,
|
|
18
18
|
"maxConnections": 256,
|
|
19
19
|
"sessionTtlMs": 86400000,
|
|
20
|
+
"quotas": {
|
|
21
|
+
"maxSessionsPerKey": 16,
|
|
22
|
+
"maxRunningRunsPerKey": 4,
|
|
23
|
+
"maxSsePerKey": 8,
|
|
24
|
+
"maxUploadBytesPerKey": 33554432
|
|
25
|
+
},
|
|
26
|
+
"history": { "maxRuns": 1000, "retentionDays": 30, "maxBytes": 67108864 },
|
|
27
|
+
"artifacts": { "retentionDays": 30, "maxBytes": 536870912, "maxArtifactBytes": 12582912, "maxQueuedBytes": 67108864 },
|
|
20
28
|
"agents": {
|
|
29
|
+
"custom": {
|
|
30
|
+
"protocol": "acp",
|
|
31
|
+
"driver": "stdio",
|
|
32
|
+
"name": "Custom ACP",
|
|
33
|
+
"enabled": false,
|
|
34
|
+
"command": "your-acp-agent",
|
|
35
|
+
"args": [],
|
|
36
|
+
"probeArgs": ["--version"],
|
|
37
|
+
"workspace": "/data/repos/project",
|
|
38
|
+
"permissionPolicy": "ask"
|
|
39
|
+
},
|
|
21
40
|
"codex": {
|
|
22
41
|
"protocol": "acp",
|
|
23
42
|
"driver": "codex",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexus-agentd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Local Nexus Gateway daemon with an HTTP/SSE API and WebUI for allowlisted ACP and remote A2A agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|
|
13
|
+
"examples",
|
|
13
14
|
"nexus-agentd.example.json",
|
|
14
15
|
"README.md"
|
|
15
16
|
],
|
|
@@ -34,7 +35,7 @@
|
|
|
34
35
|
],
|
|
35
36
|
"scripts": {
|
|
36
37
|
"build": "node scripts/generate-webui-sources.mjs && node scripts/clean-dist.mjs && tsc -p tsconfig.json",
|
|
37
|
-
"typecheck": "node scripts/generate-webui-sources.mjs && tsc -p tsconfig.json --noEmit",
|
|
38
|
+
"typecheck": "node scripts/generate-webui-sources.mjs && tsc -p tsconfig.json --noEmit && tsc -p tsconfig.webui.json",
|
|
38
39
|
"test": "tsx --test test/*.test.ts",
|
|
39
40
|
"test:webui": "node scripts/generate-webui-sources.mjs && playwright test",
|
|
40
41
|
"prepack": "npm run build"
|