@epoch-agent/server 0.20.1 → 0.22.0
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 +77 -21
- package/dist/index.d.ts +196 -8
- package/dist/index.js +136 -19
- package/dist/web/assets/index-B9qjNzNP.js +378 -0
- package/dist/web/assets/index-CaCOKEeN.css +1 -0
- package/dist/web/index.html +13 -7
- package/package.json +3 -3
- package/dist/web/assets/index-DB70ukf7.css +0 -1
- package/dist/web/assets/index-yz-IciZ8.js +0 -378
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createServer } from 'http';
|
|
2
2
|
import { createLogger, t, ApprovalRelay, QuestionRelay, serializeStream, withRoleScope, withModelScope, withToolScope, nextRunAt, observeBackgroundTasks, skillIndexDescription, SANDBOX_EXCLUDES, SANDBOX_COVERS, canProbeGit, guardSpawn, isSystemOpenable, backgroundTaskOutput, listBackgroundTasks, providerLabel, MAX_REASON_CHARS, MAX_OBJECTIVE_CHARS, MAX_GOAL_MAX_ROUNDS, DEFAULT_GOAL_MAX_ROUNDS, SCHEDULE_DEFAULT_ALLOWLIST, SCHEDULE_FORM_BUDGET_USD, SCHEDULE_DEFAULTS, coalesceRecording, readRecording } from '@epoch-agent/runtime';
|
|
3
3
|
import { timingSafeEqual, randomBytes, randomUUID } from 'crypto';
|
|
4
|
-
import { PLAN_OUTCOMES, WIRE_AUTH_COOKIE, WIRE_AUTH_COOKIE_ATTRS, WIRE_AUTH_TOKEN_PARAM, extractAllMentions, MAX_ATTACH_FILES, utf8ByteLength, MAX_SESSION_REFS, attachSessionSurface, isWireRewindScope, WIRE_REWIND_SCOPES, mcpConnectorState, MAX_ATTACH_TOTAL_BYTES, MAX_ATTACH_BYTES, truncateToBudget, WIRE_LANG_PARAM, isLang, conformsToWire, WIRE_LAST_SESSION_FIELDS, isQuestionAnswerMap, isProviderType, apiKeyEnvVar, WIRE_FILE_STAT_MAX, WIRE_SETTING_WRITE_LAYERS, isWirePlanAction, WIRE_PLAN_ACTIONS, isPermissionLevel, PERMISSION_LEVELS, WIRE_TRUST_REQUEST_FIELDS, WIRE_FORGET_WORKSPACE_FIELDS, collectPendingApprovals, unfixedRules, isWireSettingWriteLayer, parseModelRef, WIRE_GOAL_ACTIONS, OPERATION_TYPES, isOperationType, WIRE_PLUGIN_UPLOAD_FORM_FIELDS } from '@epoch-agent/protocol';
|
|
4
|
+
import { PLAN_OUTCOMES, WIRE_AUTH_COOKIE, WIRE_AUTH_COOKIE_ATTRS, WIRE_AUTH_TOKEN_PARAM, extractAllMentions, MAX_ATTACH_FILES, utf8ByteLength, MAX_SESSION_REFS, attachSessionSurface, isWireRewindScope, WIRE_REWIND_SCOPES, mcpConnectorState, MAX_ATTACH_TOTAL_BYTES, MAX_ATTACH_BYTES, truncateToBudget, isAppearanceTheme, isAppearanceLang, WIRE_LANG_PARAM, isLang, conformsToWire, WIRE_LAST_SESSION_FIELDS, isQuestionAnswerMap, isProviderType, apiKeyEnvVar, WIRE_FILE_STAT_MAX, WIRE_SETTING_WRITE_LAYERS, isWirePlanAction, WIRE_PLAN_ACTIONS, isPermissionLevel, PERMISSION_LEVELS, WIRE_TRUST_REQUEST_FIELDS, WIRE_FORGET_WORKSPACE_FIELDS, collectPendingApprovals, unfixedRules, isWireSettingWriteLayer, parseModelRef, WIRE_GOAL_ACTIONS, OPERATION_TYPES, isOperationType, WIRE_PLUGIN_UPLOAD_FORM_FIELDS } from '@epoch-agent/protocol';
|
|
5
5
|
import { resolve, join, relative, isAbsolute, sep, dirname, basename, extname } from 'path';
|
|
6
6
|
import { existsSync, realpathSync, statSync, mkdirSync, createReadStream, readdirSync, createWriteStream, accessSync, constants } from 'fs';
|
|
7
7
|
import { stat, readFile, rm } from 'fs/promises';
|
|
@@ -129,12 +129,28 @@ function generateToken() {
|
|
|
129
129
|
}
|
|
130
130
|
var UI_THEME_PARAM = "theme";
|
|
131
131
|
var UI_LANG_PARAM = "lang";
|
|
132
|
+
function storedUiPreset(stored) {
|
|
133
|
+
const preset = {};
|
|
134
|
+
if (isAppearanceTheme(stored.theme)) preset.theme = stored.theme;
|
|
135
|
+
if (isAppearanceLang(stored.lang)) preset.lang = stored.lang;
|
|
136
|
+
return preset;
|
|
137
|
+
}
|
|
138
|
+
function resolveUiPreset(host, stored) {
|
|
139
|
+
return { ...host, ...storedUiPreset(stored) };
|
|
140
|
+
}
|
|
141
|
+
function uiPresetParams(ui) {
|
|
142
|
+
const params = [];
|
|
143
|
+
if (ui.theme) params.push([UI_THEME_PARAM, ui.theme]);
|
|
144
|
+
if (ui.lang) params.push([UI_LANG_PARAM, ui.lang]);
|
|
145
|
+
return params;
|
|
146
|
+
}
|
|
132
147
|
function firstScreenUrl(host, port, token, ui = {}) {
|
|
133
148
|
const shown = displayHost(host);
|
|
134
149
|
const authority = shown.includes(":") && !shown.startsWith("[") ? `[${shown}]` : shown;
|
|
135
150
|
const params = [`${WIRE_AUTH_TOKEN_PARAM}=${encodeURIComponent(token)}`];
|
|
136
|
-
|
|
137
|
-
|
|
151
|
+
for (const [name, value] of uiPresetParams(ui)) {
|
|
152
|
+
params.push(`${name}=${encodeURIComponent(value)}`);
|
|
153
|
+
}
|
|
138
154
|
return `http://${authority}:${port}/?${params.join("&")}`;
|
|
139
155
|
}
|
|
140
156
|
var HOST_COMPOSE_TTL_MS = 10 * 6e4;
|
|
@@ -262,6 +278,7 @@ var SessionRegistry = class {
|
|
|
262
278
|
turnStartedAt: null,
|
|
263
279
|
lastFinish: null,
|
|
264
280
|
persisted: null,
|
|
281
|
+
announcedTitle: null,
|
|
265
282
|
queue: [],
|
|
266
283
|
lastActiveTick: ++this.tick
|
|
267
284
|
});
|
|
@@ -353,10 +370,13 @@ var EMPTY_INVENTORY = {
|
|
|
353
370
|
policies: 0,
|
|
354
371
|
settingsFiles: []
|
|
355
372
|
};
|
|
356
|
-
function toWireBinding(state, bound) {
|
|
357
|
-
if (state === "bound"
|
|
358
|
-
return { state: "bound", workspace: toWireSessionWorkspace(bound) };
|
|
359
|
-
|
|
373
|
+
function toWireBinding(state, bound, changeable) {
|
|
374
|
+
if (state === "bound") {
|
|
375
|
+
if (bound) return { state: "bound", workspace: toWireSessionWorkspace(bound), changeable };
|
|
376
|
+
return { state: "unbound" };
|
|
377
|
+
}
|
|
378
|
+
if (state === "none") return { state: "none", changeable };
|
|
379
|
+
return { state: "unbound" };
|
|
360
380
|
}
|
|
361
381
|
function toWireWorkspaceRef(bound) {
|
|
362
382
|
if (!bound) return null;
|
|
@@ -442,6 +462,7 @@ function toWireSummary(id, hub, row, extra = {}) {
|
|
|
442
462
|
lastFinish: state.lastFinish,
|
|
443
463
|
role: extra.role ?? null,
|
|
444
464
|
archived: row?.archived ?? false,
|
|
465
|
+
pinned: row?.pinned ?? false,
|
|
445
466
|
...extra.snippet === void 0 ? {} : { snippet: extra.snippet }
|
|
446
467
|
};
|
|
447
468
|
}
|
|
@@ -474,7 +495,9 @@ function listSessionSummaries(hubStates, catalog, query, workspaceOf = () => nul
|
|
|
474
495
|
return hits;
|
|
475
496
|
}
|
|
476
497
|
const rows = catalog?.list(limit, archived ? { archived: true } : {}) ?? [];
|
|
477
|
-
const
|
|
498
|
+
const pinnedRows = archived ? [] : catalog?.list(limit, { pinnedOnly: true }) ?? [];
|
|
499
|
+
const listedIds = new Set(rows.map((r) => r.id));
|
|
500
|
+
const summaries = [...rows, ...pinnedRows.filter((r) => !listedIds.has(r.id))].map(
|
|
478
501
|
(row) => toWireSummary(row.id, byId.get(row.id), row, {
|
|
479
502
|
workspace: workspaceOf(row.id),
|
|
480
503
|
role: roleOf(row.id)
|
|
@@ -487,6 +510,7 @@ function listSessionSummaries(hubStates, catalog, query, workspaceOf = () => nul
|
|
|
487
510
|
return [...fresh, ...summaries].sort(compareSummaries).slice(0, limit);
|
|
488
511
|
}
|
|
489
512
|
function compareSummaries(a, b) {
|
|
513
|
+
if (a.pinned !== b.pinned) return a.pinned ? -1 : 1;
|
|
490
514
|
if (a.live !== b.live) return a.live ? -1 : 1;
|
|
491
515
|
const at = a.updatedAt ?? Number.POSITIVE_INFINITY;
|
|
492
516
|
const bt = b.updatedAt ?? Number.POSITIVE_INFINITY;
|
|
@@ -499,6 +523,7 @@ var SessionHub = class {
|
|
|
499
523
|
idleGraceMs;
|
|
500
524
|
clock;
|
|
501
525
|
onCooled;
|
|
526
|
+
readTitle;
|
|
502
527
|
registry;
|
|
503
528
|
sinks = /* @__PURE__ */ new Set();
|
|
504
529
|
observers = /* @__PURE__ */ new Set();
|
|
@@ -512,6 +537,7 @@ var SessionHub = class {
|
|
|
512
537
|
this.idleGraceMs = opts.idleGraceMs ?? 3e4;
|
|
513
538
|
this.clock = opts.clock ?? Date.now;
|
|
514
539
|
this.onCooled = opts.onCooled;
|
|
540
|
+
this.readTitle = opts.readTitle;
|
|
515
541
|
this.registry = new SessionRegistry({
|
|
516
542
|
...opts.maxActiveSessions === void 0 ? {} : { maxActive: opts.maxActiveSessions },
|
|
517
543
|
...opts.maxQueuedMessages === void 0 ? {} : { maxQueued: opts.maxQueuedMessages }
|
|
@@ -661,6 +687,12 @@ var SessionHub = class {
|
|
|
661
687
|
if (this.closed) return;
|
|
662
688
|
this.publish(sessionId, { type: "sessions-changed" });
|
|
663
689
|
}
|
|
690
|
+
notifySessionTitleChanged(sessionId, title) {
|
|
691
|
+
if (this.closed) return;
|
|
692
|
+
const entry = this.registry.get(sessionId);
|
|
693
|
+
if (entry) entry.announcedTitle = title;
|
|
694
|
+
this.publish(sessionId, { type: "session-title-changed", title });
|
|
695
|
+
}
|
|
664
696
|
notifyPluginsChanged() {
|
|
665
697
|
if (this.closed) return;
|
|
666
698
|
this.publish(void 0, { type: "plugins-changed" });
|
|
@@ -732,6 +764,10 @@ var SessionHub = class {
|
|
|
732
764
|
if (through !== null && through !== entry.persisted?.id) {
|
|
733
765
|
entry.persisted = { id: through, seq: frame.seq };
|
|
734
766
|
}
|
|
767
|
+
const title = this.readTitle?.(sessionId) ?? null;
|
|
768
|
+
if (title !== null && title !== entry.announcedTitle) {
|
|
769
|
+
this.notifySessionTitleChanged(sessionId, title);
|
|
770
|
+
}
|
|
735
771
|
if (event.type === "finish") {
|
|
736
772
|
entry.lastFinish = toWireFinish(event, frame.ts, entry.turnStartedAt);
|
|
737
773
|
}
|
|
@@ -1747,6 +1783,7 @@ function config(ctx, res, lang) {
|
|
|
1747
1783
|
model: runtime.config.model,
|
|
1748
1784
|
permission: runtime.config.permission,
|
|
1749
1785
|
workspace: bound ? toWireSessionWorkspace(bound) : null,
|
|
1786
|
+
...runtime.defaultWorkDir !== void 0 ? { defaultWorkDir: workspaceRefOf(runtime.defaultWorkDir) } : {},
|
|
1750
1787
|
knownWorkspaces: toWireKnownWorkspaces(runtime.workspaces.known()),
|
|
1751
1788
|
usageScope: runtime.usageScope,
|
|
1752
1789
|
compression: runtime.compression,
|
|
@@ -1834,9 +1871,11 @@ async function patchSession(ctx, req, res, sessionId, lang) {
|
|
|
1834
1871
|
const fields = asRecord(body.value);
|
|
1835
1872
|
const rawTitle = fields["title"];
|
|
1836
1873
|
const rawArchived = fields["archived"];
|
|
1874
|
+
const rawPinned = fields["pinned"];
|
|
1837
1875
|
const wantsTitle = rawTitle !== void 0;
|
|
1838
1876
|
const wantsArchive = typeof rawArchived === "boolean";
|
|
1839
|
-
|
|
1877
|
+
const wantsPinned = typeof rawPinned === "boolean";
|
|
1878
|
+
if (!wantsTitle && !wantsArchive && !wantsPinned) {
|
|
1840
1879
|
return sendError(res, 400, "bad-patch", t("web.patch_needs_field", void 0, lang));
|
|
1841
1880
|
}
|
|
1842
1881
|
const title = typeof rawTitle === "string" ? rawTitle.trim() : "";
|
|
@@ -1852,10 +1891,15 @@ async function patchSession(ctx, req, res, sessionId, lang) {
|
|
|
1852
1891
|
t("web.session_store_unavailable", void 0, lang)
|
|
1853
1892
|
);
|
|
1854
1893
|
}
|
|
1894
|
+
const titleBefore = wantsTitle ? catalog.get(sessionId)?.title ?? null : null;
|
|
1855
1895
|
let row = wantsTitle ? catalog.rename(sessionId, title) : catalog.get(sessionId);
|
|
1856
1896
|
const archivedBefore = row?.archived;
|
|
1857
1897
|
if (row && wantsArchive) row = catalog.setArchived(sessionId, rawArchived);
|
|
1898
|
+
if (row && wantsPinned) row = catalog.setPinned(sessionId, rawPinned);
|
|
1858
1899
|
if (!row) return sendUnknownSession(res, sessionId, lang);
|
|
1900
|
+
if (wantsTitle && row.title !== titleBefore) {
|
|
1901
|
+
ctx.hub.notifySessionTitleChanged(sessionId, row.title);
|
|
1902
|
+
}
|
|
1859
1903
|
if (wantsArchive && archivedBefore !== row.archived) ctx.hub.notifySessionsChanged(sessionId);
|
|
1860
1904
|
const hub = ctx.hub.listSessions().find((s) => s.id === sessionId);
|
|
1861
1905
|
sendJson(res, 200, {
|
|
@@ -2098,6 +2142,37 @@ function parseAttachmentNames(raw) {
|
|
|
2098
2142
|
if (!Array.isArray(raw)) return [];
|
|
2099
2143
|
return raw.filter((name) => typeof name === "string" && name.length > 0);
|
|
2100
2144
|
}
|
|
2145
|
+
async function postAppearance(ctx, req, res, lang) {
|
|
2146
|
+
const control = ctx.runtime.appearance;
|
|
2147
|
+
if (!control) {
|
|
2148
|
+
return sendError(res, 503, "no-appearance-store", t("web.appearance_unavailable", {}, lang));
|
|
2149
|
+
}
|
|
2150
|
+
const body = await readJsonBody(req, void 0, lang);
|
|
2151
|
+
if (!body.ok) return sendError(res, body.status, "bad-body", body.message);
|
|
2152
|
+
const raw = body.value;
|
|
2153
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
2154
|
+
return sendError(res, 400, "bad-request", t("web.appearance_bad_request", {}, lang));
|
|
2155
|
+
}
|
|
2156
|
+
const record = raw;
|
|
2157
|
+
const rawTheme = record["theme"];
|
|
2158
|
+
const rawLang = record["lang"];
|
|
2159
|
+
const theme = isAppearanceTheme(rawTheme) ? rawTheme : void 0;
|
|
2160
|
+
const langPref = isAppearanceLang(rawLang) ? rawLang : void 0;
|
|
2161
|
+
if (rawTheme !== void 0 && theme === void 0 || rawLang !== void 0 && langPref === void 0) {
|
|
2162
|
+
return sendError(res, 400, "bad-request", t("web.appearance_bad_request", {}, lang));
|
|
2163
|
+
}
|
|
2164
|
+
const failure = control.set({
|
|
2165
|
+
...theme === void 0 ? {} : { theme },
|
|
2166
|
+
...langPref === void 0 ? {} : { lang: langPref }
|
|
2167
|
+
});
|
|
2168
|
+
if (failure) return sendError(res, 500, "write-failed", failure(lang));
|
|
2169
|
+
const stored = control.get();
|
|
2170
|
+
sendJson(res, 200, {
|
|
2171
|
+
ok: true,
|
|
2172
|
+
...isAppearanceTheme(stored.theme) ? { theme: stored.theme } : {},
|
|
2173
|
+
...isAppearanceLang(stored.lang) ? { lang: stored.lang } : {}
|
|
2174
|
+
});
|
|
2175
|
+
}
|
|
2101
2176
|
async function revokeApprovalCache(ctx, req, res, sessionId, lang) {
|
|
2102
2177
|
if (!ctx.hub.has(sessionId)) return sendUnknownSession(res, sessionId, lang);
|
|
2103
2178
|
const perms = ctx.runtime.permissions;
|
|
@@ -2756,14 +2831,15 @@ function sessionWorkspace(ctx, res, sessionId, lang) {
|
|
|
2756
2831
|
return sendUnknownSession(res, sessionId);
|
|
2757
2832
|
}
|
|
2758
2833
|
const state = ctx.runtime.workspaces.stateOf(sessionId);
|
|
2834
|
+
const changeable = ctx.runtime.workspaces.canChange(sessionId);
|
|
2759
2835
|
if (state !== "none") {
|
|
2760
2836
|
return sendJson(res, 200, {
|
|
2761
|
-
binding: toWireBinding(state, ctx.runtime.workspaces.of(sessionId))
|
|
2837
|
+
binding: toWireBinding(state, ctx.runtime.workspaces.of(sessionId), changeable)
|
|
2762
2838
|
});
|
|
2763
2839
|
}
|
|
2764
2840
|
if (ctx.hub.has(sessionId) || ctx.hub.isCooled(sessionId)) {
|
|
2765
2841
|
return sendJson(res, 200, {
|
|
2766
|
-
binding: { state: "none" }
|
|
2842
|
+
binding: { state: "none", changeable }
|
|
2767
2843
|
});
|
|
2768
2844
|
}
|
|
2769
2845
|
sendError(res, 409, "not-live-session", t("web.workspace_not_live", void 0, lang));
|
|
@@ -2777,7 +2853,7 @@ async function bindWorkspace(ctx, req, res, sessionId, lang) {
|
|
|
2777
2853
|
const parsed = readBindWorkspaceBody(asRecord(body.value), lang);
|
|
2778
2854
|
if (!parsed.ok) return sendError(res, 400, "bad-workspace", parsed.message);
|
|
2779
2855
|
const state = ctx.runtime.workspaces.stateOf(sessionId);
|
|
2780
|
-
if (state !== "unbound") {
|
|
2856
|
+
if (state !== "unbound" && !ctx.runtime.workspaces.canChange(sessionId)) {
|
|
2781
2857
|
return sendError(
|
|
2782
2858
|
res,
|
|
2783
2859
|
409,
|
|
@@ -2793,8 +2869,9 @@ async function bindWorkspace(ctx, req, res, sessionId, lang) {
|
|
|
2793
2869
|
}
|
|
2794
2870
|
if (parsed.kind === "none") {
|
|
2795
2871
|
ctx.runtime.workspaces.decideNone(sessionId);
|
|
2872
|
+
if (state !== "none") ctx.hub.notifySessionsChanged(sessionId);
|
|
2796
2873
|
return sendJson(res, 200, {
|
|
2797
|
-
binding: { state: "none" }
|
|
2874
|
+
binding: { state: "none", changeable: ctx.runtime.workspaces.canChange(sessionId) }
|
|
2798
2875
|
});
|
|
2799
2876
|
}
|
|
2800
2877
|
const outcome = ctx.runtime.workspaces.bind(sessionId, parsed.root, lang);
|
|
@@ -2802,8 +2879,13 @@ async function bindWorkspace(ctx, req, res, sessionId, lang) {
|
|
|
2802
2879
|
const mapped = bindFailureToHttp(outcome, lang);
|
|
2803
2880
|
return sendError(res, mapped.status, mapped.code, mapped.message);
|
|
2804
2881
|
}
|
|
2882
|
+
if (outcome.changed) ctx.hub.notifySessionsChanged(sessionId);
|
|
2805
2883
|
sendJson(res, 200, {
|
|
2806
|
-
binding: {
|
|
2884
|
+
binding: {
|
|
2885
|
+
state: "bound",
|
|
2886
|
+
workspace: toWireSessionWorkspace(outcome.bound),
|
|
2887
|
+
changeable: ctx.runtime.workspaces.canChange(sessionId)
|
|
2888
|
+
}
|
|
2807
2889
|
});
|
|
2808
2890
|
}
|
|
2809
2891
|
async function sessionDiff(ctx, res, sessionId, _lang) {
|
|
@@ -3344,7 +3426,30 @@ function toWirePreview2(preview) {
|
|
|
3344
3426
|
mcpServers: [...inventory.mcpServers],
|
|
3345
3427
|
ignoredBuckets: [...inventory.ignoredBuckets],
|
|
3346
3428
|
hasJsTools: inventory.jsTools,
|
|
3347
|
-
conflict: preview.conflict ? { name: preview.conflict.name, version: preview.conflict.version } : null
|
|
3429
|
+
conflict: preview.conflict ? { name: preview.conflict.name, version: preview.conflict.version } : null,
|
|
3430
|
+
dependencies: (preview.dependencies ?? []).map(toWireDependency)
|
|
3431
|
+
};
|
|
3432
|
+
}
|
|
3433
|
+
function toWireDependency(dep) {
|
|
3434
|
+
const inventory = dep.inventory;
|
|
3435
|
+
return {
|
|
3436
|
+
name: dep.name,
|
|
3437
|
+
status: dep.status,
|
|
3438
|
+
requiredBy: dep.requiredBy,
|
|
3439
|
+
source: dep.source ?? null,
|
|
3440
|
+
reason: dep.reason ?? null,
|
|
3441
|
+
version: dep.manifest?.version ?? null,
|
|
3442
|
+
versionUnmet: dep.versionUnmet ?? null,
|
|
3443
|
+
inventory: inventory ? {
|
|
3444
|
+
commands: [...inventory.commands],
|
|
3445
|
+
roles: [...inventory.roles],
|
|
3446
|
+
skills: [...inventory.skills],
|
|
3447
|
+
hooks: inventory.hooks.map((h) => ({ type: h.type, count: h.count })),
|
|
3448
|
+
denyRules: inventory.denyRules,
|
|
3449
|
+
mcpServers: [...inventory.mcpServers],
|
|
3450
|
+
ignoredBuckets: [...inventory.ignoredBuckets],
|
|
3451
|
+
hasJsTools: inventory.jsTools
|
|
3452
|
+
} : null
|
|
3348
3453
|
};
|
|
3349
3454
|
}
|
|
3350
3455
|
function controlOf(ctx, res, lang) {
|
|
@@ -5072,7 +5177,7 @@ async function dispatch(opts, req, res, lang) {
|
|
|
5072
5177
|
}
|
|
5073
5178
|
if (verdict.setCookie) res.setHeader("Set-Cookie", verdict.setCookie);
|
|
5074
5179
|
if (verdict.setCookie && isRead(method) && !pathname.startsWith("/api/")) {
|
|
5075
|
-
return redirectWithoutToken(res, req.url);
|
|
5180
|
+
return redirectWithoutToken(res, req.url, ctx.runtime.appearance);
|
|
5076
5181
|
}
|
|
5077
5182
|
const segments = splitPath(pathname);
|
|
5078
5183
|
if (segments[0] === "api") return handleApi(opts, req, res, method, segments, lang);
|
|
@@ -5101,6 +5206,9 @@ async function handleApi(opts, req, res, method, segments, lang) {
|
|
|
5101
5206
|
if (resource === "last-session" && first === void 0 && method === "PUT") {
|
|
5102
5207
|
return putLastSession(ctx, req, res, lang);
|
|
5103
5208
|
}
|
|
5209
|
+
if (resource === "appearance" && first === void 0 && method === "POST") {
|
|
5210
|
+
return postAppearance(ctx, req, res, lang);
|
|
5211
|
+
}
|
|
5104
5212
|
if (resource === "host" && first === "compose" && second === void 0 && method === "POST") {
|
|
5105
5213
|
return postHostCompose(ctx, req, res, lang);
|
|
5106
5214
|
}
|
|
@@ -5413,9 +5521,12 @@ function archivedFlag(rawUrl) {
|
|
|
5413
5521
|
const raw = new URL(rawUrl ?? "/", "http://127.0.0.1").searchParams.get("archived");
|
|
5414
5522
|
return raw === "1" || raw === "true";
|
|
5415
5523
|
}
|
|
5416
|
-
function redirectWithoutToken(res, rawUrl) {
|
|
5524
|
+
function redirectWithoutToken(res, rawUrl, store) {
|
|
5417
5525
|
const url = new URL(rawUrl ?? "/", "http://127.0.0.1");
|
|
5418
5526
|
url.searchParams.delete(WIRE_AUTH_TOKEN_PARAM);
|
|
5527
|
+
for (const [name, value] of uiPresetParams(storedUiPreset(store?.get() ?? {}))) {
|
|
5528
|
+
url.searchParams.set(name, value);
|
|
5529
|
+
}
|
|
5419
5530
|
res.writeHead(303, { ...baseHeaders(), Location: `${url.pathname}${url.search}` });
|
|
5420
5531
|
res.end();
|
|
5421
5532
|
}
|
|
@@ -5445,7 +5556,8 @@ async function createWebServer(opts) {
|
|
|
5445
5556
|
...opts.idleGraceMs === void 0 ? {} : { idleGraceMs: opts.idleGraceMs },
|
|
5446
5557
|
...opts.clock === void 0 ? {} : { clock: opts.clock },
|
|
5447
5558
|
...opts.maxActiveSessions === void 0 ? {} : { maxActiveSessions: opts.maxActiveSessions },
|
|
5448
|
-
onCooled: (sessionId) => opts.runtime.sessionFactory.release(sessionId)
|
|
5559
|
+
onCooled: (sessionId) => opts.runtime.sessionFactory.release(sessionId),
|
|
5560
|
+
readTitle: (sessionId) => opts.runtime.sessions?.get(sessionId)?.title || null
|
|
5449
5561
|
});
|
|
5450
5562
|
if (opts.runtime.session) hub.register(opts.runtime.session);
|
|
5451
5563
|
const unsubscribeTasks = observeBackgroundTasks((sessionId) => {
|
|
@@ -5469,7 +5581,12 @@ async function createWebServer(opts) {
|
|
|
5469
5581
|
return {
|
|
5470
5582
|
ok: true,
|
|
5471
5583
|
server: {
|
|
5472
|
-
url: firstScreenUrl(
|
|
5584
|
+
url: firstScreenUrl(
|
|
5585
|
+
binding.host,
|
|
5586
|
+
listened.port,
|
|
5587
|
+
binding.token,
|
|
5588
|
+
resolveUiPreset(opts.ui ?? {}, opts.runtime.appearance?.get() ?? {})
|
|
5589
|
+
),
|
|
5473
5590
|
port: listened.port,
|
|
5474
5591
|
host: binding.host,
|
|
5475
5592
|
token: binding.token,
|