@songsid/agend 2.2.0-alpha.1 → 2.2.0-alpha.3
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 +6 -4
- package/README.zh-TW.md +6 -4
- package/dist/agent-cli.js +6 -1
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +4 -1
- package/dist/agent-endpoint.js +82 -14
- 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/atomic-write.d.ts +21 -0
- package/dist/atomic-write.js +65 -0
- package/dist/atomic-write.js.map +1 -0
- package/dist/auth-api.js +2 -0
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/antigravity.js +4 -3
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/codex.d.ts +7 -0
- package/dist/backend/codex.js +7 -0
- 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.d.ts +7 -0
- package/dist/backend/kiro.js +82 -33
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/muse.d.ts +7 -0
- package/dist/backend/muse.js +8 -1
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +12 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/binary-probe.d.ts +80 -0
- package/dist/binary-probe.js +182 -0
- package/dist/binary-probe.js.map +1 -0
- 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 +131 -0
- package/dist/cache-service.js +452 -0
- package/dist/cache-service.js.map +1 -0
- package/dist/channel/adapters/discord.d.ts +5 -1
- package/dist/channel/adapters/discord.js +56 -11
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +38 -1
- package/dist/channel/adapters/telegram.js +82 -0
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-tools.js +1 -1
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/tool-router.d.ts +8 -1
- package/dist/channel/tool-router.js +4 -2
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/channel/types.d.ts +21 -0
- 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 +376 -54
- package/dist/cli.js.map +1 -1
- package/dist/connection-secrets.d.ts +5 -0
- package/dist/connection-secrets.js.map +1 -1
- package/dist/cpu-profile.js +50 -0
- package/dist/cpu-profile.js.map +1 -1
- package/dist/daemon.d.ts +18 -0
- package/dist/daemon.js +256 -63
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.d.ts +1 -0
- package/dist/delivery-outbox.js +12 -0
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/discord-permissions.d.ts +33 -0
- package/dist/discord-permissions.js +42 -0
- package/dist/discord-permissions.js.map +1 -0
- package/dist/env-file.d.ts +11 -0
- package/dist/env-file.js +27 -0
- package/dist/env-file.js.map +1 -0
- 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/fleet-context.d.ts +5 -0
- package/dist/fleet-context.js.map +1 -1
- 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 +8 -1
- package/dist/fleet-level-config.js +53 -0
- package/dist/fleet-level-config.js.map +1 -1
- package/dist/fleet-manager.d.ts +122 -7
- package/dist/fleet-manager.js +871 -152
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-stop-budget.d.ts +22 -0
- package/dist/fleet-stop-budget.js +95 -0
- package/dist/fleet-stop-budget.js.map +1 -0
- 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/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 +3 -2
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/kiro-transcript-lane.d.ts +8 -4
- package/dist/kiro-transcript-lane.js +66 -44
- package/dist/kiro-transcript-lane.js.map +1 -1
- package/dist/locale.js +96 -0
- package/dist/locale.js.map +1 -1
- package/dist/next-steps.d.ts +5 -0
- package/dist/next-steps.js +26 -0
- package/dist/next-steps.js.map +1 -0
- package/dist/outbound-handlers.js +8 -0
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +4 -0
- package/dist/outbound-schemas.js +7 -2
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/outbox-open-error.d.ts +7 -0
- package/dist/outbox-open-error.js +15 -0
- package/dist/outbox-open-error.js.map +1 -0
- 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/pane-context-cache.d.ts +25 -0
- package/dist/pane-context-cache.js +73 -0
- package/dist/pane-context-cache.js.map +1 -0
- package/dist/paths.d.ts +0 -7
- package/dist/paths.js +8 -2
- package/dist/paths.js.map +1 -1
- package/dist/provider-probe.d.ts +15 -0
- package/dist/provider-probe.js +20 -0
- package/dist/provider-probe.js.map +1 -1
- package/dist/public-link-progress.d.ts +73 -0
- package/dist/public-link-progress.js +190 -0
- package/dist/public-link-progress.js.map +1 -0
- package/dist/public-web-gateway.js +2 -2
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +6 -1
- package/dist/public-web-link.js +87 -15
- package/dist/public-web-link.js.map +1 -1
- package/dist/quickstart-api.d.ts +93 -20
- package/dist/quickstart-api.js +194 -62
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.d.ts +15 -0
- package/dist/quickstart.js +110 -33
- package/dist/quickstart.js.map +1 -1
- package/dist/reply-buttons-controller.d.ts +98 -0
- package/dist/reply-buttons-controller.js +174 -0
- package/dist/reply-buttons-controller.js.map +1 -0
- package/dist/reply-buttons.d.ts +113 -0
- package/dist/reply-buttons.js +208 -0
- package/dist/reply-buttons.js.map +1 -0
- package/dist/reply-dedup.js +3 -1
- package/dist/reply-dedup.js.map +1 -1
- package/dist/restart-guard.d.ts +53 -0
- package/dist/restart-guard.js +114 -0
- package/dist/restart-guard.js.map +1 -0
- package/dist/scheduler/db.js +6 -2
- package/dist/scheduler/db.js.map +1 -1
- package/dist/service-activation.d.ts +62 -7
- package/dist/service-activation.js +237 -55
- package/dist/service-activation.js.map +1 -1
- package/dist/service-installer.d.ts +56 -1
- package/dist/service-installer.js +152 -12
- 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/settings-api.js +92 -10
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-authority.d.ts +11 -0
- package/dist/settings-authority.js +128 -0
- package/dist/settings-authority.js.map +1 -0
- package/dist/settings-change.d.ts +5 -0
- package/dist/settings-change.js +37 -8
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-confirmation.d.ts +3 -0
- package/dist/settings-confirmation.js +7 -1
- package/dist/settings-confirmation.js.map +1 -1
- package/dist/settings-effect.js +19 -7
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.js +1 -1
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/settings-redaction.d.ts +18 -0
- package/dist/settings-redaction.js +58 -0
- package/dist/settings-redaction.js.map +1 -0
- package/dist/setup-form.d.ts +9 -1
- package/dist/setup-form.js +25 -1
- package/dist/setup-form.js.map +1 -1
- package/dist/setup-host.js +15 -6
- package/dist/setup-host.js.map +1 -1
- package/dist/setup-wizard.js +27 -19
- package/dist/setup-wizard.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/systemd-runtime.d.ts +15 -0
- package/dist/systemd-runtime.js +41 -0
- package/dist/systemd-runtime.js.map +1 -0
- package/dist/tmux-control.d.ts +44 -0
- package/dist/tmux-control.js +170 -21
- package/dist/tmux-control.js.map +1 -1
- package/dist/token-env-name.d.ts +37 -0
- package/dist/token-env-name.js +93 -0
- package/dist/token-env-name.js.map +1 -0
- package/dist/tool-permissions.js +5 -3
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +7 -16
- package/dist/topic-commands.js +56 -85
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-monitor.js +3 -0
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.d.ts +68 -0
- package/dist/transcript-sources.js +336 -29
- package/dist/transcript-sources.js.map +1 -1
- package/dist/transcript-turns.d.ts +39 -0
- package/dist/transcript-turns.js +58 -0
- package/dist/transcript-turns.js.map +1 -0
- package/dist/tunnel/cloudflared-install.d.ts +18 -0
- package/dist/tunnel/cloudflared-install.js +15 -1
- package/dist/tunnel/cloudflared-install.js.map +1 -1
- package/dist/turn-reply-guard.d.ts +3 -0
- package/dist/turn-reply-guard.js.map +1 -1
- package/dist/ui/chat-commands.js +43 -0
- package/dist/ui/chat-render.js +34 -8
- package/dist/ui/chat-store.js +51 -2
- package/dist/ui/chat-strings.js +30 -2
- package/dist/ui/chat-thread.js +141 -47
- package/dist/ui/first-run.js +64 -0
- package/dist/ui/fleet-cache.js +168 -0
- package/dist/ui/html-attachment.js +26 -0
- package/dist/ui/panel-chat.js +117 -23
- package/dist/ui/panel-fleet.js +31 -17
- package/dist/ui/panel-settings.js +29 -15
- package/dist/ui/preview-panel.js +237 -0
- package/dist/ui/preview.js +7 -3
- package/dist/ui/settings-apply.js +3 -1
- package/dist/ui/settings-dialogs.js +95 -25
- package/dist/ui/settings-discord.js +130 -0
- package/dist/ui/settings-model.js +21 -0
- package/dist/ui/settings-strings.js +50 -8
- package/dist/ui/settings-token.js +54 -0
- package/dist/ui/settings-wizard.js +63 -29
- package/dist/ui/shared/app-route.js +1 -1
- package/dist/ui/shared/app-shell.js +25 -37
- package/dist/ui/shared/app-store.js +5 -3
- package/dist/ui/shared/app-stream.js +2 -1
- package/dist/ui/shared/app.css +181 -5
- package/dist/ui/shared/app.js +2 -1
- package/dist/ui/shared/instance-nav.js +310 -0
- package/dist/ui/shared/panel-view.js +12 -190
- package/dist/ui/shared/signin.js +13 -1
- package/dist/ui/shared/ui-icons.js +2 -0
- package/dist/ui/shared/view-roster-store.js +5 -0
- package/dist/ui/shared/view-strings.js +12 -0
- package/dist/ui/signin.html +2 -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 +78 -8
- package/dist/update-install.js +158 -30
- package/dist/update-install.js.map +1 -1
- package/dist/update-launch.d.ts +22 -0
- package/dist/update-launch.js +106 -0
- package/dist/update-launch.js.map +1 -0
- package/dist/update-marker.js +3 -8
- package/dist/update-marker.js.map +1 -1
- package/dist/usage/format-rich.js +7 -1
- package/dist/usage/format-rich.js.map +1 -1
- package/dist/usage/i18n-keys.d.ts +1 -1
- package/dist/usage/i18n-keys.js +1 -1
- package/dist/usage/i18n-keys.js.map +1 -1
- package/dist/usage/providers.d.ts +4 -0
- package/dist/usage/providers.js +2 -1
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/usage-api.js +48 -2
- package/dist/usage/usage-api.js.map +1 -1
- package/dist/view-api.d.ts +40 -0
- package/dist/view-api.js +28 -10
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +22 -0
- package/dist/web-api.js +115 -16
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.d.ts +8 -0
- package/dist/web-auth.js +34 -8
- package/dist/web-auth.js.map +1 -1
- package/dist/web-chat-history.d.ts +21 -0
- package/dist/web-chat-history.js +44 -0
- package/dist/web-chat-history.js.map +1 -1
- 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-host-guard.d.ts +18 -2
- package/dist/web-host-guard.js +30 -5
- package/dist/web-host-guard.js.map +1 -1
- package/dist/web-session.d.ts +7 -1
- package/dist/web-session.js +23 -2
- package/dist/web-session.js.map +1 -1
- package/dist/web-shell-routes.d.ts +1 -1
- package/dist/web-shell-routes.js +1 -1
- package/dist/web-shell-routes.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 +118 -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 +12 -4
- package/scripts/preinstall-guard.cjs +40 -21
- package/templates/launchd.plist.ejs +2 -2
- package/templates/systemd.service.ejs +4 -4
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// panel, in the app shell. Public (/assets/): under `web.view_access: open` an anonymous reader opens it in the
|
|
3
3
|
// View-only shell, so nothing here needs a session to show, and the one write (Edit profile) asks for one.
|
|
4
4
|
//
|
|
5
|
-
// Its roster
|
|
5
|
+
// Its roster read also feeds the sidebar's list for an anonymous reader (instance-nav.js is the list on every page).
|
|
6
6
|
// Everything recurring is a passive read (#1374) taken through the navigation's lease: the pane every 800 ms and the
|
|
7
7
|
// roster every 5 s while the panel is mounted and the tab is visible, usage every 60 s while its dialog is open. They
|
|
8
8
|
// stop when the panel goes.
|
|
@@ -10,7 +10,9 @@ import { html, useEffect, useLayoutEffect, useMemo, useRef, useState } from "./a
|
|
|
10
10
|
import { t } from "./app-i18n.js";
|
|
11
11
|
import { appStore, createStore, useStore } from "./app-store.js";
|
|
12
12
|
import { useLease } from "./app-ctx.js";
|
|
13
|
-
import { PanelHeader, setTitle,
|
|
13
|
+
import { PanelHeader, setTitle, onPanelKey, signInHref } from "./app-shell.js";
|
|
14
|
+
import { backendLabel, STATUS_KEYS } from "./instance-nav.js";
|
|
15
|
+
import { viewStore } from "./view-roster-store.js";
|
|
14
16
|
import { navigate } from "./app-nav.js";
|
|
15
17
|
import { viewPath } from "./app-route.js";
|
|
16
18
|
import { Dialog } from "./ui-dialog.js";
|
|
@@ -88,190 +90,10 @@ function paintAnsi(root) {
|
|
|
88
90
|
}
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
// ── The roster
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (tags.length) return tags[0];
|
|
96
|
-
return "Other";
|
|
97
|
-
}
|
|
98
|
-
const BACKEND_LABELS = { "claude-code": "Claude Code", "kiro-cli": "Kiro CLI", codex: "Codex", grok: "Grok Build", antigravity: "Antigravity", opencode: "OpenCode" };
|
|
99
|
-
export function backendLabel(backend) { return BACKEND_LABELS[backend] || backend || "Unknown"; }
|
|
100
|
-
const KNOWN_BACKENDS = new Set(Object.keys(BACKEND_LABELS));
|
|
101
|
-
const STATUS_KEYS = { running: "statusRunning", paused: "statusPaused", stopped: "statusStopped", crashed: "statusCrashed" };
|
|
102
|
-
|
|
103
|
-
export function normalizeFilter(query) { return String(query == null ? "" : query).trim().toLowerCase(); }
|
|
104
|
-
/** Case-insensitive substring on name (incl. display name), model and backend. */
|
|
105
|
-
export function instanceMatchesFilter(it, needle, labelOf) {
|
|
106
|
-
if (!needle) return true;
|
|
107
|
-
const fields = [it.instance_name, it.display_name, it.model, it.backend, labelOf ? labelOf(it.backend) : null];
|
|
108
|
-
return fields.some((value) => typeof value === "string" && value.toLowerCase().includes(needle));
|
|
109
|
-
}
|
|
110
|
-
/** Visible groups in display order, and the "N / M" counts. */
|
|
111
|
-
export function filterSidebar(groupNames, instByGroup, rosterByName, query, labelOf) {
|
|
112
|
-
const needle = normalizeFilter(query);
|
|
113
|
-
const groups = [];
|
|
114
|
-
let shown = 0, total = 0;
|
|
115
|
-
for (const g of groupNames) {
|
|
116
|
-
const names = (instByGroup.get(g) || []).filter((name) => rosterByName.has(name));
|
|
117
|
-
total += names.length;
|
|
118
|
-
const visible = names.filter((name) => instanceMatchesFilter(rosterByName.get(name), needle, labelOf));
|
|
119
|
-
shown += visible.length;
|
|
120
|
-
if (visible.length) groups.push({ group: g, names: visible });
|
|
121
|
-
}
|
|
122
|
-
return { groups, shown, total, active: needle.length > 0 };
|
|
123
|
-
}
|
|
124
|
-
export function instanceTooltip(it) {
|
|
125
|
-
const context = it.context_pct != null && Number.isFinite(it.context_pct) ? `${Math.round(Math.min(100, Math.max(0, it.context_pct)))}%` : tn("tooltipUnavailable");
|
|
126
|
-
const status = STATUS_KEYS[it.status] ? tn(STATUS_KEYS[it.status]) : it.status || tn("statusUnknown");
|
|
127
|
-
const line = (key, value) => tn(key, { value });
|
|
128
|
-
const lines = [];
|
|
129
|
-
if (it.display_name && it.display_name !== it.instance_name) lines.push(it.display_name, `(${it.instance_name})`);
|
|
130
|
-
else lines.push(it.instance_name);
|
|
131
|
-
lines.push(line("tooltipBackend", backendLabel(it.backend)));
|
|
132
|
-
const src = (s) => (s === "instance" || s === "classic" ? " (configured)" : s === "fleet-default" ? " (fleet default)" : "");
|
|
133
|
-
lines.push(line("tooltipModel", (it.model || tn("tooltipUnavailable")) + src(it.model_source)));
|
|
134
|
-
lines.push(line("tooltipStatus", status));
|
|
135
|
-
lines.push(line("tooltipContext", context));
|
|
136
|
-
if (it.effort) lines.push(line("tooltipEffort", it.effort + src(it.effort_source)));
|
|
137
|
-
if (typeof it.description === "string" && it.description.trim()) lines.push("", it.description.trim());
|
|
138
|
-
return lines.join("\n");
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const SIDEBAR_ORDER_KEY = "agend_view_sidebar_order";
|
|
142
|
-
function readOrder() {
|
|
143
|
-
const groups = new Map(), insts = new Map();
|
|
144
|
-
try {
|
|
145
|
-
const rows = JSON.parse(stored(SIDEBAR_ORDER_KEY) || "[]");
|
|
146
|
-
if (Array.isArray(rows)) for (const row of rows) {
|
|
147
|
-
if (row && row.item_type === "group" && typeof row.item_name === "string") groups.set(row.item_name, Number(row.sort_index) || 0);
|
|
148
|
-
else if (row && row.item_type === "instance" && typeof row.item_name === "string") insts.set(row.item_name, Number(row.sort_index) || 0);
|
|
149
|
-
}
|
|
150
|
-
} catch { /* ignore invalid browser-local state */ }
|
|
151
|
-
return { groups, insts };
|
|
152
|
-
}
|
|
153
|
-
/** Display order: this browser's saved order first, then new groups/instances by tag and name. */
|
|
154
|
-
export function computeOrder(roster, order) {
|
|
155
|
-
const groups = new Map();
|
|
156
|
-
for (const it of roster) { const g = groupOf(it); if (!groups.has(g)) groups.set(g, []); groups.get(g).push(it); }
|
|
157
|
-
const gidx = (g) => (order.groups.has(g) ? order.groups.get(g) : Infinity);
|
|
158
|
-
const groupNames = [...groups.keys()].sort((a, b) => {
|
|
159
|
-
const ia = gidx(a), ib = gidx(b);
|
|
160
|
-
if (ia !== ib) return ia - ib;
|
|
161
|
-
if (a === "Other") return 1;
|
|
162
|
-
if (b === "Other") return -1;
|
|
163
|
-
return a.localeCompare(b);
|
|
164
|
-
});
|
|
165
|
-
const instByGroup = new Map();
|
|
166
|
-
for (const [g, items] of groups) {
|
|
167
|
-
instByGroup.set(g, items.map((it) => it.instance_name).sort((a, b) => {
|
|
168
|
-
const ia = order.insts.has(a) ? order.insts.get(a) : Infinity, ib = order.insts.has(b) ? order.insts.get(b) : Infinity;
|
|
169
|
-
return ia - ib;
|
|
170
|
-
}));
|
|
171
|
-
}
|
|
172
|
-
return { groupNames, instByGroup };
|
|
173
|
-
}
|
|
174
|
-
function moveIn(arr, moving, target, before) {
|
|
175
|
-
if (!arr) return;
|
|
176
|
-
const from = arr.indexOf(moving);
|
|
177
|
-
if (from === -1) return;
|
|
178
|
-
arr.splice(from, 1);
|
|
179
|
-
let to = arr.indexOf(target);
|
|
180
|
-
if (to === -1) { arr.push(moving); return; }
|
|
181
|
-
if (!before) to += 1;
|
|
182
|
-
arr.splice(to, 0, moving);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/** The roster shared by the panel and its sidebar section, for as long as the page lives. */
|
|
186
|
-
export const viewStore = createStore({ loaded: false, error: null, roster: [], filter: "", collapsed: new Set(), current: null, order: readOrder() });
|
|
187
|
-
const byName = (roster) => new Map(roster.map((r) => [r.instance_name, r]));
|
|
188
|
-
|
|
189
|
-
function persistOrder(groupNames, instByGroup) {
|
|
190
|
-
const rows = [];
|
|
191
|
-
groupNames.forEach((g, i) => rows.push({ item_type: "group", item_name: g, sort_index: i, group_name: null }));
|
|
192
|
-
for (const g of groupNames) (instByGroup.get(g) || []).forEach((n, j) => rows.push({ item_type: "instance", item_name: n, sort_index: j, group_name: g }));
|
|
193
|
-
store(SIDEBAR_ORDER_KEY, JSON.stringify(rows));
|
|
194
|
-
viewStore.set({ order: { groups: new Map(groupNames.map((g, i) => [g, i])), insts: new Map(rows.filter((r) => r.item_type === "instance").map((r) => [r.item_name, r.sort_index])) } });
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/** The sidebar section while View is mounted: the filter, then the groups and their instances (links to /view/<name>). */
|
|
198
|
-
function ViewRoster() {
|
|
199
|
-
const v = useStore(viewStore);
|
|
200
|
-
const filterRef = useRef(null);
|
|
201
|
-
const drag = useRef(null);
|
|
202
|
-
const rosterByName = byName(v.roster);
|
|
203
|
-
const { groupNames, instByGroup } = computeOrder(v.roster, v.order);
|
|
204
|
-
const shown = filterSidebar(groupNames, instByGroup, rosterByName, v.filter, backendLabel);
|
|
205
|
-
const setFilter = (value) => viewStore.set({ filter: value });
|
|
206
|
-
const toggle = (g) => { const c = new Set(v.collapsed); if (c.has(g)) c.delete(g); else c.add(g); viewStore.set({ collapsed: c }); };
|
|
207
|
-
// Drag to reorder: only the full list (a filtered subset would be ambiguous), groups among groups, instances within
|
|
208
|
-
// their group.
|
|
209
|
-
const dragProps = (item) => shown.active ? {} : {
|
|
210
|
-
draggable: "true",
|
|
211
|
-
onDragStart: (e) => { drag.current = item; e.currentTarget.classList.add("dragging"); try { e.dataTransfer.effectAllowed = "move"; e.dataTransfer.setData("text/plain", item.name); } catch { /* Firefox needs data */ } },
|
|
212
|
-
onDragEnd: (e) => { e.currentTarget.classList.remove("dragging"); drag.current = null; },
|
|
213
|
-
onDragOver: (e) => {
|
|
214
|
-
const d = drag.current;
|
|
215
|
-
if (!d || d.type !== item.type || d.name === item.name || (item.type === "instance" && d.group !== item.group)) return;
|
|
216
|
-
e.preventDefault();
|
|
217
|
-
const r = e.currentTarget.getBoundingClientRect();
|
|
218
|
-
const before = e.clientY < r.top + r.height / 2;
|
|
219
|
-
e.currentTarget.classList.toggle("drop-before", before);
|
|
220
|
-
e.currentTarget.classList.toggle("drop-after", !before);
|
|
221
|
-
},
|
|
222
|
-
onDragLeave: (e) => e.currentTarget.classList.remove("drop-before", "drop-after"),
|
|
223
|
-
onDrop: (e) => {
|
|
224
|
-
e.preventDefault();
|
|
225
|
-
const before = e.currentTarget.classList.contains("drop-before");
|
|
226
|
-
e.currentTarget.classList.remove("drop-before", "drop-after");
|
|
227
|
-
const d = drag.current;
|
|
228
|
-
if (!d || d.type !== item.type) return;
|
|
229
|
-
const names = [...groupNames], insts = new Map([...instByGroup].map(([g, n]) => [g, [...n]]));
|
|
230
|
-
if (item.type === "group") moveIn(names, d.name, item.name, before);
|
|
231
|
-
else if (d.group === item.group) moveIn(insts.get(item.group), d.name, item.name, before);
|
|
232
|
-
else return;
|
|
233
|
-
persistOrder(names, insts);
|
|
234
|
-
},
|
|
235
|
-
};
|
|
236
|
-
// The list scrolls; the filter under it stays in view (#999), so it sits outside the scrolling section.
|
|
237
|
-
return html`<div class="view-roster">
|
|
238
|
-
<div class="side-section" id="instanceList">
|
|
239
|
-
<h2 class="side-label">${tn("instancesLabel")}</h2>
|
|
240
|
-
${!v.loaded ? html`<${Skeleton} lines=${4} />` : !v.roster.length ? html`<p class="side-empty">${tn("noInstances")}</p>` : null}
|
|
241
|
-
${shown.groups.map(({ group: g, names }) => {
|
|
242
|
-
const folded = v.collapsed.has(g);
|
|
243
|
-
return html`<div key=${g} class="v-group">
|
|
244
|
-
<button type="button" class="v-group-head" aria-expanded=${folded ? "false" : "true"} onClick=${() => toggle(g)}
|
|
245
|
-
title=${tn(folded ? "expandGroup" : "collapseGroup", { group: g })} ...${dragProps({ type: "group", name: g })}>
|
|
246
|
-
<${Icon} name="chevron" size=${14} cls=${folded ? "caret folded" : "caret"} /><span class="grow">${g}</span><span class="count">${names.length}</span></button>
|
|
247
|
-
${folded ? null : html`<ul class="inst-list">${names.map((name) => {
|
|
248
|
-
const it = rosterByName.get(name);
|
|
249
|
-
const alias = typeof it.display_name === "string" && it.display_name.trim() && it.display_name.trim() !== name ? it.display_name.trim() : "";
|
|
250
|
-
const active = name === v.current;
|
|
251
|
-
return html`<li key=${name}><a class=${`inst v-inst${active ? " active" : ""}`} href=${viewPath(name)} title=${instanceTooltip(it)}
|
|
252
|
-
aria-current=${active ? "page" : undefined} onClick=${closeDrawer} ...${dragProps({ type: "instance", name, group: g })}>
|
|
253
|
-
<span class=${`dot ${it.status === "running" ? "ok" : it.status === "crashed" ? "bad" : "off"}`} aria-hidden="true"></span>
|
|
254
|
-
<span class="inst-text"><span class="inst-name">${name}</span>${alias ? html`<span class="inst-sub"><span class="inst-alias">${alias}</span></span>` : null}</span>
|
|
255
|
-
<span class="v-meta">${it.context_pct != null ? `${Math.round(it.context_pct)}%` : ""}</span>
|
|
256
|
-
<span class=${`cli-icon cli-${KNOWN_BACKENDS.has(it.backend) ? it.backend : "other"}`} title=${backendLabel(it.backend)} aria-label=${tn("tooltipBackend", { value: backendLabel(it.backend) })}></span>
|
|
257
|
-
</a></li>`;
|
|
258
|
-
})}</ul>`}
|
|
259
|
-
</div>`;
|
|
260
|
-
})}
|
|
261
|
-
${v.loaded && shown.active && !shown.groups.length ? html`<p class="side-empty">${tn("filterNone")}</p>` : null}
|
|
262
|
-
</div>
|
|
263
|
-
<div class="v-filter">
|
|
264
|
-
<label class="v-filter-box"><${Icon} name="search" size=${14} /><span class="sr-only">${tn("filterPh")}</span>
|
|
265
|
-
<input ref=${filterRef} id="filterInput" type="text" autocomplete="off" spellcheck="false" placeholder=${tn("filterPh")} value=${v.filter}
|
|
266
|
-
onInput=${(e) => setFilter(e.target.value)}
|
|
267
|
-
onKeyDown=${(e) => { if (e.key === "Escape" && v.filter) { e.preventDefault(); e.stopPropagation(); setFilter(""); } }} />
|
|
268
|
-
${v.filter ? html`<button type="button" class="icon-btn v-filter-clear" aria-label=${tn("filterClear")} title=${tn("filterClear")}
|
|
269
|
-
onClick=${() => { setFilter(""); filterRef.current && filterRef.current.focus(); }}><${Icon} name="close" size=${14} /></button>` : null}
|
|
270
|
-
</label>
|
|
271
|
-
<p class="note" aria-live="polite">${shown.shown} / ${shown.total}${shown.active ? ` ${tn("filterShown")}` : ""}</p>
|
|
272
|
-
</div>
|
|
273
|
-
</div>`;
|
|
274
|
-
}
|
|
93
|
+
// ── The roster ── The sidebar's list is instance-nav.js on every page (alpha.2, N1); its helpers are re-exported here
|
|
94
|
+
// for the View code and tests that used them from this module.
|
|
95
|
+
export { groupOf, backendLabel, normalizeFilter, instanceMatchesFilter, filterSidebar, instanceTooltip, computeOrder } from "./instance-nav.js";
|
|
96
|
+
export { viewStore };
|
|
275
97
|
|
|
276
98
|
// ── The panel ──
|
|
277
99
|
|
|
@@ -317,8 +139,6 @@ export function ViewPanel({ route, navKey }) {
|
|
|
317
139
|
const name = route.instance;
|
|
318
140
|
const it = name ? v.roster.find((r) => r.instance_name === name) : null;
|
|
319
141
|
|
|
320
|
-
// The roster is the sidebar's section while View is mounted.
|
|
321
|
-
useEffect(() => lease.hold(setSideSection(ViewRoster)), [lease]);
|
|
322
142
|
useEffect(() => { viewStore.set({ current: name }); }, [name]);
|
|
323
143
|
useEffect(() => { setTitle(it ? (it.display_name || it.instance_name) : tn("title")); }, [name, it && it.display_name, navKey]);
|
|
324
144
|
if (name && it) store("agend_last_view", name);
|
|
@@ -643,10 +463,12 @@ function UsageDialog({ onClose }) {
|
|
|
643
463
|
<button type="button" class="icon-btn" disabled=${i === 0} aria-label=${tn("usageMoveUp")} title=${tn("usageMoveUp")} onClick=${() => move(key(p), -1, data.providers)}><${Icon} name="up" size=${14} /></button>
|
|
644
464
|
<button type="button" class="icon-btn" disabled=${i === providers.length - 1} aria-label=${tn("usageMoveDown")} title=${tn("usageMoveDown")} onClick=${() => move(key(p), 1, data.providers)}><${Icon} name="down" size=${14} /></button>
|
|
645
465
|
</span></div>
|
|
646
|
-
${p.
|
|
466
|
+
${p.stale ? html`<p class="note">${usageText(p.hint || "", p.hintI18n)}</p>` : null}
|
|
467
|
+
${p.status === "error" && !p.transient ? html`<p class="u-err">${usageText(p.error || tn("usage.error_fallback"), p.errorI18n)}</p>`
|
|
468
|
+
: p.status === "error" && p.transient ? html`<p class="note">${usageText(p.error || tn("usage.error_fallback"), p.errorI18n)}</p>`
|
|
647
469
|
: p.status === "no-credentials" ? html`<p class="note">${tn("usage.not_logged_in")}</p>${p.hint ? html`<p class="note">${usageText(p.hint, p.hintI18n)}</p>` : null}`
|
|
648
470
|
: html`${(p.metrics || []).map((m, j) => html`<${UsageMetric} key=${j} m=${m} />`)}
|
|
649
|
-
${p.hint ? html`<p class="note">${usageText(p.hint, p.hintI18n)}</p>` : !(p.metrics || []).length ? html`<p class="note">${tn("usage.no_data")}</p>` : null}`}
|
|
471
|
+
${!p.stale && p.hint ? html`<p class="note">${usageText(p.hint, p.hintI18n)}</p>` : !(p.metrics || []).length ? html`<p class="note">${tn("usage.no_data")}</p>` : null}`}
|
|
650
472
|
</section>`)}
|
|
651
473
|
<div class="u-foot"><span class="note">${tn("usageUpdated")} ${new Date(data.fetchedAt).toLocaleTimeString()}</span>
|
|
652
474
|
<button type="button" class="btn btn-sm" onClick=${() => load(true)}>${tn("usageRefresh")}</button></div>`;
|
package/dist/ui/shared/signin.js
CHANGED
|
@@ -39,7 +39,17 @@
|
|
|
39
39
|
history.replaceState(null, "", here.pathname + here.search + here.hash);
|
|
40
40
|
}
|
|
41
41
|
} catch { /* nothing to clean */ }
|
|
42
|
-
|
|
42
|
+
// The command in the hint is a <code> that never breaks across lines (a break inside "--code" reads as two dashes
|
|
43
|
+
// and a word, alpha.2 sweep); the text around it is text.
|
|
44
|
+
const CMD = "agend web --code";
|
|
45
|
+
document.querySelectorAll("[data-i18n]").forEach((el) => {
|
|
46
|
+
const text = t(el.getAttribute("data-i18n"));
|
|
47
|
+
const at = text.indexOf(CMD);
|
|
48
|
+
if (at < 0) { el.textContent = text; return; }
|
|
49
|
+
const code = document.createElement("code");
|
|
50
|
+
code.textContent = CMD;
|
|
51
|
+
el.replaceChildren(text.slice(0, at), code, text.slice(at + CMD.length));
|
|
52
|
+
});
|
|
43
53
|
|
|
44
54
|
// An old deep link, /ui#instance=<name> (#1408 §3): the fragment never reaches the server, and the sign-in page is
|
|
45
55
|
// served at the URL that was asked for, so it is still in location.hash here. Exactly that one form becomes the new
|
|
@@ -94,6 +104,8 @@
|
|
|
94
104
|
// navigation; going round again would loop, so stop and let the person type a code.
|
|
95
105
|
if (recentlyBounced()) return false;
|
|
96
106
|
try {
|
|
107
|
+
// Signed out, this answers 401 — and the browser logs that request in red in DevTools. That is expected: the
|
|
108
|
+
// probe asks "is there a session?", and no is a normal answer here (decided against reshaping the auth API).
|
|
97
109
|
const r = await fetch("/auth/session", { credentials: "same-origin", cache: "no-store" });
|
|
98
110
|
return r.ok;
|
|
99
111
|
} catch { return false; }
|
|
@@ -50,6 +50,8 @@ const P = {
|
|
|
50
50
|
pause: () => html`<path d="M8 5v14M16 5v14"/>`,
|
|
51
51
|
download: () => html`<path d="M12 3v12M7 10l5 5 5-5M5 21h14"/>`,
|
|
52
52
|
org: () => html`<rect x="9" y="2" width="6" height="5" rx="1"/><rect x="2" y="17" width="6" height="5" rx="1"/><rect x="16" y="17" width="6" height="5" rx="1"/><path d="M12 7v5M5 17v-5h14v5"/>`,
|
|
53
|
+
panel: () => html`<rect x="3" y="4" width="18" height="16" rx="2"/><path d="M14 4v16"/>`,
|
|
54
|
+
back: () => html`<path d="M19 12H5M12 19l-7-7 7-7"/>`,
|
|
53
55
|
external: () => html`<path d="M14 4h6v6M20 4l-9 9"/><path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>`,
|
|
54
56
|
};
|
|
55
57
|
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// View's roster read (/api/profiles), shared by the View panel (its cards) and the anonymous reader's sidebar list
|
|
2
|
+
// (instance-nav.js), for as long as the page lives. The View panel fills it while mounted; nothing else reads the server.
|
|
3
|
+
import { createStore } from "./app-store.js";
|
|
4
|
+
|
|
5
|
+
export const viewStore = createStore({ loaded: false, error: null, roster: [], current: null });
|
|
@@ -12,6 +12,9 @@ register("view", {
|
|
|
12
12
|
"filterClear": "Clear filter",
|
|
13
13
|
"filterNone": "No instance matches",
|
|
14
14
|
"filterShown": "shown",
|
|
15
|
+
"filterStatus": "Status",
|
|
16
|
+
"filterCli": "CLI",
|
|
17
|
+
"filterFacetsReset": "Clear",
|
|
15
18
|
"displayName": "Display name",
|
|
16
19
|
"role": "Role",
|
|
17
20
|
"description": "Description",
|
|
@@ -38,6 +41,7 @@ register("view", {
|
|
|
38
41
|
"tooltipContext": "Context: {value}",
|
|
39
42
|
"tooltipModel": "Model: {value}",
|
|
40
43
|
"tooltipEffort": "Effort: {value}",
|
|
44
|
+
"tooltipCost": "Cost: {value}",
|
|
41
45
|
"tooltipUnavailable": "Unavailable",
|
|
42
46
|
"statusRunning": "Running",
|
|
43
47
|
"statusPaused": "Paused",
|
|
@@ -109,6 +113,8 @@ register("view", {
|
|
|
109
113
|
"usage.error.unreachable": "Could not reach {0}.",
|
|
110
114
|
"usage.error.token_rejected": "Token rejected. Run `{0}` once to refresh the login.",
|
|
111
115
|
"usage.error.rate_limited": "Rate limited by {0} — try again later.",
|
|
116
|
+
"usage.error.rate_limited_transient": "{0}: can't fetch usage right now (rate limited). This does not affect your agents.",
|
|
117
|
+
"usage.stale_rate_limited": "data from {0} min ago — can't refresh right now",
|
|
112
118
|
"usage.error.http": "Usage request failed (HTTP {0}).",
|
|
113
119
|
"usage.error.billing_http": "Billing request failed (HTTP {0}).",
|
|
114
120
|
"usage.error.invalid_response": "Invalid response from usage endpoint.",
|
|
@@ -163,6 +169,9 @@ register("view", {
|
|
|
163
169
|
"filterClear": "清除篩選",
|
|
164
170
|
"filterNone": "沒有符合的 instance",
|
|
165
171
|
"filterShown": "顯示中",
|
|
172
|
+
"filterStatus": "狀態",
|
|
173
|
+
"filterCli": "CLI",
|
|
174
|
+
"filterFacetsReset": "清除",
|
|
166
175
|
"displayName": "顯示名稱",
|
|
167
176
|
"role": "角色",
|
|
168
177
|
"description": "描述",
|
|
@@ -189,6 +198,7 @@ register("view", {
|
|
|
189
198
|
"tooltipContext": "上下文:{value}",
|
|
190
199
|
"tooltipModel": "模型:{value}",
|
|
191
200
|
"tooltipEffort": "推理強度:{value}",
|
|
201
|
+
"tooltipCost": "費用:{value}",
|
|
192
202
|
"tooltipUnavailable": "無資料",
|
|
193
203
|
"statusRunning": "運行中",
|
|
194
204
|
"statusPaused": "已暫停",
|
|
@@ -260,6 +270,8 @@ register("view", {
|
|
|
260
270
|
"usage.error.unreachable": "無法連線至 {0}。",
|
|
261
271
|
"usage.error.token_rejected": "Token 已被拒絕。請執行一次 `{0}` 以更新登入。",
|
|
262
272
|
"usage.error.rate_limited": "{0} 暫時限制請求速率,請稍後再試。",
|
|
273
|
+
"usage.error.rate_limited_transient": "暫時查不到 {0} 用量({0} 限制查詢頻率),不影響你的 agent。",
|
|
274
|
+
"usage.stale_rate_limited": "{0} 分鐘前的資料,暫時無法更新",
|
|
263
275
|
"usage.error.http": "用量請求失敗(HTTP {0})。",
|
|
264
276
|
"usage.error.billing_http": "計費請求失敗(HTTP {0})。",
|
|
265
277
|
"usage.error.invalid_response": "用量端點回傳了無效資料。",
|
package/dist/ui/signin.html
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
button:disabled { opacity: .6; cursor: default; }
|
|
28
28
|
#msg { min-height: 1.5em; margin-top: 14px; font-size: var(--fs-sm); color: var(--danger); }
|
|
29
29
|
.hint { margin-top: 18px; font-size: var(--fs-xs); color: var(--text-3); }
|
|
30
|
+
.hint code { white-space: nowrap; font-family: var(--font-mono); }
|
|
30
31
|
[hidden] { display: none !important; }
|
|
31
32
|
</style>
|
|
32
33
|
</head>
|
|
@@ -42,7 +43,7 @@
|
|
|
42
43
|
autocorrect="off" spellcheck="false" maxlength="12" placeholder="XXXX-XXXX" required>
|
|
43
44
|
<button id="go" type="submit" data-i18n="submit">Sign in</button>
|
|
44
45
|
<div id="msg" role="alert" aria-live="polite"></div>
|
|
45
|
-
<div class="hint" data-i18n="hint">Send /dashboard to your AgEnD bot, or run
|
|
46
|
+
<div class="hint" data-i18n="hint">Send /dashboard to your AgEnD bot, or run “<code>agend web --code</code>” on the host. A code works once and expires in 5 minutes.</div>
|
|
46
47
|
</form>
|
|
47
48
|
<noscript>This page needs JavaScript to sign you in.</noscript>
|
|
48
49
|
</main>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface DispatchDeps {
|
|
2
|
+
run(command: string, args: string[], timeoutMs: number): Promise<{
|
|
3
|
+
status: number | null;
|
|
4
|
+
stdout: string;
|
|
5
|
+
}>;
|
|
6
|
+
readFile(path: string): Promise<string>;
|
|
7
|
+
realpath(path: string): Promise<string>;
|
|
8
|
+
}
|
|
9
|
+
export declare const defaultDispatchDeps: DispatchDeps;
|
|
10
|
+
export type InstalledAgend = {
|
|
11
|
+
ok: true;
|
|
12
|
+
agend: string;
|
|
13
|
+
version: string;
|
|
14
|
+
} | {
|
|
15
|
+
ok: false;
|
|
16
|
+
reason: string;
|
|
17
|
+
};
|
|
18
|
+
/** npm's global root and prefix → the installed package, its bin target, and the bin link that must lead to it. */
|
|
19
|
+
export declare function resolveInstalledAgend(deps?: DispatchDeps): Promise<InstalledAgend>;
|
|
20
|
+
/**
|
|
21
|
+
* `<installed agend> update`, 2 s from now, from a detached `sh`. Service callers must wrap it in an independent scope before launch. The
|
|
22
|
+
* path is a positional argument, never spliced into the script.
|
|
23
|
+
*/
|
|
24
|
+
export declare const DELAYED_UPDATE_SCRIPT = "sleep 2 && exec \"$1\" update";
|
|
25
|
+
export declare function updateCommand(agend: string): {
|
|
26
|
+
command: string;
|
|
27
|
+
args: string[];
|
|
28
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #1450 C5: chat `/update` runs the INSTALLED `agend` — the one npm put in its global prefix, verified by identity —
|
|
3
|
+
* by absolute path, never whichever `agend` comes first on the fleet's PATH (a checkout, another prefix). That keeps
|
|
4
|
+
* the deliberate choice "update what is installed, on its own channel" (the CLI picks the channel from the version it
|
|
5
|
+
* replaces). No unverified fallback: when npm cannot say where it installed AgEnD, or what it says does not hold
|
|
6
|
+
* together, `/update` refuses and says to run `agend update` from a shell.
|
|
7
|
+
*
|
|
8
|
+
* Runs on the fleet's event loop: every step is async and bounded.
|
|
9
|
+
*/
|
|
10
|
+
import { execFile } from "node:child_process";
|
|
11
|
+
import { readFile, realpath } from "node:fs/promises";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { promisify } from "node:util";
|
|
14
|
+
export const defaultDispatchDeps = {
|
|
15
|
+
run: async (command, args, timeoutMs) => {
|
|
16
|
+
try {
|
|
17
|
+
const { stdout } = await promisify(execFile)(command, args, { encoding: "utf8", timeout: timeoutMs });
|
|
18
|
+
return { status: 0, stdout: String(stdout) };
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
const code = err.code;
|
|
22
|
+
return { status: typeof code === "number" ? code : null, stdout: "" };
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
readFile: path => readFile(path, "utf8"),
|
|
26
|
+
realpath: path => realpath(path),
|
|
27
|
+
};
|
|
28
|
+
/** npm's global root and prefix → the installed package, its bin target, and the bin link that must lead to it. */
|
|
29
|
+
export async function resolveInstalledAgend(deps = defaultDispatchDeps) {
|
|
30
|
+
const last = (text) => text.trim().split("\n").pop()?.trim() ?? "";
|
|
31
|
+
const [root, prefix] = await Promise.all([deps.run("npm", ["root", "-g"], 15_000), deps.run("npm", ["prefix", "-g"], 15_000)]);
|
|
32
|
+
if (root.status !== 0 || prefix.status !== 0 || !last(root.stdout) || !last(prefix.stdout)) {
|
|
33
|
+
return { ok: false, reason: "npm could not say where AgEnD is installed" };
|
|
34
|
+
}
|
|
35
|
+
const pkgDir = join(last(root.stdout), "@songsid", "agend");
|
|
36
|
+
const notAgend = { ok: false, reason: `${pkgDir} is not an installed AgEnD package` };
|
|
37
|
+
let parsed;
|
|
38
|
+
try {
|
|
39
|
+
parsed = JSON.parse(await deps.readFile(join(pkgDir, "package.json")));
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return notAgend;
|
|
43
|
+
}
|
|
44
|
+
// Whatever parsed (null, an array, a string…) is judged by shape, never dereferenced blindly.
|
|
45
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
46
|
+
return notAgend;
|
|
47
|
+
const manifest = parsed;
|
|
48
|
+
const bin = manifest.bin;
|
|
49
|
+
const binRel = typeof bin === "string" ? bin
|
|
50
|
+
: typeof bin === "object" && bin !== null && !Array.isArray(bin) && typeof bin.agend === "string" ? bin.agend : null;
|
|
51
|
+
if (manifest.name !== "@songsid/agend" || typeof manifest.version !== "string" || !manifest.version || !binRel)
|
|
52
|
+
return notAgend;
|
|
53
|
+
const agend = join(last(prefix.stdout), "bin", "agend");
|
|
54
|
+
let target, linked;
|
|
55
|
+
try {
|
|
56
|
+
[target, linked] = await Promise.all([deps.realpath(join(pkgDir, binRel)), deps.realpath(agend)]);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return { ok: false, reason: `${agend} does not lead to the installed package` };
|
|
60
|
+
}
|
|
61
|
+
if (target !== linked)
|
|
62
|
+
return { ok: false, reason: `${agend} leads to ${linked}, not the installed package's ${target}` };
|
|
63
|
+
return { ok: true, agend, version: manifest.version };
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* `<installed agend> update`, 2 s from now, from a detached `sh`. Service callers must wrap it in an independent scope before launch. The
|
|
67
|
+
* path is a positional argument, never spliced into the script.
|
|
68
|
+
*/
|
|
69
|
+
export const DELAYED_UPDATE_SCRIPT = 'sleep 2 && exec "$1" update';
|
|
70
|
+
export function updateCommand(agend) {
|
|
71
|
+
return { command: "sh", args: ["-c", DELAYED_UPDATE_SCRIPT, "sh", agend] };
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=update-dispatch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-dispatch.js","sourceRoot":"","sources":["../src/update-dispatch.ts"],"names":[],"mappings":"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"}
|
package/dist/update-install.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type PackagePreimage } from "./package-preimage.js";
|
|
1
2
|
export interface CommandResult {
|
|
2
3
|
status: number | null;
|
|
3
4
|
signal: NodeJS.Signals | null;
|
|
@@ -5,10 +6,14 @@ export interface CommandResult {
|
|
|
5
6
|
stderr: string;
|
|
6
7
|
}
|
|
7
8
|
export interface CommandRunner {
|
|
8
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Run a program; `inherit` streams its output to the terminal (npm's progress), otherwise it is captured. `env` is
|
|
11
|
+
* added to this process's environment for that one program (the install token goes to npm and nothing else).
|
|
12
|
+
*/
|
|
9
13
|
run(command: string, args: string[], options?: {
|
|
10
14
|
inherit?: boolean;
|
|
11
15
|
timeoutMs?: number;
|
|
16
|
+
env?: Record<string, string>;
|
|
12
17
|
}): CommandResult;
|
|
13
18
|
/** Progress for the operator, printed when it happens. */
|
|
14
19
|
log(message: string): void;
|
|
@@ -22,6 +27,28 @@ export interface UpdateInstallPlan {
|
|
|
22
27
|
viaNvm: boolean;
|
|
23
28
|
/** `~/.nvm/nvm.sh`. */
|
|
24
29
|
nvmSh: string;
|
|
30
|
+
/**
|
|
31
|
+
* #1450 C1: take the prefix lock for the realpath'd npm prefix this install will change, before npm runs; its token
|
|
32
|
+
* is handed to npm as AGEND_INSTALL_TOKEN. Omitted: no lock (tests of the order alone).
|
|
33
|
+
*/
|
|
34
|
+
lock?(prefix: string): {
|
|
35
|
+
ok: true;
|
|
36
|
+
token: string;
|
|
37
|
+
} | {
|
|
38
|
+
ok: false;
|
|
39
|
+
reason: string;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* #1450 C6: copy the installed package aside before npm (requires `lock`), and put it back when the new one does not
|
|
43
|
+
* verify. The successful outcome carries the preimage, so a later service failure can restore it too.
|
|
44
|
+
*/
|
|
45
|
+
rollback?: boolean;
|
|
46
|
+
now?(): Date;
|
|
47
|
+
/**
|
|
48
|
+
* nvm's Node 22 bin directory, resolved ONCE (runUpdateInstall sets it): locking, installing and verifying then run
|
|
49
|
+
* on that exact selection, never on a fresh `nvm use 22` that could pick another Node 22 in between (#1472 review).
|
|
50
|
+
*/
|
|
51
|
+
nvmBin?: string;
|
|
25
52
|
}
|
|
26
53
|
export type UpdateInstallOutcome = {
|
|
27
54
|
ok: true;
|
|
@@ -29,10 +56,18 @@ export type UpdateInstallOutcome = {
|
|
|
29
56
|
version: string;
|
|
30
57
|
dir: string;
|
|
31
58
|
bin: string;
|
|
59
|
+
entry: string;
|
|
32
60
|
node: string;
|
|
61
|
+
rollback?: {
|
|
62
|
+
root: string;
|
|
63
|
+
prefix: string;
|
|
64
|
+
preimage: PackagePreimage | null;
|
|
65
|
+
}; /** nvm transition: retire the old system copy once activation settles. */
|
|
66
|
+
retireSystemCopy?: true; /** Absolute path of the npm binary used to install this version — same binary passed to sudo for retirement. */
|
|
67
|
+
npmPath?: string;
|
|
33
68
|
} | {
|
|
34
69
|
ok: false;
|
|
35
|
-
stage: "install" | "verify";
|
|
70
|
+
stage: "lock" | "install" | "verify";
|
|
36
71
|
message: string;
|
|
37
72
|
};
|
|
38
73
|
/**
|
|
@@ -42,8 +77,12 @@ export type UpdateInstallOutcome = {
|
|
|
42
77
|
* arguments or switching to its default before `nvm use 22`.
|
|
43
78
|
*/
|
|
44
79
|
export declare const NVM_RUN_SCRIPT = "n=$1; shift; . \"$n\" --no-use >/dev/null 2>&1 && nvm use 22 >/dev/null 2>&1 && \"$@\"";
|
|
45
|
-
/**
|
|
46
|
-
export declare const
|
|
80
|
+
/** Getting nvm's Node 22, before its prefix can be locked and installed into: `$1` = nvm.sh, as data. */
|
|
81
|
+
export declare const NVM_PREPARE_SCRIPT = "n=$1; . \"$n\" --no-use && nvm install 22";
|
|
82
|
+
/** Which Node `nvm use 22` selects: its absolute path, printed once. `$1` = nvm.sh, as data. */
|
|
83
|
+
export declare const NVM_SELECT_SCRIPT = "n=$1; . \"$n\" --no-use >/dev/null 2>&1 && nvm use 22 >/dev/null 2>&1 && command -v node";
|
|
84
|
+
/** Run the remaining arguments with a frozen nvm bin directory first on PATH (`$1`, as data). */
|
|
85
|
+
export declare const NVM_BIN_SCRIPT = "d=$1; shift; PATH=\"$d:$PATH\"; export PATH; exec \"$@\"";
|
|
47
86
|
/**
|
|
48
87
|
* How to run the NEW `agend` after the install: directly, or — for an nvm install — inside nvm's Node 22, because its
|
|
49
88
|
* `#!/usr/bin/env node` would otherwise pick the parent's (old) Node from PATH (#1446 item 1). Append the subcommand.
|
|
@@ -55,11 +94,15 @@ export declare function newAgendInvocation(plan: Pick<UpdateInstallPlan, "viaNvm
|
|
|
55
94
|
/** `npm link` leaves a symlink to a source checkout outside any node_modules tree; a registry install is a directory. */
|
|
56
95
|
export declare function isLocalLinkTarget(entryIsSymlink: boolean, realTarget: string): boolean;
|
|
57
96
|
/**
|
|
58
|
-
* The identity of what npm installed: `$1` is the package directory in npm's global root. Prints its name, version
|
|
59
|
-
*
|
|
97
|
+
* The identity of what npm installed: `$1` is the package directory in npm's global root. Prints its name, version, the
|
|
98
|
+
* REAL path of its `agend` bin target, so PATH's `agend` can be held to that exact file, and of its canonical inner
|
|
99
|
+
* entry `dist/cli.js` (#1450 C4: what a service starts; since the launcher, not the bin).
|
|
60
100
|
*/
|
|
61
101
|
export declare const PACKAGE_IDENTITY_SCRIPT: string;
|
|
62
|
-
/**
|
|
102
|
+
/**
|
|
103
|
+
* Proves the native module loads AND works, in the main thread and in a worker (the fleet opens databases in both):
|
|
104
|
+
* on Node 20 better-sqlite3 13 imports fine and then SIGSEGVs on open. `$1` is the package directory.
|
|
105
|
+
*/
|
|
63
106
|
export declare const NATIVE_CHECK_SCRIPT: string;
|
|
64
107
|
/**
|
|
65
108
|
* Prove the package npm installed — read from npm's own global root and prefix in the install environment — is the
|
|
@@ -67,5 +110,32 @@ export declare const NATIVE_CHECK_SCRIPT: string;
|
|
|
67
110
|
* shebang resolves to. Used after an install, and before restarting a fleet that predates an earlier install (#1449
|
|
68
111
|
* review: an update whose verification failed must not be completed by simply running `agend update` again).
|
|
69
112
|
*/
|
|
70
|
-
export declare function verifyInstalledPackage(plan: UpdateInstallPlan, runner: CommandRunner): UpdateInstallOutcome;
|
|
113
|
+
export declare function verifyInstalledPackage(plan: UpdateInstallPlan, runner: CommandRunner, npmBin?: string): UpdateInstallOutcome;
|
|
71
114
|
export declare function runUpdateInstall(plan: UpdateInstallPlan, runner: CommandRunner): UpdateInstallOutcome;
|
|
115
|
+
/**
|
|
116
|
+
* Has the activation POSITIVELY settled on the new install — the moment the old system copy may go? launchd: the new
|
|
117
|
+
* job was proven running. Otherwise only a restart that reported "restarted": "pending" (systemd still running the
|
|
118
|
+
* job) and "failed" keep the old copy, as does any failed activation (#1473 review).
|
|
119
|
+
*/
|
|
120
|
+
export declare function activationSettled(outcome: {
|
|
121
|
+
ok: true;
|
|
122
|
+
via: "restart" | "launchd-activation";
|
|
123
|
+
} | {
|
|
124
|
+
ok: false;
|
|
125
|
+
}, restart: "restarted" | "pending" | "failed" | null): boolean;
|
|
126
|
+
/**
|
|
127
|
+
* After an nvm transition's activation SETTLED (the fleet runs the new install): remove the old system copy. Best
|
|
128
|
+
* effort, and never waits for a password. Before that point the old copy is the rollback for the old service.
|
|
129
|
+
*/
|
|
130
|
+
export type RetireSystemCopyResult = {
|
|
131
|
+
ok: true;
|
|
132
|
+
} | {
|
|
133
|
+
ok: false;
|
|
134
|
+
reason: string;
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* Remove the old system npm global after an nvm transition.
|
|
138
|
+
* @param npmPath - The absolute npm path used during this install (from UpdateInstallOutcome.npmPath).
|
|
139
|
+
* Passing undefined or a non-absolute path is a caller error and results in a failure.
|
|
140
|
+
*/
|
|
141
|
+
export declare function retireSystemCopy(runner: CommandRunner, npmPath?: string): RetireSystemCopyResult;
|