neoctl 0.2.51 → 0.2.52
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/core/query-engine.d.ts +1 -0
- package/dist/core/query-engine.js +6 -0
- package/dist/core/query-engine.js.map +1 -1
- package/dist/web/index.d.ts +14 -2
- package/dist/web/index.js +176 -18
- package/dist/web/index.js.map +1 -1
- package/package.json +1 -1
package/dist/web/index.js
CHANGED
|
@@ -478,6 +478,66 @@ function parseResumeFlag(value) {
|
|
|
478
478
|
}
|
|
479
479
|
export class WebRuntimeRouter {
|
|
480
480
|
repls = new Map();
|
|
481
|
+
deletedSessions = new Set();
|
|
482
|
+
deletingSessions = new Set();
|
|
483
|
+
assertSessionAvailable(sessionId) {
|
|
484
|
+
if (sessionId && (this.deletedSessions.has(sessionId) || this.deletingSessions.has(sessionId))) {
|
|
485
|
+
throw new Error("会话已删除或正在删除");
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
async deleteSession(scope, sessionId) {
|
|
489
|
+
if (!sessionId)
|
|
490
|
+
return actionFailure("INVALID_REQUEST", "sessionId is required");
|
|
491
|
+
if (this.deletingSessions.has(sessionId))
|
|
492
|
+
return actionFailure("SESSION_DELETING", "会话正在删除");
|
|
493
|
+
if (this.deletedSessions.has(sessionId))
|
|
494
|
+
return actionFailure("SESSION_NOT_FOUND", "会话已删除");
|
|
495
|
+
this.deletingSessions.add(sessionId);
|
|
496
|
+
try {
|
|
497
|
+
const entries = await Promise.all([...this.repls.entries()].map(async ([key, promise]) => ({ key, promise, repl: await promise.catch(() => undefined) })));
|
|
498
|
+
const owners = [...new Set(entries.filter(entry => entry.repl?.currentSessionId() === sessionId).map(entry => entry.repl))];
|
|
499
|
+
// Shared hubs should have one owner. Never orphan a legacy second writer.
|
|
500
|
+
if (owners.length > 1)
|
|
501
|
+
return actionFailure("SESSION_IN_USE", "会话被多个运行实例占用,请先关闭其它窗口");
|
|
502
|
+
// A cold current-session request must restore its owner without passing the deletion gate.
|
|
503
|
+
if (!owners.length && scope.sessionId === sessionId) {
|
|
504
|
+
const key = `session:${sessionId}`;
|
|
505
|
+
const promise = this.createRuntime({ sessionId, resume: true }).then(runtime => this.createRepl(runtime));
|
|
506
|
+
this.repls.set(key, promise);
|
|
507
|
+
try {
|
|
508
|
+
const repl = await promise;
|
|
509
|
+
entries.push({ key, promise, repl });
|
|
510
|
+
owners.push(repl);
|
|
511
|
+
}
|
|
512
|
+
catch (error) {
|
|
513
|
+
this.repls.delete(key);
|
|
514
|
+
throw error;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
const repl = owners[0] || await this.get(scope);
|
|
518
|
+
if (entries.some(entry => entry.repl?.runningSessionIds().includes(sessionId)))
|
|
519
|
+
return actionFailure("SESSION_RUNNING", "运行中的会话不可删除");
|
|
520
|
+
const result = await repl.deleteSession(sessionId);
|
|
521
|
+
if (!result.ok)
|
|
522
|
+
return result;
|
|
523
|
+
this.deletedSessions.add(sessionId);
|
|
524
|
+
// Retire every cache alias of the deleted writer, then publish only the replacement key.
|
|
525
|
+
if (owners.length) {
|
|
526
|
+
for (const entry of entries)
|
|
527
|
+
if (entry.repl === repl && this.repls.get(entry.key) === entry.promise)
|
|
528
|
+
this.repls.delete(entry.key);
|
|
529
|
+
if (result.replacementSessionId)
|
|
530
|
+
this.repls.set(`session:${result.replacementSessionId}`, Promise.resolve(repl));
|
|
531
|
+
}
|
|
532
|
+
// Publish only after the replacement is routable; SSE clients may reconnect immediately.
|
|
533
|
+
if (owners.length)
|
|
534
|
+
repl.publishSessionDeletion(sessionId, result.replacementSessionId);
|
|
535
|
+
return result;
|
|
536
|
+
}
|
|
537
|
+
finally {
|
|
538
|
+
this.deletingSessions.delete(sessionId);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
481
541
|
createRuntime;
|
|
482
542
|
createRepl;
|
|
483
543
|
clientRevision = process.env.NEO_CLIENT_REVISION?.trim() || `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -487,6 +547,7 @@ export class WebRuntimeRouter {
|
|
|
487
547
|
this.createRepl = options.createRepl ?? ((runtime) => new WebRepl(runtime));
|
|
488
548
|
}
|
|
489
549
|
get(scope = {}) {
|
|
550
|
+
this.assertSessionAvailable(scope.sessionId);
|
|
490
551
|
const key = webRuntimeScopeKey(scope);
|
|
491
552
|
let repl = this.repls.get(key);
|
|
492
553
|
if (!repl) {
|
|
@@ -551,6 +612,9 @@ function webRuntimeScopeKey(scope) {
|
|
|
551
612
|
export class WebRepl {
|
|
552
613
|
runtime;
|
|
553
614
|
demoOptions;
|
|
615
|
+
currentSessionId() { return this.runtime.engine.snapshot().session?.sessionId; }
|
|
616
|
+
deletingSession = false;
|
|
617
|
+
sessionMutations = 0;
|
|
554
618
|
pendingContinuation;
|
|
555
619
|
runControl() {
|
|
556
620
|
const execution = this.runtime.engine.getExecutionState?.();
|
|
@@ -896,6 +960,8 @@ export class WebRepl {
|
|
|
896
960
|
await this.refreshToolConfiguration();
|
|
897
961
|
}
|
|
898
962
|
async setSessionTools(value) {
|
|
963
|
+
if (this.deletingSession)
|
|
964
|
+
return actionFailure("SESSION_DELETING", "会话正在删除");
|
|
899
965
|
if (this.busy)
|
|
900
966
|
return actionFailure("TOOL_UPDATE_BLOCKED", "cannot change session tools while a response is running");
|
|
901
967
|
if (!isRecord(value))
|
|
@@ -906,6 +972,7 @@ export class WebRepl {
|
|
|
906
972
|
const normalized = this.parseToolModes(value, support);
|
|
907
973
|
if (!normalized.ok)
|
|
908
974
|
return normalized;
|
|
975
|
+
this.sessionMutations++;
|
|
909
976
|
try {
|
|
910
977
|
await this.applySessionToolOverrides(normalized.value, true);
|
|
911
978
|
return { ok: true, state: this.sessionTools() };
|
|
@@ -913,6 +980,9 @@ export class WebRepl {
|
|
|
913
980
|
catch (error) {
|
|
914
981
|
return actionFailure("TOOL_UPDATE_FAILED", error instanceof Error ? error.message : String(error));
|
|
915
982
|
}
|
|
983
|
+
finally {
|
|
984
|
+
this.sessionMutations--;
|
|
985
|
+
}
|
|
916
986
|
}
|
|
917
987
|
async loadSessionTools(sessionId) {
|
|
918
988
|
const support = this.runtime.toolSupport;
|
|
@@ -921,6 +991,8 @@ export class WebRepl {
|
|
|
921
991
|
await this.applySessionToolOverrides(await support.resolveSession(sessionId), false);
|
|
922
992
|
}
|
|
923
993
|
async setSessionPlugins(value) {
|
|
994
|
+
if (this.deletingSession)
|
|
995
|
+
return actionFailure("SESSION_DELETING", "会话正在删除");
|
|
924
996
|
if (!isRecord(value))
|
|
925
997
|
return actionFailure("INVALID_REQUEST", "plugin overrides must be an object");
|
|
926
998
|
const support = this.runtime.pluginSupport;
|
|
@@ -938,6 +1010,7 @@ export class WebRepl {
|
|
|
938
1010
|
else if (mode !== "inherit" && mode !== null && mode !== undefined)
|
|
939
1011
|
return actionFailure("PLUGIN_INVALID", `invalid plugin mode: ${id}`);
|
|
940
1012
|
}
|
|
1013
|
+
this.sessionMutations++;
|
|
941
1014
|
try {
|
|
942
1015
|
await this.applySessionPluginOverrides(overrides, true);
|
|
943
1016
|
return { ok: true, state: this.sessionPlugins() };
|
|
@@ -945,6 +1018,9 @@ export class WebRepl {
|
|
|
945
1018
|
catch (error) {
|
|
946
1019
|
return actionFailure("PLUGIN_UPDATE_FAILED", error instanceof Error ? error.message : String(error));
|
|
947
1020
|
}
|
|
1021
|
+
finally {
|
|
1022
|
+
this.sessionMutations--;
|
|
1023
|
+
}
|
|
948
1024
|
}
|
|
949
1025
|
async loadSessionPlugins(sessionId) {
|
|
950
1026
|
const support = this.runtime.pluginSupport;
|
|
@@ -1035,6 +1111,8 @@ export class WebRepl {
|
|
|
1035
1111
|
this.publishRuntimeContext();
|
|
1036
1112
|
}
|
|
1037
1113
|
async submit(text, attachments = []) {
|
|
1114
|
+
if (this.deletingSession)
|
|
1115
|
+
return actionFailure("SESSION_DELETING", "会话正在删除");
|
|
1038
1116
|
if (this.runControl().canResume && (!text.trimStart().startsWith("/") || !text.trim()))
|
|
1039
1117
|
return this.continueRun(text, attachments);
|
|
1040
1118
|
if (this.pendingContinuation)
|
|
@@ -1063,6 +1141,8 @@ export class WebRepl {
|
|
|
1063
1141
|
return { ok: true };
|
|
1064
1142
|
}
|
|
1065
1143
|
async submitImmediately(text, attachments = []) {
|
|
1144
|
+
if (this.deletingSession)
|
|
1145
|
+
return actionFailure("SESSION_DELETING", "会话正在删除");
|
|
1066
1146
|
if (this.runControl().canResume) {
|
|
1067
1147
|
const result = this.continueRun(text, attachments);
|
|
1068
1148
|
return result.ok ? { ok: true, interrupted: false } : result;
|
|
@@ -1083,6 +1163,8 @@ export class WebRepl {
|
|
|
1083
1163
|
return this.sendQueuedNow();
|
|
1084
1164
|
}
|
|
1085
1165
|
sendQueuedNow() {
|
|
1166
|
+
if (this.deletingSession)
|
|
1167
|
+
return Promise.reject(new Error("会话正在删除"));
|
|
1086
1168
|
if (this.immediateSend)
|
|
1087
1169
|
return this.immediateSend;
|
|
1088
1170
|
const queuedText = this.queuedInput;
|
|
@@ -1121,8 +1203,11 @@ export class WebRepl {
|
|
|
1121
1203
|
return { ...(options ? paginateSessions(sessions, options) : { sessions }), runningSessionIds };
|
|
1122
1204
|
}
|
|
1123
1205
|
async resumeSession(sessionId) {
|
|
1206
|
+
if (this.deletingSession)
|
|
1207
|
+
return actionFailure("SESSION_DELETING", "会话正在删除");
|
|
1124
1208
|
if (!sessionId)
|
|
1125
1209
|
return actionFailure("INVALID_REQUEST", "sessionId is required");
|
|
1210
|
+
this.sessionMutations++;
|
|
1126
1211
|
try {
|
|
1127
1212
|
const running = this.backgroundSessionRuns.get(sessionId);
|
|
1128
1213
|
if (running) {
|
|
@@ -1144,8 +1229,14 @@ export class WebRepl {
|
|
|
1144
1229
|
const message = error instanceof Error ? error.message : String(error);
|
|
1145
1230
|
return actionFailure("SESSION_RESUME_FAILED", message);
|
|
1146
1231
|
}
|
|
1232
|
+
finally {
|
|
1233
|
+
this.sessionMutations--;
|
|
1234
|
+
}
|
|
1147
1235
|
}
|
|
1148
1236
|
async newSession() {
|
|
1237
|
+
if (this.deletingSession)
|
|
1238
|
+
return actionFailure("SESSION_DELETING", "会话正在删除");
|
|
1239
|
+
this.sessionMutations++;
|
|
1149
1240
|
try {
|
|
1150
1241
|
await this.detachRunningForeground("new session");
|
|
1151
1242
|
this.runtime.engine = this.runtime.engine.forkForSession(undefined, false);
|
|
@@ -1162,8 +1253,11 @@ export class WebRepl {
|
|
|
1162
1253
|
const message = error instanceof Error ? error.message : String(error);
|
|
1163
1254
|
return actionFailure("SESSION_CREATE_FAILED", message);
|
|
1164
1255
|
}
|
|
1256
|
+
finally {
|
|
1257
|
+
this.sessionMutations--;
|
|
1258
|
+
}
|
|
1165
1259
|
}
|
|
1166
|
-
async refreshSessionView() {
|
|
1260
|
+
async refreshSessionView(publish = true) {
|
|
1167
1261
|
const metrics = await this.runtime.engine.contextMetrics();
|
|
1168
1262
|
this.runtime.usage.reset();
|
|
1169
1263
|
this.status = initialStatus(this.runtime, metrics);
|
|
@@ -1175,27 +1269,75 @@ export class WebRepl {
|
|
|
1175
1269
|
this.thinkingLineId = undefined;
|
|
1176
1270
|
this.finalizedThinkingLineId = undefined;
|
|
1177
1271
|
this.liveToolLineIds.clear();
|
|
1178
|
-
|
|
1179
|
-
|
|
1272
|
+
if (publish) {
|
|
1273
|
+
this.broadcastSync();
|
|
1274
|
+
this.publishRuntimeContext();
|
|
1275
|
+
}
|
|
1180
1276
|
}
|
|
1181
1277
|
async deleteSession(sessionId) {
|
|
1182
1278
|
if (!sessionId)
|
|
1183
1279
|
return actionFailure("INVALID_REQUEST", "sessionId is required");
|
|
1184
|
-
if (this.
|
|
1185
|
-
return actionFailure("
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1280
|
+
if (this.deletingSession)
|
|
1281
|
+
return actionFailure("SESSION_DELETING", "会话正在删除");
|
|
1282
|
+
if (this.sessionMutations)
|
|
1283
|
+
return actionFailure("SESSION_IN_USE", "会话正在保存或切换,请稍后重试");
|
|
1284
|
+
const active = this.runtime.engine.snapshot().session?.sessionId === sessionId;
|
|
1285
|
+
if (this.backgroundSessionRuns.has(sessionId) || (active && (this.busy || this.foregroundRun || this.compactingSession || this.pendingContinuation || this.immediateSend || this.backgroundTaskCount))) {
|
|
1286
|
+
return actionFailure("SESSION_RUNNING", "运行中的会话不可删除");
|
|
1287
|
+
}
|
|
1288
|
+
this.deletingSession = true;
|
|
1289
|
+
const previous = this.runtime.engine;
|
|
1290
|
+
let deleted = false;
|
|
1190
1291
|
try {
|
|
1191
|
-
|
|
1192
|
-
|
|
1292
|
+
if (active) {
|
|
1293
|
+
// Retire the writer before removing files; an uninitialized fork owns no directory.
|
|
1294
|
+
await this.runtimeContextPublishQueue;
|
|
1295
|
+
await previous.settleSessionWrites();
|
|
1296
|
+
await this.demoMessageQueues.get(previous);
|
|
1297
|
+
this.queuedInput = undefined;
|
|
1298
|
+
this.queuedAttachments = undefined;
|
|
1299
|
+
this.pendingContinuation = undefined;
|
|
1300
|
+
this.runtime.engine = previous.forkForSession(undefined, false);
|
|
1301
|
+
activateSession(this.runtime);
|
|
1302
|
+
}
|
|
1303
|
+
deleted = await this.runtime.engine.deleteSession(sessionId);
|
|
1304
|
+
if (!deleted) {
|
|
1305
|
+
if (active) {
|
|
1306
|
+
this.runtime.engine = previous;
|
|
1307
|
+
activateSession(this.runtime);
|
|
1308
|
+
}
|
|
1193
1309
|
return actionFailure("SESSION_NOT_FOUND", `session not found: ${sessionId}`);
|
|
1194
|
-
|
|
1310
|
+
}
|
|
1311
|
+
if (!active)
|
|
1312
|
+
return { ok: true };
|
|
1313
|
+
await this.runtime.engine.initialize();
|
|
1314
|
+
activateSession(this.runtime);
|
|
1315
|
+
const replacementSessionId = this.runtime.engine.snapshot().session?.sessionId;
|
|
1316
|
+
if (!replacementSessionId)
|
|
1317
|
+
throw new Error("新会话初始化失败");
|
|
1318
|
+
await Promise.all([this.loadSessionPlugins(replacementSessionId), this.loadSessionTools(replacementSessionId)]);
|
|
1319
|
+
await this.refreshSessionView(false);
|
|
1320
|
+
return { ok: true, replacementSessionId };
|
|
1195
1321
|
}
|
|
1196
1322
|
catch (error) {
|
|
1197
|
-
|
|
1198
|
-
|
|
1323
|
+
if (active && !deleted) {
|
|
1324
|
+
this.runtime.engine = previous;
|
|
1325
|
+
activateSession(this.runtime);
|
|
1326
|
+
}
|
|
1327
|
+
if (active && deleted) {
|
|
1328
|
+
// A deletion cannot be rolled back; never restore the deleted writer.
|
|
1329
|
+
return { ok: true };
|
|
1330
|
+
}
|
|
1331
|
+
return actionFailure("SESSION_DELETE_FAILED", error instanceof Error ? error.message : String(error));
|
|
1332
|
+
}
|
|
1333
|
+
finally {
|
|
1334
|
+
this.deletingSession = false;
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
publishSessionDeletion(sessionId, replacementSessionId) {
|
|
1338
|
+
for (const subscriber of this.subscribers) {
|
|
1339
|
+
this.send(subscriber, replacementSessionId ? "session.replaced" : "session.deleted", replacementSessionId
|
|
1340
|
+
? { deletedSessionId: sessionId, sessionId: replacementSessionId } : { sessionId });
|
|
1199
1341
|
}
|
|
1200
1342
|
}
|
|
1201
1343
|
appPrompt() {
|
|
@@ -1274,6 +1416,8 @@ export class WebRepl {
|
|
|
1274
1416
|
this.setStatus({ ...this.status, metrics, activityTick: this.status.activityTick + 1 });
|
|
1275
1417
|
}
|
|
1276
1418
|
async setFastMode(enabled) {
|
|
1419
|
+
if (this.deletingSession)
|
|
1420
|
+
return actionFailure("SESSION_DELETING", "会话正在删除");
|
|
1277
1421
|
const engine = this.runtime.engine;
|
|
1278
1422
|
try {
|
|
1279
1423
|
const { deferred } = await engine.updateSessionSettings({ fastMode: enabled });
|
|
@@ -1286,6 +1430,8 @@ export class WebRepl {
|
|
|
1286
1430
|
}
|
|
1287
1431
|
}
|
|
1288
1432
|
async setContextWindowK(value) {
|
|
1433
|
+
if (this.deletingSession)
|
|
1434
|
+
return actionFailure("SESSION_DELETING", "会话正在删除");
|
|
1289
1435
|
if (typeof value !== "string" || !/^\d+$/u.test(value) || Number(value) <= 0 || !Number.isSafeInteger(Number(value) * 1000)) {
|
|
1290
1436
|
return actionFailure("CONTEXT_WINDOW_INVALID", "context window must be a positive integer in k");
|
|
1291
1437
|
}
|
|
@@ -1305,6 +1451,8 @@ export class WebRepl {
|
|
|
1305
1451
|
}
|
|
1306
1452
|
}
|
|
1307
1453
|
async setSessionModel(model, reasoning) {
|
|
1454
|
+
if (this.deletingSession)
|
|
1455
|
+
return actionFailure("SESSION_DELETING", "会话正在删除");
|
|
1308
1456
|
if (typeof model !== "string" || !model.trim() || model.length > 256 || /[\s\x00-\x1f]/u.test(model.trim())) {
|
|
1309
1457
|
return actionFailure("MODEL_INVALID", "invalid model");
|
|
1310
1458
|
}
|
|
@@ -1337,6 +1485,8 @@ export class WebRepl {
|
|
|
1337
1485
|
return readSessionPrompt(this.runtime.engine);
|
|
1338
1486
|
}
|
|
1339
1487
|
async saveSessionPrompt(body) {
|
|
1488
|
+
if (this.deletingSession)
|
|
1489
|
+
return actionFailure("SESSION_DELETING", "会话正在删除");
|
|
1340
1490
|
const engine = this.runtime.engine;
|
|
1341
1491
|
const result = await updateSessionPrompt(engine, body);
|
|
1342
1492
|
if (this.runtime.engine === engine) {
|
|
@@ -1348,6 +1498,8 @@ export class WebRepl {
|
|
|
1348
1498
|
return result;
|
|
1349
1499
|
}
|
|
1350
1500
|
async compactSession() {
|
|
1501
|
+
if (this.deletingSession)
|
|
1502
|
+
return actionFailure("SESSION_DELETING", "会话正在删除");
|
|
1351
1503
|
const engine = this.runtime.engine;
|
|
1352
1504
|
if (this.isDemo())
|
|
1353
1505
|
return actionFailure("DEMO_SCRIPTED", "演示会话由后台安排压缩");
|
|
@@ -1484,6 +1636,8 @@ export class WebRepl {
|
|
|
1484
1636
|
return { ok: true };
|
|
1485
1637
|
}
|
|
1486
1638
|
continueRun(text = "", attachments = []) {
|
|
1639
|
+
if (this.deletingSession)
|
|
1640
|
+
return actionFailure("SESSION_DELETING", "会话正在删除");
|
|
1487
1641
|
if (!this.runControl().canResume)
|
|
1488
1642
|
return actionFailure("INVALID_REQUEST", "没有可恢复的执行,或续接请求已经提交");
|
|
1489
1643
|
const useQueue = !text.trim() && attachments.length === 0 && this.queuedInput !== undefined;
|
|
@@ -2440,6 +2594,8 @@ export class WebRepl {
|
|
|
2440
2594
|
});
|
|
2441
2595
|
}
|
|
2442
2596
|
publishRuntimeContext(targets) {
|
|
2597
|
+
if (this.deletingSession)
|
|
2598
|
+
return;
|
|
2443
2599
|
this.runtimeContextPublishQueue = this.runtimeContextPublishQueue.then(async () => {
|
|
2444
2600
|
const revision = ++this.runtimeContextRevision;
|
|
2445
2601
|
const payload = await this.buildRuntimeContext(revision);
|
|
@@ -2528,6 +2684,8 @@ export class WebRepl {
|
|
|
2528
2684
|
this.send(subscriber, "terminal.output", { updates });
|
|
2529
2685
|
}
|
|
2530
2686
|
broadcastSync() {
|
|
2687
|
+
if (this.deletingSession)
|
|
2688
|
+
return;
|
|
2531
2689
|
this.discardPendingDeltaOperations();
|
|
2532
2690
|
const payload = this.snapshot(false);
|
|
2533
2691
|
for (const subscriber of this.subscribers)
|
|
@@ -2590,6 +2748,10 @@ export async function handleWebRequest(req, res, router) {
|
|
|
2590
2748
|
if (req.method === "GET" && url.pathname === "/api/sessions/running")
|
|
2591
2749
|
return sendJson(res, { runningSessionIds: await router.runningSessionIds() });
|
|
2592
2750
|
const scope = webRuntimeScopeFromUrl(url);
|
|
2751
|
+
if (req.method === "POST" && url.pathname === "/api/sessions/delete") {
|
|
2752
|
+
const body = await readJsonBody(req);
|
|
2753
|
+
return sendJson(res, await router.deleteSession(scope, String(body.sessionId ?? "")));
|
|
2754
|
+
}
|
|
2593
2755
|
const repl = await router.get(scope);
|
|
2594
2756
|
if (req.method === "GET" && url.pathname === "/events")
|
|
2595
2757
|
return repl.subscribe(res, router.clientInfo());
|
|
@@ -2800,10 +2962,6 @@ export async function handleWebRequest(req, res, router) {
|
|
|
2800
2962
|
}
|
|
2801
2963
|
if (req.method === "POST" && url.pathname === "/api/sessions/new")
|
|
2802
2964
|
return sendJson(res, await repl.newSession());
|
|
2803
|
-
if (req.method === "POST" && url.pathname === "/api/sessions/delete") {
|
|
2804
|
-
const body = await readJsonBody(req);
|
|
2805
|
-
return sendJson(res, await repl.deleteSession(String(body.sessionId ?? "")));
|
|
2806
|
-
}
|
|
2807
2965
|
if (req.method === "GET" && url.pathname === "/api/login")
|
|
2808
2966
|
return sendJson(res, repl.loginForm(url.searchParams.get("provider") ?? undefined));
|
|
2809
2967
|
if (req.method === "POST" && url.pathname === "/api/login") {
|