@qwen-code/qwen-code 0.15.12-preview.3 → 0.16.0-preview.0
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/bundled/qc-helper/docs/configuration/settings.md +20 -24
- package/bundled/qc-helper/docs/qwen-serve.md +29 -10
- package/chunks/{agent-LIAWUWAO.js → agent-ZNQPH67I.js} +15 -15
- package/chunks/{anthropicContentGenerator-4QE6LTVV.js → anthropicContentGenerator-ICBDZ6R2.js} +4 -4
- package/chunks/{askUserQuestion-QFSCBTUO.js → askUserQuestion-WQILGUSQ.js} +2 -2
- package/chunks/{chunk-SQNQIOD5.js → chunk-2B7UBDY5.js} +2 -2
- package/chunks/chunk-3MBY4GKN.js +350 -0
- package/chunks/{chunk-GC5RXNL2.js → chunk-7QXHXMC6.js} +23 -7
- package/chunks/{chunk-XLQ4E5PS.js → chunk-C3LHPHN2.js} +11 -11
- package/chunks/{chunk-UXW7MYAW.js → chunk-CW44BRRA.js} +1 -1
- package/chunks/{chunk-G27O2LD2.js → chunk-D5NTAHYL.js} +1 -1
- package/chunks/{chunk-CBVB66WY.js → chunk-EDYSNFEM.js} +1 -1
- package/chunks/{chunk-OCC4MZRS.js → chunk-F23NCRJ2.js} +1 -1
- package/chunks/{chunk-FYMSCRHM.js → chunk-FZIUV27X.js} +1 -1
- package/chunks/{chunk-5QQ5FGTU.js → chunk-G7YTSRES.js} +1 -1
- package/chunks/{chunk-AOJ3BBY7.js → chunk-JHMX4QTD.js} +9 -9
- package/chunks/{chunk-TPGOGCWM.js → chunk-JYQUJ5DS.js} +1 -1
- package/chunks/{chunk-FKVKVE6N.js → chunk-KXZ4TJB4.js} +1 -1
- package/chunks/{chunk-AJSOD5IR.js → chunk-MNPZ2WO6.js} +535 -141
- package/chunks/{chunk-BXNCPI75.js → chunk-NAID3ZWF.js} +2 -2
- package/chunks/{chunk-JMZQICAL.js → chunk-PPHYLJSS.js} +1 -1
- package/chunks/{chunk-CM2IESUE.js → chunk-PR4T27R7.js} +1 -1
- package/chunks/{chunk-CAWKL3UC.js → chunk-VTPOO6GV.js} +1 -1
- package/chunks/{chunk-GJXIKCKL.js → chunk-XP27SJMH.js} +76 -5
- package/chunks/{chunk-B7ZL7HUA.js → chunk-XVHR7ATJ.js} +1 -1
- package/chunks/{contextCommand-SVLAZMQL.js → contextCommand-IGBCEXI4.js} +16 -16
- package/chunks/{cron-create-WUTD5ZTH.js → cron-create-AVI3Q267.js} +2 -2
- package/chunks/{cron-delete-N3UQYCRA.js → cron-delete-ZCEGDXXV.js} +2 -2
- package/chunks/{cron-list-Z6RJJ4YH.js → cron-list-VN653OK5.js} +2 -2
- package/chunks/{edit-VNAZBIZR.js → edit-74Q4AFHQ.js} +16 -16
- package/chunks/{en-NRN4QBAT.js → en-FIUWJSZR.js} +1 -0
- package/chunks/{enter-worktree-FOF5YZIV.js → enter-worktree-H72HXC7D.js} +15 -15
- package/chunks/{exit-worktree-Y6QVAO3C.js → exit-worktree-FGIQO3S3.js} +15 -15
- package/chunks/{exitPlanMode-QZKO7GH7.js → exitPlanMode-NBR2PK2D.js} +15 -15
- package/chunks/{geminiContentGenerator-DYHZPKJX.js → geminiContentGenerator-33RP4WKD.js} +3 -3
- package/chunks/{glob-G7XATELV.js → glob-WEE3CJL6.js} +15 -15
- package/chunks/{grep-4SETMY47.js → grep-DZKSBFZK.js} +15 -15
- package/chunks/{keychain-token-storage-DMFP5IJM.js → keychain-token-storage-335UOLJ6.js} +2 -2
- package/chunks/{ls-SUILOZZB.js → ls-6F3VSP6S.js} +3 -3
- package/chunks/{lsp-6TQBWVMZ.js → lsp-67Y7DJN5.js} +2 -2
- package/chunks/{monitor-JTLJBJ7H.js → monitor-EDZWEZVS.js} +15 -15
- package/chunks/{openaiContentGenerator-3H7XOZBW.js → openaiContentGenerator-5NQG3W64.js} +10 -10
- package/chunks/{qwenContentGenerator-FAU3QPYO.js → qwenContentGenerator-4DPUUS6R.js} +17 -17
- package/chunks/{qwenOAuth2-JSQ7EPR3.js → qwenOAuth2-JE7H47TE.js} +3 -3
- package/chunks/{read-file-WWUQVNCZ.js → read-file-CQOF7BQ2.js} +7 -7
- package/chunks/{ripGrep-WCOAIWL6.js → ripGrep-KR5LKGTI.js} +15 -15
- package/chunks/{send-message-Q2JRAC3J.js → send-message-GB4AQZNC.js} +2 -2
- package/chunks/{serve-VJEEEXA6.js → serve-GAD2PEST.js} +501 -286
- package/chunks/{shell-IAOKGIJ6.js → shell-E2HMCBGR.js} +15 -15
- package/chunks/{skill-NHW6222K.js → skill-KDZH6UZ6.js} +9 -9
- package/chunks/{src-OWV5HVQQ.js → src-LY4RU5AI.js} +17 -15
- package/chunks/{syntheticOutput-S4DRGMQM.js → syntheticOutput-HFL3DE7R.js} +3 -3
- package/chunks/{task-stop-7THHVAQS.js → task-stop-ZQF26RXS.js} +2 -2
- package/chunks/{todoWrite-WKUGUTPX.js → todoWrite-U4SC643O.js} +3 -3
- package/chunks/{tool-search-MSJ6SXLI.js → tool-search-U4XQVLFU.js} +7 -7
- package/chunks/{web-fetch-OZE6ZQUF.js → web-fetch-BRWZ4WSE.js} +4 -4
- package/chunks/{write-file-RKCENFZ5.js → write-file-NBLRMNGB.js} +16 -16
- package/chunks/{zh-TW-XZEHEV5S.js → zh-TW-552S24LR.js} +1 -0
- package/chunks/{zh-RN3JULHO.js → zh-V32QONGV.js} +1 -0
- package/cli.js +598 -59
- package/locales/en.js +2 -0
- package/locales/zh-TW.js +1 -0
- package/locales/zh.js +1 -0
- package/package.json +2 -2
|
@@ -6,6 +6,17 @@ import {
|
|
|
6
6
|
require_mime_db,
|
|
7
7
|
require_type
|
|
8
8
|
} from "./chunk-7RYW5LQV.js";
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_RING_SIZE,
|
|
11
|
+
EVENT_SCHEMA_VERSION,
|
|
12
|
+
EventBus,
|
|
13
|
+
SERVE_STATUS_EXT_METHODS,
|
|
14
|
+
STATUS_SCHEMA_VERSION,
|
|
15
|
+
SubscriberLimitExceededError,
|
|
16
|
+
createIdleWorkspaceMcpStatus,
|
|
17
|
+
createIdleWorkspaceProvidersStatus,
|
|
18
|
+
createIdleWorkspaceSkillsStatus
|
|
19
|
+
} from "./chunk-3MBY4GKN.js";
|
|
9
20
|
import {
|
|
10
21
|
writeStderrLine,
|
|
11
22
|
writeStdoutLine
|
|
@@ -13076,6 +13087,20 @@ function bearerAuth(token) {
|
|
|
13076
13087
|
};
|
|
13077
13088
|
}
|
|
13078
13089
|
__name(bearerAuth, "bearerAuth");
|
|
13090
|
+
function createMutationGate(deps) {
|
|
13091
|
+
const passthrough = /* @__PURE__ */ __name((_req, _res, next) => next(), "passthrough");
|
|
13092
|
+
if (deps.requireAuth || deps.tokenConfigured) {
|
|
13093
|
+
return () => passthrough;
|
|
13094
|
+
}
|
|
13095
|
+
const strictDenier = /* @__PURE__ */ __name((_req, res) => {
|
|
13096
|
+
res.status(401).json({
|
|
13097
|
+
error: "This route requires the daemon to be configured with a bearer token. Set QWEN_SERVER_TOKEN or pass --token to enable bearer auth.",
|
|
13098
|
+
code: "token_required"
|
|
13099
|
+
});
|
|
13100
|
+
}, "strictDenier");
|
|
13101
|
+
return (opts = {}) => opts.strict ? strictDenier : passthrough;
|
|
13102
|
+
}
|
|
13103
|
+
__name(createMutationGate, "createMutationGate");
|
|
13079
13104
|
|
|
13080
13105
|
// packages/cli/src/serve/httpAcpBridge.ts
|
|
13081
13106
|
init_esbuild_shims();
|
|
@@ -13084,267 +13109,6 @@ import { randomUUID } from "node:crypto";
|
|
|
13084
13109
|
import { promises as fs, realpathSync } from "node:fs";
|
|
13085
13110
|
import * as path from "node:path";
|
|
13086
13111
|
import { Readable, Writable } from "node:stream";
|
|
13087
|
-
|
|
13088
|
-
// packages/cli/src/serve/eventBus.ts
|
|
13089
|
-
init_esbuild_shims();
|
|
13090
|
-
var EVENT_SCHEMA_VERSION = 1;
|
|
13091
|
-
var DEFAULT_MAX_QUEUED = 256;
|
|
13092
|
-
var DEFAULT_RING_SIZE = 4e3;
|
|
13093
|
-
var DEFAULT_MAX_SUBSCRIBERS = 64;
|
|
13094
|
-
var SubscriberLimitExceededError = class extends Error {
|
|
13095
|
-
static {
|
|
13096
|
-
__name(this, "SubscriberLimitExceededError");
|
|
13097
|
-
}
|
|
13098
|
-
limit;
|
|
13099
|
-
constructor(limit) {
|
|
13100
|
-
super(`EventBus subscriber limit reached (${limit})`);
|
|
13101
|
-
this.name = "SubscriberLimitExceededError";
|
|
13102
|
-
this.limit = limit;
|
|
13103
|
-
}
|
|
13104
|
-
};
|
|
13105
|
-
var EventBus = class {
|
|
13106
|
-
constructor(ringSize = DEFAULT_RING_SIZE, maxSubscribers = DEFAULT_MAX_SUBSCRIBERS) {
|
|
13107
|
-
this.ringSize = ringSize;
|
|
13108
|
-
this.maxSubscribers = maxSubscribers;
|
|
13109
|
-
}
|
|
13110
|
-
static {
|
|
13111
|
-
__name(this, "EventBus");
|
|
13112
|
-
}
|
|
13113
|
-
nextId = 1;
|
|
13114
|
-
ring = [];
|
|
13115
|
-
subs = /* @__PURE__ */ new Set();
|
|
13116
|
-
closed = false;
|
|
13117
|
-
/** Most recent id ever assigned by `publish`. 0 if no events published. */
|
|
13118
|
-
get lastEventId() {
|
|
13119
|
-
return this.nextId - 1;
|
|
13120
|
-
}
|
|
13121
|
-
/** Snapshot of the live subscriber count. */
|
|
13122
|
-
get subscriberCount() {
|
|
13123
|
-
return this.subs.size;
|
|
13124
|
-
}
|
|
13125
|
-
/**
|
|
13126
|
-
* Publish an event to the bus. Returns the constructed `BridgeEvent`
|
|
13127
|
-
* (with `id` + `v` assigned) on success, or `undefined` when the
|
|
13128
|
-
* bus is closed.
|
|
13129
|
-
*
|
|
13130
|
-
* **Never throws** (BX9_p contract). Closing the bus mid-publish
|
|
13131
|
-
* is the only abnormal path and is handled as a return-undefined
|
|
13132
|
-
* no-op; subscriber-enqueue failures are caught internally and
|
|
13133
|
-
* translated to per-subscriber eviction. Call sites can rely on
|
|
13134
|
-
* this — the historical `try { publish(...) } catch {}` blocks in
|
|
13135
|
-
* `httpAcpBridge.ts` are defense-in-depth, not load-bearing, and
|
|
13136
|
-
* may be removed in a future cleanup pass without changing
|
|
13137
|
-
* behavior. Don't add new try/catch wrappers around `publish()`.
|
|
13138
|
-
*/
|
|
13139
|
-
publish(input) {
|
|
13140
|
-
if (this.closed) return void 0;
|
|
13141
|
-
const event = {
|
|
13142
|
-
id: this.nextId++,
|
|
13143
|
-
v: EVENT_SCHEMA_VERSION,
|
|
13144
|
-
...input
|
|
13145
|
-
};
|
|
13146
|
-
this.ring.push(event);
|
|
13147
|
-
if (this.ring.length > this.ringSize) this.ring.shift();
|
|
13148
|
-
for (const sub of Array.from(this.subs)) {
|
|
13149
|
-
if (sub.evicted) continue;
|
|
13150
|
-
if (!sub.queue.push(event)) {
|
|
13151
|
-
sub.evicted = true;
|
|
13152
|
-
const evictionFrame = {
|
|
13153
|
-
v: EVENT_SCHEMA_VERSION,
|
|
13154
|
-
type: "client_evicted",
|
|
13155
|
-
data: { reason: "queue_overflow", droppedAfter: event.id }
|
|
13156
|
-
};
|
|
13157
|
-
sub.queue.forcePush(evictionFrame);
|
|
13158
|
-
sub.queue.close();
|
|
13159
|
-
sub.dispose();
|
|
13160
|
-
}
|
|
13161
|
-
}
|
|
13162
|
-
return event;
|
|
13163
|
-
}
|
|
13164
|
-
/**
|
|
13165
|
-
* Note: registration is synchronous — by the time `subscribe()` returns,
|
|
13166
|
-
* the subscriber is already attached and will receive any subsequent
|
|
13167
|
-
* `publish()` even if the consumer hasn't started iterating yet. (A
|
|
13168
|
-
* generator-style implementation would defer registration to the first
|
|
13169
|
-
* `next()` call, which races with publishes that happen before the
|
|
13170
|
-
* consumer's first await.)
|
|
13171
|
-
*
|
|
13172
|
-
* The returned iterator is NOT safe to drive from concurrent callers —
|
|
13173
|
-
* two simultaneous `.next()` calls would race for the same event from
|
|
13174
|
-
* the underlying queue. Daemon usage is sequential (`for await ... of`
|
|
13175
|
-
* inside the SSE route), so this is safe in production. Callers that
|
|
13176
|
-
* fan an iterator out to multiple consumers must serialize themselves.
|
|
13177
|
-
*/
|
|
13178
|
-
subscribe(opts = {}) {
|
|
13179
|
-
if (this.closed) {
|
|
13180
|
-
return emptyAsyncIterable();
|
|
13181
|
-
}
|
|
13182
|
-
if (this.subs.size >= this.maxSubscribers) {
|
|
13183
|
-
throw new SubscriberLimitExceededError(this.maxSubscribers);
|
|
13184
|
-
}
|
|
13185
|
-
const queue = new BoundedAsyncQueue(
|
|
13186
|
-
opts.maxQueued ?? DEFAULT_MAX_QUEUED
|
|
13187
|
-
);
|
|
13188
|
-
const sub = { queue, evicted: false, dispose: /* @__PURE__ */ __name(() => {
|
|
13189
|
-
}, "dispose") };
|
|
13190
|
-
this.subs.add(sub);
|
|
13191
|
-
if (opts.lastEventId !== void 0) {
|
|
13192
|
-
for (const e of this.ring) {
|
|
13193
|
-
if (e.id !== void 0 && e.id > opts.lastEventId) {
|
|
13194
|
-
queue.forcePush(e);
|
|
13195
|
-
}
|
|
13196
|
-
}
|
|
13197
|
-
}
|
|
13198
|
-
let disposed = false;
|
|
13199
|
-
const dispose = /* @__PURE__ */ __name(() => {
|
|
13200
|
-
if (disposed) return;
|
|
13201
|
-
disposed = true;
|
|
13202
|
-
this.subs.delete(sub);
|
|
13203
|
-
opts.signal?.removeEventListener("abort", onAbort);
|
|
13204
|
-
}, "dispose");
|
|
13205
|
-
sub.dispose = dispose;
|
|
13206
|
-
const onAbort = /* @__PURE__ */ __name(() => {
|
|
13207
|
-
queue.close({ drain: false });
|
|
13208
|
-
dispose();
|
|
13209
|
-
}, "onAbort");
|
|
13210
|
-
if (opts.signal) {
|
|
13211
|
-
if (opts.signal.aborted) {
|
|
13212
|
-
onAbort();
|
|
13213
|
-
} else {
|
|
13214
|
-
opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
13215
|
-
}
|
|
13216
|
-
}
|
|
13217
|
-
return {
|
|
13218
|
-
[Symbol.asyncIterator]: () => ({
|
|
13219
|
-
async next() {
|
|
13220
|
-
const r = await queue.next();
|
|
13221
|
-
if (r.done) dispose();
|
|
13222
|
-
return r;
|
|
13223
|
-
},
|
|
13224
|
-
async return() {
|
|
13225
|
-
queue.close();
|
|
13226
|
-
dispose();
|
|
13227
|
-
return { value: void 0, done: true };
|
|
13228
|
-
}
|
|
13229
|
-
})
|
|
13230
|
-
};
|
|
13231
|
-
}
|
|
13232
|
-
/** Close all live subscribers and prevent further `publish`/`subscribe`. */
|
|
13233
|
-
close() {
|
|
13234
|
-
if (this.closed) return;
|
|
13235
|
-
this.closed = true;
|
|
13236
|
-
for (const sub of this.subs) sub.queue.close();
|
|
13237
|
-
this.subs.clear();
|
|
13238
|
-
}
|
|
13239
|
-
};
|
|
13240
|
-
function emptyAsyncIterable() {
|
|
13241
|
-
return {
|
|
13242
|
-
[Symbol.asyncIterator]: () => ({
|
|
13243
|
-
async next() {
|
|
13244
|
-
return { value: void 0, done: true };
|
|
13245
|
-
}
|
|
13246
|
-
})
|
|
13247
|
-
};
|
|
13248
|
-
}
|
|
13249
|
-
__name(emptyAsyncIterable, "emptyAsyncIterable");
|
|
13250
|
-
var BoundedAsyncQueue = class {
|
|
13251
|
-
constructor(maxSize) {
|
|
13252
|
-
this.maxSize = maxSize;
|
|
13253
|
-
}
|
|
13254
|
-
static {
|
|
13255
|
-
__name(this, "BoundedAsyncQueue");
|
|
13256
|
-
}
|
|
13257
|
-
buf = [];
|
|
13258
|
-
resolvers = [];
|
|
13259
|
-
closed = false;
|
|
13260
|
-
/**
|
|
13261
|
-
* Number of force-pushed items still in `buf`. The cap check in
|
|
13262
|
-
* `push()` only applies to LIVE items; this counter tells us how
|
|
13263
|
-
* many slots in `buf` are replay-injected and shouldn't count.
|
|
13264
|
-
*
|
|
13265
|
-
* Position invariant: under the bus's two callers,
|
|
13266
|
-
* 1. subscribe-time replay (`Last-Event-ID` resume) — forcePush
|
|
13267
|
-
* fires BEFORE any live `push()`, so replay items are at the
|
|
13268
|
-
* front of `buf`;
|
|
13269
|
-
* 2. eviction terminal frame — forcePush fires AFTER `push()`
|
|
13270
|
-
* rejection, then `close()` is called immediately, so the
|
|
13271
|
-
* eviction frame is at the BACK of `buf`.
|
|
13272
|
-
*
|
|
13273
|
-
* `next()` decrements `forcedInBuf` whenever the counter is > 0 on
|
|
13274
|
-
* shift, which is correct for case (1). For case (2) it slightly
|
|
13275
|
-
* misaccounts (decrements on the first live shift), but that's
|
|
13276
|
-
* harmless: the queue is closed so no `push()` runs the cap check
|
|
13277
|
-
* again. The counter only matters for live cap enforcement.
|
|
13278
|
-
*/
|
|
13279
|
-
forcedInBuf = 0;
|
|
13280
|
-
/** Returns true if accepted, false if dropped due to overflow. */
|
|
13281
|
-
push(value) {
|
|
13282
|
-
if (this.closed) return false;
|
|
13283
|
-
const r = this.resolvers.shift();
|
|
13284
|
-
if (r) {
|
|
13285
|
-
r({ value, done: false });
|
|
13286
|
-
return true;
|
|
13287
|
-
}
|
|
13288
|
-
if (this.buf.length - this.forcedInBuf >= this.maxSize) return false;
|
|
13289
|
-
this.buf.push(value);
|
|
13290
|
-
return true;
|
|
13291
|
-
}
|
|
13292
|
-
/** Bypasses the size cap. Used for replay frames and terminal eviction. */
|
|
13293
|
-
forcePush(value) {
|
|
13294
|
-
if (this.closed) return;
|
|
13295
|
-
const r = this.resolvers.shift();
|
|
13296
|
-
if (r) {
|
|
13297
|
-
r({ value, done: false });
|
|
13298
|
-
return;
|
|
13299
|
-
}
|
|
13300
|
-
this.buf.push(value);
|
|
13301
|
-
this.forcedInBuf += 1;
|
|
13302
|
-
}
|
|
13303
|
-
/**
|
|
13304
|
-
* Mark the queue closed. By default `next()` continues to drain
|
|
13305
|
-
* any items already in `buf` before returning `done: true` —
|
|
13306
|
-
* that's what the eviction path relies on (the synthetic
|
|
13307
|
-
* `client_evicted` frame is force-pushed THEN close is called,
|
|
13308
|
-
* and we want the consumer to see the terminal frame before the
|
|
13309
|
-
* iterator unwinds).
|
|
13310
|
-
*
|
|
13311
|
-
* Pass `{ drain: false }` to drop buffered items immediately
|
|
13312
|
-
* (the AbortSignal-driven unsubscribe path uses this — the
|
|
13313
|
-
* subscribe docstring says abort should close the iterator
|
|
13314
|
-
* promptly, but draining hundreds of queued events first
|
|
13315
|
-
* contradicts that and adds post-abort work to the SSE route).
|
|
13316
|
-
*/
|
|
13317
|
-
close(opts = {}) {
|
|
13318
|
-
if (this.closed) return;
|
|
13319
|
-
this.closed = true;
|
|
13320
|
-
if (opts.drain === false) {
|
|
13321
|
-
this.buf.length = 0;
|
|
13322
|
-
this.forcedInBuf = 0;
|
|
13323
|
-
}
|
|
13324
|
-
while (this.resolvers.length > 0) {
|
|
13325
|
-
this.resolvers.shift()({
|
|
13326
|
-
value: void 0,
|
|
13327
|
-
done: true
|
|
13328
|
-
});
|
|
13329
|
-
}
|
|
13330
|
-
}
|
|
13331
|
-
next() {
|
|
13332
|
-
if (this.buf.length > 0) {
|
|
13333
|
-
const value = this.buf.shift();
|
|
13334
|
-
if (this.forcedInBuf > 0) this.forcedInBuf -= 1;
|
|
13335
|
-
return Promise.resolve({ value, done: false });
|
|
13336
|
-
}
|
|
13337
|
-
if (this.closed) {
|
|
13338
|
-
return Promise.resolve({
|
|
13339
|
-
value: void 0,
|
|
13340
|
-
done: true
|
|
13341
|
-
});
|
|
13342
|
-
}
|
|
13343
|
-
return new Promise((resolve2) => this.resolvers.push(resolve2));
|
|
13344
|
-
}
|
|
13345
|
-
};
|
|
13346
|
-
|
|
13347
|
-
// packages/cli/src/serve/httpAcpBridge.ts
|
|
13348
13112
|
var SessionNotFoundError = class extends Error {
|
|
13349
13113
|
static {
|
|
13350
13114
|
__name(this, "SessionNotFoundError");
|
|
@@ -13454,6 +13218,28 @@ var InvalidPermissionOptionError = class extends Error {
|
|
|
13454
13218
|
this.optionId = optionId;
|
|
13455
13219
|
}
|
|
13456
13220
|
};
|
|
13221
|
+
var MAX_DISPLAY_NAME_LENGTH = 256;
|
|
13222
|
+
function hasControlCharacter(value) {
|
|
13223
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
13224
|
+
const code = value.charCodeAt(i);
|
|
13225
|
+
if (code <= 31 || code === 127) {
|
|
13226
|
+
return true;
|
|
13227
|
+
}
|
|
13228
|
+
}
|
|
13229
|
+
return false;
|
|
13230
|
+
}
|
|
13231
|
+
__name(hasControlCharacter, "hasControlCharacter");
|
|
13232
|
+
var InvalidSessionMetadataError = class extends Error {
|
|
13233
|
+
static {
|
|
13234
|
+
__name(this, "InvalidSessionMetadataError");
|
|
13235
|
+
}
|
|
13236
|
+
field;
|
|
13237
|
+
constructor(field, reason) {
|
|
13238
|
+
super(`Invalid session metadata: ${field} ${reason}`);
|
|
13239
|
+
this.name = "InvalidSessionMetadataError";
|
|
13240
|
+
this.field = field;
|
|
13241
|
+
}
|
|
13242
|
+
};
|
|
13457
13243
|
var BridgeClient = class {
|
|
13458
13244
|
constructor(resolveEntry, resolvePendingRestoreEvents, registerPending, rollbackPending, permissionTimeoutMs, maxPendingPerSession) {
|
|
13459
13245
|
this.resolveEntry = resolveEntry;
|
|
@@ -13620,6 +13406,7 @@ var BridgeClient = class {
|
|
|
13620
13406
|
};
|
|
13621
13407
|
var DEFAULT_INIT_TIMEOUT_MS = 1e4;
|
|
13622
13408
|
var DEFAULT_MAX_SESSIONS = 20;
|
|
13409
|
+
var MAX_EVENT_RING_SIZE = 1e6;
|
|
13623
13410
|
var DEFAULT_PERMISSION_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
13624
13411
|
var DEFAULT_MAX_PENDING_PER_SESSION = 64;
|
|
13625
13412
|
function createHttpAcpBridge(opts) {
|
|
@@ -13645,6 +13432,12 @@ function createHttpAcpBridge(opts) {
|
|
|
13645
13432
|
`Invalid sessionScope: ${JSON.stringify(defaultSessionScope)}. Expected 'single' or 'thread'.`
|
|
13646
13433
|
);
|
|
13647
13434
|
}
|
|
13435
|
+
const eventRingSize = opts.eventRingSize ?? DEFAULT_RING_SIZE;
|
|
13436
|
+
if (!Number.isInteger(eventRingSize) || eventRingSize < 1 || eventRingSize > MAX_EVENT_RING_SIZE) {
|
|
13437
|
+
throw new TypeError(
|
|
13438
|
+
`Invalid eventRingSize: ${opts.eventRingSize}. Must be a positive integer in [1, ${MAX_EVENT_RING_SIZE}].`
|
|
13439
|
+
);
|
|
13440
|
+
}
|
|
13648
13441
|
const channelFactory = opts.channelFactory ?? defaultSpawnChannelFactory;
|
|
13649
13442
|
const initTimeoutMs = opts.initializeTimeoutMs ?? DEFAULT_INIT_TIMEOUT_MS;
|
|
13650
13443
|
if (initTimeoutMs <= 0) {
|
|
@@ -13693,6 +13486,7 @@ function createHttpAcpBridge(opts) {
|
|
|
13693
13486
|
if (count === void 0) return;
|
|
13694
13487
|
if (count <= 1) {
|
|
13695
13488
|
entry.clientIds.delete(clientId);
|
|
13489
|
+
entry.clientLastSeenAt.delete(clientId);
|
|
13696
13490
|
} else {
|
|
13697
13491
|
entry.clientIds.set(clientId, count - 1);
|
|
13698
13492
|
}
|
|
@@ -13935,7 +13729,8 @@ function createHttpAcpBridge(opts) {
|
|
|
13935
13729
|
sessionId: entry.sessionId,
|
|
13936
13730
|
workspaceCwd: entry.workspaceCwd,
|
|
13937
13731
|
attached: false,
|
|
13938
|
-
clientId
|
|
13732
|
+
clientId,
|
|
13733
|
+
createdAt: entry.createdAt
|
|
13939
13734
|
};
|
|
13940
13735
|
}
|
|
13941
13736
|
__name(doSpawn, "doSpawn");
|
|
@@ -14010,10 +13805,58 @@ function createHttpAcpBridge(opts) {
|
|
|
14010
13805
|
}
|
|
14011
13806
|
return workspaceKey;
|
|
14012
13807
|
}, "resolveWorkspaceKey");
|
|
14013
|
-
const
|
|
13808
|
+
const liveChannelInfo = /* @__PURE__ */ __name(() => {
|
|
13809
|
+
if (!channelInfo || channelInfo.isDying) return void 0;
|
|
13810
|
+
return channelInfo;
|
|
13811
|
+
}, "liveChannelInfo");
|
|
13812
|
+
const channelInfoForEntry = /* @__PURE__ */ __name((entry) => {
|
|
13813
|
+
if (channelInfo?.channel === entry.channel) return channelInfo;
|
|
13814
|
+
for (const info of aliveChannels) {
|
|
13815
|
+
if (info.channel === entry.channel) return info;
|
|
13816
|
+
}
|
|
13817
|
+
return void 0;
|
|
13818
|
+
}, "channelInfoForEntry");
|
|
13819
|
+
const getChannelClosedReject = /* @__PURE__ */ __name((info) => {
|
|
13820
|
+
if (!info.statusClosedReject) {
|
|
13821
|
+
info.statusClosedReject = info.channel.exited.then(() => {
|
|
13822
|
+
throw new Error("agent channel closed mid-request (workspace status)");
|
|
13823
|
+
});
|
|
13824
|
+
}
|
|
13825
|
+
return info.statusClosedReject;
|
|
13826
|
+
}, "getChannelClosedReject");
|
|
13827
|
+
const requestWorkspaceStatus = /* @__PURE__ */ __name(async (method, idle) => {
|
|
13828
|
+
const info = liveChannelInfo();
|
|
13829
|
+
if (!info) return idle();
|
|
13830
|
+
const response = await withTimeout(
|
|
13831
|
+
Promise.race([
|
|
13832
|
+
info.connection.extMethod(method, { cwd: boundWorkspace }),
|
|
13833
|
+
getChannelClosedReject(info)
|
|
13834
|
+
]),
|
|
13835
|
+
initTimeoutMs,
|
|
13836
|
+
method
|
|
13837
|
+
);
|
|
13838
|
+
return response;
|
|
13839
|
+
}, "requestWorkspaceStatus");
|
|
13840
|
+
const requestSessionStatus = /* @__PURE__ */ __name(async (sessionId, method) => {
|
|
13841
|
+
const entry = byId.get(sessionId);
|
|
13842
|
+
if (!entry) throw new SessionNotFoundError(sessionId);
|
|
13843
|
+
const info = channelInfoForEntry(entry);
|
|
13844
|
+
if (!info || info.isDying) throw new SessionNotFoundError(sessionId);
|
|
13845
|
+
const response = await Promise.race([
|
|
13846
|
+
withTimeout(
|
|
13847
|
+
entry.connection.extMethod(method, { sessionId }),
|
|
13848
|
+
initTimeoutMs,
|
|
13849
|
+
method
|
|
13850
|
+
),
|
|
13851
|
+
getTransportClosedReject(entry)
|
|
13852
|
+
]);
|
|
13853
|
+
return response;
|
|
13854
|
+
}, "requestSessionStatus");
|
|
13855
|
+
const createSessionEntry = /* @__PURE__ */ __name((ci, sessionId, workspaceCwd, events = new EventBus(eventRingSize)) => {
|
|
14014
13856
|
const entry = {
|
|
14015
13857
|
sessionId,
|
|
14016
13858
|
workspaceCwd,
|
|
13859
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14017
13860
|
channel: ci.channel,
|
|
14018
13861
|
connection: ci.connection,
|
|
14019
13862
|
events,
|
|
@@ -14021,6 +13864,7 @@ function createHttpAcpBridge(opts) {
|
|
|
14021
13864
|
modelChangeQueue: Promise.resolve(),
|
|
14022
13865
|
pendingPermissionIds: /* @__PURE__ */ new Set(),
|
|
14023
13866
|
clientIds: /* @__PURE__ */ new Map(),
|
|
13867
|
+
clientLastSeenAt: /* @__PURE__ */ new Map(),
|
|
14024
13868
|
attachCount: 0,
|
|
14025
13869
|
spawnOwnerWantedKill: false
|
|
14026
13870
|
};
|
|
@@ -14052,6 +13896,7 @@ function createHttpAcpBridge(opts) {
|
|
|
14052
13896
|
workspaceCwd: existing.workspaceCwd,
|
|
14053
13897
|
attached: true,
|
|
14054
13898
|
clientId,
|
|
13899
|
+
createdAt: existing.createdAt,
|
|
14055
13900
|
// Late attachers get the same ACP state the original restore
|
|
14056
13901
|
// caller saw; spawn-only sessions don't carry a state payload.
|
|
14057
13902
|
state: existing.restoreState ?? {}
|
|
@@ -14085,13 +13930,14 @@ function createHttpAcpBridge(opts) {
|
|
|
14085
13930
|
return {
|
|
14086
13931
|
...restored,
|
|
14087
13932
|
attached: true,
|
|
14088
|
-
clientId: registerClient(entry, req.clientId)
|
|
13933
|
+
clientId: registerClient(entry, req.clientId),
|
|
13934
|
+
createdAt: entry.createdAt
|
|
14089
13935
|
};
|
|
14090
13936
|
}
|
|
14091
13937
|
if (byId.size + inFlightSpawns.size + inFlightRestores.size >= maxSessions) {
|
|
14092
13938
|
throw new SessionLimitExceededError(maxSessions);
|
|
14093
13939
|
}
|
|
14094
|
-
const restoreEvents = new EventBus();
|
|
13940
|
+
const restoreEvents = new EventBus(eventRingSize);
|
|
14095
13941
|
let registeredEntry;
|
|
14096
13942
|
let ci;
|
|
14097
13943
|
const coalesceState = { count: 0 };
|
|
@@ -14170,6 +14016,7 @@ function createHttpAcpBridge(opts) {
|
|
|
14170
14016
|
workspaceCwd: racedEntry.workspaceCwd,
|
|
14171
14017
|
attached: true,
|
|
14172
14018
|
clientId: clientId2,
|
|
14019
|
+
createdAt: racedEntry.createdAt,
|
|
14173
14020
|
state: racedEntry.restoreState ?? {}
|
|
14174
14021
|
};
|
|
14175
14022
|
}
|
|
@@ -14188,6 +14035,7 @@ function createHttpAcpBridge(opts) {
|
|
|
14188
14035
|
workspaceCwd: entry.workspaceCwd,
|
|
14189
14036
|
attached: false,
|
|
14190
14037
|
clientId,
|
|
14038
|
+
createdAt: entry.createdAt,
|
|
14191
14039
|
state
|
|
14192
14040
|
};
|
|
14193
14041
|
})().finally(() => {
|
|
@@ -14245,7 +14093,8 @@ function createHttpAcpBridge(opts) {
|
|
|
14245
14093
|
sessionId: existing.sessionId,
|
|
14246
14094
|
workspaceCwd: existing.workspaceCwd,
|
|
14247
14095
|
attached: true,
|
|
14248
|
-
clientId
|
|
14096
|
+
clientId,
|
|
14097
|
+
createdAt: existing.createdAt
|
|
14249
14098
|
};
|
|
14250
14099
|
}
|
|
14251
14100
|
const inFlight = inFlightSpawns.get(workspaceKey);
|
|
@@ -14410,6 +14259,86 @@ function createHttpAcpBridge(opts) {
|
|
|
14410
14259
|
}
|
|
14411
14260
|
return resolvePending(requestId, response, originatorClientId);
|
|
14412
14261
|
},
|
|
14262
|
+
async closeSession(sessionId, context) {
|
|
14263
|
+
const entry = byId.get(sessionId);
|
|
14264
|
+
if (!entry) throw new SessionNotFoundError(sessionId);
|
|
14265
|
+
let originatorClientId;
|
|
14266
|
+
if (context?.clientId !== void 0) {
|
|
14267
|
+
originatorClientId = resolveTrustedClientId(entry, context.clientId);
|
|
14268
|
+
}
|
|
14269
|
+
writeStderrLine(
|
|
14270
|
+
`qwen serve: closing session ${JSON.stringify(sessionId)}` + (originatorClientId ? ` by client ${JSON.stringify(originatorClientId)}` : "")
|
|
14271
|
+
);
|
|
14272
|
+
if (defaultEntry === entry) defaultEntry = void 0;
|
|
14273
|
+
const ci = channelInfo;
|
|
14274
|
+
if (ci && ci.channel === entry.channel) {
|
|
14275
|
+
ci.sessionIds.delete(sessionId);
|
|
14276
|
+
}
|
|
14277
|
+
for (const id of Array.from(entry.pendingPermissionIds)) {
|
|
14278
|
+
resolvePending(id, { outcome: { outcome: "cancelled" } });
|
|
14279
|
+
}
|
|
14280
|
+
byId.delete(sessionId);
|
|
14281
|
+
try {
|
|
14282
|
+
entry.events.publish({
|
|
14283
|
+
type: "session_closed",
|
|
14284
|
+
data: {
|
|
14285
|
+
sessionId,
|
|
14286
|
+
reason: "client_close",
|
|
14287
|
+
...originatorClientId ? { closedBy: originatorClientId } : {}
|
|
14288
|
+
}
|
|
14289
|
+
});
|
|
14290
|
+
} catch {
|
|
14291
|
+
}
|
|
14292
|
+
entry.events.close();
|
|
14293
|
+
try {
|
|
14294
|
+
await entry.connection.cancel({ sessionId });
|
|
14295
|
+
} catch {
|
|
14296
|
+
}
|
|
14297
|
+
if (ci && ci.sessionIds.size === 0 && ci.pendingRestoreIds.size === 0) {
|
|
14298
|
+
ci.isDying = true;
|
|
14299
|
+
await ci.channel.kill().catch((err) => {
|
|
14300
|
+
writeStderrLine(
|
|
14301
|
+
`qwen serve: closeSession channel kill failed for session ${JSON.stringify(sessionId)}: ${String(err)}`
|
|
14302
|
+
);
|
|
14303
|
+
});
|
|
14304
|
+
}
|
|
14305
|
+
},
|
|
14306
|
+
updateSessionMetadata(sessionId, metadata, context) {
|
|
14307
|
+
const entry = byId.get(sessionId);
|
|
14308
|
+
if (!entry) throw new SessionNotFoundError(sessionId);
|
|
14309
|
+
if (context?.clientId !== void 0) {
|
|
14310
|
+
resolveTrustedClientId(entry, context.clientId);
|
|
14311
|
+
}
|
|
14312
|
+
if (metadata.displayName !== void 0) {
|
|
14313
|
+
if (typeof metadata.displayName !== "string" || metadata.displayName.length > MAX_DISPLAY_NAME_LENGTH) {
|
|
14314
|
+
throw new InvalidSessionMetadataError(
|
|
14315
|
+
"displayName",
|
|
14316
|
+
`must be a string of at most ${MAX_DISPLAY_NAME_LENGTH} characters`
|
|
14317
|
+
);
|
|
14318
|
+
}
|
|
14319
|
+
if (hasControlCharacter(metadata.displayName)) {
|
|
14320
|
+
throw new InvalidSessionMetadataError(
|
|
14321
|
+
"displayName",
|
|
14322
|
+
"must not contain control characters"
|
|
14323
|
+
);
|
|
14324
|
+
}
|
|
14325
|
+
const nextDisplayName = metadata.displayName || void 0;
|
|
14326
|
+
if (entry.displayName !== nextDisplayName) {
|
|
14327
|
+
entry.displayName = nextDisplayName;
|
|
14328
|
+
writeStderrLine(
|
|
14329
|
+
`qwen serve: updated session metadata ${JSON.stringify(sessionId)} displayName=${entry.displayName === void 0 ? "cleared" : "set"}` + (context?.clientId ? ` by client ${JSON.stringify(context.clientId)}` : "")
|
|
14330
|
+
);
|
|
14331
|
+
try {
|
|
14332
|
+
entry.events.publish({
|
|
14333
|
+
type: "session_metadata_updated",
|
|
14334
|
+
data: { sessionId, displayName: entry.displayName }
|
|
14335
|
+
});
|
|
14336
|
+
} catch {
|
|
14337
|
+
}
|
|
14338
|
+
}
|
|
14339
|
+
}
|
|
14340
|
+
return { displayName: entry.displayName };
|
|
14341
|
+
},
|
|
14413
14342
|
listWorkspaceSessions(workspaceCwd) {
|
|
14414
14343
|
if (!path.isAbsolute(workspaceCwd)) return [];
|
|
14415
14344
|
const key = workspaceCwd === boundWorkspace ? boundWorkspace : canonicalizeWorkspace(workspaceCwd);
|
|
@@ -14419,12 +14348,69 @@ function createHttpAcpBridge(opts) {
|
|
|
14419
14348
|
if (entry.workspaceCwd === key) {
|
|
14420
14349
|
out.push({
|
|
14421
14350
|
sessionId: entry.sessionId,
|
|
14422
|
-
workspaceCwd: entry.workspaceCwd
|
|
14351
|
+
workspaceCwd: entry.workspaceCwd,
|
|
14352
|
+
createdAt: entry.createdAt,
|
|
14353
|
+
displayName: entry.displayName,
|
|
14354
|
+
clientCount: entry.clientIds.size,
|
|
14355
|
+
hasActivePrompt: entry.activePromptOriginatorClientId !== void 0
|
|
14423
14356
|
});
|
|
14424
14357
|
}
|
|
14425
14358
|
}
|
|
14426
14359
|
return out;
|
|
14427
14360
|
},
|
|
14361
|
+
recordHeartbeat(sessionId, context) {
|
|
14362
|
+
const entry = byId.get(sessionId);
|
|
14363
|
+
if (!entry) throw new SessionNotFoundError(sessionId);
|
|
14364
|
+
const clientId = resolveTrustedClientId(entry, context?.clientId);
|
|
14365
|
+
const lastSeenAt = Date.now();
|
|
14366
|
+
entry.sessionLastSeenAt = lastSeenAt;
|
|
14367
|
+
if (clientId !== void 0) {
|
|
14368
|
+
entry.clientLastSeenAt.set(clientId, lastSeenAt);
|
|
14369
|
+
}
|
|
14370
|
+
return {
|
|
14371
|
+
sessionId: entry.sessionId,
|
|
14372
|
+
...clientId !== void 0 ? { clientId } : {},
|
|
14373
|
+
lastSeenAt
|
|
14374
|
+
};
|
|
14375
|
+
},
|
|
14376
|
+
getHeartbeatState(sessionId) {
|
|
14377
|
+
const entry = byId.get(sessionId);
|
|
14378
|
+
if (!entry) return void 0;
|
|
14379
|
+
return {
|
|
14380
|
+
...entry.sessionLastSeenAt !== void 0 ? { sessionLastSeenAt: entry.sessionLastSeenAt } : {},
|
|
14381
|
+
clientLastSeenAt: new Map(entry.clientLastSeenAt)
|
|
14382
|
+
};
|
|
14383
|
+
},
|
|
14384
|
+
async getWorkspaceMcpStatus() {
|
|
14385
|
+
return requestWorkspaceStatus(
|
|
14386
|
+
SERVE_STATUS_EXT_METHODS.workspaceMcp,
|
|
14387
|
+
() => createIdleWorkspaceMcpStatus(boundWorkspace)
|
|
14388
|
+
);
|
|
14389
|
+
},
|
|
14390
|
+
async getWorkspaceSkillsStatus() {
|
|
14391
|
+
return requestWorkspaceStatus(
|
|
14392
|
+
SERVE_STATUS_EXT_METHODS.workspaceSkills,
|
|
14393
|
+
() => createIdleWorkspaceSkillsStatus(boundWorkspace)
|
|
14394
|
+
);
|
|
14395
|
+
},
|
|
14396
|
+
async getWorkspaceProvidersStatus() {
|
|
14397
|
+
return requestWorkspaceStatus(
|
|
14398
|
+
SERVE_STATUS_EXT_METHODS.workspaceProviders,
|
|
14399
|
+
() => createIdleWorkspaceProvidersStatus(boundWorkspace)
|
|
14400
|
+
);
|
|
14401
|
+
},
|
|
14402
|
+
async getSessionContextStatus(sessionId) {
|
|
14403
|
+
return requestSessionStatus(
|
|
14404
|
+
sessionId,
|
|
14405
|
+
SERVE_STATUS_EXT_METHODS.sessionContext
|
|
14406
|
+
);
|
|
14407
|
+
},
|
|
14408
|
+
async getSessionSupportedCommandsStatus(sessionId) {
|
|
14409
|
+
return requestSessionStatus(
|
|
14410
|
+
sessionId,
|
|
14411
|
+
SERVE_STATUS_EXT_METHODS.sessionSupportedCommands
|
|
14412
|
+
);
|
|
14413
|
+
},
|
|
14428
14414
|
async setSessionModel(sessionId, req, context) {
|
|
14429
14415
|
const entry = byId.get(sessionId);
|
|
14430
14416
|
if (!entry) throw new SessionNotFoundError(sessionId);
|
|
@@ -14779,11 +14765,41 @@ var SERVE_CAPABILITY_REGISTRY = {
|
|
|
14779
14765
|
session_prompt: { since: "v1" },
|
|
14780
14766
|
session_cancel: { since: "v1" },
|
|
14781
14767
|
session_events: { since: "v1" },
|
|
14768
|
+
// Daemon emits `slow_client_warning` synthetic frames at 75% queue
|
|
14769
|
+
// fill and honors `?maxQueued=N` (range [16, 2048]) on
|
|
14770
|
+
// `GET /session/:id/events`. Old daemons silently lack both — SDK
|
|
14771
|
+
// clients pre-flight this tag before opting in.
|
|
14772
|
+
slow_client_warning: { since: "v1" },
|
|
14773
|
+
// SDK consumers can detect `KnownDaemonEvent` schema support without
|
|
14774
|
+
// pinning against this SDK release — `narrowDaemonEvent` falls back
|
|
14775
|
+
// to `kind: 'unknown'` for daemons that don't advertise the tag,
|
|
14776
|
+
// so the tag is purely informational.
|
|
14777
|
+
typed_event_schema: { since: "v1" },
|
|
14782
14778
|
session_set_model: { since: "v1" },
|
|
14783
14779
|
client_identity: { since: "v1" },
|
|
14780
|
+
client_heartbeat: { since: "v1" },
|
|
14784
14781
|
session_permission_vote: { since: "v1" },
|
|
14785
|
-
permission_vote: { since: "v1" }
|
|
14782
|
+
permission_vote: { since: "v1" },
|
|
14783
|
+
workspace_mcp: { since: "v1" },
|
|
14784
|
+
workspace_skills: { since: "v1" },
|
|
14785
|
+
workspace_providers: { since: "v1" },
|
|
14786
|
+
session_context: { since: "v1" },
|
|
14787
|
+
session_supported_commands: { since: "v1" },
|
|
14788
|
+
session_close: { since: "v1" },
|
|
14789
|
+
session_metadata: { since: "v1" },
|
|
14790
|
+
// Issue #4175 PR 15. Daemon was booted with `--require-auth` (or
|
|
14791
|
+
// `requireAuth: true`), so even loopback callers must carry a bearer
|
|
14792
|
+
// token. Advertised CONDITIONALLY — only when the flag is on — so
|
|
14793
|
+
// SDK clients can branch on its presence to surface a clear "this
|
|
14794
|
+
// deployment requires auth" hint instead of speculatively trying
|
|
14795
|
+
// requests and parsing the resulting 401 body. Loopback developer
|
|
14796
|
+
// defaults (no flag) omit the tag, preserving the bit-for-bit shape
|
|
14797
|
+
// older clients expect.
|
|
14798
|
+
require_auth: { since: "v1" }
|
|
14786
14799
|
};
|
|
14800
|
+
var CONDITIONAL_SERVE_FEATURES = /* @__PURE__ */ new Map([
|
|
14801
|
+
["require_auth", (toggles) => toggles.requireAuth === true]
|
|
14802
|
+
]);
|
|
14787
14803
|
var SERVE_FEATURES = Object.freeze(
|
|
14788
14804
|
Object.keys(SERVE_CAPABILITY_REGISTRY)
|
|
14789
14805
|
);
|
|
@@ -14799,10 +14815,13 @@ function getRegisteredServeFeatures() {
|
|
|
14799
14815
|
return [...SERVE_FEATURES];
|
|
14800
14816
|
}
|
|
14801
14817
|
__name(getRegisteredServeFeatures, "getRegisteredServeFeatures");
|
|
14802
|
-
function getAdvertisedServeFeatures(protocolVersion = SERVE_PROTOCOL_VERSION) {
|
|
14803
|
-
return SERVE_FEATURES.filter(
|
|
14804
|
-
(
|
|
14805
|
-
|
|
14818
|
+
function getAdvertisedServeFeatures(protocolVersion = SERVE_PROTOCOL_VERSION, toggles = {}) {
|
|
14819
|
+
return SERVE_FEATURES.filter((feature) => {
|
|
14820
|
+
if (!isFeatureAvailableInProtocol(feature, protocolVersion)) return false;
|
|
14821
|
+
const predicate = CONDITIONAL_SERVE_FEATURES.get(feature);
|
|
14822
|
+
if (predicate !== void 0) return predicate(toggles);
|
|
14823
|
+
return true;
|
|
14824
|
+
});
|
|
14806
14825
|
}
|
|
14807
14826
|
__name(getAdvertisedServeFeatures, "getAdvertisedServeFeatures");
|
|
14808
14827
|
function getServeFeatures() {
|
|
@@ -14828,6 +14847,11 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
14828
14847
|
const boundWorkspace = deps.boundWorkspace ?? canonicalizeWorkspace(opts.workspace ?? process.cwd());
|
|
14829
14848
|
const bridge = deps.bridge ?? createHttpAcpBridge({
|
|
14830
14849
|
maxSessions: opts.maxSessions,
|
|
14850
|
+
// Symmetric with `runQwenServe.ts` — direct embeds / tests that
|
|
14851
|
+
// call `createServeApp` without supplying their own bridge and
|
|
14852
|
+
// pass `ServeOptions.eventRingSize` would otherwise silently
|
|
14853
|
+
// get the default 8000 ring instead of their configured value.
|
|
14854
|
+
...opts.eventRingSize !== void 0 ? { eventRingSize: opts.eventRingSize } : {},
|
|
14831
14855
|
boundWorkspace
|
|
14832
14856
|
});
|
|
14833
14857
|
app.use(denyBrowserOriginCors);
|
|
@@ -14853,20 +14877,31 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
14853
14877
|
}
|
|
14854
14878
|
}, "healthHandler");
|
|
14855
14879
|
const loopback = isLoopbackBind(opts.hostname);
|
|
14856
|
-
|
|
14880
|
+
const exposeHealthPreAuth = loopback && !opts.requireAuth;
|
|
14881
|
+
if (exposeHealthPreAuth) {
|
|
14857
14882
|
app.get("/health", healthHandler);
|
|
14858
14883
|
}
|
|
14859
14884
|
app.use(bearerAuth(opts.token));
|
|
14860
14885
|
app.use(import_express.default.json({ limit: "10mb" }));
|
|
14861
|
-
if (!
|
|
14886
|
+
if (!exposeHealthPreAuth) {
|
|
14862
14887
|
app.get("/health", healthHandler);
|
|
14863
14888
|
}
|
|
14889
|
+
const mutate = createMutationGate({
|
|
14890
|
+
tokenConfigured: opts.token !== void 0,
|
|
14891
|
+
requireAuth: opts.requireAuth === true
|
|
14892
|
+
});
|
|
14864
14893
|
app.get("/capabilities", (_req, res) => {
|
|
14865
14894
|
const envelope = {
|
|
14866
14895
|
v: CAPABILITIES_SCHEMA_VERSION,
|
|
14867
14896
|
protocolVersions: getServeProtocolVersions(),
|
|
14868
14897
|
mode: opts.mode,
|
|
14869
|
-
|
|
14898
|
+
// PR 15. Pass `requireAuth` so the `require_auth` tag appears
|
|
14899
|
+
// ONLY when the operator opted in. Tag presence = behavior is
|
|
14900
|
+
// on; older daemons without this PR omit the tag and SDKs that
|
|
14901
|
+
// post-PR feature-detect on it stay backward compatible.
|
|
14902
|
+
features: getAdvertisedServeFeatures(void 0, {
|
|
14903
|
+
requireAuth: opts.requireAuth === true
|
|
14904
|
+
}),
|
|
14870
14905
|
modelServices: [],
|
|
14871
14906
|
// #3803 §02: surface the bound workspace so clients can detect
|
|
14872
14907
|
// mismatch pre-flight and omit `cwd` on `POST /session`.
|
|
@@ -14874,7 +14909,28 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
14874
14909
|
};
|
|
14875
14910
|
res.status(200).json(envelope);
|
|
14876
14911
|
});
|
|
14877
|
-
app.
|
|
14912
|
+
app.get("/workspace/mcp", async (_req, res) => {
|
|
14913
|
+
try {
|
|
14914
|
+
res.status(200).json(await bridge.getWorkspaceMcpStatus());
|
|
14915
|
+
} catch (err) {
|
|
14916
|
+
sendBridgeError(res, err, { route: "GET /workspace/mcp" });
|
|
14917
|
+
}
|
|
14918
|
+
});
|
|
14919
|
+
app.get("/workspace/skills", async (_req, res) => {
|
|
14920
|
+
try {
|
|
14921
|
+
res.status(200).json(await bridge.getWorkspaceSkillsStatus());
|
|
14922
|
+
} catch (err) {
|
|
14923
|
+
sendBridgeError(res, err, { route: "GET /workspace/skills" });
|
|
14924
|
+
}
|
|
14925
|
+
});
|
|
14926
|
+
app.get("/workspace/providers", async (_req, res) => {
|
|
14927
|
+
try {
|
|
14928
|
+
res.status(200).json(await bridge.getWorkspaceProvidersStatus());
|
|
14929
|
+
} catch (err) {
|
|
14930
|
+
sendBridgeError(res, err, { route: "GET /workspace/providers" });
|
|
14931
|
+
}
|
|
14932
|
+
});
|
|
14933
|
+
app.post("/session", mutate(), async (req, res) => {
|
|
14878
14934
|
const body = safeBody(req);
|
|
14879
14935
|
const hasCwd = "cwd" in body;
|
|
14880
14936
|
if (hasCwd && typeof body["cwd"] !== "string") {
|
|
@@ -14968,9 +15024,39 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
14968
15024
|
});
|
|
14969
15025
|
}
|
|
14970
15026
|
}, "restoreSessionHandler");
|
|
14971
|
-
app.post("/session/:id/load", restoreSessionHandler("load"));
|
|
14972
|
-
app.post("/session/:id/resume", restoreSessionHandler("resume"));
|
|
14973
|
-
app.
|
|
15027
|
+
app.post("/session/:id/load", mutate(), restoreSessionHandler("load"));
|
|
15028
|
+
app.post("/session/:id/resume", mutate(), restoreSessionHandler("resume"));
|
|
15029
|
+
app.get("/session/:id/context", async (req, res) => {
|
|
15030
|
+
const sessionId = req.params["id"];
|
|
15031
|
+
if (!sessionId) {
|
|
15032
|
+
res.status(400).json({ error: "`sessionId` route parameter is required" });
|
|
15033
|
+
return;
|
|
15034
|
+
}
|
|
15035
|
+
try {
|
|
15036
|
+
res.status(200).json(await bridge.getSessionContextStatus(sessionId));
|
|
15037
|
+
} catch (err) {
|
|
15038
|
+
sendBridgeError(res, err, {
|
|
15039
|
+
route: "GET /session/:id/context",
|
|
15040
|
+
sessionId
|
|
15041
|
+
});
|
|
15042
|
+
}
|
|
15043
|
+
});
|
|
15044
|
+
app.get("/session/:id/supported-commands", async (req, res) => {
|
|
15045
|
+
const sessionId = req.params["id"];
|
|
15046
|
+
if (!sessionId) {
|
|
15047
|
+
res.status(400).json({ error: "`sessionId` route parameter is required" });
|
|
15048
|
+
return;
|
|
15049
|
+
}
|
|
15050
|
+
try {
|
|
15051
|
+
res.status(200).json(await bridge.getSessionSupportedCommandsStatus(sessionId));
|
|
15052
|
+
} catch (err) {
|
|
15053
|
+
sendBridgeError(res, err, {
|
|
15054
|
+
route: "GET /session/:id/supported-commands",
|
|
15055
|
+
sessionId
|
|
15056
|
+
});
|
|
15057
|
+
}
|
|
15058
|
+
});
|
|
15059
|
+
app.post("/session/:id/prompt", mutate(), async (req, res) => {
|
|
14974
15060
|
const sessionId = req.params["id"];
|
|
14975
15061
|
const body = safeBody(req);
|
|
14976
15062
|
const prompt = body["prompt"];
|
|
@@ -15030,7 +15116,28 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
15030
15116
|
res.off("close", onResClose);
|
|
15031
15117
|
}
|
|
15032
15118
|
});
|
|
15033
|
-
app.post("/session/:id/
|
|
15119
|
+
app.post("/session/:id/heartbeat", mutate(), (req, res) => {
|
|
15120
|
+
const sessionId = req.params["id"];
|
|
15121
|
+
if (!sessionId) {
|
|
15122
|
+
res.status(400).json({ error: "`sessionId` route parameter is required" });
|
|
15123
|
+
return;
|
|
15124
|
+
}
|
|
15125
|
+
const clientId = parseClientIdHeader(req, res);
|
|
15126
|
+
if (clientId === null) return;
|
|
15127
|
+
try {
|
|
15128
|
+
const result = bridge.recordHeartbeat(
|
|
15129
|
+
sessionId,
|
|
15130
|
+
clientId !== void 0 ? { clientId } : void 0
|
|
15131
|
+
);
|
|
15132
|
+
res.status(200).json(result);
|
|
15133
|
+
} catch (err) {
|
|
15134
|
+
sendBridgeError(res, err, {
|
|
15135
|
+
route: "POST /session/:id/heartbeat",
|
|
15136
|
+
sessionId
|
|
15137
|
+
});
|
|
15138
|
+
}
|
|
15139
|
+
});
|
|
15140
|
+
app.post("/session/:id/cancel", mutate(), async (req, res) => {
|
|
15034
15141
|
const sessionId = req.params["id"];
|
|
15035
15142
|
const body = safeBody(req);
|
|
15036
15143
|
const clientId = parseClientIdHeader(req, res);
|
|
@@ -15052,6 +15159,51 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
15052
15159
|
});
|
|
15053
15160
|
}
|
|
15054
15161
|
});
|
|
15162
|
+
app.delete("/session/:id", async (req, res) => {
|
|
15163
|
+
const sessionId = req.params["id"];
|
|
15164
|
+
const clientId = parseClientIdHeader(req, res);
|
|
15165
|
+
if (clientId === null) return;
|
|
15166
|
+
try {
|
|
15167
|
+
await bridge.closeSession(
|
|
15168
|
+
sessionId,
|
|
15169
|
+
clientId !== void 0 ? { clientId } : void 0
|
|
15170
|
+
);
|
|
15171
|
+
res.status(204).end();
|
|
15172
|
+
} catch (err) {
|
|
15173
|
+
sendBridgeError(res, err, {
|
|
15174
|
+
route: "DELETE /session/:id",
|
|
15175
|
+
sessionId
|
|
15176
|
+
});
|
|
15177
|
+
}
|
|
15178
|
+
});
|
|
15179
|
+
app.patch("/session/:id/metadata", (req, res) => {
|
|
15180
|
+
const sessionId = req.params["id"];
|
|
15181
|
+
const body = safeBody(req);
|
|
15182
|
+
const clientId = parseClientIdHeader(req, res);
|
|
15183
|
+
if (clientId === null) return;
|
|
15184
|
+
const displayName = body["displayName"];
|
|
15185
|
+
if (displayName !== void 0 && typeof displayName !== "string") {
|
|
15186
|
+
res.status(400).json({
|
|
15187
|
+
error: "`displayName` must be a string",
|
|
15188
|
+
code: "invalid_metadata",
|
|
15189
|
+
field: "displayName"
|
|
15190
|
+
});
|
|
15191
|
+
return;
|
|
15192
|
+
}
|
|
15193
|
+
try {
|
|
15194
|
+
const effective = bridge.updateSessionMetadata(
|
|
15195
|
+
sessionId,
|
|
15196
|
+
{ displayName },
|
|
15197
|
+
clientId !== void 0 ? { clientId } : void 0
|
|
15198
|
+
);
|
|
15199
|
+
res.status(200).json({ sessionId, ...effective });
|
|
15200
|
+
} catch (err) {
|
|
15201
|
+
sendBridgeError(res, err, {
|
|
15202
|
+
route: "PATCH /session/:id/metadata",
|
|
15203
|
+
sessionId
|
|
15204
|
+
});
|
|
15205
|
+
}
|
|
15206
|
+
});
|
|
15055
15207
|
app.get("/workspace/:id/sessions", (req, res) => {
|
|
15056
15208
|
const workspaceCwd = req.params["id"] ?? "";
|
|
15057
15209
|
if (!path2.isAbsolute(workspaceCwd)) {
|
|
@@ -15071,7 +15223,7 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
15071
15223
|
const sessions = bridge.listWorkspaceSessions(workspaceCwd);
|
|
15072
15224
|
res.status(200).json({ sessions });
|
|
15073
15225
|
});
|
|
15074
|
-
app.post("/session/:id/model", async (req, res) => {
|
|
15226
|
+
app.post("/session/:id/model", mutate(), async (req, res) => {
|
|
15075
15227
|
const sessionId = req.params["id"];
|
|
15076
15228
|
const body = safeBody(req);
|
|
15077
15229
|
const modelId = body["modelId"];
|
|
@@ -15101,7 +15253,7 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
15101
15253
|
});
|
|
15102
15254
|
}
|
|
15103
15255
|
});
|
|
15104
|
-
app.post("/session/:id/permission/:requestId", (req, res) => {
|
|
15256
|
+
app.post("/session/:id/permission/:requestId", mutate(), (req, res) => {
|
|
15105
15257
|
const sessionId = req.params["id"];
|
|
15106
15258
|
const requestId = req.params["requestId"];
|
|
15107
15259
|
const response = parsePermissionVoteBody(req, res);
|
|
@@ -15133,7 +15285,7 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
15133
15285
|
}
|
|
15134
15286
|
res.status(200).json({});
|
|
15135
15287
|
});
|
|
15136
|
-
app.post("/permission/:requestId", (req, res) => {
|
|
15288
|
+
app.post("/permission/:requestId", mutate(), (req, res) => {
|
|
15137
15289
|
const requestId = req.params["requestId"];
|
|
15138
15290
|
const response = parsePermissionVoteBody(req, res);
|
|
15139
15291
|
if (response === void 0) return;
|
|
@@ -15161,12 +15313,15 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
15161
15313
|
app.get("/session/:id/events", (req, res) => {
|
|
15162
15314
|
const sessionId = req.params["id"];
|
|
15163
15315
|
const lastEventId = parseLastEventId(req.headers["last-event-id"]);
|
|
15316
|
+
const maxQueued = parseMaxQueuedQuery(req.query["maxQueued"], res);
|
|
15317
|
+
if (maxQueued === null) return;
|
|
15164
15318
|
let iter;
|
|
15165
15319
|
const abort = new AbortController();
|
|
15166
15320
|
try {
|
|
15167
15321
|
const iterable = bridge.subscribeEvents(sessionId, {
|
|
15168
15322
|
signal: abort.signal,
|
|
15169
|
-
lastEventId
|
|
15323
|
+
lastEventId,
|
|
15324
|
+
...maxQueued !== void 0 ? { maxQueued } : {}
|
|
15170
15325
|
});
|
|
15171
15326
|
iter = iterable[Symbol.asyncIterator]();
|
|
15172
15327
|
} catch (err) {
|
|
@@ -15376,11 +15531,43 @@ function isValidOutcome(raw) {
|
|
|
15376
15531
|
return obj["outcome"] === "selected" && typeof obj["optionId"] === "string" && obj["optionId"].length > 0;
|
|
15377
15532
|
}
|
|
15378
15533
|
__name(isValidOutcome, "isValidOutcome");
|
|
15534
|
+
var MIN_QUERY_MAX_QUEUED = 16;
|
|
15535
|
+
var MAX_QUERY_MAX_QUEUED = 2048;
|
|
15536
|
+
function parseMaxQueuedQuery(raw, res) {
|
|
15537
|
+
if (raw === void 0) return void 0;
|
|
15538
|
+
if (typeof raw !== "string" || !/^\d+$/.test(raw)) {
|
|
15539
|
+
writeStderrLine(
|
|
15540
|
+
`qwen serve: rejected ?maxQueued ${safeLogValue(raw)} (not a decimal integer)`
|
|
15541
|
+
);
|
|
15542
|
+
res.status(400).json({
|
|
15543
|
+
error: "`maxQueued` must be a decimal integer",
|
|
15544
|
+
code: "invalid_max_queued"
|
|
15545
|
+
});
|
|
15546
|
+
return null;
|
|
15547
|
+
}
|
|
15548
|
+
const n = Number.parseInt(raw, 10);
|
|
15549
|
+
if (!Number.isFinite(n) || n < MIN_QUERY_MAX_QUEUED || n > MAX_QUERY_MAX_QUEUED) {
|
|
15550
|
+
writeStderrLine(
|
|
15551
|
+
`qwen serve: rejected ?maxQueued ${safeLogValue(raw)} (outside [${MIN_QUERY_MAX_QUEUED}, ${MAX_QUERY_MAX_QUEUED}])`
|
|
15552
|
+
);
|
|
15553
|
+
res.status(400).json({
|
|
15554
|
+
error: `\`maxQueued\` must be in [${MIN_QUERY_MAX_QUEUED}, ${MAX_QUERY_MAX_QUEUED}]`,
|
|
15555
|
+
code: "invalid_max_queued"
|
|
15556
|
+
});
|
|
15557
|
+
return null;
|
|
15558
|
+
}
|
|
15559
|
+
return n;
|
|
15560
|
+
}
|
|
15561
|
+
__name(parseMaxQueuedQuery, "parseMaxQueuedQuery");
|
|
15562
|
+
function safeLogValue(raw) {
|
|
15563
|
+
return JSON.stringify(String(raw)).slice(0, 82);
|
|
15564
|
+
}
|
|
15565
|
+
__name(safeLogValue, "safeLogValue");
|
|
15379
15566
|
function parseLastEventId(raw) {
|
|
15380
15567
|
if (typeof raw !== "string" || !/^\d+$/.test(raw)) {
|
|
15381
15568
|
if (typeof raw === "string" && raw.length > 0) {
|
|
15382
15569
|
writeStderrLine(
|
|
15383
|
-
`qwen serve: rejected Last-Event-ID
|
|
15570
|
+
`qwen serve: rejected Last-Event-ID ${safeLogValue(raw)} (not a decimal integer)`
|
|
15384
15571
|
);
|
|
15385
15572
|
}
|
|
15386
15573
|
return void 0;
|
|
@@ -15388,7 +15575,7 @@ function parseLastEventId(raw) {
|
|
|
15388
15575
|
const n = Number.parseInt(raw, 10);
|
|
15389
15576
|
if (!Number.isFinite(n) || n > Number.MAX_SAFE_INTEGER) {
|
|
15390
15577
|
writeStderrLine(
|
|
15391
|
-
`qwen serve: rejected Last-Event-ID
|
|
15578
|
+
`qwen serve: rejected Last-Event-ID ${safeLogValue(raw)} (exceeds Number.MAX_SAFE_INTEGER)`
|
|
15392
15579
|
);
|
|
15393
15580
|
return void 0;
|
|
15394
15581
|
}
|
|
@@ -15451,6 +15638,14 @@ function sendBridgeError(res, err, ctx) {
|
|
|
15451
15638
|
});
|
|
15452
15639
|
return;
|
|
15453
15640
|
}
|
|
15641
|
+
if (err instanceof InvalidSessionMetadataError) {
|
|
15642
|
+
res.status(400).json({
|
|
15643
|
+
error: err.message,
|
|
15644
|
+
code: "invalid_metadata",
|
|
15645
|
+
field: err.field
|
|
15646
|
+
});
|
|
15647
|
+
return;
|
|
15648
|
+
}
|
|
15454
15649
|
if (err instanceof SessionLimitExceededError) {
|
|
15455
15650
|
res.set("Retry-After", "5");
|
|
15456
15651
|
res.status(503).json({
|
|
@@ -15538,6 +15733,11 @@ async function runQwenServe(optsIn, deps = {}) {
|
|
|
15538
15733
|
`Refusing to bind ${opts.hostname}:${opts.port} without a bearer token. Set ${QWEN_SERVER_TOKEN_ENV} or pass --token, or rebind to loopback (127.0.0.1, localhost, ::1, or [::1]).`
|
|
15539
15734
|
);
|
|
15540
15735
|
}
|
|
15736
|
+
if (opts.requireAuth && !token) {
|
|
15737
|
+
throw new Error(
|
|
15738
|
+
`Refusing to start with --require-auth set but no bearer token configured. Set ${QWEN_SERVER_TOKEN_ENV} or pass --token, or omit --require-auth to keep the loopback developer default.`
|
|
15739
|
+
);
|
|
15740
|
+
}
|
|
15541
15741
|
const rawWorkspace = opts.workspace ?? process.cwd();
|
|
15542
15742
|
if (!path3.isAbsolute(rawWorkspace)) {
|
|
15543
15743
|
throw new Error(
|
|
@@ -15570,6 +15770,7 @@ async function runQwenServe(optsIn, deps = {}) {
|
|
|
15570
15770
|
const boundWorkspace = canonicalizeWorkspace(rawWorkspace);
|
|
15571
15771
|
const bridge = deps.bridge ?? createHttpAcpBridge({
|
|
15572
15772
|
maxSessions: opts.maxSessions,
|
|
15773
|
+
...opts.eventRingSize !== void 0 ? { eventRingSize: opts.eventRingSize } : {},
|
|
15573
15774
|
boundWorkspace
|
|
15574
15775
|
});
|
|
15575
15776
|
let actualPort = opts.port;
|
|
@@ -15618,6 +15819,10 @@ async function runQwenServe(optsIn, deps = {}) {
|
|
|
15618
15819
|
writeStderrLine(
|
|
15619
15820
|
`qwen serve: bearer auth disabled (loopback default). Set ${QWEN_SERVER_TOKEN_ENV} to enable.`
|
|
15620
15821
|
);
|
|
15822
|
+
} else if (opts.requireAuth) {
|
|
15823
|
+
writeStderrLine(
|
|
15824
|
+
"qwen serve: --require-auth enabled (bearer token mandatory on every route, including loopback /health)."
|
|
15825
|
+
);
|
|
15621
15826
|
}
|
|
15622
15827
|
let shuttingDown = false;
|
|
15623
15828
|
let closePromise;
|
|
@@ -15732,22 +15937,32 @@ function createInMemoryChannel() {
|
|
|
15732
15937
|
__name(createInMemoryChannel, "createInMemoryChannel");
|
|
15733
15938
|
export {
|
|
15734
15939
|
CAPABILITIES_SCHEMA_VERSION,
|
|
15940
|
+
CONDITIONAL_SERVE_FEATURES,
|
|
15735
15941
|
EVENT_SCHEMA_VERSION,
|
|
15736
15942
|
EventBus,
|
|
15737
15943
|
SERVE_CAPABILITY_REGISTRY,
|
|
15738
15944
|
SERVE_FEATURES,
|
|
15739
15945
|
SERVE_PROTOCOL_VERSION,
|
|
15946
|
+
SERVE_STATUS_EXT_METHODS,
|
|
15740
15947
|
STAGE1_FEATURES,
|
|
15948
|
+
STATUS_SCHEMA_VERSION,
|
|
15741
15949
|
SUPPORTED_SERVE_PROTOCOL_VERSIONS,
|
|
15742
15950
|
SessionNotFoundError,
|
|
15951
|
+
bearerAuth,
|
|
15743
15952
|
createHttpAcpBridge,
|
|
15953
|
+
createIdleWorkspaceMcpStatus,
|
|
15954
|
+
createIdleWorkspaceProvidersStatus,
|
|
15955
|
+
createIdleWorkspaceSkillsStatus,
|
|
15744
15956
|
createInMemoryChannel,
|
|
15957
|
+
createMutationGate,
|
|
15745
15958
|
createServeApp,
|
|
15746
15959
|
defaultSpawnChannelFactory,
|
|
15960
|
+
denyBrowserOriginCors,
|
|
15747
15961
|
getAdvertisedServeFeatures,
|
|
15748
15962
|
getRegisteredServeFeatures,
|
|
15749
15963
|
getServeFeatures,
|
|
15750
15964
|
getServeProtocolVersions,
|
|
15965
|
+
hostAllowlist,
|
|
15751
15966
|
runQwenServe
|
|
15752
15967
|
};
|
|
15753
15968
|
/**
|