@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.
Files changed (64) hide show
  1. package/bundled/qc-helper/docs/configuration/settings.md +20 -24
  2. package/bundled/qc-helper/docs/qwen-serve.md +29 -10
  3. package/chunks/{agent-LIAWUWAO.js → agent-ZNQPH67I.js} +15 -15
  4. package/chunks/{anthropicContentGenerator-4QE6LTVV.js → anthropicContentGenerator-ICBDZ6R2.js} +4 -4
  5. package/chunks/{askUserQuestion-QFSCBTUO.js → askUserQuestion-WQILGUSQ.js} +2 -2
  6. package/chunks/{chunk-SQNQIOD5.js → chunk-2B7UBDY5.js} +2 -2
  7. package/chunks/chunk-3MBY4GKN.js +350 -0
  8. package/chunks/{chunk-GC5RXNL2.js → chunk-7QXHXMC6.js} +23 -7
  9. package/chunks/{chunk-XLQ4E5PS.js → chunk-C3LHPHN2.js} +11 -11
  10. package/chunks/{chunk-UXW7MYAW.js → chunk-CW44BRRA.js} +1 -1
  11. package/chunks/{chunk-G27O2LD2.js → chunk-D5NTAHYL.js} +1 -1
  12. package/chunks/{chunk-CBVB66WY.js → chunk-EDYSNFEM.js} +1 -1
  13. package/chunks/{chunk-OCC4MZRS.js → chunk-F23NCRJ2.js} +1 -1
  14. package/chunks/{chunk-FYMSCRHM.js → chunk-FZIUV27X.js} +1 -1
  15. package/chunks/{chunk-5QQ5FGTU.js → chunk-G7YTSRES.js} +1 -1
  16. package/chunks/{chunk-AOJ3BBY7.js → chunk-JHMX4QTD.js} +9 -9
  17. package/chunks/{chunk-TPGOGCWM.js → chunk-JYQUJ5DS.js} +1 -1
  18. package/chunks/{chunk-FKVKVE6N.js → chunk-KXZ4TJB4.js} +1 -1
  19. package/chunks/{chunk-AJSOD5IR.js → chunk-MNPZ2WO6.js} +535 -141
  20. package/chunks/{chunk-BXNCPI75.js → chunk-NAID3ZWF.js} +2 -2
  21. package/chunks/{chunk-JMZQICAL.js → chunk-PPHYLJSS.js} +1 -1
  22. package/chunks/{chunk-CM2IESUE.js → chunk-PR4T27R7.js} +1 -1
  23. package/chunks/{chunk-CAWKL3UC.js → chunk-VTPOO6GV.js} +1 -1
  24. package/chunks/{chunk-GJXIKCKL.js → chunk-XP27SJMH.js} +76 -5
  25. package/chunks/{chunk-B7ZL7HUA.js → chunk-XVHR7ATJ.js} +1 -1
  26. package/chunks/{contextCommand-SVLAZMQL.js → contextCommand-IGBCEXI4.js} +16 -16
  27. package/chunks/{cron-create-WUTD5ZTH.js → cron-create-AVI3Q267.js} +2 -2
  28. package/chunks/{cron-delete-N3UQYCRA.js → cron-delete-ZCEGDXXV.js} +2 -2
  29. package/chunks/{cron-list-Z6RJJ4YH.js → cron-list-VN653OK5.js} +2 -2
  30. package/chunks/{edit-VNAZBIZR.js → edit-74Q4AFHQ.js} +16 -16
  31. package/chunks/{en-NRN4QBAT.js → en-FIUWJSZR.js} +1 -0
  32. package/chunks/{enter-worktree-FOF5YZIV.js → enter-worktree-H72HXC7D.js} +15 -15
  33. package/chunks/{exit-worktree-Y6QVAO3C.js → exit-worktree-FGIQO3S3.js} +15 -15
  34. package/chunks/{exitPlanMode-QZKO7GH7.js → exitPlanMode-NBR2PK2D.js} +15 -15
  35. package/chunks/{geminiContentGenerator-DYHZPKJX.js → geminiContentGenerator-33RP4WKD.js} +3 -3
  36. package/chunks/{glob-G7XATELV.js → glob-WEE3CJL6.js} +15 -15
  37. package/chunks/{grep-4SETMY47.js → grep-DZKSBFZK.js} +15 -15
  38. package/chunks/{keychain-token-storage-DMFP5IJM.js → keychain-token-storage-335UOLJ6.js} +2 -2
  39. package/chunks/{ls-SUILOZZB.js → ls-6F3VSP6S.js} +3 -3
  40. package/chunks/{lsp-6TQBWVMZ.js → lsp-67Y7DJN5.js} +2 -2
  41. package/chunks/{monitor-JTLJBJ7H.js → monitor-EDZWEZVS.js} +15 -15
  42. package/chunks/{openaiContentGenerator-3H7XOZBW.js → openaiContentGenerator-5NQG3W64.js} +10 -10
  43. package/chunks/{qwenContentGenerator-FAU3QPYO.js → qwenContentGenerator-4DPUUS6R.js} +17 -17
  44. package/chunks/{qwenOAuth2-JSQ7EPR3.js → qwenOAuth2-JE7H47TE.js} +3 -3
  45. package/chunks/{read-file-WWUQVNCZ.js → read-file-CQOF7BQ2.js} +7 -7
  46. package/chunks/{ripGrep-WCOAIWL6.js → ripGrep-KR5LKGTI.js} +15 -15
  47. package/chunks/{send-message-Q2JRAC3J.js → send-message-GB4AQZNC.js} +2 -2
  48. package/chunks/{serve-VJEEEXA6.js → serve-GAD2PEST.js} +501 -286
  49. package/chunks/{shell-IAOKGIJ6.js → shell-E2HMCBGR.js} +15 -15
  50. package/chunks/{skill-NHW6222K.js → skill-KDZH6UZ6.js} +9 -9
  51. package/chunks/{src-OWV5HVQQ.js → src-LY4RU5AI.js} +17 -15
  52. package/chunks/{syntheticOutput-S4DRGMQM.js → syntheticOutput-HFL3DE7R.js} +3 -3
  53. package/chunks/{task-stop-7THHVAQS.js → task-stop-ZQF26RXS.js} +2 -2
  54. package/chunks/{todoWrite-WKUGUTPX.js → todoWrite-U4SC643O.js} +3 -3
  55. package/chunks/{tool-search-MSJ6SXLI.js → tool-search-U4XQVLFU.js} +7 -7
  56. package/chunks/{web-fetch-OZE6ZQUF.js → web-fetch-BRWZ4WSE.js} +4 -4
  57. package/chunks/{write-file-RKCENFZ5.js → write-file-NBLRMNGB.js} +16 -16
  58. package/chunks/{zh-TW-XZEHEV5S.js → zh-TW-552S24LR.js} +1 -0
  59. package/chunks/{zh-RN3JULHO.js → zh-V32QONGV.js} +1 -0
  60. package/cli.js +598 -59
  61. package/locales/en.js +2 -0
  62. package/locales/zh-TW.js +1 -0
  63. package/locales/zh.js +1 -0
  64. 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 createSessionEntry = /* @__PURE__ */ __name((ci, sessionId, workspaceCwd, events = new EventBus()) => {
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
- (feature) => isFeatureAvailableInProtocol(feature, protocolVersion)
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
- if (loopback) {
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 (!loopback) {
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
- features: getAdvertisedServeFeatures(),
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.post("/session", async (req, res) => {
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.post("/session/:id/prompt", async (req, res) => {
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/cancel", async (req, res) => {
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 "${raw.slice(0, 80)}" (not a decimal integer)`
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 "${raw.slice(0, 80)}" (exceeds Number.MAX_SAFE_INTEGER)`
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
  /**