dominds 1.25.9 → 1.25.10
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.
|
@@ -1347,6 +1347,54 @@ async function maybeWriteUnexpectedIdleAfterToolRoundDebugDump(args) {
|
|
|
1347
1347
|
await promises_1.default.mkdir(debugDir, { recursive: true });
|
|
1348
1348
|
await promises_1.default.writeFile(node_path_1.default.join(debugDir, fileName), `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
|
|
1349
1349
|
}
|
|
1350
|
+
async function writeMissingImmediateFollowupTriggerDebugDump(args) {
|
|
1351
|
+
const capturedAt = (0, time_1.formatUnifiedTimestamp)(new Date());
|
|
1352
|
+
const debugDir = node_path_1.default.resolve(process.cwd(), '.dialogs', 'debug');
|
|
1353
|
+
const trigger = args.expectation.trigger;
|
|
1354
|
+
const fileName = [
|
|
1355
|
+
'kernel-driver-missing-immediate-followup-trigger',
|
|
1356
|
+
sanitizeDebugFileSegment(capturedAt),
|
|
1357
|
+
sanitizeDebugFileSegment(args.dlg.id.rootId),
|
|
1358
|
+
sanitizeDebugFileSegment(args.dlg.id.selfId),
|
|
1359
|
+
sanitizeDebugFileSegment(trigger.triggerId),
|
|
1360
|
+
`${(0, id_1.generateShortId)()}.json`,
|
|
1361
|
+
].join('-');
|
|
1362
|
+
const activeCallees = await persistence_1.DialogPersistence.loadActiveCallees(args.dlg.id, args.dlg.status);
|
|
1363
|
+
const suspension = await args.dlg.getSuspensionStatus();
|
|
1364
|
+
const payload = {
|
|
1365
|
+
kind: args.repairOutcome === 'repaired'
|
|
1366
|
+
? 'kernel_driver_missing_immediate_followup_trigger_repaired'
|
|
1367
|
+
: 'kernel_driver_missing_immediate_followup_trigger_repair_failed',
|
|
1368
|
+
capturedAt,
|
|
1369
|
+
rtwsRootAbs: process.cwd(),
|
|
1370
|
+
repairOutcome: args.repairOutcome,
|
|
1371
|
+
checkPoint: args.checkPoint,
|
|
1372
|
+
dialog: {
|
|
1373
|
+
rootId: args.dlg.id.rootId,
|
|
1374
|
+
selfId: args.dlg.id.selfId,
|
|
1375
|
+
value: args.dlg.id.valueOf(),
|
|
1376
|
+
agentId: args.dlg.agentId,
|
|
1377
|
+
status: args.dlg.status,
|
|
1378
|
+
currentCourse: args.dlg.currentCourse,
|
|
1379
|
+
activeGenCourse: args.dlg.activeGenCourseOrUndefined ?? null,
|
|
1380
|
+
activeGenSeq: args.dlg.activeGenSeqOrUndefined ?? null,
|
|
1381
|
+
hasQueuedPrompt: args.dlg.hasQueuedPrompt(),
|
|
1382
|
+
queuedPrompt: args.dlg.peekQueuedPrompt() ?? null,
|
|
1383
|
+
},
|
|
1384
|
+
expectation: args.expectation,
|
|
1385
|
+
latestBeforeRepair: args.latestBeforeRepair,
|
|
1386
|
+
latestAfterRepair: args.latestAfterRepair,
|
|
1387
|
+
activeCallees,
|
|
1388
|
+
suspension,
|
|
1389
|
+
callstack: new Error(args.repairOutcome === 'repaired'
|
|
1390
|
+
? 'kernel-driver missing immediate followup trigger repaired'
|
|
1391
|
+
: 'kernel-driver missing immediate followup trigger repair failed').stack ?? null,
|
|
1392
|
+
};
|
|
1393
|
+
await promises_1.default.mkdir(debugDir, { recursive: true });
|
|
1394
|
+
const debugPath = node_path_1.default.join(debugDir, fileName);
|
|
1395
|
+
await promises_1.default.writeFile(debugPath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
|
|
1396
|
+
return debugPath;
|
|
1397
|
+
}
|
|
1350
1398
|
async function maybeWriteIdleWithActiveReplyObligationDebugDump(args) {
|
|
1351
1399
|
if (args.finalDisplayState.kind !== 'idle_waiting_user') {
|
|
1352
1400
|
return;
|
|
@@ -1392,7 +1440,7 @@ async function maybeWriteIdleWithActiveReplyObligationDebugDump(args) {
|
|
|
1392
1440
|
await promises_1.default.mkdir(debugDir, { recursive: true });
|
|
1393
1441
|
await promises_1.default.writeFile(node_path_1.default.join(debugDir, fileName), `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
|
|
1394
1442
|
}
|
|
1395
|
-
|
|
1443
|
+
function buildImmediateFollowupTriggerExpectation(args) {
|
|
1396
1444
|
const reasons = [];
|
|
1397
1445
|
if (args.routed.immediateFollowupCallIds.length > 0) {
|
|
1398
1446
|
reasons.push({
|
|
@@ -1418,11 +1466,11 @@ async function upsertImmediateFollowupTrigger(args) {
|
|
|
1418
1466
|
});
|
|
1419
1467
|
}
|
|
1420
1468
|
if (reasons.length === 0) {
|
|
1421
|
-
return;
|
|
1469
|
+
return undefined;
|
|
1422
1470
|
}
|
|
1423
1471
|
const course = args.dlg.activeGenCourseOrUndefined ?? args.dlg.currentCourse;
|
|
1424
1472
|
const genseq = args.dlg.activeGenSeq;
|
|
1425
|
-
|
|
1473
|
+
const trigger = {
|
|
1426
1474
|
triggerId: `followup:c${String(course)}:g${String(genseq)}`,
|
|
1427
1475
|
kind: 'followup',
|
|
1428
1476
|
sourceGeneration: {
|
|
@@ -1431,7 +1479,74 @@ async function upsertImmediateFollowupTrigger(args) {
|
|
|
1431
1479
|
},
|
|
1432
1480
|
reasons,
|
|
1433
1481
|
continuation: args.continuation,
|
|
1482
|
+
};
|
|
1483
|
+
return {
|
|
1484
|
+
trigger,
|
|
1485
|
+
callIds: args.streamedFuncCalls.map((call) => call.id),
|
|
1486
|
+
callNames: args.streamedFuncCalls.map((call) => call.name),
|
|
1487
|
+
routed: summarizeRoutedFunctionResult(args.routed),
|
|
1488
|
+
continuation: args.continuation,
|
|
1489
|
+
invalidFuncCallCount: args.invalidFuncCallCount,
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
async function upsertImmediateFollowupTrigger(dlg, expectation) {
|
|
1493
|
+
await persistence_1.DialogPersistence.upsertNextStepTrigger(dlg.id, expectation.trigger, dlg.status);
|
|
1494
|
+
}
|
|
1495
|
+
function hasExpectedImmediateFollowupTrigger(latest, expectation) {
|
|
1496
|
+
return (latest?.nextStep.triggers.some((trigger) => trigger.kind === 'followup' && trigger.triggerId === expectation.trigger.triggerId) === true);
|
|
1497
|
+
}
|
|
1498
|
+
async function repairMissingImmediateFollowupTrigger(args) {
|
|
1499
|
+
const expectation = args.expectation;
|
|
1500
|
+
if (expectation === undefined) {
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1503
|
+
const latestBeforeRepair = await persistence_1.DialogPersistence.loadDialogLatest(args.dlg.id, args.dlg.status);
|
|
1504
|
+
if (hasExpectedImmediateFollowupTrigger(latestBeforeRepair, expectation)) {
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
await persistence_1.DialogPersistence.upsertNextStepTrigger(args.dlg.id, expectation.trigger, args.dlg.status);
|
|
1508
|
+
const latestAfterRepair = await persistence_1.DialogPersistence.loadDialogLatest(args.dlg.id, args.dlg.status);
|
|
1509
|
+
const repairSucceeded = hasExpectedImmediateFollowupTrigger(latestAfterRepair, expectation);
|
|
1510
|
+
const repairOutcome = repairSucceeded
|
|
1511
|
+
? 'repaired'
|
|
1512
|
+
: 'repair_failed';
|
|
1513
|
+
const debugPath = await writeMissingImmediateFollowupTriggerDebugDump({
|
|
1514
|
+
dlg: args.dlg,
|
|
1515
|
+
expectation,
|
|
1516
|
+
latestBeforeRepair,
|
|
1517
|
+
latestAfterRepair,
|
|
1518
|
+
checkPoint: args.checkPoint,
|
|
1519
|
+
repairOutcome,
|
|
1434
1520
|
});
|
|
1521
|
+
const message = `${repairSucceeded ? 'Repaired' : 'Failed to repair'} missing immediate follow-up trigger after function results ` +
|
|
1522
|
+
`(triggerId=${expectation.trigger.triggerId}, checkPoint=${args.checkPoint})`;
|
|
1523
|
+
log_1.log.error(message, new Error(`kernel_driver_missing_immediate_followup_trigger_${repairOutcome}`), {
|
|
1524
|
+
rootId: args.dlg.id.rootId,
|
|
1525
|
+
selfId: args.dlg.id.selfId,
|
|
1526
|
+
dialogId: args.dlg.id.valueOf(),
|
|
1527
|
+
status: args.dlg.status,
|
|
1528
|
+
triggerId: expectation.trigger.triggerId,
|
|
1529
|
+
checkPoint: args.checkPoint,
|
|
1530
|
+
repairSucceeded,
|
|
1531
|
+
sourceGeneration: expectation.trigger.sourceGeneration,
|
|
1532
|
+
reasonKinds: expectation.trigger.reasons.map((reason) => reason.kind),
|
|
1533
|
+
callIds: expectation.callIds,
|
|
1534
|
+
callNames: expectation.callNames,
|
|
1535
|
+
invalidFuncCallCount: expectation.invalidFuncCallCount,
|
|
1536
|
+
continuation: expectation.continuation,
|
|
1537
|
+
routed: expectation.routed,
|
|
1538
|
+
latestBeforeRepairNextStep: latestBeforeRepair?.nextStep ?? null,
|
|
1539
|
+
latestBeforeRepairGenerationRunState: latestBeforeRepair?.generationRunState ?? null,
|
|
1540
|
+
latestBeforeRepairDisplayState: latestBeforeRepair?.displayState ?? null,
|
|
1541
|
+
latestAfterRepairNextStep: latestAfterRepair?.nextStep ?? null,
|
|
1542
|
+
latestAfterRepairGenerationRunState: latestAfterRepair?.generationRunState ?? null,
|
|
1543
|
+
latestAfterRepairDisplayState: latestAfterRepair?.displayState ?? null,
|
|
1544
|
+
debugPath,
|
|
1545
|
+
});
|
|
1546
|
+
await args.dlg.streamError(`${message}; debug=${debugPath}`);
|
|
1547
|
+
if (!repairSucceeded) {
|
|
1548
|
+
throw new Error(`${message}; debug=${debugPath}`);
|
|
1549
|
+
}
|
|
1435
1550
|
}
|
|
1436
1551
|
function shouldImmediatelyFollowUpSuccessfulToolResult(tool) {
|
|
1437
1552
|
return resolveFuncToolFollowupMode(tool) === 'immediate';
|
|
@@ -2269,6 +2384,7 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
|
|
|
2269
2384
|
}
|
|
2270
2385
|
lastBusinessContinuation = currentBusinessContinuation;
|
|
2271
2386
|
let generationBodyError;
|
|
2387
|
+
let immediateFollowupTriggerExpectation;
|
|
2272
2388
|
const q4hAnswerCallId = normalizeQ4HAnswerCallId(currentPrompt?.q4hAnswerCallId);
|
|
2273
2389
|
const isQ4HAnswerPrompt = q4hAnswerCallId !== undefined;
|
|
2274
2390
|
try {
|
|
@@ -3139,13 +3255,38 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
|
|
|
3139
3255
|
});
|
|
3140
3256
|
break;
|
|
3141
3257
|
}
|
|
3258
|
+
// Start an immediate post-tool generation only when this round produced tool outputs that
|
|
3259
|
+
// warrant same-drive LLM reaction right away. Provider-native side-channel UI events are
|
|
3260
|
+
// meaningful output, but they are not transcript/context inputs and therefore must not
|
|
3261
|
+
// trigger another immediate generation round by themselves.
|
|
3262
|
+
const shouldStartImmediatePostToolGeneration = routed.hasImmediateFollowupToolCalls ||
|
|
3263
|
+
routed.hasImmediateTellaskOutputs ||
|
|
3264
|
+
invalidFuncCallCount > 0;
|
|
3265
|
+
if (shouldStartImmediatePostToolGeneration) {
|
|
3266
|
+
const expectation = buildImmediateFollowupTriggerExpectation({
|
|
3267
|
+
dlg,
|
|
3268
|
+
routed,
|
|
3269
|
+
invalidFuncCallCount,
|
|
3270
|
+
streamedFuncCalls,
|
|
3271
|
+
continuation: currentBusinessContinuation,
|
|
3272
|
+
});
|
|
3273
|
+
if (expectation === undefined) {
|
|
3274
|
+
throw new Error(`Immediate follow-up trigger invariant violation: expected trigger reasons missing ` +
|
|
3275
|
+
`(dialog=${dlg.id.valueOf()}, course=${String(dlg.activeGenCourseOrUndefined ?? dlg.currentCourse)}, ` +
|
|
3276
|
+
`genseq=${String(dlg.activeGenSeq)}, immediateToolCalls=${String(routed.hasImmediateFollowupToolCalls)}, ` +
|
|
3277
|
+
`immediateTellaskOutputs=${String(routed.hasImmediateTellaskOutputs)}, ` +
|
|
3278
|
+
`invalidFuncCallCount=${String(invalidFuncCallCount)})`);
|
|
3279
|
+
}
|
|
3280
|
+
immediateFollowupTriggerExpectation = expectation;
|
|
3281
|
+
await upsertImmediateFollowupTrigger(dlg, immediateFollowupTriggerExpectation);
|
|
3282
|
+
}
|
|
3142
3283
|
if (dlg.hasQueuedPrompt()) {
|
|
3143
3284
|
lastToolRoundStopDiagnostics = buildToolRoundStopDiagnostics({
|
|
3144
3285
|
dlg,
|
|
3145
3286
|
streamedFuncCalls,
|
|
3146
3287
|
routed,
|
|
3147
3288
|
lastBusinessContinuation,
|
|
3148
|
-
shouldStartImmediatePostToolGeneration
|
|
3289
|
+
shouldStartImmediatePostToolGeneration,
|
|
3149
3290
|
stopReason: 'queued_prompt_after_tool_round',
|
|
3150
3291
|
queuedPromptAfterToolRound: true,
|
|
3151
3292
|
remindersVer: dlg.remindersVer,
|
|
@@ -3166,7 +3307,7 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
|
|
|
3166
3307
|
streamedFuncCalls,
|
|
3167
3308
|
routed,
|
|
3168
3309
|
lastBusinessContinuation,
|
|
3169
|
-
shouldStartImmediatePostToolGeneration
|
|
3310
|
+
shouldStartImmediatePostToolGeneration,
|
|
3170
3311
|
stopReason: 'suspended_after_tool_round',
|
|
3171
3312
|
suspensionAfterToolRound,
|
|
3172
3313
|
queuedPromptAfterToolRound: dlg.hasQueuedPrompt(),
|
|
@@ -3176,13 +3317,6 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
|
|
|
3176
3317
|
await preserveDiligenceBudgetAcrossQ4H(dlg);
|
|
3177
3318
|
break;
|
|
3178
3319
|
}
|
|
3179
|
-
// Start an immediate post-tool generation only when this round produced tool outputs that
|
|
3180
|
-
// warrant same-drive LLM reaction right away. Provider-native side-channel UI events are
|
|
3181
|
-
// meaningful output, but they are not transcript/context inputs and therefore must not
|
|
3182
|
-
// trigger another immediate generation round by themselves.
|
|
3183
|
-
const shouldStartImmediatePostToolGeneration = routed.hasImmediateFollowupToolCalls ||
|
|
3184
|
-
routed.hasImmediateTellaskOutputs ||
|
|
3185
|
-
invalidFuncCallCount > 0;
|
|
3186
3320
|
if (!shouldStartImmediatePostToolGeneration) {
|
|
3187
3321
|
if (routed.shouldStopAfterPendingTellaskWait) {
|
|
3188
3322
|
lastToolRoundStopDiagnostics = buildToolRoundStopDiagnostics({
|
|
@@ -3239,23 +3373,19 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
|
|
|
3239
3373
|
});
|
|
3240
3374
|
break;
|
|
3241
3375
|
}
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
? currentUserPromptMsgId
|
|
3256
|
-
: undefined;
|
|
3257
|
-
continue;
|
|
3258
|
-
}
|
|
3376
|
+
await repairMissingImmediateFollowupTrigger({
|
|
3377
|
+
dlg,
|
|
3378
|
+
expectation: immediateFollowupTriggerExpectation,
|
|
3379
|
+
checkPoint: 'before_immediate_post_tool_generation_continue',
|
|
3380
|
+
});
|
|
3381
|
+
resolvingImmediateToolResultForUserPrompt =
|
|
3382
|
+
currentGenerationBelongsToUserPrompt ||
|
|
3383
|
+
currentGenerationBelongsToUserToolChain ||
|
|
3384
|
+
isUserOriginPrompt(currentPrompt);
|
|
3385
|
+
resolvingImmediateToolResultUserPromptMsgId = resolvingImmediateToolResultForUserPrompt
|
|
3386
|
+
? currentUserPromptMsgId
|
|
3387
|
+
: undefined;
|
|
3388
|
+
continue;
|
|
3259
3389
|
}
|
|
3260
3390
|
catch (err) {
|
|
3261
3391
|
generationBodyError = err;
|
|
@@ -3263,6 +3393,13 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
|
|
|
3263
3393
|
}
|
|
3264
3394
|
finally {
|
|
3265
3395
|
try {
|
|
3396
|
+
if (generationBodyError === undefined) {
|
|
3397
|
+
await repairMissingImmediateFollowupTrigger({
|
|
3398
|
+
dlg,
|
|
3399
|
+
expectation: immediateFollowupTriggerExpectation,
|
|
3400
|
+
checkPoint: 'before_notify_generating_finish',
|
|
3401
|
+
});
|
|
3402
|
+
}
|
|
3266
3403
|
await dlg.notifyGeneratingFinish(contextHealthForGen, llmGenModelForGen);
|
|
3267
3404
|
}
|
|
3268
3405
|
catch (finishErr) {
|
|
@@ -5,6 +5,7 @@ export declare function configureDomindsSelfUpdate(params: {
|
|
|
5
5
|
host: string;
|
|
6
6
|
port: number;
|
|
7
7
|
mode: ServerMode;
|
|
8
|
+
closeWebSocketClients: () => void;
|
|
8
9
|
stopServer: () => Promise<void>;
|
|
9
10
|
}): void;
|
|
10
11
|
export declare function setDomindsSelfUpdateBroadcaster(next: ((status: DomindsSelfUpdateStatus) => void) | null): void;
|
|
@@ -20,6 +20,7 @@ const BACKGROUND_CHECK_INTERVAL_MS = 30 * 60 * 1000;
|
|
|
20
20
|
const LATEST_VERSION_CHECK_TIMEOUT_MS = 60000;
|
|
21
21
|
const RESTART_PORT_RELEASE_TIMEOUT_MS = 15000;
|
|
22
22
|
const RESTART_PORT_PROBE_INTERVAL_MS = 150;
|
|
23
|
+
const RESTART_EXIT_GRACE_MS = 1000;
|
|
23
24
|
const COMMAND_OUTPUT_LOG_LIMIT = 2000;
|
|
24
25
|
const PROXY_URL_ENV_KEYS = new Set([
|
|
25
26
|
'HTTP_PROXY',
|
|
@@ -97,6 +98,9 @@ function truncateCommandOutput(value) {
|
|
|
97
98
|
return raw;
|
|
98
99
|
return `${raw.slice(0, COMMAND_OUTPUT_LOG_LIMIT)}...[truncated ${raw.length - COMMAND_OUTPUT_LOG_LIMIT} chars]`;
|
|
99
100
|
}
|
|
101
|
+
function delayMs(ms) {
|
|
102
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
103
|
+
}
|
|
100
104
|
function formatPathEnvExcerpt(pathEnv) {
|
|
101
105
|
if (pathEnv === null || pathEnv.trim() === '')
|
|
102
106
|
return null;
|
|
@@ -620,6 +624,7 @@ function configureDomindsSelfUpdate(params) {
|
|
|
620
624
|
host: params.host,
|
|
621
625
|
port: params.port,
|
|
622
626
|
mode: params.mode,
|
|
627
|
+
closeWebSocketClients: params.closeWebSocketClients,
|
|
623
628
|
stopServer: params.stopServer,
|
|
624
629
|
};
|
|
625
630
|
latestObservation = { kind: 'unknown' };
|
|
@@ -922,7 +927,28 @@ function spawnDetachedRestartHelper(params) {
|
|
|
922
927
|
}
|
|
923
928
|
async function stopAndExitForRestart() {
|
|
924
929
|
const cfg = assertRuntimeConfig();
|
|
925
|
-
|
|
930
|
+
let stopSettled = false;
|
|
931
|
+
void cfg
|
|
932
|
+
.stopServer()
|
|
933
|
+
.then(() => {
|
|
934
|
+
stopSettled = true;
|
|
935
|
+
})
|
|
936
|
+
.catch((error) => {
|
|
937
|
+
stopSettled = true;
|
|
938
|
+
log.error('Failed to stop Dominds HTTP server during restart grace window', error, {
|
|
939
|
+
host: cfg.host,
|
|
940
|
+
port: cfg.port,
|
|
941
|
+
});
|
|
942
|
+
});
|
|
943
|
+
cfg.closeWebSocketClients();
|
|
944
|
+
await delayMs(RESTART_EXIT_GRACE_MS);
|
|
945
|
+
if (!stopSettled) {
|
|
946
|
+
log.warn('Exiting Dominds process before graceful HTTP server stop completed during restart', undefined, {
|
|
947
|
+
host: cfg.host,
|
|
948
|
+
port: cfg.port,
|
|
949
|
+
graceMs: RESTART_EXIT_GRACE_MS,
|
|
950
|
+
});
|
|
951
|
+
}
|
|
926
952
|
process.exit(0);
|
|
927
953
|
}
|
|
928
954
|
async function restartDomindsIntoLatest() {
|
package/dist/server.js
CHANGED
|
@@ -233,6 +233,19 @@ async function startServer(opts = {}) {
|
|
|
233
233
|
host,
|
|
234
234
|
port: boundPort,
|
|
235
235
|
mode: serverMode,
|
|
236
|
+
closeWebSocketClients: () => {
|
|
237
|
+
if (clients.size === 0)
|
|
238
|
+
return;
|
|
239
|
+
log.info(`Closing ${clients.size} WebSocket client(s) for Dominds restart`);
|
|
240
|
+
for (const ws of clients) {
|
|
241
|
+
try {
|
|
242
|
+
ws.close(1012, 'server_restart');
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
log.warn('Failed to close WebSocket client for Dominds restart', error);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
},
|
|
236
249
|
stopServer: async () => {
|
|
237
250
|
await httpServer.stop();
|
|
238
251
|
},
|