@songsid/agend 2.1.12 → 2.2.0-alpha.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 +2 -2
- package/README.zh-TW.md +2 -2
- package/dist/auth/one-time-code.d.ts +19 -0
- package/dist/auth/one-time-code.js +49 -0
- package/dist/auth/one-time-code.js.map +1 -0
- package/dist/auth-api.d.ts +52 -0
- package/dist/auth-api.js +346 -0
- package/dist/auth-api.js.map +1 -0
- package/dist/backend/claude-code.d.ts +4 -0
- package/dist/backend/claude-code.js +97 -0
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex.js +59 -30
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/kiro-agent.d.ts +66 -0
- package/dist/backend/kiro-agent.js +302 -0
- package/dist/backend/kiro-agent.js.map +1 -0
- package/dist/backend/kiro-identity.d.ts +80 -0
- package/dist/backend/kiro-identity.js +537 -0
- package/dist/backend/kiro-identity.js.map +1 -0
- package/dist/backend/kiro-v2-store-worker.d.ts +1 -0
- package/dist/backend/kiro-v2-store-worker.js +87 -0
- package/dist/backend/kiro-v2-store-worker.js.map +1 -0
- package/dist/backend/kiro-v2-store.d.ts +54 -0
- package/dist/backend/kiro-v2-store.js +131 -0
- package/dist/backend/kiro-v2-store.js.map +1 -0
- package/dist/backend/kiro-v3-identity.d.ts +5 -0
- package/dist/backend/kiro-v3-identity.js +22 -0
- package/dist/backend/kiro-v3-identity.js.map +1 -1
- package/dist/backend/kiro.d.ts +83 -7
- package/dist/backend/kiro.js +418 -91
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/types.d.ts +45 -3
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +1 -0
- package/dist/channel/adapters/discord.js +29 -10
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +1 -0
- package/dist/channel/adapters/telegram.js +13 -1
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/types.d.ts +9 -1
- package/dist/classic-channel-manager.d.ts +18 -10
- package/dist/classic-channel-manager.js +45 -18
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +225 -127
- package/dist/cli.js.map +1 -1
- package/dist/command-table.d.ts +5 -3
- package/dist/command-table.js +29 -27
- package/dist/command-table.js.map +1 -1
- package/dist/config-validator.js +46 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/config.js +12 -0
- package/dist/config.js.map +1 -1
- package/dist/connection-secrets.d.ts +6 -0
- package/dist/connection-secrets.js +4 -0
- package/dist/connection-secrets.js.map +1 -1
- package/dist/daemon.d.ts +89 -4
- package/dist/daemon.js +661 -77
- package/dist/daemon.js.map +1 -1
- package/dist/darwin-memory.d.ts +6 -1
- package/dist/darwin-memory.js +17 -6
- package/dist/darwin-memory.js.map +1 -1
- package/dist/delivery-consumed-watch.d.ts +85 -0
- package/dist/delivery-consumed-watch.js +244 -0
- package/dist/delivery-consumed-watch.js.map +1 -0
- package/dist/delivery-outbox.d.ts +61 -0
- package/dist/delivery-outbox.js +120 -2
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/delivery-reconciliation.d.ts +14 -5
- package/dist/delivery-reconciliation.js +27 -4
- package/dist/delivery-reconciliation.js.map +1 -1
- package/dist/delivery-reconciliation.test.js +35 -1
- package/dist/delivery-reconciliation.test.js.map +1 -1
- package/dist/event-loop-watch.d.ts +28 -0
- package/dist/event-loop-watch.js +75 -3
- package/dist/event-loop-watch.js.map +1 -1
- package/dist/fleet-context.d.ts +22 -2
- package/dist/fleet-level-config.d.ts +2 -2
- package/dist/fleet-level-config.js +6 -1
- package/dist/fleet-level-config.js.map +1 -1
- package/dist/fleet-manager.d.ts +293 -12
- package/dist/fleet-manager.js +2364 -449
- package/dist/fleet-manager.js.map +1 -1
- package/dist/host-memory.d.ts +4 -0
- package/dist/host-memory.js.map +1 -1
- package/dist/instance-config-impact.js +5 -0
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +10 -5
- package/dist/instance-lifecycle.js +162 -53
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/locale.js +150 -16
- package/dist/locale.js.map +1 -1
- package/dist/login-controller.d.ts +2 -0
- package/dist/login-controller.js +17 -5
- package/dist/login-controller.js.map +1 -1
- package/dist/memory-pressure.d.ts +7 -8
- package/dist/memory-pressure.js +76 -15
- package/dist/memory-pressure.js.map +1 -1
- package/dist/monotonic-budget.d.ts +2 -0
- package/dist/monotonic-budget.js +28 -0
- package/dist/monotonic-budget.js.map +1 -0
- package/dist/needs-you-hub.d.ts +114 -0
- package/dist/needs-you-hub.js +370 -0
- package/dist/needs-you-hub.js.map +1 -0
- package/dist/needs-you-live.d.ts +116 -0
- package/dist/needs-you-live.js +228 -0
- package/dist/needs-you-live.js.map +1 -0
- package/dist/needs-you.d.ts +132 -0
- package/dist/needs-you.js +164 -0
- package/dist/needs-you.js.map +1 -0
- package/dist/node-version-guard.d.ts +30 -0
- package/dist/node-version-guard.js +50 -0
- package/dist/node-version-guard.js.map +1 -0
- package/dist/operator-control-paths.d.ts +4 -0
- package/dist/operator-control-paths.js +24 -0
- package/dist/operator-control-paths.js.map +1 -0
- package/dist/outbound-handlers.d.ts +2 -0
- package/dist/outbound-handlers.js +9 -2
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +5 -5
- package/dist/profile-control.js +4 -20
- package/dist/profile-control.js.map +1 -1
- package/dist/public-web-gateway.d.ts +18 -0
- package/dist/public-web-gateway.js +141 -0
- package/dist/public-web-gateway.js.map +1 -0
- package/dist/public-web-link.d.ts +53 -0
- package/dist/public-web-link.js +175 -0
- package/dist/public-web-link.js.map +1 -0
- package/dist/quickstart-api.d.ts +8 -1
- package/dist/quickstart-api.js +86 -48
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.d.ts +1 -0
- package/dist/quickstart.js +6 -2
- package/dist/quickstart.js.map +1 -1
- package/dist/scheduler/db.d.ts +7 -1
- package/dist/scheduler/db.js +38 -0
- package/dist/scheduler/db.js.map +1 -1
- package/dist/scheduler/index.d.ts +1 -1
- package/dist/scheduler/retry-label.d.ts +11 -0
- package/dist/scheduler/retry-label.js +24 -0
- package/dist/scheduler/retry-label.js.map +1 -0
- package/dist/scheduler/scheduler.d.ts +55 -2
- package/dist/scheduler/scheduler.js +220 -8
- package/dist/scheduler/scheduler.js.map +1 -1
- package/dist/scheduler/types.d.ts +22 -0
- package/dist/scheduler/types.js.map +1 -1
- package/dist/secret-store.d.ts +5 -0
- package/dist/secret-store.js +20 -0
- package/dist/secret-store.js.map +1 -1
- package/dist/service-activation.d.ts +110 -0
- package/dist/service-activation.js +302 -0
- package/dist/service-activation.js.map +1 -0
- package/dist/service-installer.d.ts +7 -0
- package/dist/service-installer.js +7 -4
- package/dist/service-installer.js.map +1 -1
- package/dist/service-restart-selection.d.ts +6 -5
- package/dist/service-restart-selection.js +6 -5
- package/dist/service-restart-selection.js.map +1 -1
- package/dist/settings-api.d.ts +10 -2
- package/dist/settings-api.js +175 -72
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-baseline.d.ts +20 -0
- package/dist/settings-baseline.js +104 -0
- package/dist/settings-baseline.js.map +1 -0
- package/dist/settings-change.d.ts +21 -0
- package/dist/settings-change.js +133 -0
- package/dist/settings-change.js.map +1 -0
- package/dist/settings-confirmation.d.ts +105 -0
- package/dist/settings-confirmation.js +232 -0
- package/dist/settings-confirmation.js.map +1 -0
- package/dist/settings-control.d.ts +41 -0
- package/dist/settings-control.js +294 -0
- package/dist/settings-control.js.map +1 -0
- package/dist/settings-effect.d.ts +24 -0
- package/dist/settings-effect.js +169 -0
- package/dist/settings-effect.js.map +1 -0
- package/dist/settings-http-confirmation.d.ts +29 -0
- package/dist/settings-http-confirmation.js +123 -0
- package/dist/settings-http-confirmation.js.map +1 -0
- package/dist/settings-instance-patch.d.ts +4 -0
- package/dist/settings-instance-patch.js +26 -0
- package/dist/settings-instance-patch.js.map +1 -0
- package/dist/settings-request-capability.d.ts +16 -0
- package/dist/settings-request-capability.js +32 -0
- package/dist/settings-request-capability.js.map +1 -0
- package/dist/settings-transaction.d.ts +65 -0
- package/dist/settings-transaction.js +223 -0
- package/dist/settings-transaction.js.map +1 -0
- package/dist/setup-auth.d.ts +10 -18
- package/dist/setup-auth.js +14 -38
- package/dist/setup-auth.js.map +1 -1
- package/dist/setup-form.d.ts +1 -1
- package/dist/setup-form.js +75 -18
- package/dist/setup-form.js.map +1 -1
- package/dist/setup-guide.d.ts +13 -0
- package/dist/setup-guide.js +25 -0
- package/dist/setup-guide.js.map +1 -0
- package/dist/setup-host.d.ts +16 -0
- package/dist/setup-host.js +197 -32
- package/dist/setup-host.js.map +1 -1
- package/dist/setup-wizard.js +3 -0
- package/dist/setup-wizard.js.map +1 -1
- package/dist/slash-authz.d.ts +17 -3
- package/dist/slash-authz.js +5 -0
- package/dist/slash-authz.js.map +1 -1
- package/dist/spawn-gate.js +3 -3
- package/dist/spawn-gate.js.map +1 -1
- package/dist/statusline-watcher.d.ts +5 -0
- package/dist/statusline-watcher.js +2 -0
- package/dist/statusline-watcher.js.map +1 -1
- package/dist/steer-capability.d.ts +6 -0
- package/dist/steer-capability.js +8 -0
- package/dist/steer-capability.js.map +1 -1
- package/dist/sync-work-attribution.d.ts +14 -0
- package/dist/sync-work-attribution.js +43 -8
- package/dist/sync-work-attribution.js.map +1 -1
- package/dist/tips.js +10 -5
- package/dist/tips.js.map +1 -1
- package/dist/tmux-control.d.ts +35 -5
- package/dist/tmux-control.js +291 -57
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-manager.d.ts +20 -6
- package/dist/tmux-manager.js +90 -47
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tmux-read.d.ts +59 -0
- package/dist/tmux-read.js +213 -0
- package/dist/tmux-read.js.map +1 -0
- package/dist/topic-commands.d.ts +21 -10
- package/dist/topic-commands.js +191 -58
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-delta-reader.js +2 -1
- package/dist/transcript-delta-reader.js.map +1 -1
- package/dist/tunnel/cloudflared-install.d.ts +2 -0
- package/dist/tunnel/cloudflared-install.js +1 -1
- package/dist/tunnel/cloudflared-install.js.map +1 -1
- package/dist/tunnel/purpose-lane.d.ts +14 -0
- package/dist/tunnel/purpose-lane.js +43 -0
- package/dist/tunnel/purpose-lane.js.map +1 -0
- package/dist/turn-reply-guard.d.ts +2 -0
- package/dist/turn-reply-guard.js +10 -1
- package/dist/turn-reply-guard.js.map +1 -1
- package/dist/types.d.ts +33 -0
- package/dist/ui/app.html +18 -0
- package/dist/ui/chat-render.js +478 -0
- package/dist/ui/chat-store.js +289 -0
- package/dist/ui/chat-strings.js +69 -0
- package/dist/ui/chat-thread.js +209 -0
- package/dist/ui/chat-tour.js +116 -0
- package/dist/ui/fleet-org.js +142 -0
- package/dist/ui/panel-chat.js +418 -0
- package/dist/ui/panel-fleet.js +413 -0
- package/dist/ui/panel-needs.js +202 -0
- package/dist/ui/panel-settings.js +750 -0
- package/dist/ui/preview.js +266 -0
- package/dist/ui/settings-apply.js +239 -0
- package/dist/ui/settings-confirm.js +186 -0
- package/dist/ui/settings-dialogs.js +546 -0
- package/dist/ui/settings-model.js +277 -0
- package/dist/ui/settings-strings.js +652 -0
- package/dist/ui/settings-wizard.js +140 -0
- package/dist/ui/shared/agend-auth.js +93 -0
- package/dist/ui/shared/app-ctx.js +80 -0
- package/dist/ui/shared/app-html.js +9 -0
- package/dist/ui/shared/app-i18n.js +95 -0
- package/dist/ui/shared/app-nav.js +73 -0
- package/dist/ui/shared/app-needs.js +114 -0
- package/dist/ui/shared/app-route.js +73 -0
- package/dist/ui/shared/app-session.js +66 -0
- package/dist/ui/shared/app-shell.js +344 -0
- package/dist/ui/shared/app-store.js +51 -0
- package/dist/ui/shared/app-stream.js +201 -0
- package/dist/ui/shared/app.css +676 -0
- package/dist/ui/shared/app.js +88 -0
- package/dist/ui/shared/fonts/OFL.txt +92 -0
- package/dist/ui/shared/fonts/README.md +21 -0
- package/dist/ui/shared/fonts/inter.woff2 +0 -0
- package/dist/ui/shared/htm.module.js +1 -0
- package/dist/ui/shared/panel-view.js +655 -0
- package/dist/ui/shared/preact-hooks.module.js +2 -0
- package/dist/ui/shared/preact.module.js +2 -0
- package/dist/ui/shared/signin.js +130 -0
- package/dist/ui/shared/theme.js +29 -0
- package/dist/ui/shared/tokens.css +153 -0
- package/dist/ui/shared/ui-confirm.js +43 -0
- package/dist/ui/shared/ui-dialog.js +45 -0
- package/dist/ui/shared/ui-icons.js +61 -0
- package/dist/ui/shared/ui-menu.js +39 -0
- package/dist/ui/shared/ui-states.js +20 -0
- package/dist/ui/shared/ui-toast.js +18 -0
- package/dist/ui/shared/vendor/htm.LICENSE +202 -0
- package/dist/ui/shared/vendor/preact.LICENSE +21 -0
- package/dist/ui/shared/vendor/vendor.json +39 -0
- package/dist/ui/shared/view-strings.js +309 -0
- package/dist/ui/signin.html +51 -0
- package/dist/update-check.d.ts +6 -2
- package/dist/update-check.js +12 -3
- package/dist/update-check.js.map +1 -1
- package/dist/update-install.d.ts +71 -0
- package/dist/update-install.js +162 -0
- package/dist/update-install.js.map +1 -0
- package/dist/upgrade-notices.d.ts +17 -0
- package/dist/upgrade-notices.js +93 -0
- package/dist/upgrade-notices.js.map +1 -0
- package/dist/usage/statusline-usage.d.ts +2 -0
- package/dist/usage/statusline-usage.js +14 -10
- package/dist/usage/statusline-usage.js.map +1 -1
- package/dist/view-api.d.ts +6 -1
- package/dist/view-api.js +62 -51
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +71 -14
- package/dist/web-api.js +492 -77
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.d.ts +71 -27
- package/dist/web-auth.js +169 -69
- package/dist/web-auth.js.map +1 -1
- package/dist/web-body.d.ts +3 -0
- package/dist/web-body.js +55 -0
- package/dist/web-body.js.map +1 -0
- package/dist/web-channel-echo.d.ts +9 -0
- package/dist/web-channel-echo.js +55 -0
- package/dist/web-channel-echo.js.map +1 -0
- package/dist/web-chat-history.d.ts +125 -0
- package/dist/web-chat-history.js +184 -0
- package/dist/web-chat-history.js.map +1 -0
- package/dist/web-continuation.d.ts +7 -0
- package/dist/web-continuation.js +22 -0
- package/dist/web-continuation.js.map +1 -0
- package/dist/web-host-guard.d.ts +34 -0
- package/dist/web-host-guard.js +57 -1
- package/dist/web-host-guard.js.map +1 -1
- package/dist/web-login.d.ts +72 -0
- package/dist/web-login.js +132 -0
- package/dist/web-login.js.map +1 -0
- package/dist/web-org.d.ts +55 -0
- package/dist/web-org.js +67 -0
- package/dist/web-org.js.map +1 -0
- package/dist/web-preview.d.ts +77 -0
- package/dist/web-preview.js +240 -0
- package/dist/web-preview.js.map +1 -0
- package/dist/web-request-context.d.ts +10 -0
- package/dist/web-request-context.js +13 -0
- package/dist/web-request-context.js.map +1 -0
- package/dist/web-session.d.ts +167 -0
- package/dist/web-session.js +389 -0
- package/dist/web-session.js.map +1 -0
- package/dist/web-shell-routes.d.ts +59 -0
- package/dist/web-shell-routes.js +96 -0
- package/dist/web-shell-routes.js.map +1 -0
- package/dist/web-upload.d.ts +183 -0
- package/dist/web-upload.js +468 -0
- package/dist/web-upload.js.map +1 -0
- package/dist/website-tips-renderer.d.ts +2 -2
- package/dist/website-tips-renderer.js +3 -3
- package/dist/website-tips-renderer.js.map +1 -1
- package/package.json +8 -5
- package/scripts/preinstall-guard.cjs +36 -0
- package/dist/ui/dashboard.html +0 -774
- package/dist/ui/settings.html +0 -2165
- package/dist/ui/view.html +0 -1139
package/dist/ui/dashboard.html
DELETED
|
@@ -1,774 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<meta name="referrer" content="no-referrer">
|
|
7
|
-
<title>AgEnD Dashboard</title>
|
|
8
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
|
-
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800&family=DM+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
10
|
-
<style>
|
|
11
|
-
:root {
|
|
12
|
-
--bg: #080b12; --surface: rgba(22,27,34,0.7); --raised: rgba(28,33,41,0.8);
|
|
13
|
-
--border: rgba(48,54,61,0.6); --border-bright: rgba(48,54,61,0.9);
|
|
14
|
-
--text: #f0f6fc; --text-sec: #8b949e; --text-muted: #484f58;
|
|
15
|
-
--accent: #2AABEE; --accent-dim: rgba(42,171,238,0.08); --accent-glow: rgba(42,171,238,0.2);
|
|
16
|
-
--success: #56d364; --error: #f85149; --warn: #d29922;
|
|
17
|
-
--glass: rgba(22,27,34,0.55); --glass-border: rgba(255,255,255,0.06);
|
|
18
|
-
}
|
|
19
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
20
|
-
html { font-size: 14px; }
|
|
21
|
-
body {
|
|
22
|
-
background: var(--bg); color: var(--text);
|
|
23
|
-
font-family: 'DM Sans', system-ui, sans-serif;
|
|
24
|
-
display: flex; height: 100vh;
|
|
25
|
-
background-image:
|
|
26
|
-
radial-gradient(ellipse 120% 80% at 10% 20%, rgba(42,171,238,0.04), transparent 50%),
|
|
27
|
-
radial-gradient(ellipse 80% 60% at 90% 80%, rgba(86,211,100,0.03), transparent 50%);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/* ── Sidebar ─────────────────────────────────────────── */
|
|
31
|
-
.sidebar {
|
|
32
|
-
width: 280px; flex-shrink: 0; display: flex; flex-direction: column;
|
|
33
|
-
background: var(--glass); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
|
|
34
|
-
border-right: 1px solid var(--glass-border);
|
|
35
|
-
}
|
|
36
|
-
.sidebar-header {
|
|
37
|
-
padding: 24px 20px 20px;
|
|
38
|
-
background: linear-gradient(135deg, rgba(42,171,238,0.08) 0%, transparent 60%);
|
|
39
|
-
border-bottom: 1px solid var(--glass-border);
|
|
40
|
-
}
|
|
41
|
-
.sidebar-header h1 {
|
|
42
|
-
font-family: 'Outfit', sans-serif; font-size: 22px; font-weight: 800;
|
|
43
|
-
letter-spacing: -0.03em; color: var(--text);
|
|
44
|
-
background: linear-gradient(135deg, var(--text) 60%, var(--accent));
|
|
45
|
-
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
|
|
46
|
-
}
|
|
47
|
-
.sidebar-header .uptime {
|
|
48
|
-
font-size: 11px; color: var(--text-muted); margin-top: 6px;
|
|
49
|
-
font-family: 'IBM Plex Mono', monospace; letter-spacing: 0.02em;
|
|
50
|
-
}
|
|
51
|
-
.nav-section {
|
|
52
|
-
font-family: 'Outfit', sans-serif; font-size: 10px; font-weight: 700;
|
|
53
|
-
letter-spacing: 0.14em; text-transform: uppercase; color: var(--text-muted);
|
|
54
|
-
padding: 16px 20px 8px;
|
|
55
|
-
}
|
|
56
|
-
.fleet-entry {
|
|
57
|
-
margin: 0 12px; padding: 10px 12px; cursor: pointer;
|
|
58
|
-
border-radius: 10px; display: flex; align-items: center; gap: 10px;
|
|
59
|
-
font-family: 'Outfit', sans-serif; font-weight: 600; font-size: 13px;
|
|
60
|
-
color: var(--accent); transition: all 0.15s;
|
|
61
|
-
border: 1px solid transparent;
|
|
62
|
-
}
|
|
63
|
-
.fleet-entry:hover { background: var(--accent-dim); }
|
|
64
|
-
.fleet-entry.active { background: var(--accent-dim); border-color: rgba(42,171,238,0.2); box-shadow: 0 0 20px rgba(42,171,238,0.06); }
|
|
65
|
-
.instance-list { flex: 1; overflow-y: auto; padding: 4px 0; }
|
|
66
|
-
.instance-item {
|
|
67
|
-
margin: 2px 12px; padding: 10px 12px; cursor: pointer;
|
|
68
|
-
border-radius: 10px; display: flex; align-items: center; gap: 10px;
|
|
69
|
-
transition: all 0.15s; border: 1px solid transparent;
|
|
70
|
-
}
|
|
71
|
-
.instance-item:hover { background: var(--raised); }
|
|
72
|
-
.instance-item.active { background: var(--accent-dim); border-color: rgba(42,171,238,0.15); }
|
|
73
|
-
.dot {
|
|
74
|
-
width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;
|
|
75
|
-
transition: box-shadow 0.3s;
|
|
76
|
-
}
|
|
77
|
-
.dot.running { background: var(--success); box-shadow: 0 0 8px rgba(86,211,100,0.5); }
|
|
78
|
-
.dot.stopped { background: var(--error); opacity: 0.7; }
|
|
79
|
-
.dot.crashed { background: var(--warn); }
|
|
80
|
-
.inst-identity { flex: 1; min-width: 0; }
|
|
81
|
-
.inst-name { font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
82
|
-
.inst-alias { margin-top: 2px; font-size: 11px; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
83
|
-
.inst-meta { flex-shrink: 0; font-size: 11px; color: var(--text-muted); font-family: 'IBM Plex Mono', monospace; }
|
|
84
|
-
.sidebar-footer { padding: 16px; border-top: 1px solid var(--glass-border); }
|
|
85
|
-
|
|
86
|
-
/* ── Main ────────────────────────────────────────────── */
|
|
87
|
-
.main { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
|
|
88
|
-
.no-sel { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px; color: var(--text-muted); }
|
|
89
|
-
.no-sel svg { opacity: 0.15; }
|
|
90
|
-
.no-sel span { font-family: 'Outfit', sans-serif; font-size: 15px; font-weight: 500; letter-spacing: -0.01em; }
|
|
91
|
-
|
|
92
|
-
/* ── Tabs ─────────────────────────────────────────────── */
|
|
93
|
-
.tabs {
|
|
94
|
-
display: flex; gap: 2px; padding: 8px 16px;
|
|
95
|
-
background: var(--glass); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
|
|
96
|
-
border-bottom: 1px solid var(--glass-border);
|
|
97
|
-
}
|
|
98
|
-
.tab {
|
|
99
|
-
padding: 8px 16px; cursor: pointer; border-radius: 8px;
|
|
100
|
-
font-family: 'Outfit', sans-serif; font-size: 13px; font-weight: 500;
|
|
101
|
-
color: var(--text-muted); transition: all 0.15s;
|
|
102
|
-
}
|
|
103
|
-
.tab:hover { color: var(--text-sec); background: rgba(255,255,255,0.03); }
|
|
104
|
-
.tab.active { color: var(--accent); background: var(--accent-dim); }
|
|
105
|
-
|
|
106
|
-
/* ── Action buttons ──────────────────────────────────── */
|
|
107
|
-
.actions { display: flex; gap: 8px; padding: 12px 16px; border-bottom: 1px solid var(--glass-border); }
|
|
108
|
-
.btn {
|
|
109
|
-
padding: 7px 14px; border-radius: 8px; font-size: 12px; font-weight: 600;
|
|
110
|
-
cursor: pointer; border: 1px solid var(--border); font-family: 'DM Sans', sans-serif;
|
|
111
|
-
transition: all 0.15s; display: inline-flex; align-items: center; gap: 5px;
|
|
112
|
-
}
|
|
113
|
-
.btn:active:not(:disabled) { transform: scale(0.96); }
|
|
114
|
-
.btn:disabled { opacity: 0.35; cursor: default; }
|
|
115
|
-
.btn-accent { background: var(--accent); color: #fff; border-color: transparent; }
|
|
116
|
-
.btn-accent:hover:not(:disabled) { box-shadow: 0 4px 20px rgba(42,171,238,0.3); }
|
|
117
|
-
.btn-green { background: rgba(35,134,54,0.9); color: #fff; border-color: transparent; }
|
|
118
|
-
.btn-green:hover:not(:disabled) { box-shadow: 0 4px 16px rgba(35,134,54,0.3); }
|
|
119
|
-
.btn-warn { background: rgba(158,106,3,0.9); color: #fff; border-color: transparent; }
|
|
120
|
-
.btn-warn:hover:not(:disabled) { box-shadow: 0 4px 16px rgba(158,106,3,0.3); }
|
|
121
|
-
.btn-red { background: rgba(218,54,51,0.9); color: #fff; border-color: transparent; }
|
|
122
|
-
.btn-red:hover:not(:disabled) { box-shadow: 0 4px 16px rgba(218,54,51,0.3); }
|
|
123
|
-
.btn-ghost { background: transparent; color: var(--text-sec); }
|
|
124
|
-
.btn-ghost:hover:not(:disabled) { color: var(--text); background: var(--raised); }
|
|
125
|
-
|
|
126
|
-
/* ── Toast ───────────────────────────────────────────── */
|
|
127
|
-
.toast {
|
|
128
|
-
position: fixed; bottom: 24px; right: 24px; padding: 12px 20px;
|
|
129
|
-
border-radius: 12px; font-size: 13px; font-weight: 500; z-index: 100;
|
|
130
|
-
backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
|
|
131
|
-
animation: toastIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
132
|
-
border: 1px solid var(--glass-border);
|
|
133
|
-
}
|
|
134
|
-
.toast.ok { background: rgba(35,134,54,0.85); color: #fff; }
|
|
135
|
-
.toast.err { background: rgba(218,54,51,0.85); color: #fff; }
|
|
136
|
-
@keyframes toastIn { from { transform: translateY(16px) scale(0.95); opacity: 0; } to { transform: translateY(0) scale(1); opacity: 1; } }
|
|
137
|
-
|
|
138
|
-
/* ── Chat ─────────────────────────────────────────────── */
|
|
139
|
-
.chat-view { flex: 1; display: flex; flex-direction: column; }
|
|
140
|
-
.messages { flex: 1; overflow-y: auto; padding: 20px 24px; display: flex; flex-direction: column; gap: 10px; }
|
|
141
|
-
.msg {
|
|
142
|
-
max-width: 78%; padding: 12px 16px; border-radius: 16px;
|
|
143
|
-
font-size: 13px; line-height: 1.65; white-space: pre-wrap; word-break: break-word;
|
|
144
|
-
}
|
|
145
|
-
.msg.inbound {
|
|
146
|
-
align-self: flex-end; border-bottom-right-radius: 4px;
|
|
147
|
-
background: linear-gradient(135deg, rgba(42,171,238,0.12), rgba(42,171,238,0.06));
|
|
148
|
-
border: 1px solid rgba(42,171,238,0.1);
|
|
149
|
-
}
|
|
150
|
-
.msg.outbound {
|
|
151
|
-
align-self: flex-start; border-bottom-left-radius: 4px;
|
|
152
|
-
background: var(--surface); border: 1px solid var(--glass-border);
|
|
153
|
-
border-left: 2px solid var(--success);
|
|
154
|
-
}
|
|
155
|
-
.msg .sender { font-size: 11px; font-weight: 600; margin-bottom: 4px; font-family: 'Outfit', sans-serif; }
|
|
156
|
-
.msg .sender.user { color: var(--accent); }
|
|
157
|
-
.msg .sender.agent { color: var(--success); }
|
|
158
|
-
.msg .time { font-size: 10px; color: var(--text-muted); float: right; margin-left: 12px; font-family: 'IBM Plex Mono', monospace; }
|
|
159
|
-
.msg-empty { color: var(--text-muted); text-align: center; margin-top: 80px; font-size: 13px; line-height: 2; }
|
|
160
|
-
.msg-empty svg { display: block; margin: 0 auto 12px; opacity: 0.15; }
|
|
161
|
-
.input-bar { padding: 16px 20px; border-top: 1px solid var(--glass-border); display: flex; gap: 8px; }
|
|
162
|
-
.input-bar input {
|
|
163
|
-
flex: 1; background: rgba(255,255,255,0.04); color: var(--text);
|
|
164
|
-
border: 1px solid var(--border); border-radius: 12px; padding: 11px 16px;
|
|
165
|
-
font-size: 13px; font-family: 'DM Sans', sans-serif; outline: none; transition: all 0.15s;
|
|
166
|
-
}
|
|
167
|
-
.input-bar input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); }
|
|
168
|
-
|
|
169
|
-
/* ── Detail cards ────────────────────────────────────── */
|
|
170
|
-
.detail-view { flex: 1; overflow-y: auto; padding: 24px; display: none; }
|
|
171
|
-
.card {
|
|
172
|
-
background: var(--surface); border: 1px solid var(--glass-border);
|
|
173
|
-
border-radius: 16px; padding: 20px 24px; margin-bottom: 16px;
|
|
174
|
-
backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
|
|
175
|
-
transition: border-color 0.2s;
|
|
176
|
-
}
|
|
177
|
-
.card:hover { border-color: rgba(255,255,255,0.1); }
|
|
178
|
-
.card h3 {
|
|
179
|
-
font-family: 'Outfit', sans-serif; font-size: 10px; font-weight: 700;
|
|
180
|
-
letter-spacing: 0.14em; text-transform: uppercase; color: var(--accent);
|
|
181
|
-
margin-bottom: 14px;
|
|
182
|
-
}
|
|
183
|
-
.detail-row { display: flex; align-items: center; gap: 10px; font-size: 13px; padding: 6px 0; }
|
|
184
|
-
.detail-label { color: var(--text-muted); min-width: 100px; font-family: 'IBM Plex Mono', monospace; font-size: 11px; }
|
|
185
|
-
/* Readable at a glance: the percentage is bold and the bar thick (usage readability). */
|
|
186
|
-
.progress-bar { display: inline-block; width: 120px; height: 8px; background: rgba(255,255,255,0.07); border-radius: 4px; vertical-align: middle; margin-left: 10px; overflow: hidden; }
|
|
187
|
-
.progress-fill { display: block; height: 100%; border-radius: 4px; transition: width 0.4s cubic-bezier(0.4,0,0.2,1); }
|
|
188
|
-
.pct-val { font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums; }
|
|
189
|
-
.activity-item { font-size: 12px; padding: 6px 0; border-bottom: 1px solid rgba(255,255,255,0.03); font-family: 'IBM Plex Mono', monospace; }
|
|
190
|
-
.act-time { color: var(--text-muted); margin-right: 8px; }
|
|
191
|
-
.act-event { color: var(--warn); margin-right: 4px; }
|
|
192
|
-
|
|
193
|
-
/* ── List items (tasks, schedules, teams) ─────────────── */
|
|
194
|
-
.task-view { flex: 1; overflow-y: auto; padding: 24px; display: none; }
|
|
195
|
-
.list-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
|
196
|
-
.list-header span { font-family: 'Outfit', sans-serif; font-weight: 600; font-size: 15px; }
|
|
197
|
-
.task-item {
|
|
198
|
-
background: var(--surface); border: 1px solid var(--glass-border);
|
|
199
|
-
border-radius: 12px; padding: 14px 18px; margin-bottom: 10px;
|
|
200
|
-
display: flex; align-items: center; gap: 12px;
|
|
201
|
-
backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
|
|
202
|
-
transition: all 0.15s;
|
|
203
|
-
}
|
|
204
|
-
.task-item:hover { border-color: rgba(255,255,255,0.08); transform: translateY(-1px); }
|
|
205
|
-
.task-status {
|
|
206
|
-
font-size: 10px; padding: 3px 10px; border-radius: 999px; font-weight: 600;
|
|
207
|
-
font-family: 'IBM Plex Mono', monospace; text-transform: uppercase; letter-spacing: 0.06em;
|
|
208
|
-
}
|
|
209
|
-
.task-status.open { background: var(--accent-dim); color: var(--accent); }
|
|
210
|
-
.task-status.claimed { background: rgba(210,153,34,0.12); color: var(--warn); }
|
|
211
|
-
.task-status.done { background: rgba(86,211,100,0.1); color: var(--success); }
|
|
212
|
-
.task-title { flex: 1; font-size: 13px; font-weight: 500; }
|
|
213
|
-
.task-assignee { font-size: 11px; color: var(--text-sec); }
|
|
214
|
-
.task-actions { display: flex; gap: 4px; }
|
|
215
|
-
|
|
216
|
-
/* ── Form overlay ────────────────────────────────────── */
|
|
217
|
-
.form-overlay {
|
|
218
|
-
position: fixed; inset: 0; background: rgba(0,0,0,0.65);
|
|
219
|
-
backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
|
|
220
|
-
display: flex; align-items: center; justify-content: center; z-index: 50;
|
|
221
|
-
animation: fadeIn 0.2s ease;
|
|
222
|
-
}
|
|
223
|
-
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
224
|
-
.form-card {
|
|
225
|
-
background: var(--glass); border: 1px solid var(--glass-border);
|
|
226
|
-
border-radius: 20px; padding: 32px; width: 480px; max-width: 92vw; max-height: 82vh;
|
|
227
|
-
overflow-y: auto; backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px);
|
|
228
|
-
box-shadow: 0 24px 80px rgba(0,0,0,0.4);
|
|
229
|
-
}
|
|
230
|
-
.form-card h2 {
|
|
231
|
-
font-family: 'Outfit', sans-serif; font-size: 20px; font-weight: 700;
|
|
232
|
-
margin-bottom: 24px; letter-spacing: -0.02em;
|
|
233
|
-
}
|
|
234
|
-
.form-field { margin-bottom: 16px; }
|
|
235
|
-
.form-field label {
|
|
236
|
-
display: block; font-size: 11px; font-weight: 600; color: var(--text-muted);
|
|
237
|
-
margin-bottom: 6px; text-transform: uppercase; letter-spacing: 0.08em;
|
|
238
|
-
font-family: 'Outfit', sans-serif;
|
|
239
|
-
}
|
|
240
|
-
.form-field input, .form-field select {
|
|
241
|
-
width: 100%; background: rgba(255,255,255,0.04); color: var(--text);
|
|
242
|
-
border: 1px solid var(--border); border-radius: 10px; padding: 10px 14px;
|
|
243
|
-
font-size: 13px; font-family: 'DM Sans', sans-serif; outline: none;
|
|
244
|
-
transition: all 0.15s;
|
|
245
|
-
}
|
|
246
|
-
.form-field input:focus, .form-field select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); }
|
|
247
|
-
.form-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 24px; }
|
|
248
|
-
.eye-btn { background: none; border: none; cursor: pointer; color: var(--text-muted); font-size: 14px; padding: 4px 6px; border-radius: 6px; transition: all 0.15s; }
|
|
249
|
-
.eye-btn:hover { color: var(--accent); }
|
|
250
|
-
|
|
251
|
-
@media (max-width: 700px) { .sidebar { width: 240px; } }
|
|
252
|
-
</style>
|
|
253
|
-
</head>
|
|
254
|
-
<body>
|
|
255
|
-
|
|
256
|
-
<div class="sidebar">
|
|
257
|
-
<div class="sidebar-header">
|
|
258
|
-
<h1>AgEnD</h1>
|
|
259
|
-
<div class="uptime" id="uptime">--</div>
|
|
260
|
-
</div>
|
|
261
|
-
<div style="padding:8px 12px 0">
|
|
262
|
-
<div class="fleet-entry" id="fleetEntry" onclick="selFleet()">
|
|
263
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg>
|
|
264
|
-
Fleet
|
|
265
|
-
</div>
|
|
266
|
-
</div>
|
|
267
|
-
<div class="nav-section">Instances</div>
|
|
268
|
-
<div class="instance-list" id="instanceList"></div>
|
|
269
|
-
<div class="sidebar-footer">
|
|
270
|
-
<button class="btn btn-accent" style="width:100%;justify-content:center" onclick="showCreateInstance()">+ New Instance</button>
|
|
271
|
-
</div>
|
|
272
|
-
</div>
|
|
273
|
-
|
|
274
|
-
<div class="main" id="mainArea">
|
|
275
|
-
<div class="no-sel">
|
|
276
|
-
<svg width="56" height="56" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg>
|
|
277
|
-
<span>Select Fleet or an instance to begin</span>
|
|
278
|
-
</div>
|
|
279
|
-
</div>
|
|
280
|
-
|
|
281
|
-
<script>
|
|
282
|
-
// No token in the URL: the gate redeemed it for an HttpOnly session cookie,
|
|
283
|
-
// which the browser attaches to these same-origin requests on its own.
|
|
284
|
-
const UI_LANG = localStorage.getItem("agend_lang") || (navigator.language?.toLowerCase().startsWith("zh") ? "zh-TW" : "en");
|
|
285
|
-
const UI_TEXT = {
|
|
286
|
-
en: {
|
|
287
|
-
disconnected: "disconnected", selectTarget: "Select Fleet or an instance to begin", deleteConfirm: 'Type "delete {0}" to confirm:',
|
|
288
|
-
instanceDeleted: "Instance deleted", noMessages: "No messages yet", sendFirst: "Send a message to start the conversation",
|
|
289
|
-
loadFailed: "Failed to load", configLoadFailed: "Failed to load config", topicRequired: "Topic Name is required when Directory is empty",
|
|
290
|
-
titleRequired: "Title required", scheduleFieldsRequired: "Cron, message, and target required", teamFieldsRequired: "Name and at least one member required",
|
|
291
|
-
deleteSchedule: "Delete this schedule?", deleteTeam: 'Delete team "{0}"?', creating: "Creating...", create: "Create",
|
|
292
|
-
instanceCreated: "Instance created", taskCreated: "Task created", scheduleCreated: "Schedule created", scheduleDeleted: "Schedule deleted",
|
|
293
|
-
teamCreated: "Team created", teamDeleted: "Team deleted", configSaved: "Config saved", configRestart: " (restart Fleet to apply channel changes)",
|
|
294
|
-
},
|
|
295
|
-
"zh-TW": {
|
|
296
|
-
disconnected: "已斷線", selectTarget: "請選擇 Fleet 或 instance", deleteConfirm: '請輸入「delete {0}」以確認:',
|
|
297
|
-
instanceDeleted: "Instance 已刪除", noMessages: "尚無訊息", sendFirst: "傳送訊息以開始對話",
|
|
298
|
-
loadFailed: "載入失敗", configLoadFailed: "載入設定失敗", topicRequired: "Directory 留空時必須填寫 Topic Name",
|
|
299
|
-
titleRequired: "必須填寫標題", scheduleFieldsRequired: "必須填寫 Cron、訊息與目標 instance", teamFieldsRequired: "必須填寫名稱並選擇至少一名成員",
|
|
300
|
-
deleteSchedule: "確定刪除此排程嗎?", deleteTeam: '確定刪除 team「{0}」嗎?', creating: "建立中…", create: "建立",
|
|
301
|
-
instanceCreated: "Instance 已建立", taskCreated: "Task 已建立", scheduleCreated: "排程已建立", scheduleDeleted: "排程已刪除",
|
|
302
|
-
teamCreated: "Team 已建立", teamDeleted: "Team 已刪除", configSaved: "設定已儲存", configRestart: "(請重新啟動 Fleet 以套用頻道變更)",
|
|
303
|
-
},
|
|
304
|
-
};
|
|
305
|
-
const tr = key => UI_TEXT[UI_LANG]?.[key] || UI_TEXT.en[key] || key;
|
|
306
|
-
const trf = (key, ...values) => values.reduce((text, value, index) => text.replaceAll(`{${index}}`, String(value)), tr(key));
|
|
307
|
-
let cur = null, curTab = "chat", mode = "none", instances = [];
|
|
308
|
-
// mode: "none" | "fleet" | "instance"
|
|
309
|
-
const msgs = {};
|
|
310
|
-
|
|
311
|
-
async function api(m, p, b) {
|
|
312
|
-
const o = { method: m, headers: { "Content-Type": "application/json" } };
|
|
313
|
-
if (b) o.body = JSON.stringify(b);
|
|
314
|
-
const r = await fetch(p, o);
|
|
315
|
-
return r.json();
|
|
316
|
-
}
|
|
317
|
-
function esc(s) { const d = document.createElement("div"); d.textContent = s || ""; return d.innerHTML; }
|
|
318
|
-
function fmtUp(s) { return s < 60 ? `${s}s` : s < 3600 ? `${Math.floor(s/60)}m` : `${Math.floor(s/3600)}h ${Math.floor((s%3600)/60)}m`; }
|
|
319
|
-
function toast(msg, ok=true) {
|
|
320
|
-
const el = document.createElement("div"); el.className = `toast ${ok?"ok":"err"}`; el.textContent = msg;
|
|
321
|
-
document.body.appendChild(el); setTimeout(() => el.remove(), 3000);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
const sse = new EventSource("/ui/events");
|
|
325
|
-
sse.addEventListener("status", e => {
|
|
326
|
-
const d = JSON.parse(e.data); instances = d.instances;
|
|
327
|
-
document.getElementById("uptime").textContent = fmtUp(d.uptime);
|
|
328
|
-
renderList();
|
|
329
|
-
if (cur) { const i = instances.find(x => x.name === cur); const a = document.getElementById("actions"); if (i && a) renderActions(i); }
|
|
330
|
-
});
|
|
331
|
-
sse.addEventListener("message", e => {
|
|
332
|
-
const m = JSON.parse(e.data);
|
|
333
|
-
if (!msgs[m.instance]) msgs[m.instance] = [];
|
|
334
|
-
msgs[m.instance].push(m);
|
|
335
|
-
if (msgs[m.instance].length > 500) msgs[m.instance].shift();
|
|
336
|
-
if (m.instance === cur && curTab === "chat") renderMsgs();
|
|
337
|
-
});
|
|
338
|
-
sse.onerror = () => { document.getElementById("uptime").textContent = tr("disconnected"); };
|
|
339
|
-
|
|
340
|
-
function renderList() {
|
|
341
|
-
document.getElementById("fleetEntry").className = `fleet-entry${mode==="fleet"?" active":""}`;
|
|
342
|
-
const el = document.getElementById("instanceList");
|
|
343
|
-
// Map internal source enums to user-facing labels (M2: consistency with detail/view)
|
|
344
|
-
const sourceLabel = (src) => src === "instance" || src === "classic" ? "configured" : src === "fleet-default" ? "fleet default" : src;
|
|
345
|
-
el.innerHTML = instances.map(i => {
|
|
346
|
-
// Hover tooltip: name · backend · model (source) · effort (source) · ctx · status
|
|
347
|
-
// cli-default/unresolved: resolved.display already explains "(default)"/"(not probed yet)", no extra suffix
|
|
348
|
-
const modelPart = i.model ? (i.model_source === "live" || i.model_source === "cli-default" || i.model_source === "unresolved" ? i.model : `${i.model} (${sourceLabel(i.model_source)})`) : null;
|
|
349
|
-
// R2: effort_source always shows label for consistency (instance/classic → configured)
|
|
350
|
-
const effortPart = i.effort ? `effort:${i.effort}${i.effort_source ? ` (${sourceLabel(i.effort_source)})` : ""}` : null;
|
|
351
|
-
const ctxPart = i.context_pct != null ? `ctx:${Math.round(i.context_pct)}%` : null;
|
|
352
|
-
const tip = [i.name, i.backend, modelPart, effortPart, ctxPart, i.status].filter(Boolean).join(" · ");
|
|
353
|
-
// M2: show ctx:0% when context_pct is 0, only hide when null (unavailable)
|
|
354
|
-
const ctxMeta = i.context_pct != null ? ` ctx:${Math.round(i.context_pct)}%` : "";
|
|
355
|
-
const alias = typeof i.display_name === "string" && i.display_name.trim() !== i.name ? i.display_name.trim() : "";
|
|
356
|
-
return `<div class="instance-item${i.name===cur&&mode==="instance"?" active":""}" data-n="${esc(i.name)}" title="${esc(tip)}">
|
|
357
|
-
<div class="dot ${i.status}" title="${i.status}"></div><div class="inst-identity">
|
|
358
|
-
<div class="inst-name">${esc(i.name)}</div>${alias ? `<div class="inst-alias">${esc(alias)}</div>` : ""}
|
|
359
|
-
</div><div class="inst-meta">${i.cost>0?`$${i.cost.toFixed(2)}`:""}${ctxMeta}</div></div>`;
|
|
360
|
-
}).join("");
|
|
361
|
-
el.querySelectorAll(".instance-item").forEach(x => x.addEventListener("click", () => sel(x.dataset.n)));
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
function selFleet() { mode = "fleet"; cur = null; curTab = "tasks"; renderList(); renderMain(); }
|
|
365
|
-
function sel(name) { mode = "instance"; cur = name; curTab = "chat"; renderMain(); renderList(); }
|
|
366
|
-
|
|
367
|
-
function renderMain() {
|
|
368
|
-
const main = document.getElementById("mainArea");
|
|
369
|
-
if (mode === "fleet") return renderFleetMain(main);
|
|
370
|
-
if (mode === "instance" && cur) return renderInstanceMain(main);
|
|
371
|
-
main.innerHTML = `<div class="no-sel"><svg width="56" height="56" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg><span>${tr("selectTarget")}</span></div>`;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
function renderFleetMain(main) {
|
|
375
|
-
const tabs = ["tasks","schedules","teams","config"];
|
|
376
|
-
if (!tabs.includes(curTab)) curTab = "tasks";
|
|
377
|
-
main.innerHTML = `
|
|
378
|
-
<div class="tabs">${tabs.map(t => `<div class="tab${curTab===t?" active":""}" data-t="${t}">${t[0].toUpperCase()+t.slice(1)}</div>`).join("")}</div>
|
|
379
|
-
<div class="task-view" id="taskView" style="display:${curTab==="tasks"?"block":"none"}"><div style="color:var(--text-muted);text-align:center;padding:24px">Loading...</div></div>
|
|
380
|
-
<div class="task-view" id="schedView" style="display:${curTab==="schedules"?"block":"none"}"><div style="color:var(--text-muted);text-align:center;padding:24px">Loading...</div></div>
|
|
381
|
-
<div class="task-view" id="teamView" style="display:${curTab==="teams"?"block":"none"}"><div style="color:var(--text-muted);text-align:center;padding:24px">Loading...</div></div>
|
|
382
|
-
<div class="detail-view" id="configView" style="display:${curTab==="config"?"block":"none"}"><div style="color:var(--text-muted);text-align:center;padding:24px">Loading...</div></div>`;
|
|
383
|
-
main.querySelectorAll(".tab").forEach(t => t.addEventListener("click", () => { curTab = t.dataset.t; renderMain(); }));
|
|
384
|
-
if (curTab === "tasks") loadTasks();
|
|
385
|
-
else if (curTab === "schedules") loadSchedules();
|
|
386
|
-
else if (curTab === "teams") loadTeams();
|
|
387
|
-
else if (curTab === "config") loadConfig();
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
function renderInstanceMain(main) {
|
|
391
|
-
const tabs = ["chat","detail"];
|
|
392
|
-
if (!tabs.includes(curTab)) curTab = "chat";
|
|
393
|
-
main.innerHTML = `
|
|
394
|
-
<div class="tabs">${tabs.map(t => `<div class="tab${curTab===t?" active":""}" data-t="${t}">${t[0].toUpperCase()+t.slice(1)}</div>`).join("")}</div>
|
|
395
|
-
<div class="actions" id="actions"></div>
|
|
396
|
-
<div class="chat-view" id="chatView" style="display:${curTab==="chat"?"flex":"none"}">
|
|
397
|
-
<div class="messages" id="messages"></div>
|
|
398
|
-
<div class="input-bar"><input id="msgIn" placeholder="Send a message..." autocomplete="off"><button class="btn btn-accent" onclick="sendMsg()">Send</button></div>
|
|
399
|
-
</div>
|
|
400
|
-
<div class="detail-view" id="detailView" style="display:${curTab==="detail"?"block":"none"}"><div style="color:var(--text-muted);text-align:center;padding:24px">Loading...</div></div>`;
|
|
401
|
-
main.querySelectorAll(".tab").forEach(t => t.addEventListener("click", () => { curTab = t.dataset.t; renderMain(); }));
|
|
402
|
-
const i = instances.find(x => x.name === cur); renderActions(i);
|
|
403
|
-
if (curTab === "chat") { renderMsgs(); const inp = document.getElementById("msgIn"); inp.addEventListener("keydown", e => { if (e.key==="Enter"&&!e.shiftKey&&!e.isComposing){e.preventDefault();sendMsg();} }); inp.focus(); }
|
|
404
|
-
else if (curTab === "detail") loadDetail();
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
function renderActions(i) {
|
|
408
|
-
const el = document.getElementById("actions"); if (!el || !i) return;
|
|
409
|
-
const r = i.status === "running";
|
|
410
|
-
el.innerHTML = `${r
|
|
411
|
-
?`<button class="btn btn-warn" onclick="doAction('stop')"><svg width="11" height="11" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="6" width="12" height="12" rx="1"/></svg> Stop</button><button class="btn btn-warn" onclick="doAction('restart')"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M1 4v6h6M23 20v-6h-6"/><path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"/></svg> Restart</button>`
|
|
412
|
-
:`<button class="btn btn-green" onclick="doAction('start')"><svg width="11" height="11" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg> Start</button>`}
|
|
413
|
-
<button class="btn btn-red" onclick="doAction('delete')"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg> Delete</button>`;
|
|
414
|
-
}
|
|
415
|
-
async function doAction(a) {
|
|
416
|
-
if (!cur) return;
|
|
417
|
-
if (a === "delete") {
|
|
418
|
-
const c = prompt(trf("deleteConfirm", cur));
|
|
419
|
-
if (c !== `delete ${cur}`) return;
|
|
420
|
-
const r = await api("POST", `/ui/instances/${encodeURIComponent(cur)}/delete`, { confirm: c });
|
|
421
|
-
if (r.error) { toast(r.error, false); return; }
|
|
422
|
-
toast(`${cur}: ${tr("instanceDeleted")}`); cur = null;
|
|
423
|
-
document.getElementById("mainArea").innerHTML = `<div class="no-sel"><span>${tr("instanceDeleted")}</span></div>`;
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
const ep = `/ui/${a}/${encodeURIComponent(cur)}`;
|
|
427
|
-
const r = await api("POST", ep);
|
|
428
|
-
if (r.error) toast(r.error, false);
|
|
429
|
-
else toast(`${cur} ${a}ed`);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
function renderMsgs() {
|
|
433
|
-
const el = document.getElementById("messages"); if (!el) return;
|
|
434
|
-
const m = msgs[cur] || [];
|
|
435
|
-
if (!m.length) { el.innerHTML = `<div class="msg-empty"><svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>${tr("noMessages")}<br><span style="font-size:12px;color:var(--text-muted)">${tr("sendFirst")}</span></div>`; return; }
|
|
436
|
-
el.innerHTML = m.map(x => {
|
|
437
|
-
const u = x.sender==="web-user"||(!x.sender.startsWith("agend")&&x.sender!==cur&&x.sender!=="general");
|
|
438
|
-
const t = x.ts ? new Date(x.ts).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"}) : "";
|
|
439
|
-
return `<div class="msg ${u?"inbound":"outbound"}"><div class="sender ${u?"user":"agent"}">${esc(x.sender)}<span class="time">${t}</span></div>${esc(x.text)}</div>`;
|
|
440
|
-
}).join("");
|
|
441
|
-
el.scrollTop = el.scrollHeight;
|
|
442
|
-
}
|
|
443
|
-
async function sendMsg() {
|
|
444
|
-
const inp = document.getElementById("msgIn"), txt = inp.value.trim();
|
|
445
|
-
if (!txt||!cur) return; inp.value = "";
|
|
446
|
-
await api("POST", "/ui/send", { instance: cur, message: txt });
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
async function loadDetail() {
|
|
450
|
-
const el = document.getElementById("detailView"); if (!el||!cur) return;
|
|
451
|
-
try {
|
|
452
|
-
const d = await api("GET", `/ui/instance/${encodeURIComponent(cur)}`);
|
|
453
|
-
const sl = d.statusline||{}, cost=sl.cost?.total_cost_usd??0;
|
|
454
|
-
// Context: use API's resolved context (aligned with /ctx), not statusline
|
|
455
|
-
const ctx = d.context_pct;
|
|
456
|
-
const ctxDisplay = ctx != null ? `${Math.round(ctx)}%` : "--";
|
|
457
|
-
const ctxBar = ctx != null ? `<span class="progress-bar"><span class="progress-fill" style="width:${Math.min(ctx,100)}%;background:${ctx>80?'var(--warn)':'var(--accent)'}"></span></span>` : "";
|
|
458
|
-
// Model: with source labeling
|
|
459
|
-
const model = d.model || sl.model?.display_name || "--";
|
|
460
|
-
const modelSource = d.model_source === "live" ? "" : d.model_source === "instance" ? " <span style='color:var(--text-muted);font-size:11px'>(configured)</span>" : d.model_source === "fleet-default" ? " <span style='color:var(--text-muted);font-size:11px'>(fleet default)</span>" : "";
|
|
461
|
-
const rl5=sl.rate_limits?.five_hour?.used_percentage??0, rl7=sl.rate_limits?.seven_day?.used_percentage??0;
|
|
462
|
-
// Effort: only show if configured (no live value from CLI), with source labeling
|
|
463
|
-
const effortLine = d.effort
|
|
464
|
-
? `<div class="detail-row"><span class="detail-label">Effort</span><span>${esc(d.effort)}${d.effort_source==="instance"?" <span style='color:var(--text-muted);font-size:11px'>(configured)</span>":d.effort_source==="fleet-default"?" <span style='color:var(--text-muted);font-size:11px'>(fleet default)</span>":""}</span></div>`
|
|
465
|
-
: "";
|
|
466
|
-
let h = `<div class="card"><h3>Instance</h3>
|
|
467
|
-
<div class="detail-row"><span class="detail-label">Name</span><span>${esc(d.name)}</span></div>
|
|
468
|
-
<div class="detail-row"><span class="detail-label">Status</span><span>${esc(d.status)}</span></div>
|
|
469
|
-
<div class="detail-row"><span class="detail-label">Display</span><span>${esc(d.display_name||"--")}</span></div>
|
|
470
|
-
<div class="detail-row"><span class="detail-label">Description</span><span>${esc(d.description||"--")}</span></div>
|
|
471
|
-
<div class="detail-row"><span class="detail-label">Directory</span><span style="font-family:'IBM Plex Mono',monospace;font-size:12px">${esc(d.working_directory)}</span></div></div>
|
|
472
|
-
<div class="card"><h3>Runtime</h3>
|
|
473
|
-
<div class="detail-row"><span class="detail-label">Backend</span><span>${esc(d.backend||"--")}</span></div>
|
|
474
|
-
<div class="detail-row"><span class="detail-label">Model</span><span>${esc(model)}${modelSource}</span></div>
|
|
475
|
-
${effortLine}
|
|
476
|
-
<div class="detail-row"><span class="detail-label">Cost</span><span>$${cost.toFixed(2)} <span style="color:var(--text-muted);font-size:11px">(session accumulated)</span></span></div>
|
|
477
|
-
<div class="detail-row"><span class="detail-label">Context</span><span><span class="pct-val">${ctxDisplay}</span> ${ctxBar}</span></div>
|
|
478
|
-
<div class="detail-row"><span class="detail-label">Rate 5h</span><span><span class="pct-val">${Math.round(rl5)}%</span> <span class="progress-bar"><span class="progress-fill" style="width:${Math.min(rl5,100)}%;background:${rl5>90?'var(--error)':rl5>70?'var(--warn)':'var(--accent)'}"></span></span></span></div>
|
|
479
|
-
<div class="detail-row"><span class="detail-label">Rate 7d</span><span><span class="pct-val">${Math.round(rl7)}%</span> <span class="progress-bar"><span class="progress-fill" style="width:${Math.min(rl7,100)}%;background:${rl7>90?'var(--error)':rl7>70?'var(--warn)':'var(--accent)'}"></span></span></span></div></div>`;
|
|
480
|
-
if (d.recent_activity?.length) {
|
|
481
|
-
h += `<div class="card"><h3>Recent Activity</h3>`;
|
|
482
|
-
for (const a of d.recent_activity) { const t=a.timestamp?new Date(a.timestamp.replace(" ","T")+"Z").toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"}):""; h+=`<div class="activity-item"><span class="act-time">${t}</span><span class="act-event">${esc(a.event)}</span>${esc(a.summary||"")}</div>`; }
|
|
483
|
-
h += `</div>`;
|
|
484
|
-
}
|
|
485
|
-
el.innerHTML = h;
|
|
486
|
-
} catch { el.innerHTML = `<div style="color:var(--error);padding:24px">${tr("loadFailed")}</div>`; }
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
async function loadTasks() {
|
|
490
|
-
const el = document.getElementById("taskView"); if (!el) return;
|
|
491
|
-
try {
|
|
492
|
-
const d = await api("GET", "/ui/tasks");
|
|
493
|
-
const tasks = d.tasks || [];
|
|
494
|
-
let h = `<div class="list-header"><span>${tasks.length} tasks</span>
|
|
495
|
-
<button class="btn btn-accent" onclick="showCreateTask()">+ New Task</button></div>`;
|
|
496
|
-
if (!tasks.length) h += `<div class="msg-empty"><svg width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>No tasks yet<br><span style="font-size:12px">Create one to coordinate fleet work</span></div>`;
|
|
497
|
-
else for (const t of tasks) {
|
|
498
|
-
h += `<div class="task-item">
|
|
499
|
-
<span class="task-status ${t.status}">${t.status}</span>
|
|
500
|
-
<span class="task-title">${esc(t.title)}</span>
|
|
501
|
-
<span class="task-assignee">${t.assignee?`<span style="cursor:pointer;color:var(--accent)" onclick="sel('${esc(t.assignee)}')">${esc(t.assignee)}</span>`:""}</span>
|
|
502
|
-
<div class="task-actions">
|
|
503
|
-
${t.status==="open"?`<button class="btn btn-ghost" onclick="taskAct('${t.id}','claim')">Claim</button>`:""}
|
|
504
|
-
${t.status==="claimed"?`<button class="btn btn-ghost" onclick="taskAct('${t.id}','complete')">Done</button>`:""}
|
|
505
|
-
</div></div>`;
|
|
506
|
-
}
|
|
507
|
-
el.innerHTML = h;
|
|
508
|
-
} catch { el.innerHTML = `<div style="color:var(--error);padding:24px">${tr("loadFailed")}</div>`; }
|
|
509
|
-
}
|
|
510
|
-
async function taskAct(id, action) {
|
|
511
|
-
const r = await api("POST", `/ui/tasks/${id}`, { action });
|
|
512
|
-
if (r.error) toast(r.error, false); else { toast(`Task ${action}ed`); loadTasks(); }
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
async function showCreateInstance() {
|
|
516
|
-
const ov = document.createElement("div"); ov.className = "form-overlay";
|
|
517
|
-
let backendOpts = '<option value="">Use fleet default</option>';
|
|
518
|
-
try {
|
|
519
|
-
const bd = await api("GET", "/ui/backends");
|
|
520
|
-
const first = (bd.backends || []).find(b => b.installed && !b.deprecated) || (bd.backends || []).find(b => b.installed);
|
|
521
|
-
backendOpts += (bd.backends || []).map(b => {
|
|
522
|
-
let label = b.deprecated ? `${b.name} (deprecated)` : b.name;
|
|
523
|
-
if (!b.installed) label += " (not installed)";
|
|
524
|
-
return `<option value="${b.name}" ${b.installed ? "" : "disabled"} ${b.name === first?.name ? "selected" : ""}>${label}</option>`;
|
|
525
|
-
}).join("");
|
|
526
|
-
} catch { backendOpts += ["claude-code","codex","opencode","kiro-cli","antigravity","grok","muse"].map(b => `<option value="${b}">${b}</option>`).join(""); }
|
|
527
|
-
ov.innerHTML = `<div class="form-card"><h2>Create Instance</h2>
|
|
528
|
-
<div class="form-field"><label>Directory</label><input id="ci-dir" placeholder="Optional — auto-creates workspace if empty"></div>
|
|
529
|
-
<div class="form-field"><label id="ci-topic-label">Topic Name</label><input id="ci-topic" placeholder="Auto from directory name"></div>
|
|
530
|
-
<div class="form-field"><label>Description</label><input id="ci-desc" placeholder="What this instance does"></div>
|
|
531
|
-
<div class="form-field"><label>Backend</label><select id="ci-backend">${backendOpts}</select></div>
|
|
532
|
-
<div class="form-field"><label>Model</label><input id="ci-model" placeholder="e.g. sonnet, opus, gpt-5"></div>
|
|
533
|
-
<div class="form-field"><label>Branch (git worktree)</label><input id="ci-branch" placeholder="e.g. feature-x"></div>
|
|
534
|
-
<div class="form-field"><label>Tags</label><input id="ci-tags" placeholder="comma-separated, e.g. dev, review"></div>
|
|
535
|
-
<div class="form-field"><label>System Prompt</label><textarea id="ci-prompt" rows="3" placeholder="Custom instructions for this instance"></textarea></div>
|
|
536
|
-
<div class="form-actions"><button class="btn btn-ghost" onclick="this.closest('.form-overlay').remove()">Cancel</button>
|
|
537
|
-
<button class="btn btn-accent" id="ci-go">Create</button></div></div>`;
|
|
538
|
-
document.body.appendChild(ov);
|
|
539
|
-
ov.addEventListener("click", e => { if (e.target === ov) ov.remove(); });
|
|
540
|
-
ov.querySelector("#ci-dir").addEventListener("input", () => {
|
|
541
|
-
const hasDir = ov.querySelector("#ci-dir").value.trim();
|
|
542
|
-
ov.querySelector("#ci-topic-label").textContent = hasDir ? "Topic Name" : "Topic Name *";
|
|
543
|
-
});
|
|
544
|
-
ov.querySelector("#ci-go").addEventListener("click", async () => {
|
|
545
|
-
const dir = document.getElementById("ci-dir").value.trim();
|
|
546
|
-
const topic = document.getElementById("ci-topic").value.trim();
|
|
547
|
-
if (!dir && !topic) { toast(tr("topicRequired"), false); return; }
|
|
548
|
-
const btn = ov.querySelector("#ci-go"); btn.disabled = true; btn.textContent = tr("creating");
|
|
549
|
-
const body = {};
|
|
550
|
-
if (dir) body.directory = dir;
|
|
551
|
-
const v = id => document.getElementById(id).value.trim();
|
|
552
|
-
if (v("ci-topic")) body.topic_name = v("ci-topic");
|
|
553
|
-
if (v("ci-desc")) body.description = v("ci-desc");
|
|
554
|
-
const backend = document.getElementById("ci-backend").value; if (backend) body.backend = backend;
|
|
555
|
-
if (v("ci-model")) body.model = v("ci-model");
|
|
556
|
-
if (v("ci-branch")) body.branch = v("ci-branch");
|
|
557
|
-
const tags = v("ci-tags"); if (tags) body.tags = tags.split(",").map(t => t.trim()).filter(Boolean);
|
|
558
|
-
if (v("ci-prompt")) body.systemPrompt = v("ci-prompt");
|
|
559
|
-
const r = await api("POST", "/ui/instances", body);
|
|
560
|
-
if (r.error) { toast(r.error, false); btn.disabled = false; btn.textContent = tr("create"); }
|
|
561
|
-
else { toast(tr("instanceCreated")); ov.remove(); }
|
|
562
|
-
});
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
function showCreateTask() {
|
|
566
|
-
const ov = document.createElement("div"); ov.className = "form-overlay";
|
|
567
|
-
const opts = instances.map(i => `<option value="${esc(i.name)}">${esc(i.name)}</option>`).join("");
|
|
568
|
-
ov.innerHTML = `<div class="form-card"><h2>Create Task</h2>
|
|
569
|
-
<div class="form-field"><label>Title *</label><input id="ct-title" placeholder="Task title"></div>
|
|
570
|
-
<div class="form-field"><label>Description</label><input id="ct-desc" placeholder="Details"></div>
|
|
571
|
-
<div class="form-field"><label>Priority</label><select id="ct-pri"><option value="normal">Normal</option><option value="low">Low</option><option value="high">High</option><option value="urgent">Urgent</option></select></div>
|
|
572
|
-
<div class="form-field"><label>Assignee</label><select id="ct-assign"><option value="">Unassigned</option>${opts}</select></div>
|
|
573
|
-
<div class="form-actions"><button class="btn btn-ghost" onclick="this.closest('.form-overlay').remove()">Cancel</button>
|
|
574
|
-
<button class="btn btn-accent" id="ct-go">Create</button></div></div>`;
|
|
575
|
-
document.body.appendChild(ov);
|
|
576
|
-
ov.addEventListener("click", e => { if (e.target === ov) ov.remove(); });
|
|
577
|
-
ov.querySelector("#ct-go").addEventListener("click", async () => {
|
|
578
|
-
const title = document.getElementById("ct-title").value.trim();
|
|
579
|
-
if (!title) { toast(tr("titleRequired"), false); return; }
|
|
580
|
-
const btn = ov.querySelector("#ct-go"); btn.disabled = true; btn.textContent = tr("creating");
|
|
581
|
-
const body = { title, priority: document.getElementById("ct-pri").value };
|
|
582
|
-
const desc = document.getElementById("ct-desc").value.trim(); if (desc) body.description = desc;
|
|
583
|
-
const asgn = document.getElementById("ct-assign").value; if (asgn) body.assignee = asgn;
|
|
584
|
-
const r = await api("POST", "/ui/tasks", body);
|
|
585
|
-
if (r.error) { toast(r.error, false); btn.disabled = false; btn.textContent = tr("create"); }
|
|
586
|
-
else { toast(tr("taskCreated")); ov.remove(); loadTasks(); }
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
// ── Schedules ────────────────────────────────────────────
|
|
591
|
-
async function loadSchedules() {
|
|
592
|
-
const el = document.getElementById("schedView"); if (!el) return;
|
|
593
|
-
try {
|
|
594
|
-
const d = await api("GET", "/ui/schedules");
|
|
595
|
-
const scheds = d.schedules || [];
|
|
596
|
-
let h = `<div class="list-header"><span>${scheds.length} schedules</span>
|
|
597
|
-
<button class="btn btn-accent" onclick="showCreateSchedule()">+ New Schedule</button></div>`;
|
|
598
|
-
if (!scheds.length) h += `<div class="msg-empty"><svg width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>No schedules yet<br><span style="font-size:12px">Automate recurring tasks with cron</span></div>`;
|
|
599
|
-
else for (const s of scheds) {
|
|
600
|
-
h += `<div class="task-item">
|
|
601
|
-
<span class="task-status ${s.enabled?"open":"done"}">${s.enabled?"on":"off"}</span>
|
|
602
|
-
<span class="task-title">${esc(s.label||cronDesc(s.cron))}</span>
|
|
603
|
-
<span class="task-assignee" style="font-family:'IBM Plex Mono',monospace" title="${esc(cronDesc(s.cron))}">${esc(s.cron)}</span>
|
|
604
|
-
<span class="task-assignee" style="cursor:pointer;color:var(--accent)" onclick="sel('${esc(s.target)}')">${esc(s.target)}</span>
|
|
605
|
-
<div class="task-actions">
|
|
606
|
-
<button class="btn btn-ghost btn-red" onclick="delSchedule('${s.id}')">Delete</button>
|
|
607
|
-
</div></div>`;
|
|
608
|
-
}
|
|
609
|
-
el.innerHTML = h;
|
|
610
|
-
} catch { el.innerHTML = `<div style="color:var(--error);padding:24px">${tr("loadFailed")}</div>`; }
|
|
611
|
-
}
|
|
612
|
-
async function delSchedule(id) {
|
|
613
|
-
if (!confirm(tr("deleteSchedule"))) return;
|
|
614
|
-
const r = await api("DELETE", `/ui/schedules/${id}`);
|
|
615
|
-
if (r.error) toast(r.error, false); else { toast(tr("scheduleDeleted")); loadSchedules(); }
|
|
616
|
-
}
|
|
617
|
-
function showCreateSchedule() {
|
|
618
|
-
const ov = document.createElement("div"); ov.className = "form-overlay";
|
|
619
|
-
const tgtOpts = instances.map(i => `<option value="${esc(i.name)}">${esc(i.name)}</option>`).join("");
|
|
620
|
-
ov.innerHTML = `<div class="form-card"><h2>Create Schedule</h2>
|
|
621
|
-
<div class="form-field"><label>Cron Expression *</label><input id="cs-cron" placeholder="0 9 * * 1-5"></div>
|
|
622
|
-
<div class="form-field"><label>Message *</label><input id="cs-msg" placeholder="What to send"></div>
|
|
623
|
-
<div class="form-field"><label>Target Instance *</label><select id="cs-target">${tgtOpts}</select></div>
|
|
624
|
-
<div class="form-field"><label>Label</label><input id="cs-label" placeholder="Daily standup"></div>
|
|
625
|
-
<div class="form-actions"><button class="btn btn-ghost" onclick="this.closest('.form-overlay').remove()">Cancel</button>
|
|
626
|
-
<button class="btn btn-accent" id="cs-go">Create</button></div></div>`;
|
|
627
|
-
document.body.appendChild(ov);
|
|
628
|
-
ov.addEventListener("click", e => { if (e.target === ov) ov.remove(); });
|
|
629
|
-
ov.querySelector("#cs-go").addEventListener("click", async () => {
|
|
630
|
-
const cron = document.getElementById("cs-cron").value.trim();
|
|
631
|
-
const msg = document.getElementById("cs-msg").value.trim();
|
|
632
|
-
const target = document.getElementById("cs-target").value;
|
|
633
|
-
if (!cron || !msg || !target) { toast(tr("scheduleFieldsRequired"), false); return; }
|
|
634
|
-
const btn = ov.querySelector("#cs-go"); btn.disabled = true; btn.textContent = tr("creating");
|
|
635
|
-
const body = { cron, message: msg, target, source: "web-user", reply_chat_id: "", reply_thread_id: "" };
|
|
636
|
-
const label = document.getElementById("cs-label").value.trim(); if (label) body.label = label;
|
|
637
|
-
const r = await api("POST", "/ui/schedules", body);
|
|
638
|
-
if (r.error) { toast(r.error, false); btn.disabled = false; btn.textContent = tr("create"); }
|
|
639
|
-
else { toast(tr("scheduleCreated")); ov.remove(); loadSchedules(); }
|
|
640
|
-
});
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
// ── Teams ────────────────────────────────────────────────
|
|
644
|
-
async function loadTeams() {
|
|
645
|
-
const el = document.getElementById("teamView"); if (!el) return;
|
|
646
|
-
try {
|
|
647
|
-
const d = await api("GET", "/ui/teams");
|
|
648
|
-
const teams = d.teams || {};
|
|
649
|
-
const entries = Object.entries(teams);
|
|
650
|
-
let h = `<div class="list-header"><span>${entries.length} teams</span>
|
|
651
|
-
<button class="btn btn-accent" onclick="showCreateTeam()">+ New Team</button></div>`;
|
|
652
|
-
if (!entries.length) h += `<div class="msg-empty"><svg width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>No teams yet<br><span style="font-size:12px">Group instances for coordinated work</span></div>`;
|
|
653
|
-
else for (const [name, team] of entries) {
|
|
654
|
-
h += `<div class="task-item">
|
|
655
|
-
<span class="task-title" style="font-weight:600">${esc(name)}</span>
|
|
656
|
-
<span class="task-assignee">${esc((team.members||[]).join(", "))}</span>
|
|
657
|
-
<div class="task-actions">
|
|
658
|
-
<button class="btn btn-ghost btn-red" onclick="delTeam('${esc(name)}')">Delete</button>
|
|
659
|
-
</div></div>`;
|
|
660
|
-
}
|
|
661
|
-
el.innerHTML = h;
|
|
662
|
-
} catch { el.innerHTML = `<div style="color:var(--error);padding:24px">${tr("loadFailed")}</div>`; }
|
|
663
|
-
}
|
|
664
|
-
async function delTeam(name) {
|
|
665
|
-
if (!confirm(trf("deleteTeam", name))) return;
|
|
666
|
-
const r = await api("DELETE", `/ui/teams/${encodeURIComponent(name)}`);
|
|
667
|
-
if (r.error) toast(r.error, false); else { toast(tr("teamDeleted")); loadTeams(); }
|
|
668
|
-
}
|
|
669
|
-
function showCreateTeam() {
|
|
670
|
-
const ov = document.createElement("div"); ov.className = "form-overlay";
|
|
671
|
-
const memOpts = instances.map(i => `<label style="display:flex;gap:6px;align-items:center;font-size:13px;padding:3px 0"><input type="checkbox" value="${esc(i.name)}">${esc(i.name)}</label>`).join("");
|
|
672
|
-
ov.innerHTML = `<div class="form-card"><h2>Create Team</h2>
|
|
673
|
-
<div class="form-field"><label>Team Name *</label><input id="ct2-name" placeholder="team-name"></div>
|
|
674
|
-
<div class="form-field"><label>Description</label><input id="ct2-desc" placeholder="What this team does"></div>
|
|
675
|
-
<div class="form-field"><label>Members *</label><div id="ct2-members" style="max-height:150px;overflow-y:auto">${memOpts}</div></div>
|
|
676
|
-
<div class="form-actions"><button class="btn btn-ghost" onclick="this.closest('.form-overlay').remove()">Cancel</button>
|
|
677
|
-
<button class="btn btn-accent" id="ct2-go">Create</button></div></div>`;
|
|
678
|
-
document.body.appendChild(ov);
|
|
679
|
-
ov.addEventListener("click", e => { if (e.target === ov) ov.remove(); });
|
|
680
|
-
ov.querySelector("#ct2-go").addEventListener("click", async () => {
|
|
681
|
-
const name = document.getElementById("ct2-name").value.trim();
|
|
682
|
-
const members = [...document.querySelectorAll("#ct2-members input:checked")].map(c => c.value);
|
|
683
|
-
if (!name || !members.length) { toast(tr("teamFieldsRequired"), false); return; }
|
|
684
|
-
const btn = ov.querySelector("#ct2-go"); btn.disabled = true; btn.textContent = tr("creating");
|
|
685
|
-
const body = { name, members };
|
|
686
|
-
const desc = document.getElementById("ct2-desc").value.trim(); if (desc) body.description = desc;
|
|
687
|
-
const r = await api("POST", "/ui/teams", body);
|
|
688
|
-
if (r.error) { toast(r.error, false); btn.disabled = false; btn.textContent = tr("create"); }
|
|
689
|
-
else { toast(tr("teamCreated")); ov.remove(); loadTeams(); }
|
|
690
|
-
});
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
// ── Cron description ─────────────────────────────────────
|
|
694
|
-
const DAYS = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];
|
|
695
|
-
function cronDesc(expr) {
|
|
696
|
-
if (!expr) return "";
|
|
697
|
-
const p = expr.trim().split(/\s+/);
|
|
698
|
-
if (p.length < 5) return expr;
|
|
699
|
-
const [min, hr, dom, mon, dow] = p;
|
|
700
|
-
const time = (hr !== "*" && min !== "*") ? `at ${hr.padStart(2,"0")}:${min.padStart(2,"0")}` : "";
|
|
701
|
-
if (min === "*" && hr === "*") return "Every minute";
|
|
702
|
-
if (min.startsWith("*/")) return `Every ${min.slice(2)} minutes`;
|
|
703
|
-
if (hr.startsWith("*/")) return `Every ${hr.slice(2)} hours`;
|
|
704
|
-
if (dom === "*" && mon === "*" && dow === "*") return `Daily ${time}`;
|
|
705
|
-
if (dom === "*" && mon === "*" && dow === "1-5") return `Weekdays ${time}`;
|
|
706
|
-
if (dom === "*" && mon === "*" && dow !== "*") {
|
|
707
|
-
const days = dow.split(",").map(d => { const n = parseInt(d); return isNaN(n) ? d : (DAYS[n] || d); });
|
|
708
|
-
return `${days.join(", ")} ${time}`;
|
|
709
|
-
}
|
|
710
|
-
if (dom !== "*" && mon === "*") return `${dom}${dom==="1"?"st":dom==="2"?"nd":dom==="3"?"rd":"th"} of month ${time}`;
|
|
711
|
-
return expr;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
// ── Config form ──────────────────────────────────────────
|
|
715
|
-
let cfgData = null;
|
|
716
|
-
async function loadConfig() {
|
|
717
|
-
const el = document.getElementById("configView"); if (!el) return;
|
|
718
|
-
try {
|
|
719
|
-
cfgData = await api("GET", "/ui/config");
|
|
720
|
-
const ch = cfgData.channel || {};
|
|
721
|
-
const acc = ch.access || {};
|
|
722
|
-
const defs = cfgData.defaults || {};
|
|
723
|
-
const roots = cfgData.project_roots || [];
|
|
724
|
-
el.innerHTML = `
|
|
725
|
-
<div class="card"><h3>Channel</h3>
|
|
726
|
-
<div class="form-field"><label>Type</label><input id="cfg-type" value="${esc(ch.type||"")}" disabled style="opacity:0.5"></div>
|
|
727
|
-
<div class="form-field"><label>Bot Token Env</label><input id="cfg-token-env" value="${esc(ch.bot_token_env||"AGEND_BOT_TOKEN")}" disabled style="opacity:0.5"></div>
|
|
728
|
-
<div class="form-field"><label>Group ID</label>
|
|
729
|
-
<div style="display:flex;gap:4px;align-items:center">
|
|
730
|
-
<input id="cfg-gid" type="password" value="${ch.group_id||""}" style="flex:1">
|
|
731
|
-
<button class="eye-btn" onclick="togglePw('cfg-gid')">👁</button>
|
|
732
|
-
</div></div>
|
|
733
|
-
</div>
|
|
734
|
-
<div class="card"><h3>Access Control</h3>
|
|
735
|
-
<div class="form-field"><label>Mode</label>
|
|
736
|
-
<select id="cfg-acc-mode"><option value="locked" ${acc.mode==="locked"?"selected":""}>Locked</option><option value="open" ${acc.mode==="open"?"selected":""}>Open</option></select></div>
|
|
737
|
-
<div class="form-field"><label>Allowed Users</label>
|
|
738
|
-
<div style="display:flex;gap:4px;align-items:center">
|
|
739
|
-
<input id="cfg-users" type="password" value="${(acc.allowed_users||[]).join(", ")}" style="flex:1">
|
|
740
|
-
<button class="eye-btn" onclick="togglePw('cfg-users')">👁</button>
|
|
741
|
-
</div>
|
|
742
|
-
<div style="font-size:11px;color:var(--text-muted);margin-top:4px">Comma-separated user IDs</div></div>
|
|
743
|
-
</div>
|
|
744
|
-
<div class="card"><h3>Defaults</h3>
|
|
745
|
-
<div class="form-field"><label>Backend</label>
|
|
746
|
-
<select id="cfg-backend">
|
|
747
|
-
${[...(defs.backend && !["claude-code","codex","opencode","kiro-cli","antigravity","grok","muse"].includes(defs.backend) ? [defs.backend] : []),"claude-code","codex","opencode","kiro-cli","antigravity","grok","muse"].map(b => `<option value="${b}" ${defs.backend===b?"selected":""}>${b}</option>`).join("")}
|
|
748
|
-
</select></div>
|
|
749
|
-
</div>
|
|
750
|
-
<div class="card"><h3>Project Roots</h3>
|
|
751
|
-
<div id="cfg-roots">${roots.map((r,i) => `<div style="display:flex;gap:4px;margin-bottom:6px"><input class="cfg-root" value="${esc(r)}" style="flex:1"><button class="btn btn-ghost" style="padding:4px 8px" onclick="this.parentElement.remove()">✕</button></div>`).join("")}</div>
|
|
752
|
-
<button class="btn btn-ghost" onclick="addRoot()">+ Add Root</button>
|
|
753
|
-
</div>
|
|
754
|
-
<div style="display:flex;gap:10px;align-items:center;margin-top:16px">
|
|
755
|
-
<button class="btn btn-accent" onclick="saveConfig()">Save Changes</button>
|
|
756
|
-
<span style="font-size:11px;color:var(--text-muted)">Saving will reformat fleet.yaml (comments will be lost)</span>
|
|
757
|
-
</div>`;
|
|
758
|
-
} catch { el.innerHTML = `<div style="color:var(--error);padding:24px">${tr("configLoadFailed")}</div>`; }
|
|
759
|
-
}
|
|
760
|
-
function togglePw(id) { const el = document.getElementById(id); el.type = el.type === "password" ? "text" : "password"; }
|
|
761
|
-
function addRoot() { const el = document.getElementById("cfg-roots"); el.insertAdjacentHTML("beforeend", `<div style="display:flex;gap:4px;margin-bottom:6px"><input class="cfg-root" value="" placeholder="/path/to/projects" style="flex:1"><button class="btn btn-ghost" style="padding:4px 8px" onclick="this.parentElement.remove()">✕</button></div>`); }
|
|
762
|
-
async function saveConfig() {
|
|
763
|
-
const body = {
|
|
764
|
-
channel: { group_id: document.getElementById("cfg-gid").value.trim(), access: { mode: document.getElementById("cfg-acc-mode").value, allowed_users: document.getElementById("cfg-users").value.split(",").map(s => s.trim()).filter(Boolean) } },
|
|
765
|
-
defaults: { backend: document.getElementById("cfg-backend").value },
|
|
766
|
-
project_roots: [...document.querySelectorAll(".cfg-root")].map(i => i.value.trim()).filter(Boolean),
|
|
767
|
-
};
|
|
768
|
-
const r = await api("POST", "/ui/config", body);
|
|
769
|
-
if (r.error) toast(r.error, false);
|
|
770
|
-
else { toast(tr("configSaved") + (r.needs_restart ? tr("configRestart") : "")); }
|
|
771
|
-
}
|
|
772
|
-
</script>
|
|
773
|
-
</body>
|
|
774
|
-
</html>
|