@songsid/agend 2.1.12 → 2.2.0-alpha.2
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 +4 -2
- package/README.zh-TW.md +4 -2
- package/dist/agent-cli.js +6 -1
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.js +68 -12
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-token-header.d.ts +13 -0
- package/dist/agent-token-header.js +16 -0
- package/dist/agent-token-header.js.map +1 -0
- 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/antigravity.js +4 -3
- package/dist/backend/antigravity.js.map +1 -1
- 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/grok.js +1 -1
- package/dist/backend/grok.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 +90 -7
- package/dist/backend/kiro.js +500 -124
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/muse.js +1 -1
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +51 -3
- package/dist/backend/types.js.map +1 -1
- package/dist/cache-analysis.d.ts +92 -0
- package/dist/cache-analysis.js +147 -0
- package/dist/cache-analysis.js.map +1 -0
- package/dist/cache-ledger.d.ts +108 -0
- package/dist/cache-ledger.js +230 -0
- package/dist/cache-ledger.js.map +1 -0
- package/dist/cache-prices.d.ts +32 -0
- package/dist/cache-prices.js +73 -0
- package/dist/cache-prices.js.map +1 -0
- package/dist/cache-scan.d.ts +39 -0
- package/dist/cache-scan.js +115 -0
- package/dist/cache-scan.js.map +1 -0
- package/dist/cache-service.d.ts +115 -0
- package/dist/cache-service.js +417 -0
- package/dist/cache-service.js.map +1 -0
- 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-entry.d.ts +19 -0
- package/dist/cli-entry.js +36 -0
- package/dist/cli-entry.js.map +1 -0
- package/dist/cli.js +471 -146
- 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 +95 -4
- package/dist/daemon.js +748 -111
- 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-log.d.ts +4 -1
- package/dist/event-log.js +6 -3
- package/dist/event-log.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-control-audit.d.ts +5 -0
- package/dist/fleet-control-audit.js +7 -0
- package/dist/fleet-control-audit.js.map +1 -1
- package/dist/fleet-level-config.d.ts +2 -2
- package/dist/fleet-level-config.js +28 -1
- package/dist/fleet-level-config.js.map +1 -1
- package/dist/fleet-manager.d.ts +339 -12
- package/dist/fleet-manager.js +2623 -461
- package/dist/fleet-manager.js.map +1 -1
- package/dist/full-restart.js +2 -3
- package/dist/full-restart.js.map +1 -1
- package/dist/general-knowledge/skills/persona-emoji/SKILL.md +3 -1
- package/dist/host-memory.d.ts +4 -0
- package/dist/host-memory.js.map +1 -1
- package/dist/install-lock.d.ts +49 -0
- package/dist/install-lock.js +200 -0
- package/dist/install-lock.js.map +1 -0
- package/dist/instance-config-impact.js +8 -2
- 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 +164 -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/outbound-schemas.js +1 -1
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/package-preimage.d.ts +26 -0
- package/dist/package-preimage.js +96 -0
- package/dist/package-preimage.js.map +1 -0
- 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 +16 -6
- package/dist/quickstart.js.map +1 -1
- package/dist/restart-guard.d.ts +52 -0
- package/dist/restart-guard.js +109 -0
- package/dist/restart-guard.js.map +1 -0
- package/dist/scheduler/db.d.ts +7 -1
- package/dist/scheduler/db.js +44 -2
- 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 +152 -0
- package/dist/service-activation.js +407 -0
- package/dist/service-activation.js.map +1 -0
- package/dist/service-installer.d.ts +63 -1
- package/dist/service-installer.js +141 -16
- package/dist/service-installer.js.map +1 -1
- package/dist/service-plan.d.ts +65 -0
- package/dist/service-plan.js +108 -0
- package/dist/service-plan.js.map +1 -0
- 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 +216 -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 +161 -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 +199 -35
- package/dist/setup-host.js.map +1 -1
- package/dist/setup-wizard.js +5 -1
- 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/sqlite-open-errors.d.ts +12 -0
- package/dist/sqlite-open-errors.js +13 -0
- package/dist/sqlite-open-errors.js.map +1 -0
- 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 +57 -5
- package/dist/tmux-control.js +379 -61
- 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 +205 -63
- 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-commands.js +43 -0
- package/dist/ui/chat-render.js +478 -0
- package/dist/ui/chat-store.js +318 -0
- package/dist/ui/chat-strings.js +89 -0
- package/dist/ui/chat-thread.js +246 -0
- package/dist/ui/chat-tour.js +116 -0
- package/dist/ui/fleet-cache.js +168 -0
- package/dist/ui/fleet-org.js +142 -0
- package/dist/ui/panel-chat.js +504 -0
- package/dist/ui/panel-fleet.js +427 -0
- package/dist/ui/panel-needs.js +202 -0
- package/dist/ui/panel-settings.js +754 -0
- package/dist/ui/preview-panel.js +202 -0
- package/dist/ui/preview.js +270 -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 +654 -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 +346 -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 +820 -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 +142 -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 +63 -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 +52 -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-dispatch.d.ts +28 -0
- package/dist/update-dispatch.js +73 -0
- package/dist/update-dispatch.js.map +1 -0
- package/dist/update-install.d.ts +129 -0
- package/dist/update-install.js +262 -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 +85 -14
- package/dist/web-api.js +557 -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-commands.d.ts +77 -0
- package/dist/web-commands.js +81 -0
- package/dist/web-commands.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/launcher/agend +110 -0
- package/launcher/agend-agent +110 -0
- package/launcher/agend-agent.cjs +4 -0
- package/launcher/agend.cjs +4 -0
- package/launcher/install-admission.cjs +105 -0
- package/launcher/launch.cjs +64 -0
- package/launcher/postinstall.cjs +114 -0
- package/launcher/run-in-process.cjs +13 -0
- package/launcher/runtime-platform.cjs +86 -0
- package/launcher/runtime-select.cjs +217 -0
- package/package.json +18 -7
- package/scripts/preinstall-guard.cjs +55 -0
- package/templates/launchd.plist.ejs +2 -2
- package/templates/systemd.service.ejs +1 -1
- package/dist/ui/dashboard.html +0 -774
- package/dist/ui/settings.html +0 -2165
- package/dist/ui/view.html +0 -1139
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
// #1408 step 1: the Fleet panel (/ui/fleet[/<tab>]) — tasks, schedules, teams and the fleet's config — and the
|
|
2
|
+
// "New instance" dialog. Behind the session gate (/ui/js/), loaded the first time it is opened.
|
|
3
|
+
//
|
|
4
|
+
// Opening a tab reads its list: a person's navigation, so it counts as use, as the old tab click did (#1374 — no timer
|
|
5
|
+
// here re-reads anything). Every read is under the navigation's lease: a list that arrives after the person moved on
|
|
6
|
+
// is dropped (#1408 §4).
|
|
7
|
+
import { html, useEffect, useRef, useState } from "/assets/app-html.js";
|
|
8
|
+
import { t, register } from "/assets/app-i18n.js";
|
|
9
|
+
import { appStore, useStore } from "/assets/app-store.js";
|
|
10
|
+
import { useLease } from "/assets/app-ctx.js";
|
|
11
|
+
import { PanelHeader, setTitle } from "/assets/app-shell.js";
|
|
12
|
+
import { renavigate } from "/assets/app-nav.js";
|
|
13
|
+
import { chatPath, fleetPath, FLEET_TABS } from "/assets/app-route.js";
|
|
14
|
+
import { Dialog } from "/assets/ui-dialog.js";
|
|
15
|
+
import { Empty, ErrorState, Skeleton } from "/assets/ui-states.js";
|
|
16
|
+
import { Icon } from "/assets/ui-icons.js";
|
|
17
|
+
import { toast } from "/assets/ui-toast.js";
|
|
18
|
+
import { confirmDialog } from "/assets/ui-confirm.js";
|
|
19
|
+
import { confirmedWrite } from "./settings-confirm.js";
|
|
20
|
+
import { OrgChart } from "./fleet-org.js";
|
|
21
|
+
import { CacheReport } from "./fleet-cache.js";
|
|
22
|
+
|
|
23
|
+
register("fleet", {
|
|
24
|
+
en: {
|
|
25
|
+
title: "Fleet", tasks: "Tasks", schedules: "Schedules", teams: "Teams", org: "Org chart", cache: "Cache", config: "Config",
|
|
26
|
+
newTask: "New task", newSchedule: "New schedule", newTeam: "New team",
|
|
27
|
+
noTasks: "No tasks yet", noTasksHint: "Create one to coordinate fleet work.", noSchedules: "No schedules yet", noSchedulesHint: "Automate recurring work with cron.",
|
|
28
|
+
noTeams: "No teams yet", noTeamsHint: "Group instances for coordinated work.", countTasks: "{0} tasks", countSchedules: "{0} schedules", countTeams: "{0} teams", countTask: "1 task", countSchedule: "1 schedule", countTeam: "1 team",
|
|
29
|
+
claim: "Claim", done: "Done", delete: "Delete", on: "on", off: "off", cancel: "Cancel", create: "Create", creating: "Creating…", save: "Save changes",
|
|
30
|
+
taskTitle: "Title", taskDesc: "Description", priority: "Priority", assignee: "Assignee", unassigned: "Unassigned",
|
|
31
|
+
pNormal: "Normal", pLow: "Low", pHigh: "High", pUrgent: "Urgent",
|
|
32
|
+
cron: "Cron expression", message: "Message", target: "Target instance", label: "Label", teamName: "Team name", members: "Members",
|
|
33
|
+
titleRequired: "Title required", scheduleFieldsRequired: "Cron, message, and target required", teamFieldsRequired: "Name and at least one member required",
|
|
34
|
+
deleteSchedule: "Delete this schedule?", deleteTeam: "Delete team “{0}”?", taskCreated: "Task created", scheduleCreated: "Schedule created", scheduleDeleted: "Schedule deleted",
|
|
35
|
+
teamCreated: "Team created", teamDeleted: "Team deleted", taskDone: "Task updated", configSaved: "Config saved", configRestart: " (restart the fleet to apply channel changes)",
|
|
36
|
+
channel: "Channel", type: "Type", tokenEnv: "Bot token env", groupId: "Group ID", show: "Show", hide: "Hide", access: "Access control", mode: "Mode", locked: "Locked", open: "Open",
|
|
37
|
+
allowedUsers: "Allowed users", allowedUsersNote: "Comma-separated user IDs", defaults: "Defaults", backend: "Backend", roots: "Project roots", addRoot: "Add root", removeRoot: "Remove",
|
|
38
|
+
saveNote: "Saving rewrites fleet.yaml (its comments are lost).",
|
|
39
|
+
newInstance: "New instance", directory: "Directory", directoryHint: "Optional — a workspace is created if empty", topic: "Topic name", topicHint: "From the directory name",
|
|
40
|
+
topicRequired: "Topic name is required when Directory is empty", description: "Description", descriptionHint: "What this instance does", model: "Model", modelHint: "e.g. sonnet, opus, gpt-5",
|
|
41
|
+
branch: "Branch (git worktree)", branchHint: "e.g. feature-x", tags: "Tags", tagsHint: "comma-separated, e.g. dev, review", systemPrompt: "System prompt", systemPromptHint: "Custom instructions for this instance",
|
|
42
|
+
fleetDefault: "Use the fleet default", notInstalled: "(not installed)", deprecated: "(deprecated)", instanceCreated: "Instance created",
|
|
43
|
+
},
|
|
44
|
+
"zh-TW": {
|
|
45
|
+
title: "Fleet", tasks: "Tasks", schedules: "排程", teams: "Teams", org: "組織圖", cache: "快取", config: "設定",
|
|
46
|
+
newTask: "新增 task", newSchedule: "新增排程", newTeam: "新增 team",
|
|
47
|
+
noTasks: "還沒有 task", noTasksHint: "建立一個來協調 fleet 的工作。", noSchedules: "還沒有排程", noSchedulesHint: "用 cron 自動執行例行工作。",
|
|
48
|
+
noTeams: "還沒有 team", noTeamsHint: "把 instance 分組一起工作。", countTasks: "{0} 個 task", countSchedules: "{0} 個排程", countTeams: "{0} 個 team", countTask: "1 個 task", countSchedule: "1 個排程", countTeam: "1 個 team",
|
|
49
|
+
claim: "認領", done: "完成", delete: "刪除", on: "開", off: "關", cancel: "取消", create: "建立", creating: "建立中…", save: "儲存變更",
|
|
50
|
+
taskTitle: "標題", taskDesc: "說明", priority: "優先順序", assignee: "負責人", unassigned: "未指派",
|
|
51
|
+
pNormal: "一般", pLow: "低", pHigh: "高", pUrgent: "緊急",
|
|
52
|
+
cron: "Cron 表示式", message: "訊息", target: "目標 instance", label: "標籤", teamName: "Team 名稱", members: "成員",
|
|
53
|
+
titleRequired: "必須填寫標題", scheduleFieldsRequired: "必須填寫 Cron、訊息與目標 instance", teamFieldsRequired: "必須填寫名稱並選擇至少一名成員",
|
|
54
|
+
deleteSchedule: "確定刪除此排程嗎?", deleteTeam: "確定刪除 team「{0}」嗎?", taskCreated: "Task 已建立", scheduleCreated: "排程已建立", scheduleDeleted: "排程已刪除",
|
|
55
|
+
teamCreated: "Team 已建立", teamDeleted: "Team 已刪除", taskDone: "Task 已更新", configSaved: "設定已儲存", configRestart: "(請重新啟動 fleet 以套用頻道變更)",
|
|
56
|
+
channel: "頻道", type: "類型", tokenEnv: "Bot token 環境變數", groupId: "Group ID", show: "顯示", hide: "隱藏", access: "存取控制", mode: "模式", locked: "鎖定", open: "開放",
|
|
57
|
+
allowedUsers: "允許的使用者", allowedUsersNote: "以逗號分隔的使用者 ID", defaults: "預設值", backend: "Backend", roots: "專案根目錄", addRoot: "新增根目錄", removeRoot: "移除",
|
|
58
|
+
saveNote: "儲存會改寫 fleet.yaml(其中的註解會遺失)。",
|
|
59
|
+
newInstance: "新增 instance", directory: "目錄", directoryHint: "可留空——留空會自動建立工作區", topic: "Topic 名稱", topicHint: "預設取自目錄名稱",
|
|
60
|
+
topicRequired: "目錄留空時必須填寫 Topic 名稱", description: "說明", descriptionHint: "這個 instance 做什麼", model: "模型", modelHint: "例如 sonnet、opus、gpt-5",
|
|
61
|
+
branch: "分支(git worktree)", branchHint: "例如 feature-x", tags: "標籤", tagsHint: "以逗號分隔,例如 dev, review", systemPrompt: "System prompt", systemPromptHint: "這個 instance 的自訂指示",
|
|
62
|
+
fleetDefault: "使用 fleet 預設", notInstalled: "(未安裝)", deprecated: "(已淘汰)", instanceCreated: "Instance 已建立",
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const ICONS = { tasks: "tasks", schedules: "clock", teams: "team", org: "org", cache: "chart", config: "sliders" };
|
|
67
|
+
const BACKENDS = ["claude-code", "codex", "opencode", "kiro-cli", "antigravity", "grok", "muse"];
|
|
68
|
+
|
|
69
|
+
async function api(method, path, body, lease) {
|
|
70
|
+
const o = { method, headers: { "Content-Type": "application/json" } };
|
|
71
|
+
if (body) o.body = JSON.stringify(body);
|
|
72
|
+
const r = await (lease ? lease.fetch(path, o) : fetch(path, o));
|
|
73
|
+
return r.json();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Load `path` under the lease: `null` while the first load runs, `{ error }` on failure (no answer, or an HTTP error). A refresh (a new `version`,
|
|
78
|
+
* after Claim or Create) keeps showing what is there until the new list arrives — nothing below it is torn down, so
|
|
79
|
+
* an open dialog and its draft survive (#1425 review). Only the latest request's answer is kept.
|
|
80
|
+
*/
|
|
81
|
+
function useLoad(lease, path, version) {
|
|
82
|
+
const [state, setState] = useState(null);
|
|
83
|
+
const seq = useRef(0);
|
|
84
|
+
const shownFor = useRef(null);
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
const mine = ++seq.current;
|
|
87
|
+
if (shownFor.current !== lease) { shownFor.current = lease; setState(null); }
|
|
88
|
+
(async () => {
|
|
89
|
+
try {
|
|
90
|
+
// A read the server refused (a 4xx/5xx with an { error } body) is a failure, not an empty list (alpha.2 sweep).
|
|
91
|
+
const r = await lease.fetch(path, { method: "GET", headers: { "Content-Type": "application/json" } });
|
|
92
|
+
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
|
93
|
+
const d = await r.json();
|
|
94
|
+
if (lease.current() && mine === seq.current) setState({ data: d });
|
|
95
|
+
} catch { if (lease.current() && mine === seq.current) setState({ error: true }); }
|
|
96
|
+
})();
|
|
97
|
+
}, [lease, path, version]);
|
|
98
|
+
return state;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function FleetPanel({ route, navKey }) {
|
|
102
|
+
const lease = useLease(navKey);
|
|
103
|
+
const tab = route.tab;
|
|
104
|
+
useEffect(() => { setTitle(`${t("fleet.title")} · ${t(`fleet.${tab}`)}`); }, [tab, navKey]);
|
|
105
|
+
const Body = { tasks: Tasks, schedules: Schedules, teams: Teams, org: Org, cache: Cache, config: Config }[tab] || Tasks;
|
|
106
|
+
return html`<div class="panel p-fleet">
|
|
107
|
+
<${PanelHeader} title=${t("fleet.title")} />
|
|
108
|
+
<nav class="seg" aria-label=${t("fleet.title")}>${FLEET_TABS.map(k => html`<a key=${k} href=${fleetPath(k)} class=${`seg-item${k === tab ? " active" : ""}`}
|
|
109
|
+
aria-current=${k === tab ? "page" : undefined}><${Icon} name=${ICONS[k]} size=${16} /><span>${t(`fleet.${k}`)}</span></a>`)}</nav>
|
|
110
|
+
<div class="panel-body"><div class=${`col${tab === "org" || tab === "cache" ? " col-wide" : ""}`}><${Body} lease=${lease} /></div></div>
|
|
111
|
+
</div>`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function ListHead({ count, label, action }) {
|
|
115
|
+
return html`<div class="list-head"><span>${count}</span>${action ? html`<button type="button" class="btn btn-primary" onClick=${action.onClick}><${Icon} name="plus" size=${16} />${label}</button>` : null}</div>`;
|
|
116
|
+
}
|
|
117
|
+
function Loaded({ state, children }) {
|
|
118
|
+
if (!state) return html`<${Skeleton} lines=${4} />`;
|
|
119
|
+
if (state.error) return html`<${ErrorState} onRetry=${renavigate} />`;
|
|
120
|
+
return children(state.data);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function Tasks({ lease }) {
|
|
124
|
+
const [version, setVersion] = useState(0);
|
|
125
|
+
const [creating, setCreating] = useState(false);
|
|
126
|
+
const state = useLoad(lease, "/ui/tasks", version);
|
|
127
|
+
async function act(id, action) {
|
|
128
|
+
const r = await api("POST", `/ui/tasks/${encodeURIComponent(id)}`, { action });
|
|
129
|
+
if (r.error) toast(r.error, false); else { toast(t("fleet.taskDone")); setVersion(v => v + 1); }
|
|
130
|
+
}
|
|
131
|
+
return html`<${Loaded} state=${state}>${(d) => {
|
|
132
|
+
const tasks = d.tasks || [];
|
|
133
|
+
return html`<${ListHead} count=${tasks.length === 1 ? t("fleet.countTask") : t("fleet.countTasks", tasks.length)} label=${t("fleet.newTask")} action=${{ onClick: () => setCreating(true) }} />
|
|
134
|
+
${tasks.length ? html`<ul class="rows">${tasks.map(x => html`<li key=${x.id} class="row-item">
|
|
135
|
+
<span class=${`pill ${x.status}`}>${x.status}</span><span class="grow">${x.title}</span>
|
|
136
|
+
${x.assignee ? html`<a class="link" href=${chatPath(x.assignee)}>${x.assignee}</a>` : null}
|
|
137
|
+
${x.status === "open" ? html`<button type="button" class="btn btn-ghost btn-sm" onClick=${() => act(x.id, "claim")}>${t("fleet.claim")}</button>` : null}
|
|
138
|
+
${x.status === "claimed" ? html`<button type="button" class="btn btn-ghost btn-sm" onClick=${() => act(x.id, "complete")}>${t("fleet.done")}</button>` : null}
|
|
139
|
+
</li>`)}</ul>` : html`<${Empty} icon="tasks" title=${t("fleet.noTasks")} hint=${t("fleet.noTasksHint")} />`}
|
|
140
|
+
`;
|
|
141
|
+
}}</${Loaded}>
|
|
142
|
+
${creating ? html`<${CreateTask} onClose=${() => setCreating(false)} onDone=${() => setVersion(v => v + 1)} />` : null}`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function Schedules({ lease }) {
|
|
146
|
+
const [version, setVersion] = useState(0);
|
|
147
|
+
const [creating, setCreating] = useState(false);
|
|
148
|
+
const state = useLoad(lease, "/ui/schedules", version);
|
|
149
|
+
async function del(id) {
|
|
150
|
+
if (!(await confirmDialog({ message: t("fleet.deleteSchedule"), confirmLabel: t("fleet.delete"), danger: true }))) return;
|
|
151
|
+
if (!lease.current()) return; // answered after this tab went: the question no longer stands
|
|
152
|
+
const r = await api("DELETE", `/ui/schedules/${encodeURIComponent(id)}`);
|
|
153
|
+
if (r.error) toast(r.error, false); else { toast(t("fleet.scheduleDeleted")); setVersion(v => v + 1); }
|
|
154
|
+
}
|
|
155
|
+
return html`<${Loaded} state=${state}>${(d) => {
|
|
156
|
+
const list = d.schedules || [];
|
|
157
|
+
return html`<${ListHead} count=${list.length === 1 ? t("fleet.countSchedule") : t("fleet.countSchedules", list.length)} label=${t("fleet.newSchedule")} action=${{ onClick: () => setCreating(true) }} />
|
|
158
|
+
${list.length ? html`<ul class="rows">${list.map(x => html`<li key=${x.id} class="row-item">
|
|
159
|
+
<span class=${`pill ${x.enabled ? "open" : "done"}`}>${x.enabled ? t("fleet.on") : t("fleet.off")}</span>
|
|
160
|
+
<span class="grow">${x.label || cronDesc(x.cron)}</span><span class="mono muted" title=${cronDesc(x.cron)}>${x.cron}</span>
|
|
161
|
+
<a class="link" href=${chatPath(x.target)}>${x.target}</a>
|
|
162
|
+
<button type="button" class="btn btn-ghost btn-sm danger" onClick=${() => del(x.id)}>${t("fleet.delete")}</button></li>`)}</ul>`
|
|
163
|
+
: html`<${Empty} icon="clock" title=${t("fleet.noSchedules")} hint=${t("fleet.noSchedulesHint")} />`}
|
|
164
|
+
`;
|
|
165
|
+
}}</${Loaded}>
|
|
166
|
+
${creating ? html`<${CreateSchedule} onClose=${() => setCreating(false)} onDone=${() => setVersion(v => v + 1)} />` : null}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function Teams({ lease }) {
|
|
170
|
+
const [version, setVersion] = useState(0);
|
|
171
|
+
const [creating, setCreating] = useState(false);
|
|
172
|
+
const state = useLoad(lease, "/ui/teams", version);
|
|
173
|
+
async function del(name) {
|
|
174
|
+
if (!(await confirmDialog({ message: t("fleet.deleteTeam", name), confirmLabel: t("fleet.delete"), danger: true }))) return;
|
|
175
|
+
if (!lease.current()) return;
|
|
176
|
+
const r = await api("DELETE", `/ui/teams/${encodeURIComponent(name)}`);
|
|
177
|
+
if (r.error) toast(r.error, false); else { toast(t("fleet.teamDeleted")); setVersion(v => v + 1); }
|
|
178
|
+
}
|
|
179
|
+
return html`<${Loaded} state=${state}>${(d) => {
|
|
180
|
+
const entries = Object.entries(d.teams || {});
|
|
181
|
+
return html`<${ListHead} count=${entries.length === 1 ? t("fleet.countTeam") : t("fleet.countTeams", entries.length)} label=${t("fleet.newTeam")} action=${{ onClick: () => setCreating(true) }} />
|
|
182
|
+
${entries.length ? html`<ul class="rows">${entries.map(([name, team]) => html`<li key=${name} class="row-item">
|
|
183
|
+
<span class="grow strong">${name}</span><span class="muted">${(team.members || []).join(", ")}</span>
|
|
184
|
+
<button type="button" class="btn btn-ghost btn-sm danger" onClick=${() => del(name)}>${t("fleet.delete")}</button></li>`)}</ul>`
|
|
185
|
+
: html`<${Empty} icon="team" title=${t("fleet.noTeams")} hint=${t("fleet.noTeamsHint")} />`}
|
|
186
|
+
`;
|
|
187
|
+
}}</${Loaded}>
|
|
188
|
+
${creating ? html`<${CreateTeam} onClose=${() => setCreating(false)} onDone=${() => setVersion(v => v + 1)} />` : null}`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** #1389: the org chart — its structure read once per opening, its live state from the app store (fleet-org.js). */
|
|
192
|
+
function Org({ lease }) {
|
|
193
|
+
const state = useLoad(lease, "/ui/org", 0);
|
|
194
|
+
const app = useStore(appStore);
|
|
195
|
+
return html`<${Loaded} state=${state}>${(org) => html`<${OrgChart} org=${org} app=${app} />`}</${Loaded}>`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** #1468: the prompt-cache analysis — one read per opening and per window chosen; Refresh reads again (fleet-cache.js). */
|
|
199
|
+
function Cache({ lease }) {
|
|
200
|
+
const [win, setWin] = useState("7d");
|
|
201
|
+
const [version, setVersion] = useState(0);
|
|
202
|
+
const state = useLoad(lease, `/ui/cache?window=${win}`, version);
|
|
203
|
+
return html`<${Loaded} state=${state}>${(report) => (report && Array.isArray(report.instances)
|
|
204
|
+
? html`<${CacheReport} report=${report} win=${win} setWin=${setWin} refresh=${() => setVersion((v) => v + 1)} />`
|
|
205
|
+
: html`<${ErrorState} onRetry=${renavigate} />`)}</${Loaded}>`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function Secret({ id, value, onInput, label }) {
|
|
209
|
+
const [shown, setShown] = useState(false);
|
|
210
|
+
return html`<div class="field-row"><input id=${id} type=${shown ? "text" : "password"} value=${value} onInput=${onInput} autocomplete="off" />
|
|
211
|
+
<button type="button" class="icon-btn" aria-label=${shown ? t("fleet.hide") : t("fleet.show")} aria-pressed=${shown ? "true" : "false"} title=${`${shown ? t("fleet.hide") : t("fleet.show")} ${label}`}
|
|
212
|
+
onClick=${() => setShown(!shown)}><${Icon} name="eye" size=${16} /></button></div>`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function Config({ lease }) {
|
|
216
|
+
const state = useLoad(lease, "/ui/config", 0);
|
|
217
|
+
const [form, setForm] = useState(null);
|
|
218
|
+
const [saving, setSaving] = useState(false);
|
|
219
|
+
// Each save owns the form's busy state only while it is the latest: an older save that is decided later (it waited
|
|
220
|
+
// for an admin's confirmation) never unlocks the form under a newer one still on its way (#1453 review).
|
|
221
|
+
const saves = useRef(0);
|
|
222
|
+
useEffect(() => {
|
|
223
|
+
if (!state || !state.data) return;
|
|
224
|
+
const c = state.data, ch = c.channel || {}, acc = ch.access || {};
|
|
225
|
+
setForm({ type: ch.type || "", tokenEnv: ch.bot_token_env || "AGEND_BOT_TOKEN", groupId: ch.group_id || "", mode: acc.mode || "locked",
|
|
226
|
+
users: (acc.allowed_users || []).join(", "), backend: (c.defaults || {}).backend || "claude-code", roots: [...(c.project_roots || [])] });
|
|
227
|
+
}, [state]);
|
|
228
|
+
async function save() {
|
|
229
|
+
const mine = ++saves.current;
|
|
230
|
+
const done = () => { if (lease.current() && mine === saves.current) setSaving(false); };
|
|
231
|
+
setSaving(true);
|
|
232
|
+
const body = {
|
|
233
|
+
channel: { group_id: form.groupId.trim(), access: { mode: form.mode, allowed_users: form.users.split(",").map(s => s.trim()).filter(Boolean) } },
|
|
234
|
+
defaults: { backend: form.backend },
|
|
235
|
+
project_roots: form.roots.map(r => r.trim()).filter(Boolean),
|
|
236
|
+
};
|
|
237
|
+
let r;
|
|
238
|
+
// A change to access or the connection may need a fleet admin's confirmation (#1423): the shell follows it.
|
|
239
|
+
try {
|
|
240
|
+
const res = await confirmedWrite("/ui/config", { method: "POST", body, label: t("fleet.config"),
|
|
241
|
+
onPending: () => { toast(t("app.pendingSent")); done(); } });
|
|
242
|
+
r = res.ok ? res.body || {} : { error: (res.body && res.body.error) || `HTTP ${res.status}` };
|
|
243
|
+
} catch (err) { r = { error: err.message }; }
|
|
244
|
+
done();
|
|
245
|
+
if (r.error) toast(r.error, false); else toast(t("fleet.configSaved") + (r.needs_restart ? t("fleet.configRestart") : ""));
|
|
246
|
+
}
|
|
247
|
+
const set = (k) => (e) => setForm({ ...form, [k]: e.target.value });
|
|
248
|
+
return html`<${Loaded} state=${state}>${() => !form ? html`<${Skeleton} lines=${4} />` : html`
|
|
249
|
+
<section class="card"><h3>${t("fleet.channel")}</h3>
|
|
250
|
+
<label class="field"><span>${t("fleet.type")}</span><input value=${form.type} disabled /></label>
|
|
251
|
+
<label class="field"><span>${t("fleet.tokenEnv")}</span><input value=${form.tokenEnv} disabled /></label>
|
|
252
|
+
<div class="field"><label for="cfg-gid">${t("fleet.groupId")}</label><${Secret} id="cfg-gid" value=${form.groupId} onInput=${set("groupId")} label=${t("fleet.groupId")} /></div></section>
|
|
253
|
+
<section class="card"><h3>${t("fleet.access")}</h3>
|
|
254
|
+
<label class="field"><span>${t("fleet.mode")}</span><select value=${form.mode} onChange=${set("mode")}><option value="locked">${t("fleet.locked")}</option><option value="open">${t("fleet.open")}</option></select></label>
|
|
255
|
+
<div class="field"><label for="cfg-users">${t("fleet.allowedUsers")}</label><${Secret} id="cfg-users" value=${form.users} onInput=${set("users")} label=${t("fleet.allowedUsers")} />
|
|
256
|
+
<p class="note">${t("fleet.allowedUsersNote")}</p></div></section>
|
|
257
|
+
<section class="card"><h3>${t("fleet.defaults")}</h3>
|
|
258
|
+
<label class="field"><span>${t("fleet.backend")}</span><select value=${form.backend} onChange=${set("backend")}>
|
|
259
|
+
${[...(BACKENDS.includes(form.backend) ? [] : [form.backend]), ...BACKENDS].map(b => html`<option key=${b} value=${b}>${b}</option>`)}</select></label></section>
|
|
260
|
+
<section class="card"><h3>${t("fleet.roots")}</h3>
|
|
261
|
+
${form.roots.map((r, i) => html`<div key=${i} class="field-row"><input value=${r} placeholder="/path/to/projects" aria-label=${t("fleet.roots")}
|
|
262
|
+
onInput=${(e) => { const roots = [...form.roots]; roots[i] = e.target.value; setForm({ ...form, roots }); }} />
|
|
263
|
+
<button type="button" class="icon-btn" aria-label=${t("fleet.removeRoot")} title=${t("fleet.removeRoot")} onClick=${() => setForm({ ...form, roots: form.roots.filter((_, j) => j !== i) })}><${Icon} name="close" size=${16} /></button></div>`)}
|
|
264
|
+
<button type="button" class="btn btn-ghost" onClick=${() => setForm({ ...form, roots: [...form.roots, ""] })}><${Icon} name="plus" size=${16} />${t("fleet.addRoot")}</button></section>
|
|
265
|
+
<div class="save-row"><button type="button" class="btn btn-primary" disabled=${saving} onClick=${save}>${t("fleet.save")}</button><span class="note">${t("fleet.saveNote")}</span></div>`}</${Loaded}>`;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ── Create dialogs ──
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* One form dialog: fields, Cancel / Create, the request, a toast. Its own lease ends when the dialog goes: a request
|
|
272
|
+
* that finishes after that still reports its toast (it may have created something), but never closes or refreshes
|
|
273
|
+
* whatever is on screen now (#1425 review). `ready` false keeps Create off until the form has what it needs.
|
|
274
|
+
*/
|
|
275
|
+
export function FormDialog({ title, onClose, submit, children, ready = true }) {
|
|
276
|
+
const lease = useLease("form-dialog");
|
|
277
|
+
const [busy, setBusy] = useState(false);
|
|
278
|
+
async function go() {
|
|
279
|
+
if (busy || !ready) return;
|
|
280
|
+
const body = submit.collect();
|
|
281
|
+
if (!body) return;
|
|
282
|
+
setBusy(true);
|
|
283
|
+
let r, handedOver = false;
|
|
284
|
+
// A write that needs a fleet admin's confirmation (#1423; e.g. a new instance with control-bearing settings):
|
|
285
|
+
// the dialog goes at once and the shell follows the request. From then on the dialog is done: the decision is
|
|
286
|
+
// said in a toast, and nothing here closes, refreshes or acts again.
|
|
287
|
+
try {
|
|
288
|
+
const res = await confirmedWrite(submit.path, { method: "POST", body, label: title,
|
|
289
|
+
onPending: () => { handedOver = true; toast(t("app.pendingSent")); if (lease.current()) { setBusy(false); onClose(); } } });
|
|
290
|
+
r = res.ok ? res.body || {} : { error: (res.body && res.body.error) || `HTTP ${res.status}` };
|
|
291
|
+
} catch (err) { r = { error: err.message }; }
|
|
292
|
+
if (r.error) { toast(r.error, false); if (!handedOver && lease.current()) setBusy(false); return; }
|
|
293
|
+
toast(submit.done);
|
|
294
|
+
if (handedOver || !lease.current()) return;
|
|
295
|
+
setBusy(false);
|
|
296
|
+
onClose();
|
|
297
|
+
if (submit.after) submit.after();
|
|
298
|
+
}
|
|
299
|
+
return html`<${Dialog} title=${title} onClose=${onClose} busy=${busy}
|
|
300
|
+
actions=${html`<button type="button" class="btn" onClick=${onClose} disabled=${busy}>${t("fleet.cancel")}</button>
|
|
301
|
+
<button type="button" class="btn btn-primary" onClick=${go} disabled=${busy || !ready}>${busy ? t("fleet.creating") : t("fleet.create")}</button>`}>
|
|
302
|
+
<form class="form" onSubmit=${(e) => { e.preventDefault(); go(); }}><fieldset class="form" disabled=${busy}>${children}</fieldset></form></${Dialog}>`;
|
|
303
|
+
}
|
|
304
|
+
const field = (label, input) => html`<label class="field"><span>${label}</span>${input}</label>`;
|
|
305
|
+
|
|
306
|
+
function CreateTask({ onClose, onDone }) {
|
|
307
|
+
const { instances } = useStore(appStore);
|
|
308
|
+
const [f, setF] = useState({ title: "", desc: "", priority: "normal", assignee: "" });
|
|
309
|
+
const set = (k) => (e) => setF({ ...f, [k]: e.target.value });
|
|
310
|
+
const submit = { path: "/ui/tasks", done: t("fleet.taskCreated"), after: onDone, collect: () => {
|
|
311
|
+
if (!f.title.trim()) { toast(t("fleet.titleRequired"), false); return null; }
|
|
312
|
+
const body = { title: f.title.trim(), priority: f.priority };
|
|
313
|
+
if (f.desc.trim()) body.description = f.desc.trim();
|
|
314
|
+
if (f.assignee) body.assignee = f.assignee;
|
|
315
|
+
return body;
|
|
316
|
+
} };
|
|
317
|
+
return html`<${FormDialog} title=${t("fleet.newTask")} onClose=${onClose} submit=${submit}>
|
|
318
|
+
${field(`${t("fleet.taskTitle")} *`, html`<input value=${f.title} onInput=${set("title")} required />`)}
|
|
319
|
+
${field(t("fleet.taskDesc"), html`<input value=${f.desc} onInput=${set("desc")} />`)}
|
|
320
|
+
${field(t("fleet.priority"), html`<select value=${f.priority} onChange=${set("priority")}><option value="normal">${t("fleet.pNormal")}</option><option value="low">${t("fleet.pLow")}</option><option value="high">${t("fleet.pHigh")}</option><option value="urgent">${t("fleet.pUrgent")}</option></select>`)}
|
|
321
|
+
${field(t("fleet.assignee"), html`<select value=${f.assignee} onChange=${set("assignee")}><option value="">${t("fleet.unassigned")}</option>${instances.map(i => html`<option key=${i.name} value=${i.name}>${i.name}</option>`)}</select>`)}
|
|
322
|
+
</${FormDialog}>`;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function CreateSchedule({ onClose, onDone }) {
|
|
326
|
+
const { instances } = useStore(appStore);
|
|
327
|
+
const [f, setF] = useState({ cron: "", msg: "", target: instances[0] ? instances[0].name : "", label: "" });
|
|
328
|
+
const set = (k) => (e) => setF({ ...f, [k]: e.target.value });
|
|
329
|
+
const submit = { path: "/ui/schedules", done: t("fleet.scheduleCreated"), after: onDone, collect: () => {
|
|
330
|
+
if (!f.cron.trim() || !f.msg.trim() || !f.target) { toast(t("fleet.scheduleFieldsRequired"), false); return null; }
|
|
331
|
+
const body = { cron: f.cron.trim(), message: f.msg.trim(), target: f.target, source: "web-user", reply_chat_id: "", reply_thread_id: "" };
|
|
332
|
+
if (f.label.trim()) body.label = f.label.trim();
|
|
333
|
+
return body;
|
|
334
|
+
} };
|
|
335
|
+
return html`<${FormDialog} title=${t("fleet.newSchedule")} onClose=${onClose} submit=${submit}>
|
|
336
|
+
${field(`${t("fleet.cron")} *`, html`<input value=${f.cron} onInput=${set("cron")} placeholder="0 9 * * 1-5" class="mono" />`)}
|
|
337
|
+
${f.cron.trim() ? html`<p class="note">${cronDesc(f.cron)}</p>` : null}
|
|
338
|
+
${field(`${t("fleet.message")} *`, html`<input value=${f.msg} onInput=${set("msg")} />`)}
|
|
339
|
+
${field(`${t("fleet.target")} *`, html`<select value=${f.target} onChange=${set("target")}>${instances.map(i => html`<option key=${i.name} value=${i.name}>${i.name}</option>`)}</select>`)}
|
|
340
|
+
${field(t("fleet.label"), html`<input value=${f.label} onInput=${set("label")} />`)}
|
|
341
|
+
</${FormDialog}>`;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function CreateTeam({ onClose, onDone }) {
|
|
345
|
+
const { instances } = useStore(appStore);
|
|
346
|
+
const [f, setF] = useState({ name: "", desc: "", members: [] });
|
|
347
|
+
const toggle = (n) => setF({ ...f, members: f.members.includes(n) ? f.members.filter(x => x !== n) : [...f.members, n] });
|
|
348
|
+
const submit = { path: "/ui/teams", done: t("fleet.teamCreated"), after: onDone, collect: () => {
|
|
349
|
+
if (!f.name.trim() || !f.members.length) { toast(t("fleet.teamFieldsRequired"), false); return null; }
|
|
350
|
+
const body = { name: f.name.trim(), members: f.members };
|
|
351
|
+
if (f.desc.trim()) body.description = f.desc.trim();
|
|
352
|
+
return body;
|
|
353
|
+
} };
|
|
354
|
+
return html`<${FormDialog} title=${t("fleet.newTeam")} onClose=${onClose} submit=${submit}>
|
|
355
|
+
${field(`${t("fleet.teamName")} *`, html`<input value=${f.name} onInput=${(e) => setF({ ...f, name: e.target.value })} />`)}
|
|
356
|
+
${field(t("fleet.taskDesc"), html`<input value=${f.desc} onInput=${(e) => setF({ ...f, desc: e.target.value })} />`)}
|
|
357
|
+
<fieldset class="field"><legend>${t("fleet.members")} *</legend><div class="checks">${instances.map(i => html`<label key=${i.name} class="check">
|
|
358
|
+
<input type="checkbox" checked=${f.members.includes(i.name)} onChange=${() => toggle(i.name)} />${i.name}</label>`)}</div></fieldset>
|
|
359
|
+
</${FormDialog}>`;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** "New instance": the sidebar's ✎ opens it from anywhere (#1408 step 3 merges it with Settings' create flow). */
|
|
363
|
+
export function CreateInstanceDialog({ onClose }) {
|
|
364
|
+
const lease = useLease("create-instance");
|
|
365
|
+
const [backends, setBackends] = useState(null);
|
|
366
|
+
const [f, setF] = useState({ dir: "", topic: "", desc: "", backend: "", model: "", branch: "", tags: "", prompt: "" });
|
|
367
|
+
useEffect(() => {
|
|
368
|
+
(async () => {
|
|
369
|
+
try {
|
|
370
|
+
const bd = await api("GET", "/ui/backends", null, lease);
|
|
371
|
+
if (!lease.current()) return;
|
|
372
|
+
const list = bd.backends || [];
|
|
373
|
+
const first = list.find(b => b.installed && !b.deprecated) || list.find(b => b.installed);
|
|
374
|
+
setBackends(list);
|
|
375
|
+
setF(x => ({ ...x, backend: first ? first.name : "" }));
|
|
376
|
+
} catch {
|
|
377
|
+
// The list could not be read: offer the known backends, and keep the explicit choice "fleet default" selected.
|
|
378
|
+
if (lease.current()) { setBackends(BACKENDS.map(name => ({ name, installed: true }))); setF(x => ({ ...x, backend: "" })); }
|
|
379
|
+
}
|
|
380
|
+
})();
|
|
381
|
+
}, [lease]);
|
|
382
|
+
const set = (k) => (e) => setF({ ...f, [k]: e.target.value });
|
|
383
|
+
const submit = { path: "/ui/instances", done: t("fleet.instanceCreated"), collect: () => {
|
|
384
|
+
if (!f.dir.trim() && !f.topic.trim()) { toast(t("fleet.topicRequired"), false); return null; }
|
|
385
|
+
const body = {};
|
|
386
|
+
if (f.dir.trim()) body.directory = f.dir.trim();
|
|
387
|
+
if (f.topic.trim()) body.topic_name = f.topic.trim();
|
|
388
|
+
if (f.desc.trim()) body.description = f.desc.trim();
|
|
389
|
+
if (f.backend) body.backend = f.backend;
|
|
390
|
+
if (f.model.trim()) body.model = f.model.trim();
|
|
391
|
+
if (f.branch.trim()) body.branch = f.branch.trim();
|
|
392
|
+
const tags = f.tags.split(",").map(x => x.trim()).filter(Boolean); if (tags.length) body.tags = tags;
|
|
393
|
+
if (f.prompt.trim()) body.systemPrompt = f.prompt.trim();
|
|
394
|
+
return body;
|
|
395
|
+
} };
|
|
396
|
+
// Create waits for the backend list: a submission before it would go without a backend, and the list arriving
|
|
397
|
+
// afterwards would change the shown choice under a request already sent (#1425 review).
|
|
398
|
+
return html`<${FormDialog} title=${t("fleet.newInstance")} onClose=${onClose} submit=${submit} ready=${backends !== null}>
|
|
399
|
+
${field(t("fleet.directory"), html`<input value=${f.dir} onInput=${set("dir")} placeholder=${t("fleet.directoryHint")} />`)}
|
|
400
|
+
${field(`${t("fleet.topic")}${f.dir.trim() ? "" : " *"}`, html`<input value=${f.topic} onInput=${set("topic")} placeholder=${t("fleet.topicHint")} />`)}
|
|
401
|
+
${field(t("fleet.description"), html`<input value=${f.desc} onInput=${set("desc")} placeholder=${t("fleet.descriptionHint")} />`)}
|
|
402
|
+
${field(t("fleet.backend"), backends ? html`<select value=${f.backend} onChange=${set("backend")}><option value="">${t("fleet.fleetDefault")}</option>
|
|
403
|
+
${backends.map(b => html`<option key=${b.name} value=${b.name} disabled=${!b.installed}>${b.name}${b.deprecated ? ` ${t("fleet.deprecated")}` : ""}${b.installed ? "" : ` ${t("fleet.notInstalled")}`}</option>`)}</select>` : html`<${Skeleton} lines=${1} />`)}
|
|
404
|
+
${field(t("fleet.model"), html`<input value=${f.model} onInput=${set("model")} placeholder=${t("fleet.modelHint")} />`)}
|
|
405
|
+
${field(t("fleet.branch"), html`<input value=${f.branch} onInput=${set("branch")} placeholder=${t("fleet.branchHint")} />`)}
|
|
406
|
+
${field(t("fleet.tags"), html`<input value=${f.tags} onInput=${set("tags")} placeholder=${t("fleet.tagsHint")} />`)}
|
|
407
|
+
${field(t("fleet.systemPrompt"), html`<textarea rows="3" value=${f.prompt} onInput=${set("prompt")} placeholder=${t("fleet.systemPromptHint")}></textarea>`)}
|
|
408
|
+
</${FormDialog}>`;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// ── Cron in words ──
|
|
412
|
+
const DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
413
|
+
export function cronDesc(expr) {
|
|
414
|
+
if (!expr) return "";
|
|
415
|
+
const p = expr.trim().split(/\s+/);
|
|
416
|
+
if (p.length < 5) return expr;
|
|
417
|
+
const [min, hr, dom, mon, dow] = p;
|
|
418
|
+
const time = hr !== "*" && min !== "*" ? `at ${hr.padStart(2, "0")}:${min.padStart(2, "0")}` : "";
|
|
419
|
+
if (min === "*" && hr === "*") return "Every minute";
|
|
420
|
+
if (min.startsWith("*/")) return `Every ${min.slice(2)} minutes`;
|
|
421
|
+
if (hr.startsWith("*/")) return `Every ${hr.slice(2)} hours`;
|
|
422
|
+
if (dom === "*" && mon === "*" && dow === "*") return `Daily ${time}`;
|
|
423
|
+
if (dom === "*" && mon === "*" && dow === "1-5") return `Weekdays ${time}`;
|
|
424
|
+
if (dom === "*" && mon === "*" && dow !== "*") return `${dow.split(",").map(d => { const n = parseInt(d, 10); return Number.isNaN(n) ? d : (DAYS[n] || d); }).join(", ")} ${time}`;
|
|
425
|
+
if (dom !== "*" && mon === "*") return `${dom}${dom === "1" ? "st" : dom === "2" ? "nd" : dom === "3" ? "rd" : "th"} of month ${time}`;
|
|
426
|
+
return expr;
|
|
427
|
+
}
|