@songsid/agend 2.2.0-beta.3 → 2.2.0-beta.4
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 +39 -2
- package/dist/backend/claude-code.js +115 -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/config-validator.js +4 -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 +59 -3
- package/dist/fleet-manager.js +357 -71
- 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 +44 -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-web-gateway.js +4 -3
- package/dist/public-web-gateway.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/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 +2 -0
- package/dist/ui/chat-mentions.js +63 -0
- package/dist/ui/chat-render.js +25 -0
- package/dist/ui/chat-thread.js +26 -8
- package/dist/ui/panel-chat.js +17 -9
- package/dist/ui/panel-details.js +2 -0
- package/dist/ui/panel-fleet.js +51 -22
- package/dist/ui/panel-settings.js +3 -1
- package/dist/ui/preview-panel.js +48 -14
- package/dist/ui/preview.js +5 -1
- package/dist/ui/settings-token.js +1 -1
- package/dist/ui/shared/app-store.js +3 -1
- package/dist/ui/shared/app.css +11 -7
- package/dist/ui/shared/instance-nav.js +32 -7
- package/dist/ui/shared/view-strings.js +6 -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/web-api.d.ts +3 -0
- package/dist/web-api.js +115 -17
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.js +1 -1
- 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/package.json +1 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #1691: the shape of a prompt a CLI is waiting on, read from a pane — for prompts no backend table knows (a new CLI
|
|
3
|
+
* version's dialog, #1690's "Switch model?"). Pure: the daemon decides when a shape counts (no known dialog, no idle
|
|
4
|
+
* composer, unchanged for a minute) and never answers it.
|
|
5
|
+
*
|
|
6
|
+
* A shape is bottom-anchored: the prompt is the last thing on screen apart from a short footer, so an agent's reply
|
|
7
|
+
* that merely contains a numbered list (or "[y/n]") above the CLI's own prompt rows is not one.
|
|
8
|
+
*/
|
|
9
|
+
export interface PromptShape {
|
|
10
|
+
/** "numbered": a cursor glyph on a numbered option, with a neighbouring option; "yes-no": a [y/n]-style question. */
|
|
11
|
+
kind: "numbered" | "yes-no";
|
|
12
|
+
/** Numbered options seen (0 for yes-no). */
|
|
13
|
+
options: number;
|
|
14
|
+
/** The prompt's question/title line, sanitised and bounded (see questionLine); null when there is none. */
|
|
15
|
+
question: string | null;
|
|
16
|
+
/** The prompt's rows, from the question to the last option — its identity (the same block is the same prompt). */
|
|
17
|
+
block: string;
|
|
18
|
+
}
|
|
19
|
+
export declare const QUESTION_MAX = 80;
|
|
20
|
+
/** One short line for a notice: escapes and control characters out, whitespace collapsed, at most QUESTION_MAX. */
|
|
21
|
+
export declare function questionLine(raw: string): string;
|
|
22
|
+
/** The prompt the pane is showing at its bottom, or null. */
|
|
23
|
+
export declare function unrecognisedPromptShape(pane: string): PromptShape | null;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #1691: the shape of a prompt a CLI is waiting on, read from a pane — for prompts no backend table knows (a new CLI
|
|
3
|
+
* version's dialog, #1690's "Switch model?"). Pure: the daemon decides when a shape counts (no known dialog, no idle
|
|
4
|
+
* composer, unchanged for a minute) and never answers it.
|
|
5
|
+
*
|
|
6
|
+
* A shape is bottom-anchored: the prompt is the last thing on screen apart from a short footer, so an agent's reply
|
|
7
|
+
* that merely contains a numbered list (or "[y/n]") above the CLI's own prompt rows is not one.
|
|
8
|
+
*/
|
|
9
|
+
/** Rows looked at, counted from the bottom (non-empty rows only). */
|
|
10
|
+
const BOTTOM_ROWS = 20;
|
|
11
|
+
/** Rows a prompt may have below its last option or question (a hint footer: "Enter to confirm · Esc to cancel"). */
|
|
12
|
+
const FOOTER_ROWS = 3;
|
|
13
|
+
/** How far above the first option the question is looked for. */
|
|
14
|
+
const QUESTION_ROWS = 8;
|
|
15
|
+
export const QUESTION_MAX = 80;
|
|
16
|
+
const CURSOR_OPTION = /^[ \t]*[❯›>][ \t]*(\d{1,2})[.)][ \t]+\S/;
|
|
17
|
+
const OPTION = /^[ \t]*(?:[❯›>][ \t]*)?(\d{1,2})[.)][ \t]+\S/;
|
|
18
|
+
const YES_NO = /[[(][ \t]*[yY][ \t]*\/[ \t]*[nN][ \t]*[\])][ \t]*[:?]?[ \t]*$/;
|
|
19
|
+
/** A rule or border row (box drawing, block elements, dashes): the top of a dialog. */
|
|
20
|
+
const RULE = /^[ \t]*[─━═╌┄▔▁_\-=]{8,}[ \t]*$/;
|
|
21
|
+
const ANSI = /\x1b\[[0-9;?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g;
|
|
22
|
+
/** One short line for a notice: escapes and control characters out, whitespace collapsed, at most QUESTION_MAX. */
|
|
23
|
+
export function questionLine(raw) {
|
|
24
|
+
const text = raw.replace(ANSI, "").replace(/[\x00-\x1f\x7f]/g, " ").replace(/\s+/g, " ").trim();
|
|
25
|
+
return text.length > QUESTION_MAX ? `${text.slice(0, QUESTION_MAX - 1)}…` : text;
|
|
26
|
+
}
|
|
27
|
+
/** The prompt the pane is showing at its bottom, or null. */
|
|
28
|
+
export function unrecognisedPromptShape(pane) {
|
|
29
|
+
const rows = pane.replace(ANSI, "").replace(/\r/g, "").split("\n").map(r => r.trimEnd());
|
|
30
|
+
const filled = [];
|
|
31
|
+
for (let i = rows.length - 1; i >= 0 && filled.length < BOTTOM_ROWS; i--)
|
|
32
|
+
if (rows[i].trim() !== "")
|
|
33
|
+
filled.unshift(i);
|
|
34
|
+
if (filled.length === 0)
|
|
35
|
+
return null;
|
|
36
|
+
const after = (row) => filled.filter(i => i > row).length;
|
|
37
|
+
// A cursor on a numbered option, with its neighbours: the contiguous run of option rows around it.
|
|
38
|
+
for (let k = filled.length - 1; k >= 0; k--) {
|
|
39
|
+
const at = filled[k];
|
|
40
|
+
const cursor = CURSOR_OPTION.exec(rows[at]);
|
|
41
|
+
if (!cursor)
|
|
42
|
+
continue;
|
|
43
|
+
let first = at, last = at;
|
|
44
|
+
while (first - 1 >= 0 && OPTION.test(rows[first - 1]))
|
|
45
|
+
first--;
|
|
46
|
+
while (last + 1 < rows.length && OPTION.test(rows[last + 1]))
|
|
47
|
+
last++;
|
|
48
|
+
const numbers = rows.slice(first, last + 1).map(r => Number(OPTION.exec(r)[1]));
|
|
49
|
+
const consecutive = numbers.every((n, i) => i === 0 || n === numbers[i - 1] + 1);
|
|
50
|
+
if (numbers.length < 2 || !consecutive || after(last) > FOOTER_ROWS)
|
|
51
|
+
return null;
|
|
52
|
+
// The question: the top of the dialog — the first row under a rule/border, else the topmost filled row — within
|
|
53
|
+
// QUESTION_ROWS above the options.
|
|
54
|
+
let top = -1;
|
|
55
|
+
for (let i = first - 1; i >= 0 && first - i <= QUESTION_ROWS; i--) {
|
|
56
|
+
if (RULE.test(rows[i]))
|
|
57
|
+
break;
|
|
58
|
+
if (rows[i].trim() !== "")
|
|
59
|
+
top = i;
|
|
60
|
+
}
|
|
61
|
+
const question = top >= 0 ? questionLine(rows[top]) : null;
|
|
62
|
+
const block = rows.slice(top >= 0 ? top : first, last + 1).join("\n");
|
|
63
|
+
return { kind: "numbered", options: numbers.length, question: question || null, block };
|
|
64
|
+
}
|
|
65
|
+
// A [y/n] question at the bottom.
|
|
66
|
+
for (let k = filled.length - 1; k >= 0 && filled.length - 1 - k <= FOOTER_ROWS; k--) {
|
|
67
|
+
const at = filled[k];
|
|
68
|
+
if (!YES_NO.test(rows[at]))
|
|
69
|
+
continue;
|
|
70
|
+
return { kind: "yes-no", options: 0, question: questionLine(rows[at]) || null, block: rows[at].trim() };
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=unrecognised-prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unrecognised-prompt.js","sourceRoot":"","sources":["../src/unrecognised-prompt.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
|
|
@@ -121,6 +122,8 @@ export interface WebApiContext {
|
|
|
121
122
|
webImage?(kind: "bot" | "avatar", key: string): CachedImage | null;
|
|
122
123
|
/** #1630: one Discord server emoji of the fleet's bots, from the host cache (fetched once); null for any other id. */
|
|
123
124
|
webEmoji?(id: string): Promise<CachedImage | null>;
|
|
125
|
+
/** #1682: names for mention ids (only those asked; from memory, never a request); absent: no names. */
|
|
126
|
+
webMentionNames?(query: MentionQuery): MentionNames;
|
|
124
127
|
/** #1630: the custom emoji of the servers the fleet's Discord bots are in. */
|
|
125
128
|
webEmojiList?(): Promise<Array<{
|
|
126
129
|
id: string;
|
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";
|
|
@@ -25,6 +26,8 @@ import { newWebMessageId, parseLastEventId } from "./web-chat-history.js";
|
|
|
25
26
|
import { attachmentDelivery, displayName, INLINE_MIME, isFileId, MEDIA_RANGE_MAX, publicAttachment, sniffUpload, UPLOAD_LIMITS, wellFormed } from "./web-upload.js";
|
|
26
27
|
import { getAgendHome } from "./paths.js";
|
|
27
28
|
import { OPERATOR } from "./web-prefs.js";
|
|
29
|
+
import { commitTeamCreate, commitTeamUpdate } from "./outbound-handlers.js";
|
|
30
|
+
import { parseMentionQuery } from "./web-mentions.js";
|
|
28
31
|
import { authorizeExplicitInstanceRemoval } from "./instance-removal.js";
|
|
29
32
|
import { measureSyncWork } from "./sync-work-attribution.js";
|
|
30
33
|
// ── Strict public-facing schemas ────────────────────────────────────────────
|
|
@@ -58,6 +61,12 @@ const TeamCreateSchema = z.object({
|
|
|
58
61
|
members: z.array(z.string().min(1).max(256)).min(1).max(256),
|
|
59
62
|
description: z.string().max(MAX_TEXT).optional(),
|
|
60
63
|
}).strict();
|
|
64
|
+
// #1689: an edit of a team — its members as they should be, and its description ("" clears it). No name: a team is
|
|
65
|
+
// not renamed.
|
|
66
|
+
const TeamEditSchema = z.object({
|
|
67
|
+
members: z.array(z.string().min(1).max(256)).min(1).max(256),
|
|
68
|
+
description: z.string().max(MAX_TEXT).optional(),
|
|
69
|
+
}).strict();
|
|
61
70
|
const ConfigUpdateSchema = z.object({
|
|
62
71
|
channel: z.object({
|
|
63
72
|
group_id: z.union([z.number(), z.string()]).optional(),
|
|
@@ -420,6 +429,24 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
420
429
|
(ctx.webEmojiList ? ctx.webEmojiList() : Promise.resolve([])).then(answer, () => answer([]));
|
|
421
430
|
return true;
|
|
422
431
|
}
|
|
432
|
+
// #1682: the names of the mention ids a page found in what it rendered (at most MAX_MENTION_IDS, digits only) — only
|
|
433
|
+
// those, so no page (the public link included) ever gets a directory; from memory, never a request.
|
|
434
|
+
if (method === "GET" && path === "/ui/mentions") {
|
|
435
|
+
const parsed = parseMentionQuery(url.searchParams);
|
|
436
|
+
if (!parsed.ok) {
|
|
437
|
+
json(res, 400, { error: parsed.error });
|
|
438
|
+
return true;
|
|
439
|
+
}
|
|
440
|
+
let names = { u: {}, r: {}, c: {} };
|
|
441
|
+
try {
|
|
442
|
+
names = ctx.webMentionNames?.(parsed.query) ?? names;
|
|
443
|
+
}
|
|
444
|
+
catch (err) {
|
|
445
|
+
ctx.logger.debug({ err: err.message }, "mention names unavailable");
|
|
446
|
+
}
|
|
447
|
+
json(res, 200, names);
|
|
448
|
+
return true;
|
|
449
|
+
}
|
|
423
450
|
// ── Fleet prompts (C4): the hang / exit / interactive-prompt buttons, answerable here too ──
|
|
424
451
|
if (method === "GET" && path === "/ui/prompts") {
|
|
425
452
|
json(res, 200, { prompts: ctx.listWebPrompts?.() ?? [] });
|
|
@@ -720,13 +747,18 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
720
747
|
}
|
|
721
748
|
const topicMode = ctx.fleetConfig?.channel?.mode === "topic";
|
|
722
749
|
(async () => {
|
|
750
|
+
let held = false;
|
|
723
751
|
try {
|
|
724
|
-
await ctx.startInstance(name, config, topicMode ?? false);
|
|
752
|
+
await withSpawnHoldReply(async (message) => { held = true; json(res, 202, { held: true, message, instance: name }); }, () => ctx.startInstance(name, config, topicMode ?? false));
|
|
725
753
|
ctx.emitSseEvent("status", ctx.getUiStatus());
|
|
726
|
-
|
|
754
|
+
if (!held)
|
|
755
|
+
json(res, 200, { started: name });
|
|
727
756
|
}
|
|
728
757
|
catch (err) {
|
|
729
|
-
|
|
758
|
+
if (!held)
|
|
759
|
+
json(res, 500, { error: err.message });
|
|
760
|
+
else
|
|
761
|
+
ctx.logger.error({ err, name }, "Held instance action failed");
|
|
730
762
|
}
|
|
731
763
|
})();
|
|
732
764
|
return true;
|
|
@@ -839,14 +871,19 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
839
871
|
if (method === "POST" && restartMatch) {
|
|
840
872
|
const name = decodeURIComponent(restartMatch[1]);
|
|
841
873
|
(async () => {
|
|
874
|
+
let held = false;
|
|
842
875
|
try {
|
|
843
|
-
await ctx.restartSingleInstance(name, { explicit: true });
|
|
876
|
+
await withSpawnHoldReply(async (message) => { held = true; json(res, 202, { held: true, message, instance: name }); }, () => ctx.restartSingleInstance(name, { explicit: true }));
|
|
844
877
|
ctx.emitSseEvent("status", ctx.getUiStatus());
|
|
845
|
-
|
|
878
|
+
if (!held)
|
|
879
|
+
json(res, 200, { restarted: name });
|
|
846
880
|
}
|
|
847
881
|
catch (err) {
|
|
848
882
|
const status = err.message.includes("not found") ? 404 : 500;
|
|
849
|
-
|
|
883
|
+
if (!held)
|
|
884
|
+
json(res, status, { error: err.message });
|
|
885
|
+
else
|
|
886
|
+
ctx.logger.error({ err, name }, "Held instance action failed");
|
|
850
887
|
}
|
|
851
888
|
})();
|
|
852
889
|
return true;
|
|
@@ -1027,26 +1064,82 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
1027
1064
|
return true;
|
|
1028
1065
|
}
|
|
1029
1066
|
if (method === "POST" && path === "/ui/teams") {
|
|
1067
|
+
// #1689: not over the public link (absent from its manifest too), like editing and deleting a team.
|
|
1068
|
+
if (gatewayRequestContext(req)) {
|
|
1069
|
+
json(res, 403, { error: "teams_not_over_public_link" });
|
|
1070
|
+
return true;
|
|
1071
|
+
}
|
|
1030
1072
|
(async () => {
|
|
1031
1073
|
try {
|
|
1032
1074
|
const body = await parseBody(req);
|
|
1033
1075
|
if (!permitWebContinuation(req, res, ctx))
|
|
1034
1076
|
return;
|
|
1077
|
+
// #1692: who may write, asked again once the body is in (as the edit does).
|
|
1078
|
+
const verdict = evaluateWebRequest(req, url, ctx.webToken ?? null, ctx.webSessions, { touch: false });
|
|
1079
|
+
if (verdict.kind !== "allow" || (verdict.via === "session" && verdict.session?.tier !== "admin")) {
|
|
1080
|
+
json(res, 401, { error: WEB_TOKEN_INVALID_MESSAGE });
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1035
1083
|
const parsed = parseOrReject(TeamCreateSchema, body, res);
|
|
1036
1084
|
if (!parsed)
|
|
1037
1085
|
return;
|
|
1038
|
-
|
|
1039
|
-
|
|
1086
|
+
// #1692: the create_team path — members checked, an existing team never overwritten, a failed save undone.
|
|
1087
|
+
const r = commitTeamCreate(ctx.fleetConfig, parsed, () => ctx.saveFleetConfig());
|
|
1088
|
+
if (!r.ok) {
|
|
1089
|
+
json(res, r.status, { error: r.error });
|
|
1040
1090
|
return;
|
|
1041
1091
|
}
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1092
|
+
json(res, 200, r.result);
|
|
1093
|
+
}
|
|
1094
|
+
catch (err) {
|
|
1095
|
+
json(res, 400, { error: err.message });
|
|
1096
|
+
}
|
|
1097
|
+
})();
|
|
1098
|
+
return true;
|
|
1099
|
+
}
|
|
1100
|
+
// #1689: edit a team — the same update as the update_team tool (applyTeamUpdate), from the members the page sends:
|
|
1101
|
+
// what to add and remove is worked out against the team as it is now. Never over the public link.
|
|
1102
|
+
const teamEditMatch = path.match(/^\/ui\/teams\/(.+)$/);
|
|
1103
|
+
if ((method === "PATCH" || method === "PUT") && teamEditMatch) {
|
|
1104
|
+
if (gatewayRequestContext(req)) {
|
|
1105
|
+
json(res, 403, { error: "teams_not_over_public_link" });
|
|
1106
|
+
return true;
|
|
1107
|
+
}
|
|
1108
|
+
const name = decodeURIComponent(teamEditMatch[1]);
|
|
1109
|
+
// Who may write, asked again once the body is in (#1693 review): a session revoked or a token rotated meanwhile
|
|
1110
|
+
// writes nothing.
|
|
1111
|
+
const writerNow = () => {
|
|
1112
|
+
const verdict = evaluateWebRequest(req, url, ctx.webToken ?? null, ctx.webSessions, { touch: false });
|
|
1113
|
+
return verdict.kind === "allow" && !(verdict.via === "session" && verdict.session?.tier !== "admin");
|
|
1114
|
+
};
|
|
1115
|
+
(async () => {
|
|
1116
|
+
try {
|
|
1117
|
+
const body = await parseBody(req);
|
|
1118
|
+
if (!permitWebContinuation(req, res, ctx))
|
|
1119
|
+
return;
|
|
1120
|
+
if (!writerNow()) {
|
|
1121
|
+
json(res, 401, { error: WEB_TOKEN_INVALID_MESSAGE });
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
const parsed = parseOrReject(TeamEditSchema, body, res);
|
|
1125
|
+
if (!parsed)
|
|
1126
|
+
return;
|
|
1127
|
+
const teams = ctx.fleetConfig?.teams;
|
|
1128
|
+
const current = teams && Object.hasOwn(teams, name) ? teams[name].members : null;
|
|
1129
|
+
if (!current) {
|
|
1130
|
+
json(res, 404, { error: `Team not found: ${name}` });
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
const want = [...new Set(parsed.members)];
|
|
1134
|
+
// The update and its save as one: a failed save leaves the team as it was (500).
|
|
1135
|
+
const r = commitTeamUpdate(ctx.fleetConfig, {
|
|
1136
|
+
name, add: want.filter(m => !current.includes(m)), remove: current.filter(m => !want.includes(m)), description: parsed.description,
|
|
1137
|
+
}, () => ctx.saveFleetConfig());
|
|
1138
|
+
if (!r.ok) {
|
|
1139
|
+
json(res, r.status, { error: r.error });
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
json(res, 200, { updated: r.result });
|
|
1050
1143
|
}
|
|
1051
1144
|
catch (err) {
|
|
1052
1145
|
json(res, 400, { error: err.message });
|
|
@@ -1056,8 +1149,13 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
1056
1149
|
}
|
|
1057
1150
|
const teamDelMatch = path.match(/^\/ui\/teams\/(.+)$/);
|
|
1058
1151
|
if (method === "DELETE" && teamDelMatch) {
|
|
1152
|
+
if (gatewayRequestContext(req)) {
|
|
1153
|
+
json(res, 403, { error: "teams_not_over_public_link" });
|
|
1154
|
+
return true;
|
|
1155
|
+
}
|
|
1059
1156
|
const name = decodeURIComponent(teamDelMatch[1]);
|
|
1060
|
-
|
|
1157
|
+
// Own keys (#1689): a team is one fleet.yaml defines — "__proto__" or "constructor" is not found.
|
|
1158
|
+
if (!ctx.fleetConfig?.teams || !Object.hasOwn(ctx.fleetConfig.teams, name)) {
|
|
1061
1159
|
json(res, 404, { error: `Team not found: ${name}` });
|
|
1062
1160
|
return true;
|
|
1063
1161
|
}
|