@luckydraw/cumulus 1.0.10 → 1.0.12
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 +16 -0
- package/dist/gateway/adapters/webchat.d.ts +1 -1
- package/dist/gateway/adapters/webchat.d.ts.map +1 -1
- package/dist/gateway/adapters/webchat.js +31 -1
- package/dist/gateway/adapters/webchat.js.map +1 -1
- package/dist/gateway/config.d.ts +75 -2
- package/dist/gateway/config.d.ts.map +1 -1
- package/dist/gateway/config.js +101 -5
- package/dist/gateway/config.js.map +1 -1
- package/dist/gateway/daemon.js +16 -0
- package/dist/gateway/daemon.js.map +1 -1
- package/dist/gateway/hooks.d.ts +1 -0
- package/dist/gateway/hooks.d.ts.map +1 -1
- package/dist/gateway/hooks.js +1 -0
- package/dist/gateway/hooks.js.map +1 -1
- package/dist/gateway/server.d.ts +3 -0
- package/dist/gateway/server.d.ts.map +1 -1
- package/dist/gateway/server.js +76 -6
- package/dist/gateway/server.js.map +1 -1
- package/dist/gateway/static/widget.js +423 -54
- package/dist/lib/gateway.d.ts +35 -2
- package/dist/lib/gateway.d.ts.map +1 -1
- package/dist/lib/gateway.js +56 -4
- package/dist/lib/gateway.js.map +1 -1
- package/dist/lib/huggingface-provider.d.ts +12 -0
- package/dist/lib/huggingface-provider.d.ts.map +1 -1
- package/dist/lib/huggingface-provider.js +45 -14
- package/dist/lib/huggingface-provider.js.map +1 -1
- package/package.json +1 -1
package/dist/gateway/server.js
CHANGED
|
@@ -41,6 +41,37 @@ import { isAutomatedSender } from './senders.js';
|
|
|
41
41
|
import { validateTranscriptFlush, ingestTranscriptFlush } from './transcript-ingest.js';
|
|
42
42
|
const threadQueues = new Map();
|
|
43
43
|
const threadBusy = new Map();
|
|
44
|
+
// ─── Thread activity notification (task 146) ─────────────────────────────────
|
|
45
|
+
// The sidebar shows a thread as working, including threads the viewer has not
|
|
46
|
+
// opened. That needs a push, and `markThreadBusy` is the only place worth
|
|
47
|
+
// hanging it on: task 100 made it the point EVERY transport lands on, so one
|
|
48
|
+
// notification here covers WS turns, agent injects, scheduled triggers, job
|
|
49
|
+
// completions and queue drains without a second notion of "busy" to drift.
|
|
50
|
+
//
|
|
51
|
+
// Registered by the webchat adapter (only it can reach the sockets), same shape
|
|
52
|
+
// as `setUserQueueDelivery` — this module must not import the adapter.
|
|
53
|
+
let threadActivityListener = null;
|
|
54
|
+
export function setThreadActivityListener(fn) {
|
|
55
|
+
threadActivityListener = fn;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Announce a change in whether a thread is running a turn.
|
|
59
|
+
*
|
|
60
|
+
* Transition-only: callers mark busy repeatedly (an inject re-marks a thread
|
|
61
|
+
* that is already working), and a listener that re-broadcast every call would
|
|
62
|
+
* fan out a socket write per no-op. A never-throw wrapper because this is a
|
|
63
|
+
* cosmetic signal — it must never be able to fail a turn.
|
|
64
|
+
*/
|
|
65
|
+
function notifyThreadActivity(threadName, busy) {
|
|
66
|
+
if (!threadActivityListener)
|
|
67
|
+
return;
|
|
68
|
+
try {
|
|
69
|
+
threadActivityListener(threadName, busy);
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
console.error(`[Gateway] thread activity listener failed for "${threadName}": ${err instanceof Error ? err.message : String(err)}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
44
75
|
const userQueues = new Map();
|
|
45
76
|
let userQueueDelivery;
|
|
46
77
|
export function setUserQueueDelivery(fn) {
|
|
@@ -183,6 +214,21 @@ let federationRouter;
|
|
|
183
214
|
* server, so later mutations (e.g. broadcastToThread wiring) are visible here.
|
|
184
215
|
*/
|
|
185
216
|
let agentPipelineOpts;
|
|
217
|
+
/**
|
|
218
|
+
* THE only writer of `threadBusy`.
|
|
219
|
+
*
|
|
220
|
+
* Every mutation goes through here so the activity notification (task 146)
|
|
221
|
+
* cannot be missed: `markThreadBusy` is the drain point, but the queue-drain and
|
|
222
|
+
* direct-inject paths mark a thread busy by hand on their way into `sendMessage`,
|
|
223
|
+
* and a notification hung only on the exported function would report those turns
|
|
224
|
+
* as never having started.
|
|
225
|
+
*/
|
|
226
|
+
function setBusyState(threadName, busy) {
|
|
227
|
+
const was = !!threadBusy.get(threadName);
|
|
228
|
+
threadBusy.set(threadName, busy);
|
|
229
|
+
if (was !== busy)
|
|
230
|
+
notifyThreadActivity(threadName, busy);
|
|
231
|
+
}
|
|
186
232
|
/**
|
|
187
233
|
* Mark a thread as busy/idle from any transport (SSE turns, webchat adapter,
|
|
188
234
|
* agent injects). Busy→idle is THE drain point for queued agent messages —
|
|
@@ -191,12 +237,12 @@ let agentPipelineOpts;
|
|
|
191
237
|
*/
|
|
192
238
|
export function markThreadBusy(threadName, busy) {
|
|
193
239
|
if (busy) {
|
|
194
|
-
|
|
240
|
+
setBusyState(threadName, true);
|
|
195
241
|
return;
|
|
196
242
|
}
|
|
197
243
|
// Only clear if still marked — another path may have set it busy again
|
|
198
244
|
if (threadBusy.get(threadName)) {
|
|
199
|
-
|
|
245
|
+
setBusyState(threadName, false);
|
|
200
246
|
}
|
|
201
247
|
// User-queued messages take priority: their drain pass toggles busy and
|
|
202
248
|
// lands back here when the user queue is empty.
|
|
@@ -279,7 +325,7 @@ async function drainQueue(threadName, opts) {
|
|
|
279
325
|
const queue = threadQueues.get(threadName);
|
|
280
326
|
if (!queue || queue.length === 0)
|
|
281
327
|
return;
|
|
282
|
-
|
|
328
|
+
setBusyState(threadName, true);
|
|
283
329
|
const item = queue.shift();
|
|
284
330
|
try {
|
|
285
331
|
await processMessage(threadName, item, opts);
|
|
@@ -365,6 +411,8 @@ async function processMessage(threadName, item, opts) {
|
|
|
365
411
|
gatewayAgentsConfig: opts.gatewayAgentsConfig,
|
|
366
412
|
hfApiKey: opts.hfApiKey,
|
|
367
413
|
openaiApiKey: opts.openaiApiKey,
|
|
414
|
+
// Custom OpenAI-compatible endpoints + their own credentials (task 147).
|
|
415
|
+
customProviders: opts.customProviders,
|
|
368
416
|
// Catalog: lets the spawn resolve non-Claude provider + context window (task 118).
|
|
369
417
|
models: opts.models,
|
|
370
418
|
// Config-driven Claude sub-model default (task 113). Read per turn, so a live
|
|
@@ -422,6 +470,7 @@ async function handleWebhook(type, name, req, res, pipelineOpts, hooksConfig) {
|
|
|
422
470
|
broadcastToThread: pipelineOpts.broadcastToThread,
|
|
423
471
|
hfApiKey: pipelineOpts.hfApiKey,
|
|
424
472
|
openaiApiKey: pipelineOpts.openaiApiKey,
|
|
473
|
+
customProviders: pipelineOpts.customProviders,
|
|
425
474
|
models: pipelineOpts.models,
|
|
426
475
|
claudeModels: pipelineOpts.claudeModels,
|
|
427
476
|
};
|
|
@@ -669,7 +718,7 @@ async function handleAgentInject(req, res, pipelineOpts, scope) {
|
|
|
669
718
|
// Ensure thread exists
|
|
670
719
|
await getOrCreateThread(targetName);
|
|
671
720
|
// Send as a message through the pipeline (fire-and-forget)
|
|
672
|
-
|
|
721
|
+
setBusyState(targetName, true);
|
|
673
722
|
const broadcast = pipelineOpts.broadcastToThread;
|
|
674
723
|
sendMessage({
|
|
675
724
|
threadName: targetName,
|
|
@@ -678,6 +727,7 @@ async function handleAgentInject(req, res, pipelineOpts, scope) {
|
|
|
678
727
|
claudePath: pipelineOpts.claudePath,
|
|
679
728
|
hfApiKey: pipelineOpts.hfApiKey,
|
|
680
729
|
openaiApiKey: pipelineOpts.openaiApiKey,
|
|
730
|
+
customProviders: pipelineOpts.customProviders,
|
|
681
731
|
models: pipelineOpts.models,
|
|
682
732
|
sharedMcpPort: pipelineOpts.sharedMcpPort,
|
|
683
733
|
projectRoot: pipelineOpts.projectRoot,
|
|
@@ -865,7 +915,7 @@ function drainAgentQueue(threadName, pipelineOpts) {
|
|
|
865
915
|
formatted = formatAgentBatch(queue);
|
|
866
916
|
}
|
|
867
917
|
// Fire-and-forget: send the batched message as a new turn
|
|
868
|
-
|
|
918
|
+
setBusyState(threadName, true);
|
|
869
919
|
const broadcast = pipelineOpts.broadcastToThread;
|
|
870
920
|
sendMessage({
|
|
871
921
|
threadName,
|
|
@@ -878,6 +928,7 @@ function drainAgentQueue(threadName, pipelineOpts) {
|
|
|
878
928
|
gatewayAgentsConfig: pipelineOpts.gatewayAgentsConfig,
|
|
879
929
|
hfApiKey: pipelineOpts.hfApiKey,
|
|
880
930
|
openaiApiKey: pipelineOpts.openaiApiKey,
|
|
931
|
+
customProviders: pipelineOpts.customProviders,
|
|
881
932
|
models: pipelineOpts.models,
|
|
882
933
|
// Config-driven Claude sub-model default (task 113) — the drained turn must
|
|
883
934
|
// resolve the same model the live turn would have.
|
|
@@ -1082,6 +1133,12 @@ async function handleListThreads(res, scopeNs, namespaces) {
|
|
|
1082
1133
|
messageCount: lineCount,
|
|
1083
1134
|
lastActivity: stat.mtime.toISOString(),
|
|
1084
1135
|
};
|
|
1136
|
+
// Whether a turn is running right now (task 146). Present on BOTH listings
|
|
1137
|
+
// — a tab that opens mid-turn must render the activity indicator without
|
|
1138
|
+
// waiting for the next transition, which may be minutes away.
|
|
1139
|
+
if (threadBusy.get(name)) {
|
|
1140
|
+
entry.busy = true;
|
|
1141
|
+
}
|
|
1085
1142
|
if (tc.projectDir) {
|
|
1086
1143
|
entry.projectDir = tc.projectDir;
|
|
1087
1144
|
}
|
|
@@ -2637,7 +2694,16 @@ export async function startGatewayServer(options) {
|
|
|
2637
2694
|
if (claudeModels.length > 0 && !claudeModels.some(m => m.default)) {
|
|
2638
2695
|
claudeModels[0].default = true;
|
|
2639
2696
|
}
|
|
2640
|
-
|
|
2697
|
+
// Custom providers, for the dropdown's provider label (task 147).
|
|
2698
|
+
// ONLY id + label: this endpoint accepts any valid key, including a
|
|
2699
|
+
// namespace-scoped key that ships in a public app's HTML (097 P7), and
|
|
2700
|
+
// baseUrl is private infrastructure. The admin-only /api/config serves
|
|
2701
|
+
// baseUrl + the masked credential.
|
|
2702
|
+
const customProviders = (pipelineOpts.customProviders || []).map(p => ({
|
|
2703
|
+
id: p.id,
|
|
2704
|
+
label: p.label,
|
|
2705
|
+
}));
|
|
2706
|
+
jsonResponse(res, 200, { models, claudeModels, customProviders });
|
|
2641
2707
|
return;
|
|
2642
2708
|
}
|
|
2643
2709
|
// GET/PUT /api/config — gateway-level settings are ADMIN-ONLY (task 128).
|
|
@@ -2660,6 +2726,7 @@ export async function startGatewayServer(options) {
|
|
|
2660
2726
|
model: pipelineOpts.model,
|
|
2661
2727
|
hfApiKey: pipelineOpts.hfApiKey,
|
|
2662
2728
|
openaiApiKey: pipelineOpts.openaiApiKey,
|
|
2729
|
+
customProviders: pipelineOpts.customProviders,
|
|
2663
2730
|
licenseKey: storedLicenseKey,
|
|
2664
2731
|
}),
|
|
2665
2732
|
license: licenseView(),
|
|
@@ -2695,6 +2762,8 @@ export async function startGatewayServer(options) {
|
|
|
2695
2762
|
pipelineOpts.hfApiKey = applied.hfApiKey;
|
|
2696
2763
|
if (patch.openaiApiKey !== undefined)
|
|
2697
2764
|
pipelineOpts.openaiApiKey = applied.openaiApiKey;
|
|
2765
|
+
if (patch.customProviders !== undefined)
|
|
2766
|
+
pipelineOpts.customProviders = applied.customProviders;
|
|
2698
2767
|
// Task 129: licence key is persist-ONLY — track the stored value for
|
|
2699
2768
|
// display, but never rewire enforcement (licenseStatus stays as
|
|
2700
2769
|
// verified at startup; a reload is what applies it — task 127).
|
|
@@ -2709,6 +2778,7 @@ export async function startGatewayServer(options) {
|
|
|
2709
2778
|
model: pipelineOpts.model,
|
|
2710
2779
|
hfApiKey: pipelineOpts.hfApiKey,
|
|
2711
2780
|
openaiApiKey: pipelineOpts.openaiApiKey,
|
|
2781
|
+
customProviders: pipelineOpts.customProviders,
|
|
2712
2782
|
licenseKey: storedLicenseKey,
|
|
2713
2783
|
}),
|
|
2714
2784
|
license: licenseView(),
|