@luckydraw/cumulus 0.31.66 → 1.0.1
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/CHANGELOG.md +12 -556
- package/LICENSE +150 -0
- package/README.md +27 -8
- package/dist/gateway/adapters/webchat.d.ts +15 -0
- package/dist/gateway/adapters/webchat.d.ts.map +1 -1
- package/dist/gateway/adapters/webchat.js +78 -5
- package/dist/gateway/adapters/webchat.js.map +1 -1
- package/dist/gateway/config.d.ts +17 -2
- package/dist/gateway/config.d.ts.map +1 -1
- package/dist/gateway/config.js +10 -3
- package/dist/gateway/config.js.map +1 -1
- package/dist/gateway/daemon.d.ts +3 -1
- package/dist/gateway/daemon.d.ts.map +1 -1
- package/dist/gateway/daemon.js +128 -39
- package/dist/gateway/daemon.js.map +1 -1
- package/dist/gateway/namespaces.d.ts +34 -0
- package/dist/gateway/namespaces.d.ts.map +1 -1
- package/dist/gateway/namespaces.js +58 -0
- package/dist/gateway/namespaces.js.map +1 -1
- package/dist/gateway/server.d.ts +8 -0
- package/dist/gateway/server.d.ts.map +1 -1
- package/dist/gateway/server.js +150 -41
- package/dist/gateway/server.js.map +1 -1
- package/dist/gateway/setup.d.ts +32 -0
- package/dist/gateway/setup.d.ts.map +1 -1
- package/dist/gateway/setup.js +23 -3
- package/dist/gateway/setup.js.map +1 -1
- package/dist/gateway/static/blex-render.js +341 -0
- package/dist/gateway/static/chat.html +1 -0
- package/dist/gateway/static/widget.js +1009 -738
- package/dist/lib/gateway.d.ts +30 -8
- package/dist/lib/gateway.d.ts.map +1 -1
- package/dist/lib/gateway.js +36 -11
- package/dist/lib/gateway.js.map +1 -1
- package/dist/lib/history.d.ts +22 -0
- package/dist/lib/history.d.ts.map +1 -1
- package/dist/lib/history.js +59 -21
- package/dist/lib/history.js.map +1 -1
- package/dist/lib/huggingface-provider.d.ts.map +1 -1
- package/dist/lib/huggingface-provider.js +11 -3
- package/dist/lib/huggingface-provider.js.map +1 -1
- package/dist/lib/license.d.ts +76 -0
- package/dist/lib/license.d.ts.map +1 -0
- package/dist/lib/license.js +141 -0
- package/dist/lib/license.js.map +1 -0
- package/docs/agentic-harness-primer.md +283 -0
- package/docs/conditional-continuation.md +167 -0
- package/docs/web-app-agent-guide.md +559 -0
- package/examples/web-app-agent/README.md +334 -0
- package/examples/web-app-agent/agent/mcp-shim.js +105 -0
- package/examples/web-app-agent/gateway.config.example.json +70 -0
- package/examples/web-app-agent/package.json +13 -0
- package/examples/web-app-agent/public/agent/blex-mount.js +136 -0
- package/examples/web-app-agent/public/agent/bridge-mount.js +91 -0
- package/examples/web-app-agent/public/agent/chat-client.js +104 -0
- package/examples/web-app-agent/public/agent/commands.js +256 -0
- package/examples/web-app-agent/public/agent/device-thread.js +48 -0
- package/examples/web-app-agent/public/agent/panel.css +113 -0
- package/examples/web-app-agent/public/agent/panel.js +392 -0
- package/examples/web-app-agent/public/app.js +250 -0
- package/examples/web-app-agent/public/index.html +126 -0
- package/examples/web-app-agent/server.js +379 -0
- package/package.json +7 -3
package/dist/gateway/server.js
CHANGED
|
@@ -23,16 +23,17 @@ import * as path from 'path';
|
|
|
23
23
|
import { loadThreadConfig, saveThreadConfig, setConfigThreadsDir } from '../lib/config.js';
|
|
24
24
|
import { loadEmbeddings, generateMissingEmbeddings } from '../lib/embeddings.js';
|
|
25
25
|
import { sendMessage, getOrCreateThread, clearThreadCache, getActiveSubprocessCount, setGatewayThreadsDir, } from '../lib/gateway.js';
|
|
26
|
+
import { verifyLicense, majorOf, LICENSE_CONTACT } from '../lib/license.js';
|
|
26
27
|
import { getPromptsDirForThreadPath, isValidCaptureId, readPromptCapture, } from '../lib/prompt-capture.js';
|
|
27
28
|
import { catchUpSegmentation, loadSegmentBoundaries, saveSegmentBoundaries, } from '../lib/segments.js';
|
|
28
29
|
import { listTemplates, scaffoldFromTemplate } from '../lib/templates.js';
|
|
29
|
-
import { checkForUpdate, performUpdate, readChangelog } from '../lib/version-check.js';
|
|
30
|
+
import { checkForUpdate, getCurrentVersion, performUpdate, readChangelog, } from '../lib/version-check.js';
|
|
30
31
|
import { serveStaticFile, getMediaDir } from './adapters/webchat.js';
|
|
31
32
|
import { authenticate, getPresentedKey } from './auth.js';
|
|
32
33
|
import { BridgeGateway } from './bridge/gateway.js';
|
|
33
34
|
import { applyConfigPatch, maskedConfigView } from './config.js';
|
|
34
35
|
import { handleEmailHook, handleFormHook, handleGenericWebhook, } from './hooks.js';
|
|
35
|
-
import { allNamespaceKeys, extraMcpServersForThread, namespaceForKey, namespaceForThread, resolveExecutorProxy, threadAccessibleToScope, threadVisibleToScope, } from './namespaces.js';
|
|
36
|
+
import { allNamespaceKeys, demoMintDenial, extraMcpServersForThread, licenseBannerLines, namespaceForKey, namespaceForThread, resolveExecutorProxy, threadAccessibleToScope, threadVisibleToScope, } from './namespaces.js';
|
|
36
37
|
import { configureVapid, sendNotification, subscribe as pushSubscribe, unsubscribe as pushUnsubscribe, } from './push.js';
|
|
37
38
|
import { validateTranscriptFlush, ingestTranscriptFlush } from './transcript-ingest.js';
|
|
38
39
|
const threadQueues = new Map();
|
|
@@ -379,6 +380,39 @@ function denyOutOfNamespace(res, threadName, scope) {
|
|
|
379
380
|
jsonResponse(res, 403, { error: 'Thread not in your namespace' });
|
|
380
381
|
return true;
|
|
381
382
|
}
|
|
383
|
+
/** Thread names that currently exist on disk (one `.jsonl` per thread). */
|
|
384
|
+
export function listExistingThreadNames() {
|
|
385
|
+
if (!fs.existsSync(THREADS_DIR))
|
|
386
|
+
return [];
|
|
387
|
+
return fs
|
|
388
|
+
.readdirSync(THREADS_DIR)
|
|
389
|
+
.filter(f => f.endsWith('.jsonl'))
|
|
390
|
+
.map(f => f.replace(/\.jsonl$/, ''));
|
|
391
|
+
}
|
|
392
|
+
/** The 402 body for a demo-cap refusal. Exported so tests assert one string. */
|
|
393
|
+
export function demoCapMessage(d) {
|
|
394
|
+
return (`Demo mode: namespace "${d.namespace}" has reached its limit of ${d.cap} visitor threads ` +
|
|
395
|
+
`(${d.count} in use). Existing threads keep working. ` +
|
|
396
|
+
`Commercial and production use requires a licence — contact ${LICENSE_CONTACT}.`);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* 402s and returns true when the request would MINT a new thread in an
|
|
400
|
+
* unlicensed namespace that is already at the demo cap (task 127). Non-namespaced
|
|
401
|
+
* threads, already-existing threads and licensed namespaces all pass through.
|
|
402
|
+
*/
|
|
403
|
+
function denyOverDemoCap(res, threadName, scope) {
|
|
404
|
+
const denial = demoMintDenial(threadName, scope.namespaces, listExistingThreadNames(), scope.license);
|
|
405
|
+
if (!denial)
|
|
406
|
+
return false;
|
|
407
|
+
jsonResponse(res, 402, {
|
|
408
|
+
error: demoCapMessage(denial),
|
|
409
|
+
demoMode: true,
|
|
410
|
+
namespace: denial.namespace,
|
|
411
|
+
cap: denial.cap,
|
|
412
|
+
contact: LICENSE_CONTACT,
|
|
413
|
+
});
|
|
414
|
+
return true;
|
|
415
|
+
}
|
|
382
416
|
async function handleAgentInject(req, res, pipelineOpts, scope) {
|
|
383
417
|
let body;
|
|
384
418
|
try {
|
|
@@ -420,6 +454,23 @@ async function handleAgentInject(req, res, pipelineOpts, scope) {
|
|
|
420
454
|
jsonResponse(res, 403, { error: `Thread not in your namespace: ${denied.join(', ')}` });
|
|
421
455
|
return;
|
|
422
456
|
}
|
|
457
|
+
// Task 127: injecting into a thread that doesn't exist yet mints it. Checked
|
|
458
|
+
// once against a single inventory read so a multi-target inject can't slip
|
|
459
|
+
// extra threads past the cap.
|
|
460
|
+
const existing = listExistingThreadNames();
|
|
461
|
+
for (const t of targetNames) {
|
|
462
|
+
const capDenial = demoMintDenial(t, scope.namespaces, existing, scope.license);
|
|
463
|
+
if (capDenial) {
|
|
464
|
+
jsonResponse(res, 402, {
|
|
465
|
+
error: demoCapMessage(capDenial),
|
|
466
|
+
demoMode: true,
|
|
467
|
+
namespace: capDenial.namespace,
|
|
468
|
+
cap: capDenial.cap,
|
|
469
|
+
contact: LICENSE_CONTACT,
|
|
470
|
+
});
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
423
474
|
const results = [];
|
|
424
475
|
for (const targetName of targetNames) {
|
|
425
476
|
const type = messageType || 'direct';
|
|
@@ -1084,9 +1135,12 @@ async function handleIngestTranscript(req, res, scope) {
|
|
|
1084
1135
|
jsonResponse(res, 400, { error: parsed.error });
|
|
1085
1136
|
return;
|
|
1086
1137
|
}
|
|
1087
|
-
// P7: the target thread arrives in the body, so gate it here.
|
|
1138
|
+
// P7: the target thread arrives in the body, so gate it here. Task 127's demo
|
|
1139
|
+
// cap rides the same spot for the same reason.
|
|
1088
1140
|
if (denyOutOfNamespace(res, parsed.flush.targetThread, scope))
|
|
1089
1141
|
return;
|
|
1142
|
+
if (denyOverDemoCap(res, parsed.flush.targetThread, scope))
|
|
1143
|
+
return;
|
|
1090
1144
|
try {
|
|
1091
1145
|
const result = await ingestTranscriptFlush(parsed.flush);
|
|
1092
1146
|
jsonResponse(res, 200, result);
|
|
@@ -1364,6 +1418,8 @@ async function handleMigrate(req, res, opts, scope) {
|
|
|
1364
1418
|
// P7: the thread arrives in the body — gate before creating/cloning anything.
|
|
1365
1419
|
if (denyOutOfNamespace(res, threadName, scope))
|
|
1366
1420
|
return;
|
|
1421
|
+
if (denyOverDemoCap(res, threadName, scope))
|
|
1422
|
+
return;
|
|
1367
1423
|
const result = {
|
|
1368
1424
|
threadName,
|
|
1369
1425
|
synced: 0,
|
|
@@ -1674,6 +1730,34 @@ export async function startGatewayServer(options) {
|
|
|
1674
1730
|
// them into the valid-key set. The namespace→key mapping stays in
|
|
1675
1731
|
// `namespaces` for scope resolution.
|
|
1676
1732
|
const namespaces = options.namespaces ?? [];
|
|
1733
|
+
// Licence verification (task 127). Offline, once at startup — SIGHUP config
|
|
1734
|
+
// reload is a full process restart, so this re-runs there too. Pasting a key
|
|
1735
|
+
// therefore requires a reload, which is the right cadence for a rare act.
|
|
1736
|
+
const licenseStatus = verifyLicense(options.licenseKey, majorOf(getCurrentVersion()));
|
|
1737
|
+
// Task 129: the licence key is editable in Settings but NEVER live-applied —
|
|
1738
|
+
// enforcement stays `licenseStatus` above, verified once at startup (127's
|
|
1739
|
+
// reload cadence). This tracks the on-disk value only, so GET /api/config can
|
|
1740
|
+
// render masked state and a truthful pendingReload flag after a PUT.
|
|
1741
|
+
let storedLicenseKey = options.licenseKey;
|
|
1742
|
+
const licenseView = () => ({
|
|
1743
|
+
licensed: licenseStatus.licensed,
|
|
1744
|
+
...(licenseStatus.licensed
|
|
1745
|
+
? { licensee: licenseStatus.licensee }
|
|
1746
|
+
: { reason: licenseStatus.reason }),
|
|
1747
|
+
pendingReload: (storedLicenseKey ?? '') !== (options.licenseKey ?? ''),
|
|
1748
|
+
});
|
|
1749
|
+
const emitLicenseBanner = () => {
|
|
1750
|
+
for (const line of licenseBannerLines(licenseStatus, namespaces, listExistingThreadNames())) {
|
|
1751
|
+
console.log(line);
|
|
1752
|
+
}
|
|
1753
|
+
};
|
|
1754
|
+
emitLicenseBanner();
|
|
1755
|
+
// Periodic repeat while unlicensed, so a long-running demo deployment keeps
|
|
1756
|
+
// saying so. `unref` keeps it from holding the process open.
|
|
1757
|
+
const licenseBannerTimer = licenseStatus.licensed
|
|
1758
|
+
? undefined
|
|
1759
|
+
: setInterval(emitLicenseBanner, 60 * 60 * 1000);
|
|
1760
|
+
licenseBannerTimer?.unref?.();
|
|
1677
1761
|
const nsKeys = allNamespaceKeys(namespaces);
|
|
1678
1762
|
let currentApiKeys = Array.from(new Set([...options.apiKeys, ...nsKeys]));
|
|
1679
1763
|
let scheduler;
|
|
@@ -1783,6 +1867,13 @@ export async function startGatewayServer(options) {
|
|
|
1783
1867
|
jsonResponse(res, 403, { error: 'Thread not in your namespace' });
|
|
1784
1868
|
return;
|
|
1785
1869
|
}
|
|
1870
|
+
// Demo capacity gate (task 127) — rides the same choke point, one notch
|
|
1871
|
+
// later: access says WHO may touch a thread, this says whether a NEW one may
|
|
1872
|
+
// be minted. Ordered after the 403 so an out-of-namespace caller never learns
|
|
1873
|
+
// a namespace's occupancy from the status code.
|
|
1874
|
+
const scope = { ns: scopeNs, namespaces, license: licenseStatus };
|
|
1875
|
+
if (pathThread && denyOverDemoCap(res, pathThread, scope))
|
|
1876
|
+
return;
|
|
1786
1877
|
try {
|
|
1787
1878
|
// POST /bridge/call — route a tool call to the connected app tab (task
|
|
1788
1879
|
// 097). Body: { thread, command, params?, confirm?, summary? }. Commands
|
|
@@ -1812,6 +1903,8 @@ export async function startGatewayServer(options) {
|
|
|
1812
1903
|
jsonResponse(res, 403, { error: 'Thread not in your namespace' });
|
|
1813
1904
|
return;
|
|
1814
1905
|
}
|
|
1906
|
+
if (denyOverDemoCap(res, thread, scope))
|
|
1907
|
+
return;
|
|
1815
1908
|
const callParams = body.params ?? {};
|
|
1816
1909
|
const manifestEntry = bridgeGateway.manifest(thread)?.find(c => c.name === command);
|
|
1817
1910
|
const needsConfirm = Boolean(body.confirm) || manifestEntry?.risk === 'export';
|
|
@@ -1929,7 +2022,7 @@ export async function startGatewayServer(options) {
|
|
|
1929
2022
|
}
|
|
1930
2023
|
// POST /api/ingest-transcript — store-only transcript ingestion (task 096)
|
|
1931
2024
|
if (routePath === '/api/ingest-transcript' && req.method === 'POST') {
|
|
1932
|
-
await handleIngestTranscript(req, res,
|
|
2025
|
+
await handleIngestTranscript(req, res, scope);
|
|
1933
2026
|
return;
|
|
1934
2027
|
}
|
|
1935
2028
|
// GET /api/thread/:name/history
|
|
@@ -2022,15 +2115,12 @@ export async function startGatewayServer(options) {
|
|
|
2022
2115
|
// PUT /api/config reassigns pipelineOpts.* in place, so this is the single
|
|
2023
2116
|
// authoritative in-memory source (task 104).
|
|
2024
2117
|
if (routePath === '/api/models' && req.method === 'GET') {
|
|
2025
|
-
//
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
];
|
|
2032
|
-
}
|
|
2033
|
-
else {
|
|
2118
|
+
// Serve the configured catalog truthfully — task 129 deleted the
|
|
2119
|
+
// hardcoded fallbacks (a lone Claude entry / the Opus-Sonnet pair).
|
|
2120
|
+
// An unconfigured gateway gets empty arrays; setup seeds a catalog,
|
|
2121
|
+
// and the Settings gear (topbar, task 128) is the recovery path.
|
|
2122
|
+
const models = (pipelineOpts.models || []).map(m => ({ ...m }));
|
|
2123
|
+
if (models.length > 0) {
|
|
2034
2124
|
// Clear all defaults, then set the global default (or first if none specified)
|
|
2035
2125
|
const globalDefault = pipelineOpts.model || models[0].id;
|
|
2036
2126
|
for (const m of models) {
|
|
@@ -2038,31 +2128,39 @@ export async function startGatewayServer(options) {
|
|
|
2038
2128
|
}
|
|
2039
2129
|
}
|
|
2040
2130
|
// Claude CLI model variants (Opus/Sonnet/...) for the sub-model picker.
|
|
2041
|
-
//
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
{ id: 'claude-opus-5[1m]', label: 'Opus 5', default: true },
|
|
2046
|
-
{ id: 'claude-sonnet-5[1m]', label: 'Sonnet 5' },
|
|
2047
|
-
];
|
|
2048
|
-
}
|
|
2049
|
-
else if (!claudeModels.some(m => m.default)) {
|
|
2131
|
+
// First-is-default is a normalization rule for a configured list, not
|
|
2132
|
+
// model knowledge — it stays.
|
|
2133
|
+
const claudeModels = (pipelineOpts.claudeModels || []).map(m => ({ ...m }));
|
|
2134
|
+
if (claudeModels.length > 0 && !claudeModels.some(m => m.default)) {
|
|
2050
2135
|
claudeModels[0].default = true;
|
|
2051
2136
|
}
|
|
2052
2137
|
jsonResponse(res, 200, { models, claudeModels });
|
|
2053
2138
|
return;
|
|
2054
2139
|
}
|
|
2140
|
+
// GET/PUT /api/config — gateway-level settings are ADMIN-ONLY (task 128).
|
|
2141
|
+
// Task 104 opened these to "any valid key", which predated scoped app keys
|
|
2142
|
+
// becoming a public surface (097 P4 ships one to every app visitor's
|
|
2143
|
+
// browser); a scoped key must not read or rewrite the gateway's model
|
|
2144
|
+
// catalog, default model, or provider credentials.
|
|
2145
|
+
if (routePath === '/api/config' && scopeNs) {
|
|
2146
|
+
jsonResponse(res, 403, { error: 'Gateway settings require an admin key' });
|
|
2147
|
+
return;
|
|
2148
|
+
}
|
|
2055
2149
|
// GET /api/config — live-mutable config subset (task 104), credentials
|
|
2056
|
-
// MASKED.
|
|
2057
|
-
//
|
|
2150
|
+
// MASKED. Lets the in-app editor render current state without ever
|
|
2151
|
+
// exposing a full provider key.
|
|
2058
2152
|
if (routePath === '/api/config' && req.method === 'GET') {
|
|
2059
|
-
jsonResponse(res, 200,
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2153
|
+
jsonResponse(res, 200, {
|
|
2154
|
+
...maskedConfigView({
|
|
2155
|
+
models: pipelineOpts.models,
|
|
2156
|
+
claudeModels: pipelineOpts.claudeModels,
|
|
2157
|
+
model: pipelineOpts.model,
|
|
2158
|
+
hfApiKey: pipelineOpts.hfApiKey,
|
|
2159
|
+
openaiApiKey: pipelineOpts.openaiApiKey,
|
|
2160
|
+
licenseKey: storedLicenseKey,
|
|
2161
|
+
}),
|
|
2162
|
+
license: licenseView(),
|
|
2163
|
+
});
|
|
2066
2164
|
return;
|
|
2067
2165
|
}
|
|
2068
2166
|
// PUT /api/config — live-apply the model catalog / default / provider
|
|
@@ -2070,7 +2168,7 @@ export async function startGatewayServer(options) {
|
|
|
2070
2168
|
// in-memory options in place so /api/models and the per-turn spawn reflect
|
|
2071
2169
|
// it with NO restart; onConfigApplied propagates to the other spawn-path
|
|
2072
2170
|
// holders (webchat). PUT (not PATCH) so it rides the existing CORS
|
|
2073
|
-
// allow-methods.
|
|
2171
|
+
// allow-methods. Admin (non-namespaced) keys only — gated above.
|
|
2074
2172
|
if (routePath === '/api/config' && req.method === 'PUT') {
|
|
2075
2173
|
let patch;
|
|
2076
2174
|
try {
|
|
@@ -2094,15 +2192,24 @@ export async function startGatewayServer(options) {
|
|
|
2094
2192
|
pipelineOpts.hfApiKey = applied.hfApiKey;
|
|
2095
2193
|
if (patch.openaiApiKey !== undefined)
|
|
2096
2194
|
pipelineOpts.openaiApiKey = applied.openaiApiKey;
|
|
2195
|
+
// Task 129: licence key is persist-ONLY — track the stored value for
|
|
2196
|
+
// display, but never rewire enforcement (licenseStatus stays as
|
|
2197
|
+
// verified at startup; a reload is what applies it — task 127).
|
|
2198
|
+
if (patch.licenseKey !== undefined)
|
|
2199
|
+
storedLicenseKey = applied.licenseKey;
|
|
2097
2200
|
// Propagate to the daemon's OTHER holders (webchat pipelineOpts, etc.).
|
|
2098
2201
|
options.onConfigApplied?.(applied);
|
|
2099
|
-
jsonResponse(res, 200,
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2202
|
+
jsonResponse(res, 200, {
|
|
2203
|
+
...maskedConfigView({
|
|
2204
|
+
models: pipelineOpts.models,
|
|
2205
|
+
claudeModels: pipelineOpts.claudeModels,
|
|
2206
|
+
model: pipelineOpts.model,
|
|
2207
|
+
hfApiKey: pipelineOpts.hfApiKey,
|
|
2208
|
+
openaiApiKey: pipelineOpts.openaiApiKey,
|
|
2209
|
+
licenseKey: storedLicenseKey,
|
|
2210
|
+
}),
|
|
2211
|
+
license: licenseView(),
|
|
2212
|
+
});
|
|
2106
2213
|
}
|
|
2107
2214
|
catch (err) {
|
|
2108
2215
|
jsonResponse(res, 500, { error: String(err) });
|
|
@@ -2125,7 +2232,7 @@ export async function startGatewayServer(options) {
|
|
|
2125
2232
|
}
|
|
2126
2233
|
// POST /api/agents/inject — agent-to-agent messaging
|
|
2127
2234
|
if (routePath === '/api/agents/inject' && req.method === 'POST') {
|
|
2128
|
-
await handleAgentInject(req, res, pipelineOpts,
|
|
2235
|
+
await handleAgentInject(req, res, pipelineOpts, scope);
|
|
2129
2236
|
return;
|
|
2130
2237
|
}
|
|
2131
2238
|
// GET /api/federation/status — connected spokes and their threads (hub only)
|
|
@@ -2140,7 +2247,7 @@ export async function startGatewayServer(options) {
|
|
|
2140
2247
|
}
|
|
2141
2248
|
// POST /api/migrate — sync thread + clone repo + set projectDir
|
|
2142
2249
|
if (routePath === '/api/migrate' && req.method === 'POST') {
|
|
2143
|
-
await handleMigrate(req, res, pipelineOpts,
|
|
2250
|
+
await handleMigrate(req, res, pipelineOpts, scope);
|
|
2144
2251
|
return;
|
|
2145
2252
|
}
|
|
2146
2253
|
// POST /api/push/subscribe — register push subscription
|
|
@@ -2241,6 +2348,8 @@ export async function startGatewayServer(options) {
|
|
|
2241
2348
|
// Abort any in-flight message processing
|
|
2242
2349
|
threadQueues.clear();
|
|
2243
2350
|
threadBusy.clear();
|
|
2351
|
+
if (licenseBannerTimer)
|
|
2352
|
+
clearInterval(licenseBannerTimer);
|
|
2244
2353
|
return new Promise(resolveClose => {
|
|
2245
2354
|
server.close(() => {
|
|
2246
2355
|
clearThreadCache();
|