@songsid/agend 2.2.0-beta.3 → 2.2.0-beta.5
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/dist/agent-cli.js +6 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/backend/claude-code.d.ts +46 -2
- package/dist/backend/claude-code.js +125 -31
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/credential-profile.d.ts +33 -3
- package/dist/backend/credential-profile.js +107 -5
- package/dist/backend/credential-profile.js.map +1 -1
- package/dist/backend/types.d.ts +1 -1
- package/dist/cache-service.d.ts +2 -0
- package/dist/cache-service.js +2 -1
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +13 -0
- package/dist/channel/adapters/discord.js +42 -0
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +6 -1
- package/dist/channel/adapters/telegram.js +27 -2
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/types.d.ts +22 -1
- package/dist/cli.js +35 -0
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +7 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.d.ts +96 -0
- package/dist/daemon.js +339 -16
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.d.ts +18 -5
- package/dist/delivery-outbox.js +121 -14
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/fleet-context.d.ts +1 -0
- package/dist/fleet-manager.d.ts +89 -3
- package/dist/fleet-manager.js +541 -86
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +43 -10
- package/dist/host-memory.d.ts +10 -0
- package/dist/host-memory.js +33 -1
- package/dist/host-memory.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +25 -6
- package/dist/instance-lifecycle.js +73 -26
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/locale.js +48 -18
- package/dist/locale.js.map +1 -1
- package/dist/login-controller.d.ts +20 -3
- package/dist/login-controller.js +71 -19
- package/dist/login-controller.js.map +1 -1
- package/dist/login-flows.d.ts +5 -2
- package/dist/login-flows.js +5 -4
- package/dist/login-flows.js.map +1 -1
- package/dist/memory-pressure.d.ts +13 -1
- package/dist/memory-pressure.js +50 -10
- package/dist/memory-pressure.js.map +1 -1
- package/dist/outbound-handlers.d.ts +61 -0
- package/dist/outbound-handlers.js +100 -35
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +1 -0
- package/dist/outbound-schemas.js +2 -0
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-preview.d.ts +100 -0
- package/dist/public-preview.js +233 -0
- package/dist/public-preview.js.map +1 -0
- package/dist/public-web-gateway.js +6 -4
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +20 -0
- package/dist/public-web-link.js +26 -1
- package/dist/public-web-link.js.map +1 -1
- package/dist/restart-progress.d.ts +29 -0
- package/dist/restart-progress.js +23 -0
- package/dist/restart-progress.js.map +1 -1
- package/dist/settings-api.js +10 -4
- package/dist/settings-api.js.map +1 -1
- package/dist/spawn-gate.js +5 -1
- package/dist/spawn-gate.js.map +1 -1
- package/dist/spawn-hold-reply.d.ts +12 -0
- package/dist/spawn-hold-reply.js +39 -0
- package/dist/spawn-hold-reply.js.map +1 -0
- package/dist/topic-commands.js +9 -2
- package/dist/topic-commands.js.map +1 -1
- package/dist/tunnel/purpose-lane.d.ts +5 -1
- package/dist/tunnel/purpose-lane.js +14 -7
- package/dist/tunnel/purpose-lane.js.map +1 -1
- package/dist/turn-resume.d.ts +2 -0
- package/dist/turn-resume.js +1 -1
- package/dist/turn-resume.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/ui/chat-mentions.js +63 -0
- package/dist/ui/chat-render.js +25 -0
- package/dist/ui/chat-strings.js +14 -2
- package/dist/ui/chat-thread.js +28 -10
- package/dist/ui/panel-chat.js +26 -11
- package/dist/ui/panel-details.js +2 -0
- package/dist/ui/panel-fleet.js +199 -57
- package/dist/ui/panel-needs.js +4 -3
- package/dist/ui/panel-settings.js +4 -2
- package/dist/ui/preview-panel.js +48 -14
- package/dist/ui/preview-status.js +28 -0
- package/dist/ui/preview.js +40 -3
- package/dist/ui/settings-token.js +1 -1
- package/dist/ui/shared/app-i18n.js +4 -0
- package/dist/ui/shared/app-prefs.js +1 -0
- package/dist/ui/shared/app-shell.js +146 -2
- package/dist/ui/shared/app-store.js +3 -1
- package/dist/ui/shared/app.css +47 -20
- package/dist/ui/shared/instance-nav.js +79 -23
- package/dist/ui/shared/view-strings.js +10 -0
- package/dist/unrecognised-prompt.d.ts +23 -0
- package/dist/unrecognised-prompt.js +74 -0
- package/dist/unrecognised-prompt.js.map +1 -0
- package/dist/update-install.d.ts +8 -0
- package/dist/update-install.js +9 -3
- package/dist/update-install.js.map +1 -1
- package/dist/update-marker.d.ts +40 -0
- package/dist/update-marker.js +49 -0
- package/dist/update-marker.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 +34 -5
- package/dist/usage/providers.js +160 -27
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/statusline-usage.d.ts +6 -2
- package/dist/usage/statusline-usage.js +10 -4
- package/dist/usage/statusline-usage.js.map +1 -1
- package/dist/web-api.d.ts +20 -0
- package/dist/web-api.js +216 -30
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.js +3 -2
- package/dist/web-auth.js.map +1 -1
- package/dist/web-mentions.d.ts +44 -0
- package/dist/web-mentions.js +75 -0
- package/dist/web-mentions.js.map +1 -0
- package/dist/web-prefs.js +2 -0
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-preview.d.ts +14 -1
- package/dist/web-preview.js +35 -13
- package/dist/web-preview.js.map +1 -1
- package/dist/web-schedules.d.ts +12 -0
- package/dist/web-schedules.js +34 -0
- package/dist/web-schedules.js.map +1 -0
- package/package.json +1 -1
|
@@ -12,11 +12,15 @@ export interface StatuslineRateLimits {
|
|
|
12
12
|
instance: string;
|
|
13
13
|
/** When that instance last wrote the file. */
|
|
14
14
|
observedAtMs: number;
|
|
15
|
+
/** Where claude said the conversation is (#1699: under a credential profile's own config dir for a profile). */
|
|
16
|
+
transcriptPath: string | null;
|
|
15
17
|
}
|
|
16
18
|
/** A statusline window's `resets_at` as epoch ms, or null when it has no usable reset time. */
|
|
17
19
|
export declare function statuslineResetsAtMs(value: unknown): number | null;
|
|
18
20
|
/**
|
|
19
21
|
* The most recent rate-limit reading any claude-code instance has written, or
|
|
20
|
-
* null when no instance has one worth using.
|
|
22
|
+
* null when no instance has one worth using. `from` keeps only the readings of
|
|
23
|
+
* one subscription (#1699): it is asked where each reading's conversation is —
|
|
24
|
+
* a credential profile's instances write under that profile's own config dir.
|
|
21
25
|
*/
|
|
22
|
-
export declare function readStatuslineRateLimits(agendHome?: string, now?: number): StatuslineRateLimits | null;
|
|
26
|
+
export declare function readStatuslineRateLimits(agendHome?: string, now?: number, from?: (transcriptPath: string | null) => boolean): StatuslineRateLimits | null;
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
* window is currently binding, or extra-usage credits; only the API has those.
|
|
32
32
|
*
|
|
33
33
|
* Merge rule across instances: **the freshest file wins.** Every instance on
|
|
34
|
-
*
|
|
34
|
+
* one subscription shares one account (a credential profile is a subscription
|
|
35
|
+
* of its own, #1699 — the caller says which), so they observe the same counter;
|
|
35
36
|
* when two readings disagree the newer one is simply the more recent
|
|
36
37
|
* observation. Taking the maximum instead would be wrong precisely when it
|
|
37
38
|
* matters — a pre-reset 95% from an hour ago would drown out a fresh 4%.
|
|
@@ -92,13 +93,16 @@ function readOne(dir, name, now) {
|
|
|
92
93
|
const sevenDay = parseWindow(rl.seven_day, now);
|
|
93
94
|
if (!fiveHour && !sevenDay)
|
|
94
95
|
return null;
|
|
95
|
-
|
|
96
|
+
const transcriptPath = typeof parsed.transcript_path === "string" && parsed.transcript_path ? parsed.transcript_path : null;
|
|
97
|
+
return { fiveHour, sevenDay, instance: name, observedAtMs, transcriptPath };
|
|
96
98
|
}
|
|
97
99
|
/**
|
|
98
100
|
* The most recent rate-limit reading any claude-code instance has written, or
|
|
99
|
-
* null when no instance has one worth using.
|
|
101
|
+
* null when no instance has one worth using. `from` keeps only the readings of
|
|
102
|
+
* one subscription (#1699): it is asked where each reading's conversation is —
|
|
103
|
+
* a credential profile's instances write under that profile's own config dir.
|
|
100
104
|
*/
|
|
101
|
-
export function readStatuslineRateLimits(agendHome = getAgendHome(), now = Date.now()) {
|
|
105
|
+
export function readStatuslineRateLimits(agendHome = getAgendHome(), now = Date.now(), from) {
|
|
102
106
|
const instancesDir = join(agendHome, "instances");
|
|
103
107
|
let names;
|
|
104
108
|
try {
|
|
@@ -116,6 +120,8 @@ export function readStatuslineRateLimits(agendHome = getAgendHome(), now = Date.
|
|
|
116
120
|
// the failure that makes people stop believing the panel.
|
|
117
121
|
if (now - reading.observedAtMs > STATUSLINE_MAX_AGE_MS)
|
|
118
122
|
continue;
|
|
123
|
+
if (from && !from(reading.transcriptPath))
|
|
124
|
+
continue;
|
|
119
125
|
if (!best || reading.observedAtMs > best.observedAtMs)
|
|
120
126
|
best = reading;
|
|
121
127
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statusline-usage.js","sourceRoot":"","sources":["../../src/usage/statusline-usage.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"statusline-usage.js","sourceRoot":"","sources":["../../src/usage/statusline-usage.ts"],"names":[],"mappings":"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"}
|
package/dist/web-api.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { type WebChatHistory } from "./web-chat-history.js";
|
|
|
9
9
|
import { type WebFileLedger } from "./web-upload.js";
|
|
10
10
|
import type { WebSessionStore } from "./web-session.js";
|
|
11
11
|
import { type WebPrefsStore } from "./web-prefs.js";
|
|
12
|
+
import { type MentionNames, type MentionQuery } from "./web-mentions.js";
|
|
12
13
|
import type { ExplicitInstanceRemoval } from "./instance-removal.js";
|
|
13
14
|
/**
|
|
14
15
|
* Push a single SSE frame to every client. If a client throws (closed socket
|
|
@@ -112,6 +113,8 @@ export interface WebApiContext {
|
|
|
112
113
|
previewForUi?(hostHeader: string | undefined, secure: boolean): PreviewAvailability & {
|
|
113
114
|
boot: string | null;
|
|
114
115
|
};
|
|
116
|
+
/** #1723: the same for a page of the public link (exposure `exposureId`, at `dashboardOrigin`); absent: none there. */
|
|
117
|
+
publicPreviewForUi?(exposureId: string, dashboardOrigin: string): PublicPreviewForUi;
|
|
115
118
|
emitSseEvent(event: string, data: unknown): void;
|
|
116
119
|
/** The web chat's recent messages (history + SSE replay); absent in contexts that have no chat. */
|
|
117
120
|
readonly webChatHistory?: WebChatHistory;
|
|
@@ -121,6 +124,8 @@ export interface WebApiContext {
|
|
|
121
124
|
webImage?(kind: "bot" | "avatar", key: string): CachedImage | null;
|
|
122
125
|
/** #1630: one Discord server emoji of the fleet's bots, from the host cache (fetched once); null for any other id. */
|
|
123
126
|
webEmoji?(id: string): Promise<CachedImage | null>;
|
|
127
|
+
/** #1682: names for mention ids (only those asked; from memory, never a request); absent: no names. */
|
|
128
|
+
webMentionNames?(query: MentionQuery): MentionNames;
|
|
124
129
|
/** #1630: the custom emoji of the servers the fleet's Discord bots are in. */
|
|
125
130
|
webEmojiList?(): Promise<Array<{
|
|
126
131
|
id: string;
|
|
@@ -214,6 +219,10 @@ export interface WebApiContext {
|
|
|
214
219
|
};
|
|
215
220
|
list(target?: string): unknown[];
|
|
216
221
|
create(params: unknown): unknown;
|
|
222
|
+
/** #1724: the update MCP update_schedule makes (cron and at clear each other). */
|
|
223
|
+
update?(id: string, params: Record<string, unknown>): unknown;
|
|
224
|
+
/** #1426: a pending retry of a schedule's occurrence, for the row's "retrying". */
|
|
225
|
+
getRetry?(id: string): unknown;
|
|
217
226
|
delete(id: string): void;
|
|
218
227
|
} | null;
|
|
219
228
|
}
|
|
@@ -221,10 +230,21 @@ export interface WebApiContext {
|
|
|
221
230
|
* Handle a Web UI request. Returns true if handled, false to pass through.
|
|
222
231
|
*/
|
|
223
232
|
export declare function handleWebRequest(req: IncomingMessage, res: ServerResponse, url: URL, ctx: WebApiContext): boolean;
|
|
233
|
+
/**
|
|
234
|
+
* #1723: a public-link page's preview: where it may frame from (`frameSrc` — the quick-tunnel wildcard while its own
|
|
235
|
+
* preview tunnel is starting or up, the exact origin for a configured web.preview_origin, null for none) and, once
|
|
236
|
+
* the preview tunnel failed, why (`detail`, a fixed reason the page words).
|
|
237
|
+
*/
|
|
238
|
+
export type PublicPreviewForUi = PreviewAvailability & {
|
|
239
|
+
boot: string | null;
|
|
240
|
+
frameSrc: string | null;
|
|
241
|
+
detail?: string;
|
|
242
|
+
};
|
|
224
243
|
export interface AppShellContext {
|
|
225
244
|
previewForUi?(hostHeader: string | undefined, secure: boolean): (PreviewAvailability & {
|
|
226
245
|
boot: string | null;
|
|
227
246
|
}) | null;
|
|
247
|
+
publicPreviewForUi?(exposureId: string, dashboardOrigin: string): PublicPreviewForUi;
|
|
228
248
|
/** #1523 N3: `web.usage_panel` — the header's ◔ is offered unless it is false (the page needs no probe read for it). */
|
|
229
249
|
readonly fleetConfig?: {
|
|
230
250
|
web?: {
|
package/dist/web-api.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withSpawnHoldReply } from "./spawn-hold-reply.js";
|
|
1
2
|
import { binaryProbe } from "./binary-probe.js";
|
|
2
3
|
import { settingsRequestExecution, settingsWrite, isSettingsReplay } from "./settings-request-capability.js";
|
|
3
4
|
import { readBoundedWebBody } from "./web-body.js";
|
|
@@ -16,7 +17,9 @@ import { shellRoute } from "./web-shell-routes.js";
|
|
|
16
17
|
import { readFileSync } from "node:fs";
|
|
17
18
|
import { join, dirname } from "node:path";
|
|
18
19
|
import { fileURLToPath } from "node:url";
|
|
19
|
-
import { CreateInstanceArgs, validateArgs } from "./outbound-schemas.js";
|
|
20
|
+
import { CreateInstanceArgs, CreateScheduleArgs, UpdateScheduleArgs, validateArgs } from "./outbound-schemas.js";
|
|
21
|
+
import { scheduleView } from "./web-schedules.js";
|
|
22
|
+
import { DEFAULT_SCHEDULER_CONFIG } from "./scheduler/index.js";
|
|
20
23
|
import { readStatuslineModel, resolveInstanceContext } from "./topic-commands.js";
|
|
21
24
|
import { z } from "zod";
|
|
22
25
|
import { evaluateWebRequest, isPassiveWebRead, isSecureRequest, isWebRequestAuthorized, WEB_TOKEN_INVALID_MESSAGE } from "./web-auth.js";
|
|
@@ -25,6 +28,8 @@ import { newWebMessageId, parseLastEventId } from "./web-chat-history.js";
|
|
|
25
28
|
import { attachmentDelivery, displayName, INLINE_MIME, isFileId, MEDIA_RANGE_MAX, publicAttachment, sniffUpload, UPLOAD_LIMITS, wellFormed } from "./web-upload.js";
|
|
26
29
|
import { getAgendHome } from "./paths.js";
|
|
27
30
|
import { OPERATOR } from "./web-prefs.js";
|
|
31
|
+
import { commitTeamCreate, commitTeamUpdate } from "./outbound-handlers.js";
|
|
32
|
+
import { parseMentionQuery } from "./web-mentions.js";
|
|
28
33
|
import { authorizeExplicitInstanceRemoval } from "./instance-removal.js";
|
|
29
34
|
import { measureSyncWork } from "./sync-work-attribution.js";
|
|
30
35
|
// ── Strict public-facing schemas ────────────────────────────────────────────
|
|
@@ -46,18 +51,17 @@ const TaskUpdateSchema = z.object({
|
|
|
46
51
|
description: z.string().max(MAX_TEXT).optional(),
|
|
47
52
|
priority: z.string().max(64).optional(),
|
|
48
53
|
}).strict();
|
|
49
|
-
const ScheduleCreateSchema = z.object({
|
|
50
|
-
cron: z.string().min(1).max(256),
|
|
51
|
-
message: z.string().min(1).max(MAX_TEXT),
|
|
52
|
-
target: z.string().min(1).max(256),
|
|
53
|
-
label: z.string().max(256).optional(),
|
|
54
|
-
timezone: z.string().max(128).optional(),
|
|
55
|
-
}).strict();
|
|
56
54
|
const TeamCreateSchema = z.object({
|
|
57
55
|
name: z.string().min(1).max(128).regex(/^[A-Za-z0-9._-]+$/),
|
|
58
56
|
members: z.array(z.string().min(1).max(256)).min(1).max(256),
|
|
59
57
|
description: z.string().max(MAX_TEXT).optional(),
|
|
60
58
|
}).strict();
|
|
59
|
+
// #1689: an edit of a team — its members as they should be, and its description ("" clears it). No name: a team is
|
|
60
|
+
// not renamed.
|
|
61
|
+
const TeamEditSchema = z.object({
|
|
62
|
+
members: z.array(z.string().min(1).max(256)).min(1).max(256),
|
|
63
|
+
description: z.string().max(MAX_TEXT).optional(),
|
|
64
|
+
}).strict();
|
|
61
65
|
const ConfigUpdateSchema = z.object({
|
|
62
66
|
channel: z.object({
|
|
63
67
|
group_id: z.union([z.number(), z.string()]).optional(),
|
|
@@ -420,6 +424,24 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
420
424
|
(ctx.webEmojiList ? ctx.webEmojiList() : Promise.resolve([])).then(answer, () => answer([]));
|
|
421
425
|
return true;
|
|
422
426
|
}
|
|
427
|
+
// #1682: the names of the mention ids a page found in what it rendered (at most MAX_MENTION_IDS, digits only) — only
|
|
428
|
+
// those, so no page (the public link included) ever gets a directory; from memory, never a request.
|
|
429
|
+
if (method === "GET" && path === "/ui/mentions") {
|
|
430
|
+
const parsed = parseMentionQuery(url.searchParams);
|
|
431
|
+
if (!parsed.ok) {
|
|
432
|
+
json(res, 400, { error: parsed.error });
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
let names = { u: {}, r: {}, c: {} };
|
|
436
|
+
try {
|
|
437
|
+
names = ctx.webMentionNames?.(parsed.query) ?? names;
|
|
438
|
+
}
|
|
439
|
+
catch (err) {
|
|
440
|
+
ctx.logger.debug({ err: err.message }, "mention names unavailable");
|
|
441
|
+
}
|
|
442
|
+
json(res, 200, names);
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
423
445
|
// ── Fleet prompts (C4): the hang / exit / interactive-prompt buttons, answerable here too ──
|
|
424
446
|
if (method === "GET" && path === "/ui/prompts") {
|
|
425
447
|
json(res, 200, { prompts: ctx.listWebPrompts?.() ?? [] });
|
|
@@ -499,6 +521,18 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
499
521
|
});
|
|
500
522
|
return true;
|
|
501
523
|
}
|
|
524
|
+
// #1723: the public link page's preview, asked while it says "Preview is starting…" — its state and, once up, its
|
|
525
|
+
// origin and boot. Public link only: a local page's preview is decided by its own load.
|
|
526
|
+
if (path === "/ui/preview-status" && method === "GET") {
|
|
527
|
+
const gateway = gatewayRequestContext(req);
|
|
528
|
+
if (!gateway || !ctx.publicPreviewForUi) {
|
|
529
|
+
json(res, 404, { error: "not offered here" });
|
|
530
|
+
return true;
|
|
531
|
+
}
|
|
532
|
+
const p = ctx.publicPreviewForUi(gateway.exposureId, gateway.expectedOrigin);
|
|
533
|
+
json(res, 200, { code: p.code, previewOrigin: p.previewOrigin, boot: p.previewOrigin ? p.boot : null, detail: p.detail ?? null });
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
502
536
|
// #1665: the web preferences that follow the person between devices. Read by any page of the panel, the public link
|
|
503
537
|
// included (it reads what is in force); written only by a signed-in admin session or the CLI's header token — never
|
|
504
538
|
// over the public link (absent from its route manifest, and refused here too), never by a read-only session.
|
|
@@ -720,13 +754,18 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
720
754
|
}
|
|
721
755
|
const topicMode = ctx.fleetConfig?.channel?.mode === "topic";
|
|
722
756
|
(async () => {
|
|
757
|
+
let held = false;
|
|
723
758
|
try {
|
|
724
|
-
await ctx.startInstance(name, config, topicMode ?? false);
|
|
759
|
+
await withSpawnHoldReply(async (message) => { held = true; json(res, 202, { held: true, message, instance: name }); }, () => ctx.startInstance(name, config, topicMode ?? false));
|
|
725
760
|
ctx.emitSseEvent("status", ctx.getUiStatus());
|
|
726
|
-
|
|
761
|
+
if (!held)
|
|
762
|
+
json(res, 200, { started: name });
|
|
727
763
|
}
|
|
728
764
|
catch (err) {
|
|
729
|
-
|
|
765
|
+
if (!held)
|
|
766
|
+
json(res, 500, { error: err.message });
|
|
767
|
+
else
|
|
768
|
+
ctx.logger.error({ err, name }, "Held instance action failed");
|
|
730
769
|
}
|
|
731
770
|
})();
|
|
732
771
|
return true;
|
|
@@ -839,14 +878,19 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
839
878
|
if (method === "POST" && restartMatch) {
|
|
840
879
|
const name = decodeURIComponent(restartMatch[1]);
|
|
841
880
|
(async () => {
|
|
881
|
+
let held = false;
|
|
842
882
|
try {
|
|
843
|
-
await ctx.restartSingleInstance(name, { explicit: true });
|
|
883
|
+
await withSpawnHoldReply(async (message) => { held = true; json(res, 202, { held: true, message, instance: name }); }, () => ctx.restartSingleInstance(name, { explicit: true }));
|
|
844
884
|
ctx.emitSseEvent("status", ctx.getUiStatus());
|
|
845
|
-
|
|
885
|
+
if (!held)
|
|
886
|
+
json(res, 200, { restarted: name });
|
|
846
887
|
}
|
|
847
888
|
catch (err) {
|
|
848
889
|
const status = err.message.includes("not found") ? 404 : 500;
|
|
849
|
-
|
|
890
|
+
if (!held)
|
|
891
|
+
json(res, status, { error: err.message });
|
|
892
|
+
else
|
|
893
|
+
ctx.logger.error({ err, name }, "Held instance action failed");
|
|
850
894
|
}
|
|
851
895
|
})();
|
|
852
896
|
return true;
|
|
@@ -964,10 +1008,28 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
964
1008
|
json(res, 200, { schedules: [] });
|
|
965
1009
|
return true;
|
|
966
1010
|
}
|
|
967
|
-
|
|
1011
|
+
// #1724: each row with its next run and one-shot time in the fleet's zone — worked out here, not on the page.
|
|
1012
|
+
const scheduler = ctx.scheduler;
|
|
1013
|
+
const now = Date.now();
|
|
1014
|
+
json(res, 200, {
|
|
1015
|
+
schedules: scheduler.list().map((s) => scheduleView(s, scheduler.getRetry?.(s.id) ?? null, now)),
|
|
1016
|
+
// The zone a new schedule (and a "Once" date and time typed without one) is read in.
|
|
1017
|
+
default_timezone: ctx.fleetConfig?.defaults?.scheduler?.default_timezone
|
|
1018
|
+
?? DEFAULT_SCHEDULER_CONFIG.default_timezone,
|
|
1019
|
+
});
|
|
968
1020
|
return true;
|
|
969
1021
|
}
|
|
1022
|
+
// #1724: who may write a schedule, asked again once the body is in (a session revoked meanwhile, a non-admin tier).
|
|
1023
|
+
const scheduleWriterGone = () => {
|
|
1024
|
+
const verdict = evaluateWebRequest(req, url, ctx.webToken ?? null, ctx.webSessions, { touch: false });
|
|
1025
|
+
return verdict.kind !== "allow" || (verdict.via === "session" && verdict.session?.tier !== "admin");
|
|
1026
|
+
};
|
|
970
1027
|
if (method === "POST" && path === "/ui/schedules") {
|
|
1028
|
+
// #1724: schedules are not created, edited or deleted over the public link (absent from its manifest too, #1689).
|
|
1029
|
+
if (gatewayRequestContext(req)) {
|
|
1030
|
+
json(res, 403, { error: "schedule_not_over_public_link" });
|
|
1031
|
+
return true;
|
|
1032
|
+
}
|
|
971
1033
|
if (!ctx.scheduler) {
|
|
972
1034
|
json(res, 500, { error: "Scheduler not initialized" });
|
|
973
1035
|
return true;
|
|
@@ -977,10 +1039,23 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
977
1039
|
const body = await parseBody(req);
|
|
978
1040
|
if (!permitWebContinuation(req, res, ctx))
|
|
979
1041
|
return;
|
|
980
|
-
|
|
1042
|
+
if (scheduleWriterGone()) {
|
|
1043
|
+
json(res, 401, { error: WEB_TOKEN_INVALID_MESSAGE });
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
// #1724: the create_schedule tool's own schema — cron or at (exactly one) — plus a target, which the web names.
|
|
1047
|
+
const parsed = parseOrReject(CreateScheduleArgs, body, res);
|
|
981
1048
|
if (!parsed)
|
|
982
1049
|
return;
|
|
983
|
-
|
|
1050
|
+
if (typeof parsed.target !== "string" || !parsed.target) {
|
|
1051
|
+
json(res, 400, { error: "target: an instance is required" });
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
const schedule = ctx.scheduler.create({
|
|
1055
|
+
cron: parsed.cron, at: parsed.at, message: parsed.message, target: parsed.target, label: parsed.label, timezone: parsed.timezone,
|
|
1056
|
+
...(parsed.silent !== undefined ? { silent: parsed.silent } : {}),
|
|
1057
|
+
source: "web", reply_chat_id: "", reply_thread_id: null,
|
|
1058
|
+
});
|
|
984
1059
|
json(res, 200, schedule);
|
|
985
1060
|
}
|
|
986
1061
|
catch (err) {
|
|
@@ -990,7 +1065,48 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
990
1065
|
return true;
|
|
991
1066
|
}
|
|
992
1067
|
const schedDelMatch = path.match(/^\/ui\/schedules\/(.+)$/);
|
|
1068
|
+
// #1724: Edit — the update_schedule tool's validation (UpdateScheduleArgs) and its call (scheduler.update): cron and at
|
|
1069
|
+
// clear each other there; silent is not touched.
|
|
1070
|
+
if (method === "PATCH" && schedDelMatch) {
|
|
1071
|
+
if (gatewayRequestContext(req)) {
|
|
1072
|
+
json(res, 403, { error: "schedule_not_over_public_link" });
|
|
1073
|
+
return true;
|
|
1074
|
+
}
|
|
1075
|
+
if (!ctx.scheduler || typeof ctx.scheduler.update !== "function") {
|
|
1076
|
+
json(res, 500, { error: "Scheduler not initialized" });
|
|
1077
|
+
return true;
|
|
1078
|
+
}
|
|
1079
|
+
const id = decodeURIComponent(schedDelMatch[1]);
|
|
1080
|
+
(async () => {
|
|
1081
|
+
try {
|
|
1082
|
+
const body = await parseBody(req);
|
|
1083
|
+
if (!permitWebContinuation(req, res, ctx))
|
|
1084
|
+
return;
|
|
1085
|
+
if (scheduleWriterGone()) {
|
|
1086
|
+
json(res, 401, { error: WEB_TOKEN_INVALID_MESSAGE });
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
if (!body || typeof body !== "object" || Array.isArray(body) || Object.hasOwn(body, "id")) {
|
|
1090
|
+
json(res, 400, { error: "expected the fields to change (the id is the path's)" });
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
const parsed = parseOrReject(UpdateScheduleArgs, { ...body, id }, res);
|
|
1094
|
+
if (!parsed)
|
|
1095
|
+
return;
|
|
1096
|
+
const { id: _id, ...changes } = parsed;
|
|
1097
|
+
json(res, 200, ctx.scheduler.update(id, changes));
|
|
1098
|
+
}
|
|
1099
|
+
catch (err) {
|
|
1100
|
+
json(res, 400, { error: err.message });
|
|
1101
|
+
}
|
|
1102
|
+
})();
|
|
1103
|
+
return true;
|
|
1104
|
+
}
|
|
993
1105
|
if (method === "DELETE" && schedDelMatch) {
|
|
1106
|
+
if (gatewayRequestContext(req)) {
|
|
1107
|
+
json(res, 403, { error: "schedule_not_over_public_link" });
|
|
1108
|
+
return true;
|
|
1109
|
+
}
|
|
994
1110
|
if (!ctx.scheduler) {
|
|
995
1111
|
json(res, 500, { error: "Scheduler not initialized" });
|
|
996
1112
|
return true;
|
|
@@ -1027,26 +1143,82 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
1027
1143
|
return true;
|
|
1028
1144
|
}
|
|
1029
1145
|
if (method === "POST" && path === "/ui/teams") {
|
|
1146
|
+
// #1689: not over the public link (absent from its manifest too), like editing and deleting a team.
|
|
1147
|
+
if (gatewayRequestContext(req)) {
|
|
1148
|
+
json(res, 403, { error: "teams_not_over_public_link" });
|
|
1149
|
+
return true;
|
|
1150
|
+
}
|
|
1030
1151
|
(async () => {
|
|
1031
1152
|
try {
|
|
1032
1153
|
const body = await parseBody(req);
|
|
1033
1154
|
if (!permitWebContinuation(req, res, ctx))
|
|
1034
1155
|
return;
|
|
1156
|
+
// #1692: who may write, asked again once the body is in (as the edit does).
|
|
1157
|
+
const verdict = evaluateWebRequest(req, url, ctx.webToken ?? null, ctx.webSessions, { touch: false });
|
|
1158
|
+
if (verdict.kind !== "allow" || (verdict.via === "session" && verdict.session?.tier !== "admin")) {
|
|
1159
|
+
json(res, 401, { error: WEB_TOKEN_INVALID_MESSAGE });
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1035
1162
|
const parsed = parseOrReject(TeamCreateSchema, body, res);
|
|
1036
1163
|
if (!parsed)
|
|
1037
1164
|
return;
|
|
1038
|
-
|
|
1039
|
-
|
|
1165
|
+
// #1692: the create_team path — members checked, an existing team never overwritten, a failed save undone.
|
|
1166
|
+
const r = commitTeamCreate(ctx.fleetConfig, parsed, () => ctx.saveFleetConfig());
|
|
1167
|
+
if (!r.ok) {
|
|
1168
|
+
json(res, r.status, { error: r.error });
|
|
1040
1169
|
return;
|
|
1041
1170
|
}
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1171
|
+
json(res, 200, r.result);
|
|
1172
|
+
}
|
|
1173
|
+
catch (err) {
|
|
1174
|
+
json(res, 400, { error: err.message });
|
|
1175
|
+
}
|
|
1176
|
+
})();
|
|
1177
|
+
return true;
|
|
1178
|
+
}
|
|
1179
|
+
// #1689: edit a team — the same update as the update_team tool (applyTeamUpdate), from the members the page sends:
|
|
1180
|
+
// what to add and remove is worked out against the team as it is now. Never over the public link.
|
|
1181
|
+
const teamEditMatch = path.match(/^\/ui\/teams\/(.+)$/);
|
|
1182
|
+
if ((method === "PATCH" || method === "PUT") && teamEditMatch) {
|
|
1183
|
+
if (gatewayRequestContext(req)) {
|
|
1184
|
+
json(res, 403, { error: "teams_not_over_public_link" });
|
|
1185
|
+
return true;
|
|
1186
|
+
}
|
|
1187
|
+
const name = decodeURIComponent(teamEditMatch[1]);
|
|
1188
|
+
// Who may write, asked again once the body is in (#1693 review): a session revoked or a token rotated meanwhile
|
|
1189
|
+
// writes nothing.
|
|
1190
|
+
const writerNow = () => {
|
|
1191
|
+
const verdict = evaluateWebRequest(req, url, ctx.webToken ?? null, ctx.webSessions, { touch: false });
|
|
1192
|
+
return verdict.kind === "allow" && !(verdict.via === "session" && verdict.session?.tier !== "admin");
|
|
1193
|
+
};
|
|
1194
|
+
(async () => {
|
|
1195
|
+
try {
|
|
1196
|
+
const body = await parseBody(req);
|
|
1197
|
+
if (!permitWebContinuation(req, res, ctx))
|
|
1198
|
+
return;
|
|
1199
|
+
if (!writerNow()) {
|
|
1200
|
+
json(res, 401, { error: WEB_TOKEN_INVALID_MESSAGE });
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
const parsed = parseOrReject(TeamEditSchema, body, res);
|
|
1204
|
+
if (!parsed)
|
|
1205
|
+
return;
|
|
1206
|
+
const teams = ctx.fleetConfig?.teams;
|
|
1207
|
+
const current = teams && Object.hasOwn(teams, name) ? teams[name].members : null;
|
|
1208
|
+
if (!current) {
|
|
1209
|
+
json(res, 404, { error: `Team not found: ${name}` });
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
const want = [...new Set(parsed.members)];
|
|
1213
|
+
// The update and its save as one: a failed save leaves the team as it was (500).
|
|
1214
|
+
const r = commitTeamUpdate(ctx.fleetConfig, {
|
|
1215
|
+
name, add: want.filter(m => !current.includes(m)), remove: current.filter(m => !want.includes(m)), description: parsed.description,
|
|
1216
|
+
}, () => ctx.saveFleetConfig());
|
|
1217
|
+
if (!r.ok) {
|
|
1218
|
+
json(res, r.status, { error: r.error });
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
json(res, 200, { updated: r.result });
|
|
1050
1222
|
}
|
|
1051
1223
|
catch (err) {
|
|
1052
1224
|
json(res, 400, { error: err.message });
|
|
@@ -1056,8 +1228,13 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
1056
1228
|
}
|
|
1057
1229
|
const teamDelMatch = path.match(/^\/ui\/teams\/(.+)$/);
|
|
1058
1230
|
if (method === "DELETE" && teamDelMatch) {
|
|
1231
|
+
if (gatewayRequestContext(req)) {
|
|
1232
|
+
json(res, 403, { error: "teams_not_over_public_link" });
|
|
1233
|
+
return true;
|
|
1234
|
+
}
|
|
1059
1235
|
const name = decodeURIComponent(teamDelMatch[1]);
|
|
1060
|
-
|
|
1236
|
+
// Own keys (#1689): a team is one fleet.yaml defines — "__proto__" or "constructor" is not found.
|
|
1237
|
+
if (!ctx.fleetConfig?.teams || !Object.hasOwn(ctx.fleetConfig.teams, name)) {
|
|
1061
1238
|
json(res, 404, { error: `Team not found: ${name}` });
|
|
1062
1239
|
return true;
|
|
1063
1240
|
}
|
|
@@ -1350,8 +1527,12 @@ function handleUpload(req, res, url, ctx) {
|
|
|
1350
1527
|
});
|
|
1351
1528
|
}
|
|
1352
1529
|
function shellPreview(req, ctx, mode) {
|
|
1353
|
-
if (mode !== "full"
|
|
1530
|
+
if (mode !== "full")
|
|
1354
1531
|
return null;
|
|
1532
|
+
// #1723: a page of the public link asks its own exposure's preview — never the local listener's Host-based choice.
|
|
1533
|
+
const gateway = gatewayRequestContext(req);
|
|
1534
|
+
if (gateway)
|
|
1535
|
+
return ctx.publicPreviewForUi?.(gateway.exposureId, gateway.expectedOrigin) ?? null;
|
|
1355
1536
|
return ctx.previewForUi?.(typeof req.headers.host === "string" ? req.headers.host : undefined, isSecureRequest(req)) ?? null;
|
|
1356
1537
|
}
|
|
1357
1538
|
/**
|
|
@@ -1422,6 +1603,8 @@ function shellBodyTag(req, ctx, mode) {
|
|
|
1422
1603
|
+ ` data-preview-boot="${attr(p?.previewOrigin ? p.boot : "")}" data-preview-reason="${attr(p ? p.reason : "Previews are not available on this fleet.")}"`
|
|
1423
1604
|
// #1554: no availability asked (the public link, or not the full app) still says why, in the page's language.
|
|
1424
1605
|
+ ` data-preview-reason-code="${attr(p ? p.code ?? "" : gatewayRequestContext(req) ? "publicLink" : "notOffered")}"`
|
|
1606
|
+
// #1723: why the public link's preview tunnel failed (a fixed reason the page words), when it did.
|
|
1607
|
+
+ `${p?.detail ? ` data-preview-detail="${attr(p.detail)}"` : ""}`
|
|
1425
1608
|
// #1523 N3: whether the fleet serves /api/ai-usage (web.usage_panel), for every page's ◔.
|
|
1426
1609
|
+ ` data-usage-panel="${ctx.fleetConfig?.web?.usage_panel === false ? "0" : "1"}"`
|
|
1427
1610
|
// #1589: the public link shows attachments as downloads only (no inline text or media): its scope is unchanged.
|
|
@@ -1438,7 +1621,10 @@ export function serveAppShell(req, res, ctx, mode) {
|
|
|
1438
1621
|
const own = mode === "full" && !gatewayRequestContext(req)
|
|
1439
1622
|
? dashboardOriginFor(typeof req.headers.host === "string" ? req.headers.host : undefined, isSecureRequest(req)) : null;
|
|
1440
1623
|
const mediaSrc = own ? `${own}/ui/file/` : "'none'";
|
|
1441
|
-
|
|
1624
|
+
// A public-link page frames what its exposure's preview names (the quick-tunnel wildcard: its host is not known when
|
|
1625
|
+
// the page may load); every other page frames exactly the origin this load chose (#1723).
|
|
1626
|
+
const frameSrc = p && "frameSrc" in p ? p.frameSrc : p?.previewOrigin ? `${p.previewOrigin}/frame` : null;
|
|
1627
|
+
sendPanelHtml(res, html.replace('<html lang="en">', shellHtmlTag(ctx, mode)).replace("<body>", shellBodyTag(req, ctx, mode)), 200, {}, { ...(frameSrc ? { frameSrc } : {}), mediaSrc });
|
|
1442
1628
|
}
|
|
1443
1629
|
catch {
|
|
1444
1630
|
json(res, 500, { error: "app.html not found" });
|