bunnyquery 1.0.5 → 1.0.6
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/bunnyquery.js +65 -39
- package/package.json +1 -1
package/bunnyquery.js
CHANGED
|
@@ -34,9 +34,12 @@
|
|
|
34
34
|
|
|
35
35
|
// ---- MCP OAuth constants (mirrors src/code/mcp_oauth.ts) ---------------
|
|
36
36
|
// The embed has no build-time env, so we always point at the production
|
|
37
|
-
// MCP host. Override BunnyQuery.MCP_BASE_URL before init() to swap
|
|
37
|
+
// MCP host. Override BunnyQuery.MCP_BASE_URL before init() to swap, or
|
|
38
|
+
// pass `true` as the third arg to BunnyQuery.init() to target the dev
|
|
39
|
+
// host (mcp-dev.broadwayinc.computer).
|
|
38
40
|
const DEFAULTS = {
|
|
39
41
|
MCP_BASE_URL: 'https://mcp.broadwayinc.computer',
|
|
42
|
+
MCP_DEV_BASE_URL: 'https://mcp-dev.broadwayinc.computer',
|
|
40
43
|
MCP_NAME: 'BunnyQuery',
|
|
41
44
|
DEFAULT_CLAUDE_MODEL: 'claude-sonnet-4-6',
|
|
42
45
|
DEFAULT_OPENAI_MODEL: 'gpt-5.4',
|
|
@@ -58,6 +61,11 @@
|
|
|
58
61
|
const TOKEN_KEY = STORAGE_PREFIX + 'mcp_token';
|
|
59
62
|
const STATE_KEY = STORAGE_PREFIX + 'mcp_state';
|
|
60
63
|
|
|
64
|
+
// Resolve the active MCP base URL each time it's needed so a runtime
|
|
65
|
+
// override of `BunnyQuery.MCP_BASE_URL` (e.g. via the `dev` flag on
|
|
66
|
+
// init()) is reflected in subsequent send/auth calls.
|
|
67
|
+
const _mcpBaseUrl = () => (global.BunnyQuery && global.BunnyQuery.MCP_BASE_URL) || DEFAULTS.MCP_BASE_URL;
|
|
68
|
+
|
|
61
69
|
// ----- helpers ----------------------------------------------------------
|
|
62
70
|
const $ = (tag, attrs, children) => {
|
|
63
71
|
const el = document.createElement(tag);
|
|
@@ -376,7 +384,7 @@
|
|
|
376
384
|
{
|
|
377
385
|
type: 'url',
|
|
378
386
|
name: DEFAULTS.MCP_NAME,
|
|
379
|
-
url:
|
|
387
|
+
url: _mcpBaseUrl(),
|
|
380
388
|
authorization_token: '$ACCESS_TOKEN',
|
|
381
389
|
},
|
|
382
390
|
],
|
|
@@ -422,7 +430,7 @@
|
|
|
422
430
|
{
|
|
423
431
|
type: 'mcp',
|
|
424
432
|
server_label: DEFAULTS.MCP_NAME,
|
|
425
|
-
server_url:
|
|
433
|
+
server_url: _mcpBaseUrl(),
|
|
426
434
|
require_approval: 'never',
|
|
427
435
|
headers: { Authorization: 'Bearer $ACCESS_TOKEN' },
|
|
428
436
|
},
|
|
@@ -576,7 +584,7 @@
|
|
|
576
584
|
|
|
577
585
|
this.readOnly = !!(info.opt && info.opt.freeze_database);
|
|
578
586
|
|
|
579
|
-
this.oauth = new McpOAuth(
|
|
587
|
+
this.oauth = new McpOAuth(_mcpBaseUrl());
|
|
580
588
|
|
|
581
589
|
this.messages = [];
|
|
582
590
|
this.attachments = [];
|
|
@@ -585,6 +593,7 @@
|
|
|
585
593
|
this.sending = false;
|
|
586
594
|
this.uploading = false;
|
|
587
595
|
this.pendingTimer = null;
|
|
596
|
+
this._pollingPending = false;
|
|
588
597
|
this._loadingOlder = false;
|
|
589
598
|
|
|
590
599
|
this.refs = {};
|
|
@@ -593,11 +602,17 @@
|
|
|
593
602
|
this._bootstrap();
|
|
594
603
|
}
|
|
595
604
|
|
|
596
|
-
static async init(skapi, elementId) {
|
|
605
|
+
static async init(skapi, elementId, dev) {
|
|
597
606
|
const container = typeof elementId === 'string'
|
|
598
607
|
? document.getElementById(elementId)
|
|
599
608
|
: elementId;
|
|
600
609
|
if (!container) throw new Error(`BunnyQuery: container "${elementId}" not found`);
|
|
610
|
+
// When `dev` is truthy, route every MCP call (Claude tool URL,
|
|
611
|
+
// OpenAI tool URL, and OAuth) to the dev host. Setting the
|
|
612
|
+
// static here makes the change visible to any subsequent
|
|
613
|
+
// BunnyQuery instance on the page; pass `false` (or omit) to
|
|
614
|
+
// pin back to production.
|
|
615
|
+
BunnyQuery.MCP_BASE_URL = dev ? DEFAULTS.MCP_DEV_BASE_URL : DEFAULTS.MCP_BASE_URL;
|
|
601
616
|
const info = await skapi.__connection;
|
|
602
617
|
console.log('[BunnyQuery] initializing with info', info);
|
|
603
618
|
return new BunnyQuery(skapi, info, container);
|
|
@@ -1124,46 +1139,57 @@
|
|
|
1124
1139
|
}
|
|
1125
1140
|
}
|
|
1126
1141
|
|
|
1142
|
+
// Polling state machine. Invariant: at most ONE of the following
|
|
1143
|
+
// is true at any moment — (a) `pendingTimer` is armed, or (b)
|
|
1144
|
+
// `_pollingPending` is true (fetch in flight). Any call to
|
|
1145
|
+
// `_schedulePendingPoll` while either is true is a no-op; the
|
|
1146
|
+
// in-flight cycle's `finally` will continue the loop.
|
|
1147
|
+
//
|
|
1148
|
+
// This replaces an earlier clear-and-rearm pattern that, under
|
|
1149
|
+
// overlapping callers (`_sendMessage`, `_loadFirstHistoryPage`,
|
|
1150
|
+
// and the poll's own `finally`), could leave two timers
|
|
1151
|
+
// concurrently armed. Each tick's `finally` then scheduled
|
|
1152
|
+
// another, and the effective polling rate kept compounding —
|
|
1153
|
+
// the chat looked like it was polling faster and faster.
|
|
1127
1154
|
_schedulePendingPoll() {
|
|
1128
|
-
if (this.pendingTimer)
|
|
1129
|
-
if (!this._anyPending()) return;
|
|
1130
|
-
// Bail if a poll is already in flight — its `finally` will
|
|
1131
|
-
// schedule the next one. Without this guard, any caller that
|
|
1132
|
-
// fires while we're awaiting `getChatHistory` (e.g. another
|
|
1133
|
-
// _sendMessage, _loadFirstHistoryPage, or our own previous
|
|
1134
|
-
// tick whose timer already elapsed) would spawn a second
|
|
1135
|
-
// concurrent poller, and each subsequent cycle would compound
|
|
1136
|
-
// — making the polling appear faster and faster.
|
|
1155
|
+
if (this.pendingTimer != null) return;
|
|
1137
1156
|
if (this._pollingPending) return;
|
|
1138
|
-
this.
|
|
1157
|
+
if (!this._anyPending()) return;
|
|
1158
|
+
this.pendingTimer = setTimeout(() => {
|
|
1139
1159
|
this.pendingTimer = null;
|
|
1140
|
-
this.
|
|
1141
|
-
try {
|
|
1142
|
-
const res = await getChatHistory(
|
|
1143
|
-
this.skapi,
|
|
1144
|
-
{ service: this.serviceId, owner: this.ownerId, platform: this.platform },
|
|
1145
|
-
{ ascending: false }
|
|
1146
|
-
);
|
|
1147
|
-
const fresh = mapHistoryToMessages(
|
|
1148
|
-
this._filterByClearHorizon((res && res.list) || []),
|
|
1149
|
-
this.platform
|
|
1150
|
-
);
|
|
1151
|
-
// Keep older messages whose ids are not on the freshly
|
|
1152
|
-
// fetched first page. The fresh page is appended last so
|
|
1153
|
-
// it always reflects the latest server state.
|
|
1154
|
-
const freshIds = new Set(fresh.filter((m) => m.id).map((m) => m.id));
|
|
1155
|
-
const older = this.messages.filter((m) => m.id && !freshIds.has(m.id));
|
|
1156
|
-
this.messages = older.concat(fresh);
|
|
1157
|
-
this._renderMessages();
|
|
1158
|
-
} catch (err) {
|
|
1159
|
-
console.error('[BunnyQuery] pending poll failed', err);
|
|
1160
|
-
} finally {
|
|
1161
|
-
this._pollingPending = false;
|
|
1162
|
-
this._schedulePendingPoll();
|
|
1163
|
-
}
|
|
1160
|
+
this._runPendingPoll();
|
|
1164
1161
|
}, DEFAULTS.PENDING_POLL_INTERVAL_MS);
|
|
1165
1162
|
}
|
|
1166
1163
|
|
|
1164
|
+
async _runPendingPoll() {
|
|
1165
|
+
// Defensive: if somehow re-entered, drop the duplicate.
|
|
1166
|
+
if (this._pollingPending) return;
|
|
1167
|
+
this._pollingPending = true;
|
|
1168
|
+
try {
|
|
1169
|
+
const res = await getChatHistory(
|
|
1170
|
+
this.skapi,
|
|
1171
|
+
{ service: this.serviceId, owner: this.ownerId, platform: this.platform },
|
|
1172
|
+
{ ascending: false }
|
|
1173
|
+
);
|
|
1174
|
+
const fresh = mapHistoryToMessages(
|
|
1175
|
+
this._filterByClearHorizon((res && res.list) || []),
|
|
1176
|
+
this.platform
|
|
1177
|
+
);
|
|
1178
|
+
// Keep older messages whose ids are not on the freshly
|
|
1179
|
+
// fetched first page. The fresh page is appended last so
|
|
1180
|
+
// it always reflects the latest server state.
|
|
1181
|
+
const freshIds = new Set(fresh.filter((m) => m.id).map((m) => m.id));
|
|
1182
|
+
const older = this.messages.filter((m) => m.id && !freshIds.has(m.id));
|
|
1183
|
+
this.messages = older.concat(fresh);
|
|
1184
|
+
this._renderMessages();
|
|
1185
|
+
} catch (err) {
|
|
1186
|
+
console.error('[BunnyQuery] pending poll failed', err);
|
|
1187
|
+
} finally {
|
|
1188
|
+
this._pollingPending = false;
|
|
1189
|
+
this._schedulePendingPoll();
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1167
1193
|
// ---- attachments --------------------------------------------------
|
|
1168
1194
|
_onAttachFromInput(e) {
|
|
1169
1195
|
const files = Array.from(e.target.files || []);
|