@epoch-agent/server 0.10.1 → 0.12.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 +55 -4
- package/dist/index.d.ts +323 -25
- package/dist/index.js +184 -25
- package/dist/web/assets/index-BNFet5_f.css +1 -0
- package/dist/web/assets/index-BO0EH88w.js +378 -0
- package/dist/web/index.html +2 -2
- package/package.json +3 -3
- package/dist/web/assets/index-D-mlA3qO.js +0 -377
- package/dist/web/assets/index-x_cigNDu.css +0 -1
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, isSystemOpenable, backgroundTaskOutput, listBackgroundTasks, providerLabel, MAX_REASON_CHARS, MAX_OBJECTIVE_CHARS, MAX_GOAL_MAX_ROUNDS, DEFAULT_GOAL_MAX_ROUNDS, SCHEDULE_DEFAULT_ALLOWLIST, SCHEDULE_DEFAULTS, coalesceRecording, readRecording } from '@epoch-agent/runtime';
|
|
3
3
|
import { timingSafeEqual, randomBytes } 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, isQuestionAnswerMap, isProviderType, apiKeyEnvVar, WIRE_FILE_STAT_MAX, WIRE_SETTING_WRITE_LAYERS, isWirePlanAction, WIRE_PLAN_ACTIONS, isPermissionLevel, PERMISSION_LEVELS,
|
|
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';
|
|
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';
|
|
@@ -197,6 +197,7 @@ var SessionRegistry = class {
|
|
|
197
197
|
controller: null,
|
|
198
198
|
turnStartedAt: null,
|
|
199
199
|
lastFinish: null,
|
|
200
|
+
persisted: null,
|
|
200
201
|
queue: [],
|
|
201
202
|
lastActiveTick: ++this.tick
|
|
202
203
|
});
|
|
@@ -376,6 +377,7 @@ function toWireSummary(id, hub, row, extra = {}) {
|
|
|
376
377
|
turnStartedAt: state.turnStartedAt,
|
|
377
378
|
lastFinish: state.lastFinish,
|
|
378
379
|
role: extra.role ?? null,
|
|
380
|
+
archived: row?.archived ?? false,
|
|
379
381
|
...extra.snippet === void 0 ? {} : { snippet: extra.snippet }
|
|
380
382
|
};
|
|
381
383
|
}
|
|
@@ -388,7 +390,7 @@ function toWireSummaryWorkspace(bound, live, row) {
|
|
|
388
390
|
}
|
|
389
391
|
return row?.workspaceState === "none" ? { state: "none" } : { state: "unknown" };
|
|
390
392
|
}
|
|
391
|
-
function listSessionSummaries(hubStates, catalog, query, workspaceOf = () => null, roleOf = () => null, limit = DEFAULT_SESSION_LIST_LIMIT) {
|
|
393
|
+
function listSessionSummaries(hubStates, catalog, query, workspaceOf = () => null, roleOf = () => null, limit = DEFAULT_SESSION_LIST_LIMIT, archived = false) {
|
|
392
394
|
const byId = new Map(hubStates.map((s) => [s.id, s]));
|
|
393
395
|
if (query) {
|
|
394
396
|
if (!catalog) return [];
|
|
@@ -407,7 +409,7 @@ function listSessionSummaries(hubStates, catalog, query, workspaceOf = () => nul
|
|
|
407
409
|
}
|
|
408
410
|
return hits;
|
|
409
411
|
}
|
|
410
|
-
const rows = catalog?.list(limit) ?? [];
|
|
412
|
+
const rows = catalog?.list(limit, archived ? { archived: true } : {}) ?? [];
|
|
411
413
|
const summaries = rows.map(
|
|
412
414
|
(row) => toWireSummary(row.id, byId.get(row.id), row, {
|
|
413
415
|
workspace: workspaceOf(row.id),
|
|
@@ -415,7 +417,9 @@ function listSessionSummaries(hubStates, catalog, query, workspaceOf = () => nul
|
|
|
415
417
|
})
|
|
416
418
|
);
|
|
417
419
|
const known = new Set(rows.map((r) => r.id));
|
|
418
|
-
const fresh = hubStates.filter((s) => !known.has(s.id)).
|
|
420
|
+
const fresh = archived ? [] : hubStates.filter((s) => !known.has(s.id)).filter((s) => catalog?.get(s.id)?.archived !== true).map(
|
|
421
|
+
(s) => toWireSummary(s.id, s, null, { workspace: workspaceOf(s.id), role: roleOf(s.id) })
|
|
422
|
+
);
|
|
419
423
|
return [...fresh, ...summaries].sort(compareSummaries).slice(0, limit);
|
|
420
424
|
}
|
|
421
425
|
function compareSummaries(a, b) {
|
|
@@ -462,6 +466,9 @@ var SessionHub = class {
|
|
|
462
466
|
isCooled(sessionId) {
|
|
463
467
|
return this.registry.isCooled(sessionId);
|
|
464
468
|
}
|
|
469
|
+
persistedThrough(sessionId) {
|
|
470
|
+
return this.registry.get(sessionId)?.persisted ?? null;
|
|
471
|
+
}
|
|
465
472
|
stateOf(sessionId) {
|
|
466
473
|
return this.registry.get(sessionId)?.state ?? "idle";
|
|
467
474
|
}
|
|
@@ -539,21 +546,22 @@ var SessionHub = class {
|
|
|
539
546
|
}
|
|
540
547
|
const queued = { message, ...decorate ? { decorate } : {} };
|
|
541
548
|
if (entry.state !== "idle") {
|
|
549
|
+
const steered = decorate === void 0 && entry.queue.every((q) => q.decorate === void 0);
|
|
542
550
|
if (!this.registry.enqueue(entry, queued)) {
|
|
543
551
|
return { ok: false, reason: "queue-full", state: entry.state };
|
|
544
552
|
}
|
|
545
|
-
return { ok: true, queued: true };
|
|
553
|
+
return { ok: true, queued: true, steered };
|
|
546
554
|
}
|
|
547
555
|
this.beginTurn(entry, queued);
|
|
548
556
|
return { ok: true, queued: false };
|
|
549
557
|
}
|
|
550
558
|
abort(sessionId) {
|
|
551
559
|
const entry = this.registry.get(sessionId);
|
|
552
|
-
if (!entry) return false;
|
|
553
|
-
entry.queue.length
|
|
554
|
-
if (!entry.controller) return false;
|
|
560
|
+
if (!entry) return { running: false, returned: [] };
|
|
561
|
+
const returned = entry.queue.splice(0, entry.queue.length).map((q) => q.message);
|
|
562
|
+
if (!entry.controller) return { running: false, returned };
|
|
555
563
|
entry.controller.abort();
|
|
556
|
-
return true;
|
|
564
|
+
return { running: true, returned };
|
|
557
565
|
}
|
|
558
566
|
listPending(sessionId) {
|
|
559
567
|
return this.registry.get(sessionId)?.relay.listPending() ?? [];
|
|
@@ -634,12 +642,19 @@ var SessionHub = class {
|
|
|
634
642
|
const sessionId = entry.session.sessionId;
|
|
635
643
|
const signal = entry.controller?.signal;
|
|
636
644
|
try {
|
|
637
|
-
const run = entry.session.run(queued.message,
|
|
645
|
+
const run = entry.session.run(queued.message, {
|
|
646
|
+
...signal ? { signal } : {},
|
|
647
|
+
steer: this.steerFrom(entry)
|
|
648
|
+
});
|
|
638
649
|
const raw = queued.decorate ? queued.decorate(run) : run;
|
|
639
650
|
for await (const event of serializeStream(raw, entry.relay, entry.questions)) {
|
|
640
651
|
const blocking = event.type === "approval-request" || event.type === "question";
|
|
641
652
|
if (blocking) this.requestOwner.set(event.requestId, sessionId);
|
|
642
653
|
const frame = this.publish(sessionId, stripArtifactData(event));
|
|
654
|
+
const through = entry.session.persistedThrough?.() ?? null;
|
|
655
|
+
if (through !== null && through !== entry.persisted?.id) {
|
|
656
|
+
entry.persisted = { id: through, seq: frame.seq };
|
|
657
|
+
}
|
|
643
658
|
if (event.type === "finish") {
|
|
644
659
|
entry.lastFinish = toWireFinish(event, frame.ts, entry.turnStartedAt);
|
|
645
660
|
}
|
|
@@ -654,10 +669,22 @@ var SessionHub = class {
|
|
|
654
669
|
}
|
|
655
670
|
this.registry.touch(sessionId);
|
|
656
671
|
entry.turnStartedAt = null;
|
|
672
|
+
entry.persisted = null;
|
|
657
673
|
this.setState(entry, "idle");
|
|
658
674
|
this.drainQueue(entry);
|
|
659
675
|
}
|
|
660
676
|
}
|
|
677
|
+
steerFrom(entry) {
|
|
678
|
+
const runLength = () => {
|
|
679
|
+
let n = 0;
|
|
680
|
+
while (n < entry.queue.length && entry.queue[n].decorate === void 0) n += 1;
|
|
681
|
+
return n;
|
|
682
|
+
};
|
|
683
|
+
return {
|
|
684
|
+
pending: () => runLength() > 0,
|
|
685
|
+
take: () => entry.queue.splice(0, runLength()).map((q) => q.message)
|
|
686
|
+
};
|
|
687
|
+
}
|
|
661
688
|
drainQueue(entry) {
|
|
662
689
|
if (this.closed || !this.registry.has(entry.session.sessionId)) return;
|
|
663
690
|
if (this.present === 0) {
|
|
@@ -1500,9 +1527,13 @@ function toWireSettingRow(row) {
|
|
|
1500
1527
|
overridable: row.overridable,
|
|
1501
1528
|
writes: row.writes.map(toWireSettingWrite),
|
|
1502
1529
|
...row.valueKind === void 0 ? {} : { valueKind: row.valueKind },
|
|
1503
|
-
...row.choices === void 0 ? {} : { choices: [...row.choices] }
|
|
1530
|
+
...row.choices === void 0 ? {} : { choices: [...row.choices] },
|
|
1531
|
+
...row.pending === void 0 ? {} : { pending: toWireSettingPending(row.pending) }
|
|
1504
1532
|
};
|
|
1505
1533
|
}
|
|
1534
|
+
function toWireSettingPending(pending) {
|
|
1535
|
+
return { ...toWireSettingWrite(pending), value: pending.value };
|
|
1536
|
+
}
|
|
1506
1537
|
function toWireSettingStep(step) {
|
|
1507
1538
|
return { layer: step.layer, ...step.value === void 0 ? {} : { value: step.value } };
|
|
1508
1539
|
}
|
|
@@ -1604,11 +1635,18 @@ var APPROVAL_OUTCOMES = /* @__PURE__ */ new Set([
|
|
|
1604
1635
|
function health(ctx, res) {
|
|
1605
1636
|
sendJson(res, 200, { ok: true, version: ctx.version });
|
|
1606
1637
|
}
|
|
1638
|
+
function lastSessionField(ctx) {
|
|
1639
|
+
const recorded = ctx.runtime.lastOpenSession?.get();
|
|
1640
|
+
if (!recorded) return {};
|
|
1641
|
+
if (!ctx.hub.has(recorded) && !ctx.runtime.sessions?.get(recorded)) return {};
|
|
1642
|
+
return { lastSessionId: recorded };
|
|
1643
|
+
}
|
|
1607
1644
|
function config(ctx, res, lang) {
|
|
1608
1645
|
const { runtime } = ctx;
|
|
1609
1646
|
const bound = runtime.workspaces.of(runtime.sessionId);
|
|
1610
1647
|
sendJson(res, 200, {
|
|
1611
1648
|
sessionId: runtime.sessionId,
|
|
1649
|
+
...lastSessionField(ctx),
|
|
1612
1650
|
model: runtime.config.model,
|
|
1613
1651
|
permission: runtime.config.permission,
|
|
1614
1652
|
workspace: bound ? toWireSessionWorkspace(bound) : null,
|
|
@@ -1638,13 +1676,15 @@ function config(ctx, res, lang) {
|
|
|
1638
1676
|
nativeDirPicker: ctx.nativeDirPicker
|
|
1639
1677
|
});
|
|
1640
1678
|
}
|
|
1641
|
-
function listSessions(ctx, res, query, _lang) {
|
|
1679
|
+
function listSessions(ctx, res, query, _lang, archived = false) {
|
|
1642
1680
|
const sessions = listSessionSummaries(
|
|
1643
1681
|
ctx.hub.listSessions(),
|
|
1644
1682
|
ctx.runtime.sessions,
|
|
1645
1683
|
query ?? null,
|
|
1646
1684
|
(id) => ctx.runtime.workspaces.of(id),
|
|
1647
|
-
(id) => ctx.runtime.sessionFactory.get(id)?.role ?? null
|
|
1685
|
+
(id) => ctx.runtime.sessionFactory.get(id)?.role ?? null,
|
|
1686
|
+
void 0,
|
|
1687
|
+
archived
|
|
1648
1688
|
);
|
|
1649
1689
|
sendJson(res, 200, { sessions });
|
|
1650
1690
|
}
|
|
@@ -1686,9 +1726,16 @@ async function deleteSession(ctx, res, sessionId, lang) {
|
|
|
1686
1726
|
async function patchSession(ctx, req, res, sessionId, lang) {
|
|
1687
1727
|
const body = await readJsonBody(req, void 0, lang);
|
|
1688
1728
|
if (!body.ok) return sendError(res, body.status, "bad-body", body.message);
|
|
1689
|
-
const
|
|
1690
|
-
const
|
|
1691
|
-
|
|
1729
|
+
const fields = asRecord(body.value);
|
|
1730
|
+
const rawTitle = fields["title"];
|
|
1731
|
+
const rawArchived = fields["archived"];
|
|
1732
|
+
const wantsTitle = rawTitle !== void 0;
|
|
1733
|
+
const wantsArchive = typeof rawArchived === "boolean";
|
|
1734
|
+
if (!wantsTitle && !wantsArchive) {
|
|
1735
|
+
return sendError(res, 400, "bad-patch", t("web.patch_needs_field", void 0, lang));
|
|
1736
|
+
}
|
|
1737
|
+
const title = typeof rawTitle === "string" ? rawTitle.trim() : "";
|
|
1738
|
+
if (wantsTitle && title.length === 0) {
|
|
1692
1739
|
return sendError(res, 400, "bad-title", t("web.title_required", void 0, lang));
|
|
1693
1740
|
}
|
|
1694
1741
|
const catalog = ctx.runtime.sessions;
|
|
@@ -1700,8 +1747,11 @@ async function patchSession(ctx, req, res, sessionId, lang) {
|
|
|
1700
1747
|
t("web.session_store_unavailable", void 0, lang)
|
|
1701
1748
|
);
|
|
1702
1749
|
}
|
|
1703
|
-
|
|
1750
|
+
let row = wantsTitle ? catalog.rename(sessionId, title) : catalog.get(sessionId);
|
|
1751
|
+
const archivedBefore = row?.archived;
|
|
1752
|
+
if (row && wantsArchive) row = catalog.setArchived(sessionId, rawArchived);
|
|
1704
1753
|
if (!row) return sendUnknownSession(res, sessionId, lang);
|
|
1754
|
+
if (wantsArchive && archivedBefore !== row.archived) ctx.hub.notifySessionsChanged(sessionId);
|
|
1705
1755
|
const hub = ctx.hub.listSessions().find((s) => s.id === sessionId);
|
|
1706
1756
|
sendJson(res, 200, {
|
|
1707
1757
|
session: toWireSummary(sessionId, hub, row, {
|
|
@@ -1714,11 +1764,17 @@ function listMessages(ctx, res, sessionId, lang) {
|
|
|
1714
1764
|
if (!ctx.hub.has(sessionId) && !ctx.runtime.sessions?.get(sessionId)) {
|
|
1715
1765
|
return sendUnknownSession(res, sessionId, lang);
|
|
1716
1766
|
}
|
|
1717
|
-
const
|
|
1767
|
+
const all = ctx.runtime.sessionStore?.loadMessages(sessionId) ?? [];
|
|
1718
1768
|
const usage = ctx.runtime.sessionUsage(sessionId);
|
|
1769
|
+
const flight = ctx.hub.persistedThrough(sessionId);
|
|
1770
|
+
const openFrom = ctx.runtime.sessionStore?.openTurnFrom(sessionId) ?? null;
|
|
1771
|
+
const messages = flight ? all.filter((m) => m.id <= flight.id) : all;
|
|
1772
|
+
const interrupted = flight === null && openFrom !== null ? openFrom : void 0;
|
|
1719
1773
|
sendJson(res, 200, {
|
|
1720
1774
|
messages,
|
|
1721
|
-
...usage ? { usage } : {}
|
|
1775
|
+
...usage ? { usage } : {},
|
|
1776
|
+
...flight ? { inFlightThroughSeq: flight.seq } : {},
|
|
1777
|
+
...interrupted !== void 0 ? { interruptedTurnFrom: interrupted } : {}
|
|
1722
1778
|
});
|
|
1723
1779
|
}
|
|
1724
1780
|
async function postMessage(ctx, req, res, sessionId, lang) {
|
|
@@ -1752,6 +1808,7 @@ async function postMessage(ctx, req, res, sessionId, lang) {
|
|
|
1752
1808
|
return sendJson(res, 202, {
|
|
1753
1809
|
accepted: true,
|
|
1754
1810
|
queued: started.queued,
|
|
1811
|
+
...started.steered === void 0 ? {} : { steered: started.steered },
|
|
1755
1812
|
...expansion.notice ? { command: expansion.notice } : {},
|
|
1756
1813
|
...attached.references.length > 0 ? { references: attached.references } : {},
|
|
1757
1814
|
...attached.files.length > 0 ? { files: attached.files } : {}
|
|
@@ -1773,7 +1830,11 @@ function pickTurnRole(live, raw) {
|
|
|
1773
1830
|
}
|
|
1774
1831
|
function abortSession(ctx, res, sessionId, lang) {
|
|
1775
1832
|
if (!ctx.hub.has(sessionId)) return sendUnknownSession(res, sessionId, lang);
|
|
1776
|
-
|
|
1833
|
+
const stopped = ctx.hub.abort(sessionId);
|
|
1834
|
+
sendJson(res, 200, {
|
|
1835
|
+
aborted: stopped.running,
|
|
1836
|
+
...stopped.returned.length > 0 ? { returned: stopped.returned } : {}
|
|
1837
|
+
});
|
|
1777
1838
|
}
|
|
1778
1839
|
function listApprovals(ctx, res, sessionId, lang) {
|
|
1779
1840
|
if (!ctx.hub.has(sessionId)) return sendUnknownSession(res, sessionId, lang);
|
|
@@ -2088,6 +2149,9 @@ async function rewind(ctx, req, res, sessionId, lang) {
|
|
|
2088
2149
|
const input = readRewindInput(body.value, lang);
|
|
2089
2150
|
if (!input.ok) return sendError(res, 400, input.code, input.message);
|
|
2090
2151
|
const { turnIndex, scope, overwrite } = input.value;
|
|
2152
|
+
if (scope !== "files" && ctx.hub.stateOf(sessionId) !== "idle") {
|
|
2153
|
+
return sendError(res, 409, "turn-running", t("web.rewind_turn_running", void 0, lang));
|
|
2154
|
+
}
|
|
2091
2155
|
const result = await control.rewind(turnIndex, {
|
|
2092
2156
|
scope,
|
|
2093
2157
|
...overwrite === void 0 ? {} : { overwrite }
|
|
@@ -2284,6 +2348,32 @@ function listBlockedGoals(ctx, res, _lang) {
|
|
|
2284
2348
|
);
|
|
2285
2349
|
sendJson(res, 200, { items });
|
|
2286
2350
|
}
|
|
2351
|
+
async function putLastSession(ctx, req, res, lang) {
|
|
2352
|
+
const control = ctx.runtime.lastOpenSession;
|
|
2353
|
+
if (!control) {
|
|
2354
|
+
return sendError(
|
|
2355
|
+
res,
|
|
2356
|
+
503,
|
|
2357
|
+
"no-last-session-store",
|
|
2358
|
+
t("web.last_session_unavailable", {}, lang)
|
|
2359
|
+
);
|
|
2360
|
+
}
|
|
2361
|
+
const body = await readJsonBody(req, void 0, lang);
|
|
2362
|
+
if (!body.ok) return sendError(res, body.status, "bad-body", body.message);
|
|
2363
|
+
if (!conformsToWire(body.value, WIRE_LAST_SESSION_FIELDS)) {
|
|
2364
|
+
return sendError(res, 400, "bad-request", t("web.last_session_bad_request", {}, lang));
|
|
2365
|
+
}
|
|
2366
|
+
const { sessionId } = body.value;
|
|
2367
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
2368
|
+
return sendError(res, 400, "bad-request", t("web.last_session_bad_request", {}, lang));
|
|
2369
|
+
}
|
|
2370
|
+
if (!ctx.hub.has(sessionId) && !ctx.runtime.sessions?.get(sessionId)) {
|
|
2371
|
+
return sendUnknownSession(res, sessionId, lang);
|
|
2372
|
+
}
|
|
2373
|
+
const failure = control.set(sessionId);
|
|
2374
|
+
if (failure) return sendError(res, 500, "write-failed", failure(lang));
|
|
2375
|
+
sendJson(res, 200, { ok: true });
|
|
2376
|
+
}
|
|
2287
2377
|
function readConfig(ctx, res, lang) {
|
|
2288
2378
|
if (ctx.lanExposed) return sendLanExposed(res, lang);
|
|
2289
2379
|
const outcome = ctx.runtime.mcp.readConfig();
|
|
@@ -2959,6 +3049,8 @@ function sendUploadPreview(res, outcome) {
|
|
|
2959
3049
|
ok: false,
|
|
2960
3050
|
plan: null,
|
|
2961
3051
|
token: null,
|
|
3052
|
+
prefill: null,
|
|
3053
|
+
fromManifest: null,
|
|
2962
3054
|
reason: outcome.reason,
|
|
2963
3055
|
detail: outcome.detail
|
|
2964
3056
|
});
|
|
@@ -2977,6 +3069,8 @@ function sendUploadPreview(res, outcome) {
|
|
|
2977
3069
|
skippedLinks: [...skippedLinks]
|
|
2978
3070
|
},
|
|
2979
3071
|
token: outcome.token,
|
|
3072
|
+
prefill: outcome.prefill,
|
|
3073
|
+
fromManifest: [...outcome.fromManifest],
|
|
2980
3074
|
reason: null,
|
|
2981
3075
|
detail: null
|
|
2982
3076
|
});
|
|
@@ -3142,6 +3236,39 @@ function addFailure2(reason, detail, lang) {
|
|
|
3142
3236
|
function stringList2(value) {
|
|
3143
3237
|
return value.filter((v) => typeof v === "string");
|
|
3144
3238
|
}
|
|
3239
|
+
async function removeRole(ctx, req, res, lang) {
|
|
3240
|
+
if (ctx.lanExposed) {
|
|
3241
|
+
return sendError(
|
|
3242
|
+
res,
|
|
3243
|
+
403,
|
|
3244
|
+
"role-remove-lan-exposed",
|
|
3245
|
+
t("web.role_remove_lan_exposed", void 0, lang)
|
|
3246
|
+
);
|
|
3247
|
+
}
|
|
3248
|
+
const body = await readJsonBody(req);
|
|
3249
|
+
if (!body.ok) return sendError(res, body.status, "bad-body", body.message);
|
|
3250
|
+
const payload = asRecord(body.value);
|
|
3251
|
+
const name = typeof payload["name"] === "string" ? payload["name"].trim() : "";
|
|
3252
|
+
const outcome = ctx.runtime.roleWrite.remove(name, lang);
|
|
3253
|
+
if (!outcome.ok) {
|
|
3254
|
+
return sendJson(res, 200, {
|
|
3255
|
+
ok: false,
|
|
3256
|
+
name: "",
|
|
3257
|
+
path: null,
|
|
3258
|
+
takenOverBy: null,
|
|
3259
|
+
detail: outcome.detail,
|
|
3260
|
+
reason: outcome.reason
|
|
3261
|
+
});
|
|
3262
|
+
}
|
|
3263
|
+
sendJson(res, 200, {
|
|
3264
|
+
ok: true,
|
|
3265
|
+
name: outcome.name,
|
|
3266
|
+
path: outcome.path,
|
|
3267
|
+
takenOverBy: outcome.takenOverBy,
|
|
3268
|
+
detail: null,
|
|
3269
|
+
reason: null
|
|
3270
|
+
});
|
|
3271
|
+
}
|
|
3145
3272
|
function stringList3(value) {
|
|
3146
3273
|
if (!Array.isArray(value)) return null;
|
|
3147
3274
|
const out = [];
|
|
@@ -3361,6 +3488,12 @@ function listPending(ctx, res, lang) {
|
|
|
3361
3488
|
const items = collectPendingApprovals(control.list(), control.recentRuns(RECENT_RUNS_LIMIT));
|
|
3362
3489
|
sendJson(res, 200, { items });
|
|
3363
3490
|
}
|
|
3491
|
+
function listRunning(ctx, res, lang) {
|
|
3492
|
+
const control = schedulesOf(ctx, res, lang);
|
|
3493
|
+
if (!control) return;
|
|
3494
|
+
const items = control.running().map(({ scheduleId, startedAt }) => ({ scheduleId, startedAt }));
|
|
3495
|
+
sendJson(res, 200, { items });
|
|
3496
|
+
}
|
|
3364
3497
|
function getSchedule(ctx, res, id, lang) {
|
|
3365
3498
|
const control = schedulesOf(ctx, res, lang);
|
|
3366
3499
|
if (!control) return;
|
|
@@ -3394,10 +3527,20 @@ async function patchSchedule(ctx, req, res, id, lang) {
|
|
|
3394
3527
|
if (!body.ok) return sendError(res, body.status, "bad-body", body.message);
|
|
3395
3528
|
const parsed = parseUpdate2(body.value, lang);
|
|
3396
3529
|
if (!parsed.ok) return sendError(res, 400, "bad-schedule", parsed.message);
|
|
3397
|
-
const
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3530
|
+
const { enabled, ...rest } = parsed.value;
|
|
3531
|
+
const saved = Object.keys(rest).length > 0 ? await control.update(id, rest, { bypassAcknowledged: bypassAcknowledged(body.value) }) : { issues: [] };
|
|
3532
|
+
if (saved.issues.length > 0) return sendJson(res, 200, toSaveResponse(control, saved));
|
|
3533
|
+
const registration = enabled === void 0 ? saved.registration : await control.setEnabled(id, enabled);
|
|
3534
|
+
const next = control.get(id) ?? saved.schedule;
|
|
3535
|
+
sendJson(
|
|
3536
|
+
res,
|
|
3537
|
+
200,
|
|
3538
|
+
toSaveResponse(control, {
|
|
3539
|
+
...next ? { schedule: next } : {},
|
|
3540
|
+
issues: [],
|
|
3541
|
+
...registration ? { registration } : {}
|
|
3542
|
+
})
|
|
3543
|
+
);
|
|
3401
3544
|
}
|
|
3402
3545
|
async function deleteSchedule(ctx, res, id, lang) {
|
|
3403
3546
|
const control = schedulesOf(ctx, res, lang);
|
|
@@ -4738,6 +4881,9 @@ async function handleApi(opts, req, res, method, segments, lang) {
|
|
|
4738
4881
|
return config(ctx, res, lang);
|
|
4739
4882
|
}
|
|
4740
4883
|
if (resource === "events" && method === "GET") return openStream(opts, req, res);
|
|
4884
|
+
if (resource === "last-session" && first === void 0 && method === "PUT") {
|
|
4885
|
+
return putLastSession(ctx, req, res, lang);
|
|
4886
|
+
}
|
|
4741
4887
|
if (resource === "sessions") {
|
|
4742
4888
|
return handleSessions(opts, req, res, method, first, second, detail, lang);
|
|
4743
4889
|
}
|
|
@@ -4768,6 +4914,9 @@ async function handleApi(opts, req, res, method, segments, lang) {
|
|
|
4768
4914
|
if (resource === "roles" && first === void 0 && method === "POST") {
|
|
4769
4915
|
return addRole(ctx, req, res, lang);
|
|
4770
4916
|
}
|
|
4917
|
+
if (resource === "roles" && first === "remove" && method === "POST") {
|
|
4918
|
+
return removeRole(ctx, req, res, lang);
|
|
4919
|
+
}
|
|
4771
4920
|
if (resource === "skills" && first === "import" && second === "preview" && method === "POST") {
|
|
4772
4921
|
return previewSkillImport(ctx, req, res, lang);
|
|
4773
4922
|
}
|
|
@@ -4865,6 +5014,10 @@ async function handleSchedules(ctx, req, res, method, scheduleId, action, runId,
|
|
|
4865
5014
|
if (isRead(method)) return listPending(ctx, res, lang);
|
|
4866
5015
|
return methodNotAllowed(res, "GET", lang);
|
|
4867
5016
|
}
|
|
5017
|
+
if (action === void 0 && scheduleId === "running") {
|
|
5018
|
+
if (isRead(method)) return listRunning(ctx, res, lang);
|
|
5019
|
+
return methodNotAllowed(res, "GET", lang);
|
|
5020
|
+
}
|
|
4868
5021
|
if (action === void 0) {
|
|
4869
5022
|
if (isRead(method)) return getSchedule(ctx, res, scheduleId, lang);
|
|
4870
5023
|
if (method === "PATCH") return patchSchedule(ctx, req, res, scheduleId, lang);
|
|
@@ -4888,7 +5041,9 @@ async function handleSchedules(ctx, req, res, method, scheduleId, action, runId,
|
|
|
4888
5041
|
async function handleSessions(opts, req, res, method, sessionId, action, detail, lang) {
|
|
4889
5042
|
const { ctx } = opts;
|
|
4890
5043
|
if (sessionId === void 0) {
|
|
4891
|
-
if (isRead(method))
|
|
5044
|
+
if (isRead(method)) {
|
|
5045
|
+
return listSessions(ctx, res, searchQuery(req.url), lang, archivedFlag(req.url));
|
|
5046
|
+
}
|
|
4892
5047
|
if (method === "POST") return createSession(ctx, req, res, lang);
|
|
4893
5048
|
return methodNotAllowed(res, "GET, POST", lang);
|
|
4894
5049
|
}
|
|
@@ -5025,6 +5180,10 @@ function searchQuery(rawUrl) {
|
|
|
5025
5180
|
const value = new URL(rawUrl ?? "/", "http://127.0.0.1").searchParams.get("q")?.trim();
|
|
5026
5181
|
return value ? value : void 0;
|
|
5027
5182
|
}
|
|
5183
|
+
function archivedFlag(rawUrl) {
|
|
5184
|
+
const raw = new URL(rawUrl ?? "/", "http://127.0.0.1").searchParams.get("archived");
|
|
5185
|
+
return raw === "1" || raw === "true";
|
|
5186
|
+
}
|
|
5028
5187
|
function redirectWithoutToken(res, rawUrl) {
|
|
5029
5188
|
const url = new URL(rawUrl ?? "/", "http://127.0.0.1");
|
|
5030
5189
|
url.searchParams.delete(WIRE_AUTH_TOKEN_PARAM);
|