@qwen-code/qwen-code 0.15.12-preview.2 → 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 +30 -34
- package/bundled/qc-helper/docs/features/lsp.md +87 -10
- package/bundled/qc-helper/docs/qwen-serve.md +74 -24
- package/bundled/qc-helper/docs/reference/keyboard-shortcuts.md +11 -11
- package/bundled/stuck/SKILL.md +124 -0
- package/chunks/{agent-UQY6A6OS.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/{ca-VQSV6JHA.js → ca-S3XJMT6P.js} +26 -0
- package/chunks/{chunk-SQNQIOD5.js → chunk-2B7UBDY5.js} +2 -2
- package/chunks/chunk-3MBY4GKN.js +350 -0
- package/chunks/{chunk-FSYKVGER.js → chunk-7QXHXMC6.js} +23 -7
- package/chunks/{chunk-PCL3EJGY.js → chunk-C3LHPHN2.js} +3924 -3683
- 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-SOIEFHIK.js → chunk-G7YTSRES.js} +1 -100
- package/chunks/{chunk-MXBWOU2L.js → chunk-JHMX4QTD.js} +15 -15
- package/chunks/{chunk-TPGOGCWM.js → chunk-JYQUJ5DS.js} +1 -1
- package/chunks/{chunk-FKVKVE6N.js → chunk-KXZ4TJB4.js} +1 -1
- package/chunks/{chunk-2WFU3IUH.js → chunk-MNPZ2WO6.js} +4864 -2303
- 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-MQRG6RMG.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/{de-M2IPQRBS.js → de-MNR4SMAI.js} +26 -0
- package/chunks/{edit-3KCBTA25.js → edit-74Q4AFHQ.js} +20 -16
- package/chunks/{en-N5GMPCVT.js → en-FIUWJSZR.js} +28 -0
- package/chunks/{enter-worktree-VWS5QZTU.js → enter-worktree-H72HXC7D.js} +15 -15
- package/chunks/{exit-worktree-RVXFWAPD.js → exit-worktree-FGIQO3S3.js} +15 -15
- package/chunks/{exitPlanMode-UL5DILDG.js → exitPlanMode-NBR2PK2D.js} +15 -15
- package/chunks/{fr-BTHRYEXO.js → fr-OFJFHLCR.js} +26 -0
- package/chunks/{geminiContentGenerator-O2OPGHJG.js → geminiContentGenerator-33RP4WKD.js} +3 -3
- package/chunks/{glob-57BSREPN.js → glob-WEE3CJL6.js} +15 -15
- package/chunks/{grep-XO5JOC7T.js → grep-DZKSBFZK.js} +15 -15
- package/chunks/{ja-D63TAEBO.js → ja-V6OQ6VL7.js} +26 -0
- 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-BECPGO3K.js → monitor-EDZWEZVS.js} +33 -24
- package/chunks/{openaiContentGenerator-KEZQHIRM.js → openaiContentGenerator-5NQG3W64.js} +10 -10
- package/chunks/{pt-XUV7FSKC.js → pt-ZLE6SA4A.js} +26 -0
- package/chunks/{qwenContentGenerator-RPMRXTNH.js → qwenContentGenerator-4DPUUS6R.js} +17 -17
- package/chunks/{qwenOAuth2-JSQ7EPR3.js → qwenOAuth2-JE7H47TE.js} +3 -3
- package/chunks/{read-file-LGHEIQNH.js → read-file-CQOF7BQ2.js} +7 -7
- package/chunks/{ripGrep-6SFSXZ2G.js → ripGrep-KR5LKGTI.js} +15 -15
- package/chunks/{ru-7KHWMN3A.js → ru-A4OHIUNN.js} +26 -0
- package/chunks/{send-message-Q2JRAC3J.js → send-message-GB4AQZNC.js} +2 -2
- package/chunks/{serve-27O2AFE3.js → serve-GAD2PEST.js} +1299 -408
- package/chunks/{shell-J7K5KYCH.js → shell-E2HMCBGR.js} +15 -15
- package/chunks/{skill-2R7P4ATS.js → skill-KDZH6UZ6.js} +9 -9
- package/chunks/{src-CGEDVW67.js → src-LY4RU5AI.js} +96 -24
- 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-XOH3ZWVS.js → tool-search-U4XQVLFU.js} +7 -7
- package/chunks/{web-fetch-OZE6ZQUF.js → web-fetch-BRWZ4WSE.js} +4 -4
- package/chunks/{write-file-74NQ27Q2.js → write-file-NBLRMNGB.js} +20 -16
- package/chunks/{zh-TW-O36Q4V7E.js → zh-TW-552S24LR.js} +28 -0
- package/chunks/{zh-VGHU6XBB.js → zh-V32QONGV.js} +28 -0
- package/cli.js +8027 -5104
- package/locales/ca.js +40 -0
- package/locales/de.js +40 -0
- package/locales/en.js +43 -0
- package/locales/fr.js +41 -0
- package/locales/ja.js +39 -0
- package/locales/pt.js +39 -0
- package/locales/ru.js +39 -0
- package/locales/zh-TW.js +41 -0
- package/locales/zh.js +41 -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,276 +13109,45 @@ 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
|
-
}
|
|
13112
|
+
var SessionNotFoundError = class extends Error {
|
|
13110
13113
|
static {
|
|
13111
|
-
__name(this, "
|
|
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
|
-
};
|
|
13114
|
+
__name(this, "SessionNotFoundError");
|
|
13231
13115
|
}
|
|
13232
|
-
|
|
13233
|
-
|
|
13234
|
-
|
|
13235
|
-
this.
|
|
13236
|
-
|
|
13237
|
-
this.subs.clear();
|
|
13116
|
+
sessionId;
|
|
13117
|
+
constructor(sessionId, extra) {
|
|
13118
|
+
super(`No session with id "${sessionId}"` + (extra ? `. ${extra}` : ""));
|
|
13119
|
+
this.name = "SessionNotFoundError";
|
|
13120
|
+
this.sessionId = sessionId;
|
|
13238
13121
|
}
|
|
13239
13122
|
};
|
|
13240
|
-
|
|
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
|
-
}
|
|
13123
|
+
var RestoreInProgressError = class extends Error {
|
|
13254
13124
|
static {
|
|
13255
|
-
__name(this, "
|
|
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;
|
|
13125
|
+
__name(this, "RestoreInProgressError");
|
|
13291
13126
|
}
|
|
13292
|
-
|
|
13293
|
-
|
|
13294
|
-
|
|
13295
|
-
|
|
13296
|
-
|
|
13297
|
-
|
|
13298
|
-
|
|
13299
|
-
|
|
13300
|
-
this.
|
|
13301
|
-
this.
|
|
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));
|
|
13127
|
+
sessionId;
|
|
13128
|
+
activeAction;
|
|
13129
|
+
requestedAction;
|
|
13130
|
+
constructor(sessionId, activeAction, requestedAction) {
|
|
13131
|
+
super(
|
|
13132
|
+
`Session "${sessionId}" is already being restored via session/${activeAction}; retry session/${requestedAction} after it completes`
|
|
13133
|
+
);
|
|
13134
|
+
this.name = "RestoreInProgressError";
|
|
13135
|
+
this.sessionId = sessionId;
|
|
13136
|
+
this.activeAction = activeAction;
|
|
13137
|
+
this.requestedAction = requestedAction;
|
|
13344
13138
|
}
|
|
13345
13139
|
};
|
|
13346
|
-
|
|
13347
|
-
// packages/cli/src/serve/httpAcpBridge.ts
|
|
13348
|
-
var SessionNotFoundError = class extends Error {
|
|
13140
|
+
var InvalidSessionScopeError = class extends Error {
|
|
13349
13141
|
static {
|
|
13350
|
-
__name(this, "
|
|
13142
|
+
__name(this, "InvalidSessionScopeError");
|
|
13351
13143
|
}
|
|
13352
|
-
|
|
13353
|
-
constructor(
|
|
13354
|
-
super(
|
|
13355
|
-
|
|
13356
|
-
|
|
13144
|
+
sessionScope;
|
|
13145
|
+
constructor(sessionScope) {
|
|
13146
|
+
super(
|
|
13147
|
+
`Invalid sessionScope: ${JSON.stringify(sessionScope)}. Expected 'single' or 'thread'.`
|
|
13148
|
+
);
|
|
13149
|
+
this.name = "InvalidSessionScopeError";
|
|
13150
|
+
this.sessionScope = sessionScope;
|
|
13357
13151
|
}
|
|
13358
13152
|
};
|
|
13359
13153
|
var SessionLimitExceededError = class extends Error {
|
|
@@ -13383,7 +13177,32 @@ var WorkspaceMismatchError = class extends Error {
|
|
|
13383
13177
|
this.requested = safeRequested;
|
|
13384
13178
|
}
|
|
13385
13179
|
};
|
|
13180
|
+
var InvalidClientIdError = class extends Error {
|
|
13181
|
+
static {
|
|
13182
|
+
__name(this, "InvalidClientIdError");
|
|
13183
|
+
}
|
|
13184
|
+
sessionId;
|
|
13185
|
+
clientId;
|
|
13186
|
+
constructor(sessionId, clientId) {
|
|
13187
|
+
super(`Client id "${clientId}" is not registered for session ${sessionId}`);
|
|
13188
|
+
this.name = "InvalidClientIdError";
|
|
13189
|
+
this.sessionId = sessionId;
|
|
13190
|
+
this.clientId = clientId;
|
|
13191
|
+
}
|
|
13192
|
+
};
|
|
13386
13193
|
var MAX_WORKSPACE_PATH_LENGTH = 4096;
|
|
13194
|
+
var MAX_RESOLVED_PERMISSION_RECORDS = 512;
|
|
13195
|
+
function isServeDebugLoggingEnabled() {
|
|
13196
|
+
const value = process.env["QWEN_SERVE_DEBUG"];
|
|
13197
|
+
if (!value) return false;
|
|
13198
|
+
return !["0", "false", "off", "no"].includes(value.trim().toLowerCase());
|
|
13199
|
+
}
|
|
13200
|
+
__name(isServeDebugLoggingEnabled, "isServeDebugLoggingEnabled");
|
|
13201
|
+
function writeServeDebugLine(message) {
|
|
13202
|
+
if (!isServeDebugLoggingEnabled()) return;
|
|
13203
|
+
writeStderrLine(`qwen serve debug: ${message}`);
|
|
13204
|
+
}
|
|
13205
|
+
__name(writeServeDebugLine, "writeServeDebugLine");
|
|
13387
13206
|
var InvalidPermissionOptionError = class extends Error {
|
|
13388
13207
|
static {
|
|
13389
13208
|
__name(this, "InvalidPermissionOptionError");
|
|
@@ -13399,9 +13218,32 @@ var InvalidPermissionOptionError = class extends Error {
|
|
|
13399
13218
|
this.optionId = optionId;
|
|
13400
13219
|
}
|
|
13401
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
|
+
};
|
|
13402
13243
|
var BridgeClient = class {
|
|
13403
|
-
constructor(resolveEntry, registerPending, rollbackPending, permissionTimeoutMs, maxPendingPerSession) {
|
|
13244
|
+
constructor(resolveEntry, resolvePendingRestoreEvents, registerPending, rollbackPending, permissionTimeoutMs, maxPendingPerSession) {
|
|
13404
13245
|
this.resolveEntry = resolveEntry;
|
|
13246
|
+
this.resolvePendingRestoreEvents = resolvePendingRestoreEvents;
|
|
13405
13247
|
this.registerPending = registerPending;
|
|
13406
13248
|
this.rollbackPending = rollbackPending;
|
|
13407
13249
|
this.permissionTimeoutMs = permissionTimeoutMs;
|
|
@@ -13459,7 +13301,8 @@ var BridgeClient = class {
|
|
|
13459
13301
|
sessionId: entry.sessionId,
|
|
13460
13302
|
toolCall: params.toolCall,
|
|
13461
13303
|
options: params.options
|
|
13462
|
-
}
|
|
13304
|
+
},
|
|
13305
|
+
...entry.activePromptOriginatorClientId ? { originatorClientId: entry.activePromptOriginatorClientId } : {}
|
|
13463
13306
|
});
|
|
13464
13307
|
if (!published) {
|
|
13465
13308
|
this.rollbackPending(requestId);
|
|
@@ -13481,8 +13324,13 @@ var BridgeClient = class {
|
|
|
13481
13324
|
}
|
|
13482
13325
|
async sessionUpdate(params) {
|
|
13483
13326
|
const entry = this.resolveEntry(params.sessionId);
|
|
13484
|
-
|
|
13485
|
-
|
|
13327
|
+
const events = entry?.events ?? this.resolvePendingRestoreEvents(params.sessionId);
|
|
13328
|
+
if (!events) return;
|
|
13329
|
+
events.publish({
|
|
13330
|
+
type: "session_update",
|
|
13331
|
+
data: params,
|
|
13332
|
+
...entry?.activePromptOriginatorClientId ? { originatorClientId: entry.activePromptOriginatorClientId } : {}
|
|
13333
|
+
});
|
|
13486
13334
|
}
|
|
13487
13335
|
async writeTextFile(params) {
|
|
13488
13336
|
let realTarget = params.path;
|
|
@@ -13558,10 +13406,11 @@ var BridgeClient = class {
|
|
|
13558
13406
|
};
|
|
13559
13407
|
var DEFAULT_INIT_TIMEOUT_MS = 1e4;
|
|
13560
13408
|
var DEFAULT_MAX_SESSIONS = 20;
|
|
13409
|
+
var MAX_EVENT_RING_SIZE = 1e6;
|
|
13561
13410
|
var DEFAULT_PERMISSION_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
13562
13411
|
var DEFAULT_MAX_PENDING_PER_SESSION = 64;
|
|
13563
13412
|
function createHttpAcpBridge(opts) {
|
|
13564
|
-
const
|
|
13413
|
+
const defaultSessionScope = opts.sessionScope ?? "single";
|
|
13565
13414
|
let maxSessions;
|
|
13566
13415
|
if (opts.maxSessions === void 0) {
|
|
13567
13416
|
maxSessions = DEFAULT_MAX_SESSIONS;
|
|
@@ -13578,9 +13427,15 @@ function createHttpAcpBridge(opts) {
|
|
|
13578
13427
|
} else {
|
|
13579
13428
|
maxSessions = opts.maxSessions;
|
|
13580
13429
|
}
|
|
13581
|
-
if (
|
|
13430
|
+
if (defaultSessionScope !== "single" && defaultSessionScope !== "thread") {
|
|
13582
13431
|
throw new TypeError(
|
|
13583
|
-
`Invalid sessionScope: ${JSON.stringify(
|
|
13432
|
+
`Invalid sessionScope: ${JSON.stringify(defaultSessionScope)}. Expected 'single' or 'thread'.`
|
|
13433
|
+
);
|
|
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}].`
|
|
13584
13439
|
);
|
|
13585
13440
|
}
|
|
13586
13441
|
const channelFactory = opts.channelFactory ?? defaultSpawnChannelFactory;
|
|
@@ -13606,8 +13461,49 @@ function createHttpAcpBridge(opts) {
|
|
|
13606
13461
|
let inFlightChannelSpawn;
|
|
13607
13462
|
const byId = /* @__PURE__ */ new Map();
|
|
13608
13463
|
const pendingPermissions = /* @__PURE__ */ new Map();
|
|
13464
|
+
const resolvedPermissions = /* @__PURE__ */ new Map();
|
|
13465
|
+
const resolvedPermissionOrder = [];
|
|
13609
13466
|
let shuttingDown = false;
|
|
13610
13467
|
const inFlightSpawns = /* @__PURE__ */ new Map();
|
|
13468
|
+
const inFlightRestores = /* @__PURE__ */ new Map();
|
|
13469
|
+
const pendingRestoreEvents = /* @__PURE__ */ new Map();
|
|
13470
|
+
const createClientId = /* @__PURE__ */ __name(() => `client_${randomUUID()}`, "createClientId");
|
|
13471
|
+
const registerClient = /* @__PURE__ */ __name((entry, requestedClientId) => {
|
|
13472
|
+
if (requestedClientId && entry.clientIds.has(requestedClientId)) {
|
|
13473
|
+
entry.clientIds.set(
|
|
13474
|
+
requestedClientId,
|
|
13475
|
+
(entry.clientIds.get(requestedClientId) ?? 0) + 1
|
|
13476
|
+
);
|
|
13477
|
+
return requestedClientId;
|
|
13478
|
+
}
|
|
13479
|
+
const clientId = createClientId();
|
|
13480
|
+
entry.clientIds.set(clientId, 1);
|
|
13481
|
+
return clientId;
|
|
13482
|
+
}, "registerClient");
|
|
13483
|
+
const unregisterClient = /* @__PURE__ */ __name((entry, clientId) => {
|
|
13484
|
+
if (clientId === void 0) return;
|
|
13485
|
+
const count = entry.clientIds.get(clientId);
|
|
13486
|
+
if (count === void 0) return;
|
|
13487
|
+
if (count <= 1) {
|
|
13488
|
+
entry.clientIds.delete(clientId);
|
|
13489
|
+
entry.clientLastSeenAt.delete(clientId);
|
|
13490
|
+
} else {
|
|
13491
|
+
entry.clientIds.set(clientId, count - 1);
|
|
13492
|
+
}
|
|
13493
|
+
}, "unregisterClient");
|
|
13494
|
+
const resolveTrustedClientId = /* @__PURE__ */ __name((entry, clientId) => {
|
|
13495
|
+
if (clientId === void 0) return void 0;
|
|
13496
|
+
if (!entry.clientIds.has(clientId)) {
|
|
13497
|
+
throw new InvalidClientIdError(entry.sessionId, clientId);
|
|
13498
|
+
}
|
|
13499
|
+
return clientId;
|
|
13500
|
+
}, "resolveTrustedClientId");
|
|
13501
|
+
const resolveAnyTrustedClientId = /* @__PURE__ */ __name((clientId) => {
|
|
13502
|
+
for (const entry of byId.values()) {
|
|
13503
|
+
if (entry.clientIds.has(clientId)) return clientId;
|
|
13504
|
+
}
|
|
13505
|
+
throw new InvalidClientIdError("unknown", clientId);
|
|
13506
|
+
}, "resolveAnyTrustedClientId");
|
|
13611
13507
|
const registerPending = /* @__PURE__ */ __name((p) => {
|
|
13612
13508
|
const entry = byId.get(p.sessionId);
|
|
13613
13509
|
if (!entry) {
|
|
@@ -13617,7 +13513,38 @@ function createHttpAcpBridge(opts) {
|
|
|
13617
13513
|
pendingPermissions.set(p.requestId, p);
|
|
13618
13514
|
entry.pendingPermissionIds.add(p.requestId);
|
|
13619
13515
|
}, "registerPending");
|
|
13620
|
-
const
|
|
13516
|
+
const rememberResolvedPermission = /* @__PURE__ */ __name((record) => {
|
|
13517
|
+
if (!resolvedPermissions.has(record.requestId)) {
|
|
13518
|
+
resolvedPermissionOrder.push(record.requestId);
|
|
13519
|
+
}
|
|
13520
|
+
resolvedPermissions.set(record.requestId, record);
|
|
13521
|
+
while (resolvedPermissionOrder.length > MAX_RESOLVED_PERMISSION_RECORDS) {
|
|
13522
|
+
const oldest = resolvedPermissionOrder.shift();
|
|
13523
|
+
if (oldest !== void 0) resolvedPermissions.delete(oldest);
|
|
13524
|
+
}
|
|
13525
|
+
}, "rememberResolvedPermission");
|
|
13526
|
+
const publishPermissionAlreadyResolved = /* @__PURE__ */ __name((record) => {
|
|
13527
|
+
const entry = byId.get(record.sessionId);
|
|
13528
|
+
if (!entry) return;
|
|
13529
|
+
try {
|
|
13530
|
+
writeServeDebugLine(
|
|
13531
|
+
`permission ${JSON.stringify(record.requestId)} for session ${JSON.stringify(record.sessionId)} was already resolved; publishing duplicate-vote notification.`
|
|
13532
|
+
);
|
|
13533
|
+
entry.events.publish({
|
|
13534
|
+
type: "permission_already_resolved",
|
|
13535
|
+
data: {
|
|
13536
|
+
requestId: record.requestId,
|
|
13537
|
+
sessionId: record.sessionId,
|
|
13538
|
+
outcome: record.outcome
|
|
13539
|
+
}
|
|
13540
|
+
});
|
|
13541
|
+
} catch {
|
|
13542
|
+
writeServeDebugLine(
|
|
13543
|
+
`skipped duplicate-vote notification for permission ${JSON.stringify(record.requestId)} during shutdown.`
|
|
13544
|
+
);
|
|
13545
|
+
}
|
|
13546
|
+
}, "publishPermissionAlreadyResolved");
|
|
13547
|
+
const resolvePending = /* @__PURE__ */ __name((requestId, response, originatorClientId) => {
|
|
13621
13548
|
const pending = pendingPermissions.get(requestId);
|
|
13622
13549
|
if (!pending) return false;
|
|
13623
13550
|
pendingPermissions.delete(requestId);
|
|
@@ -13627,11 +13554,17 @@ function createHttpAcpBridge(opts) {
|
|
|
13627
13554
|
try {
|
|
13628
13555
|
entry.events.publish({
|
|
13629
13556
|
type: "permission_resolved",
|
|
13630
|
-
data: { requestId, outcome: response.outcome }
|
|
13557
|
+
data: { requestId, outcome: response.outcome },
|
|
13558
|
+
...originatorClientId ? { originatorClientId } : {}
|
|
13631
13559
|
});
|
|
13632
13560
|
} catch {
|
|
13633
13561
|
}
|
|
13634
13562
|
}
|
|
13563
|
+
rememberResolvedPermission({
|
|
13564
|
+
requestId,
|
|
13565
|
+
sessionId: pending.sessionId,
|
|
13566
|
+
outcome: response.outcome
|
|
13567
|
+
});
|
|
13635
13568
|
pending.resolve(response);
|
|
13636
13569
|
return true;
|
|
13637
13570
|
}, "resolvePending");
|
|
@@ -13656,6 +13589,7 @@ function createHttpAcpBridge(opts) {
|
|
|
13656
13589
|
}
|
|
13657
13590
|
return void 0;
|
|
13658
13591
|
},
|
|
13592
|
+
(sessionId) => sessionId ? pendingRestoreEvents.get(sessionId) : void 0,
|
|
13659
13593
|
registerPending,
|
|
13660
13594
|
(rid) => (
|
|
13661
13595
|
// Roll back a register-then-publish-failed pending so the agent
|
|
@@ -13671,6 +13605,7 @@ function createHttpAcpBridge(opts) {
|
|
|
13671
13605
|
connection,
|
|
13672
13606
|
client,
|
|
13673
13607
|
sessionIds: /* @__PURE__ */ new Set(),
|
|
13608
|
+
pendingRestoreIds: /* @__PURE__ */ new Set(),
|
|
13674
13609
|
isDying: false
|
|
13675
13610
|
};
|
|
13676
13611
|
aliveChannels.add(info);
|
|
@@ -13746,7 +13681,7 @@ function createHttpAcpBridge(opts) {
|
|
|
13746
13681
|
}
|
|
13747
13682
|
}
|
|
13748
13683
|
__name(ensureChannel, "ensureChannel");
|
|
13749
|
-
async function doSpawn(modelServiceId) {
|
|
13684
|
+
async function doSpawn(modelServiceId, effectiveScope, requestedClientId) {
|
|
13750
13685
|
const ci = await ensureChannel();
|
|
13751
13686
|
let newSessionResp;
|
|
13752
13687
|
try {
|
|
@@ -13769,26 +13704,21 @@ function createHttpAcpBridge(opts) {
|
|
|
13769
13704
|
if (shuttingDown) {
|
|
13770
13705
|
throw new Error("HttpAcpBridge is shutting down");
|
|
13771
13706
|
}
|
|
13772
|
-
const entry =
|
|
13773
|
-
|
|
13774
|
-
|
|
13775
|
-
|
|
13776
|
-
|
|
13777
|
-
|
|
13778
|
-
|
|
13779
|
-
modelChangeQueue: Promise.resolve(),
|
|
13780
|
-
pendingPermissionIds: /* @__PURE__ */ new Set(),
|
|
13781
|
-
attachCount: 0,
|
|
13782
|
-
spawnOwnerWantedKill: false
|
|
13783
|
-
};
|
|
13784
|
-
ci.sessionIds.add(entry.sessionId);
|
|
13785
|
-
byId.set(entry.sessionId, entry);
|
|
13786
|
-
if (!defaultEntry) defaultEntry = entry;
|
|
13707
|
+
const entry = createSessionEntry(
|
|
13708
|
+
ci,
|
|
13709
|
+
newSessionResp.sessionId,
|
|
13710
|
+
boundWorkspace
|
|
13711
|
+
);
|
|
13712
|
+
const clientId = registerClient(entry, requestedClientId);
|
|
13713
|
+
if (effectiveScope === "single" && !defaultEntry) defaultEntry = entry;
|
|
13787
13714
|
if (modelServiceId) {
|
|
13788
|
-
await applyModelServiceId(
|
|
13789
|
-
|
|
13790
|
-
|
|
13791
|
-
|
|
13715
|
+
await applyModelServiceId(
|
|
13716
|
+
entry,
|
|
13717
|
+
modelServiceId,
|
|
13718
|
+
initTimeoutMs,
|
|
13719
|
+
clientId
|
|
13720
|
+
).catch(() => {
|
|
13721
|
+
});
|
|
13792
13722
|
}
|
|
13793
13723
|
if (!byId.has(entry.sessionId)) {
|
|
13794
13724
|
throw new Error(
|
|
@@ -13798,11 +13728,13 @@ function createHttpAcpBridge(opts) {
|
|
|
13798
13728
|
return {
|
|
13799
13729
|
sessionId: entry.sessionId,
|
|
13800
13730
|
workspaceCwd: entry.workspaceCwd,
|
|
13801
|
-
attached: false
|
|
13731
|
+
attached: false,
|
|
13732
|
+
clientId,
|
|
13733
|
+
createdAt: entry.createdAt
|
|
13802
13734
|
};
|
|
13803
13735
|
}
|
|
13804
13736
|
__name(doSpawn, "doSpawn");
|
|
13805
|
-
async function applyModelServiceId(entry, modelId, timeoutMs) {
|
|
13737
|
+
async function applyModelServiceId(entry, modelId, timeoutMs, originatorClientId) {
|
|
13806
13738
|
const conn = entry.connection;
|
|
13807
13739
|
const transportClosed = getTransportClosedReject(entry);
|
|
13808
13740
|
const work = entry.modelChangeQueue.then(async () => {
|
|
@@ -13820,7 +13752,8 @@ function createHttpAcpBridge(opts) {
|
|
|
13820
13752
|
]);
|
|
13821
13753
|
entry.events.publish({
|
|
13822
13754
|
type: "model_switched",
|
|
13823
|
-
data: { sessionId: entry.sessionId, modelId }
|
|
13755
|
+
data: { sessionId: entry.sessionId, modelId },
|
|
13756
|
+
...originatorClientId ? { originatorClientId } : {}
|
|
13824
13757
|
});
|
|
13825
13758
|
} catch (err) {
|
|
13826
13759
|
entry.events.publish({
|
|
@@ -13829,7 +13762,8 @@ function createHttpAcpBridge(opts) {
|
|
|
13829
13762
|
sessionId: entry.sessionId,
|
|
13830
13763
|
requestedModelId: modelId,
|
|
13831
13764
|
error: err instanceof Error ? err.message : String(err)
|
|
13832
|
-
}
|
|
13765
|
+
},
|
|
13766
|
+
...originatorClientId ? { originatorClientId } : {}
|
|
13833
13767
|
});
|
|
13834
13768
|
throw err;
|
|
13835
13769
|
}
|
|
@@ -13859,6 +13793,266 @@ function createHttpAcpBridge(opts) {
|
|
|
13859
13793
|
}
|
|
13860
13794
|
return entry.transportClosedReject;
|
|
13861
13795
|
}, "getTransportClosedReject");
|
|
13796
|
+
const resolveWorkspaceKey = /* @__PURE__ */ __name((workspaceCwd) => {
|
|
13797
|
+
if (!path.isAbsolute(workspaceCwd)) {
|
|
13798
|
+
throw new Error(
|
|
13799
|
+
`workspaceCwd must be an absolute path; got "${workspaceCwd}"`
|
|
13800
|
+
);
|
|
13801
|
+
}
|
|
13802
|
+
const workspaceKey = workspaceCwd === boundWorkspace ? boundWorkspace : canonicalizeWorkspace(workspaceCwd);
|
|
13803
|
+
if (workspaceKey !== boundWorkspace) {
|
|
13804
|
+
throw new WorkspaceMismatchError(boundWorkspace, workspaceKey);
|
|
13805
|
+
}
|
|
13806
|
+
return workspaceKey;
|
|
13807
|
+
}, "resolveWorkspaceKey");
|
|
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)) => {
|
|
13856
|
+
const entry = {
|
|
13857
|
+
sessionId,
|
|
13858
|
+
workspaceCwd,
|
|
13859
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13860
|
+
channel: ci.channel,
|
|
13861
|
+
connection: ci.connection,
|
|
13862
|
+
events,
|
|
13863
|
+
promptQueue: Promise.resolve(),
|
|
13864
|
+
modelChangeQueue: Promise.resolve(),
|
|
13865
|
+
pendingPermissionIds: /* @__PURE__ */ new Set(),
|
|
13866
|
+
clientIds: /* @__PURE__ */ new Map(),
|
|
13867
|
+
clientLastSeenAt: /* @__PURE__ */ new Map(),
|
|
13868
|
+
attachCount: 0,
|
|
13869
|
+
spawnOwnerWantedKill: false
|
|
13870
|
+
};
|
|
13871
|
+
ci.sessionIds.add(entry.sessionId);
|
|
13872
|
+
byId.set(entry.sessionId, entry);
|
|
13873
|
+
return entry;
|
|
13874
|
+
}, "createSessionEntry");
|
|
13875
|
+
const isAcpSessionResourceNotFound = /* @__PURE__ */ __name((err, sessionId) => {
|
|
13876
|
+
if (!err || typeof err !== "object") return false;
|
|
13877
|
+
const maybe = err;
|
|
13878
|
+
if (maybe.code !== -32002) return false;
|
|
13879
|
+
const expectedUri = `session:${sessionId}`;
|
|
13880
|
+
if (maybe.data && typeof maybe.data === "object" && maybe.data.uri === expectedUri) {
|
|
13881
|
+
return true;
|
|
13882
|
+
}
|
|
13883
|
+
return typeof maybe.message === "string" && maybe.message === `Resource not found: ${expectedUri}`;
|
|
13884
|
+
}, "isAcpSessionResourceNotFound");
|
|
13885
|
+
async function restoreSession(action, req) {
|
|
13886
|
+
if (shuttingDown) {
|
|
13887
|
+
throw new Error("HttpAcpBridge is shutting down");
|
|
13888
|
+
}
|
|
13889
|
+
const workspaceKey = resolveWorkspaceKey(req.workspaceCwd);
|
|
13890
|
+
const existing = byId.get(req.sessionId);
|
|
13891
|
+
if (existing) {
|
|
13892
|
+
existing.attachCount++;
|
|
13893
|
+
const clientId = registerClient(existing, req.clientId);
|
|
13894
|
+
return {
|
|
13895
|
+
sessionId: existing.sessionId,
|
|
13896
|
+
workspaceCwd: existing.workspaceCwd,
|
|
13897
|
+
attached: true,
|
|
13898
|
+
clientId,
|
|
13899
|
+
createdAt: existing.createdAt,
|
|
13900
|
+
// Late attachers get the same ACP state the original restore
|
|
13901
|
+
// caller saw; spawn-only sessions don't carry a state payload.
|
|
13902
|
+
state: existing.restoreState ?? {}
|
|
13903
|
+
};
|
|
13904
|
+
}
|
|
13905
|
+
const inFlight = inFlightRestores.get(req.sessionId);
|
|
13906
|
+
if (inFlight) {
|
|
13907
|
+
if (action !== inFlight.action) {
|
|
13908
|
+
throw new RestoreInProgressError(
|
|
13909
|
+
req.sessionId,
|
|
13910
|
+
inFlight.action,
|
|
13911
|
+
action
|
|
13912
|
+
);
|
|
13913
|
+
}
|
|
13914
|
+
inFlight.coalesceState.count++;
|
|
13915
|
+
let restored;
|
|
13916
|
+
try {
|
|
13917
|
+
restored = await inFlight.promise;
|
|
13918
|
+
} catch (err) {
|
|
13919
|
+
inFlight.coalesceState.count--;
|
|
13920
|
+
throw err;
|
|
13921
|
+
}
|
|
13922
|
+
const entry = byId.get(restored.sessionId);
|
|
13923
|
+
if (!entry) {
|
|
13924
|
+
inFlight.coalesceState.count--;
|
|
13925
|
+
throw new SessionNotFoundError(
|
|
13926
|
+
restored.sessionId,
|
|
13927
|
+
"the agent child likely crashed during session restore \u2014 retry to restore the session"
|
|
13928
|
+
);
|
|
13929
|
+
}
|
|
13930
|
+
return {
|
|
13931
|
+
...restored,
|
|
13932
|
+
attached: true,
|
|
13933
|
+
clientId: registerClient(entry, req.clientId),
|
|
13934
|
+
createdAt: entry.createdAt
|
|
13935
|
+
};
|
|
13936
|
+
}
|
|
13937
|
+
if (byId.size + inFlightSpawns.size + inFlightRestores.size >= maxSessions) {
|
|
13938
|
+
throw new SessionLimitExceededError(maxSessions);
|
|
13939
|
+
}
|
|
13940
|
+
const restoreEvents = new EventBus(eventRingSize);
|
|
13941
|
+
let registeredEntry;
|
|
13942
|
+
let ci;
|
|
13943
|
+
const coalesceState = { count: 0 };
|
|
13944
|
+
const promise = (async () => {
|
|
13945
|
+
pendingRestoreEvents.set(req.sessionId, restoreEvents);
|
|
13946
|
+
ci = await ensureChannel();
|
|
13947
|
+
ci.pendingRestoreIds.add(req.sessionId);
|
|
13948
|
+
const transportClosed = ci.channel.exited.then(() => {
|
|
13949
|
+
throw new Error(`agent channel closed during session/${action}`);
|
|
13950
|
+
});
|
|
13951
|
+
transportClosed.catch(() => {
|
|
13952
|
+
});
|
|
13953
|
+
let state;
|
|
13954
|
+
try {
|
|
13955
|
+
if (action === "load") {
|
|
13956
|
+
state = await Promise.race([
|
|
13957
|
+
withTimeout(
|
|
13958
|
+
ci.connection.loadSession({
|
|
13959
|
+
sessionId: req.sessionId,
|
|
13960
|
+
cwd: workspaceKey,
|
|
13961
|
+
// Restore path drops per-request `mcpServers` (matches
|
|
13962
|
+
// `doSpawn`); daemon-wide MCP comes from settings on
|
|
13963
|
+
// the agent side. The SDK's `RestoreSessionRequest`
|
|
13964
|
+
// intentionally has no `mcpServers` field for the
|
|
13965
|
+
// same reason.
|
|
13966
|
+
mcpServers: []
|
|
13967
|
+
}),
|
|
13968
|
+
initTimeoutMs,
|
|
13969
|
+
"loadSession"
|
|
13970
|
+
),
|
|
13971
|
+
transportClosed
|
|
13972
|
+
]);
|
|
13973
|
+
} else {
|
|
13974
|
+
state = await Promise.race([
|
|
13975
|
+
withTimeout(
|
|
13976
|
+
ci.connection.unstable_resumeSession({
|
|
13977
|
+
sessionId: req.sessionId,
|
|
13978
|
+
cwd: workspaceKey,
|
|
13979
|
+
mcpServers: []
|
|
13980
|
+
}),
|
|
13981
|
+
initTimeoutMs,
|
|
13982
|
+
"resumeSession"
|
|
13983
|
+
),
|
|
13984
|
+
transportClosed
|
|
13985
|
+
]);
|
|
13986
|
+
}
|
|
13987
|
+
} catch (err) {
|
|
13988
|
+
restoreEvents.close();
|
|
13989
|
+
if (isAcpSessionResourceNotFound(err, req.sessionId)) {
|
|
13990
|
+
throw new SessionNotFoundError(req.sessionId);
|
|
13991
|
+
}
|
|
13992
|
+
if (ci.sessionIds.size === 0 && ci.pendingRestoreIds.size === 1 && ci.pendingRestoreIds.has(req.sessionId)) {
|
|
13993
|
+
ci.isDying = true;
|
|
13994
|
+
await ci.channel.kill().catch(() => {
|
|
13995
|
+
});
|
|
13996
|
+
}
|
|
13997
|
+
throw err;
|
|
13998
|
+
}
|
|
13999
|
+
if (shuttingDown) {
|
|
14000
|
+
restoreEvents.close();
|
|
14001
|
+
throw new Error("HttpAcpBridge is shutting down");
|
|
14002
|
+
}
|
|
14003
|
+
if (ci.isDying || !aliveChannels.has(ci)) {
|
|
14004
|
+
restoreEvents.close();
|
|
14005
|
+
throw new Error(
|
|
14006
|
+
`Session ${req.sessionId} restored on a closed agent channel`
|
|
14007
|
+
);
|
|
14008
|
+
}
|
|
14009
|
+
const racedEntry = byId.get(req.sessionId);
|
|
14010
|
+
if (racedEntry) {
|
|
14011
|
+
restoreEvents.close();
|
|
14012
|
+
racedEntry.attachCount += 1 + coalesceState.count;
|
|
14013
|
+
const clientId2 = registerClient(racedEntry, req.clientId);
|
|
14014
|
+
return {
|
|
14015
|
+
sessionId: racedEntry.sessionId,
|
|
14016
|
+
workspaceCwd: racedEntry.workspaceCwd,
|
|
14017
|
+
attached: true,
|
|
14018
|
+
clientId: clientId2,
|
|
14019
|
+
createdAt: racedEntry.createdAt,
|
|
14020
|
+
state: racedEntry.restoreState ?? {}
|
|
14021
|
+
};
|
|
14022
|
+
}
|
|
14023
|
+
const entry = createSessionEntry(
|
|
14024
|
+
ci,
|
|
14025
|
+
req.sessionId,
|
|
14026
|
+
workspaceKey,
|
|
14027
|
+
restoreEvents
|
|
14028
|
+
);
|
|
14029
|
+
entry.restoreState = state;
|
|
14030
|
+
const clientId = registerClient(entry, req.clientId);
|
|
14031
|
+
entry.attachCount = coalesceState.count;
|
|
14032
|
+
registeredEntry = entry;
|
|
14033
|
+
return {
|
|
14034
|
+
sessionId: entry.sessionId,
|
|
14035
|
+
workspaceCwd: entry.workspaceCwd,
|
|
14036
|
+
attached: false,
|
|
14037
|
+
clientId,
|
|
14038
|
+
createdAt: entry.createdAt,
|
|
14039
|
+
state
|
|
14040
|
+
};
|
|
14041
|
+
})().finally(() => {
|
|
14042
|
+
ci?.pendingRestoreIds.delete(req.sessionId);
|
|
14043
|
+
pendingRestoreEvents.delete(req.sessionId);
|
|
14044
|
+
if (!registeredEntry) {
|
|
14045
|
+
restoreEvents.close();
|
|
14046
|
+
}
|
|
14047
|
+
});
|
|
14048
|
+
inFlightRestores.set(req.sessionId, { action, promise, coalesceState });
|
|
14049
|
+
try {
|
|
14050
|
+
return await promise;
|
|
14051
|
+
} finally {
|
|
14052
|
+
inFlightRestores.delete(req.sessionId);
|
|
14053
|
+
}
|
|
14054
|
+
}
|
|
14055
|
+
__name(restoreSession, "restoreSession");
|
|
13862
14056
|
return {
|
|
13863
14057
|
get sessionCount() {
|
|
13864
14058
|
return byId.size;
|
|
@@ -13866,35 +14060,41 @@ function createHttpAcpBridge(opts) {
|
|
|
13866
14060
|
get pendingPermissionCount() {
|
|
13867
14061
|
return pendingPermissions.size;
|
|
13868
14062
|
},
|
|
14063
|
+
async loadSession(req) {
|
|
14064
|
+
return restoreSession("load", req);
|
|
14065
|
+
},
|
|
14066
|
+
async resumeSession(req) {
|
|
14067
|
+
return restoreSession("resume", req);
|
|
14068
|
+
},
|
|
13869
14069
|
async spawnOrAttach(req) {
|
|
13870
14070
|
if (shuttingDown) {
|
|
13871
14071
|
throw new Error("HttpAcpBridge is shutting down");
|
|
13872
14072
|
}
|
|
13873
|
-
|
|
13874
|
-
|
|
13875
|
-
|
|
13876
|
-
);
|
|
13877
|
-
}
|
|
13878
|
-
const workspaceKey = req.workspaceCwd === boundWorkspace ? boundWorkspace : canonicalizeWorkspace(req.workspaceCwd);
|
|
13879
|
-
if (workspaceKey !== boundWorkspace) {
|
|
13880
|
-
throw new WorkspaceMismatchError(boundWorkspace, workspaceKey);
|
|
14073
|
+
const workspaceKey = resolveWorkspaceKey(req.workspaceCwd);
|
|
14074
|
+
if (req.sessionScope !== void 0 && req.sessionScope !== "single" && req.sessionScope !== "thread") {
|
|
14075
|
+
throw new InvalidSessionScopeError(req.sessionScope);
|
|
13881
14076
|
}
|
|
13882
|
-
|
|
14077
|
+
const effectiveScope = req.sessionScope ?? defaultSessionScope;
|
|
14078
|
+
if (effectiveScope === "single") {
|
|
13883
14079
|
const existing = defaultEntry;
|
|
13884
14080
|
if (existing) {
|
|
13885
14081
|
existing.attachCount++;
|
|
14082
|
+
const clientId = registerClient(existing, req.clientId);
|
|
13886
14083
|
if (req.modelServiceId) {
|
|
13887
14084
|
await applyModelServiceId(
|
|
13888
14085
|
existing,
|
|
13889
14086
|
req.modelServiceId,
|
|
13890
|
-
initTimeoutMs
|
|
14087
|
+
initTimeoutMs,
|
|
14088
|
+
clientId
|
|
13891
14089
|
).catch(() => {
|
|
13892
14090
|
});
|
|
13893
14091
|
}
|
|
13894
14092
|
return {
|
|
13895
14093
|
sessionId: existing.sessionId,
|
|
13896
14094
|
workspaceCwd: existing.workspaceCwd,
|
|
13897
|
-
attached: true
|
|
14095
|
+
attached: true,
|
|
14096
|
+
clientId,
|
|
14097
|
+
createdAt: existing.createdAt
|
|
13898
14098
|
};
|
|
13899
14099
|
}
|
|
13900
14100
|
const inFlight = inFlightSpawns.get(workspaceKey);
|
|
@@ -13908,22 +14108,24 @@ function createHttpAcpBridge(opts) {
|
|
|
13908
14108
|
"the agent child likely crashed during initialization \u2014 retry to spawn a new session"
|
|
13909
14109
|
);
|
|
13910
14110
|
}
|
|
14111
|
+
const clientId = registerClient(attachedEntry, req.clientId);
|
|
13911
14112
|
if (req.modelServiceId) {
|
|
13912
14113
|
await applyModelServiceId(
|
|
13913
14114
|
attachedEntry,
|
|
13914
14115
|
req.modelServiceId,
|
|
13915
|
-
initTimeoutMs
|
|
14116
|
+
initTimeoutMs,
|
|
14117
|
+
clientId
|
|
13916
14118
|
).catch(() => {
|
|
13917
14119
|
});
|
|
13918
14120
|
}
|
|
13919
|
-
return { ...session, attached: true };
|
|
14121
|
+
return { ...session, attached: true, clientId };
|
|
13920
14122
|
}
|
|
13921
14123
|
}
|
|
13922
|
-
if (byId.size + inFlightSpawns.size >= maxSessions) {
|
|
14124
|
+
if (byId.size + inFlightSpawns.size + inFlightRestores.size >= maxSessions) {
|
|
13923
14125
|
throw new SessionLimitExceededError(maxSessions);
|
|
13924
14126
|
}
|
|
13925
|
-
const promise = doSpawn(req.modelServiceId);
|
|
13926
|
-
const tracker =
|
|
14127
|
+
const promise = doSpawn(req.modelServiceId, effectiveScope, req.clientId);
|
|
14128
|
+
const tracker = effectiveScope === "single" ? workspaceKey : `${workspaceKey}#${randomUUID()}`;
|
|
13927
14129
|
inFlightSpawns.set(tracker, promise);
|
|
13928
14130
|
try {
|
|
13929
14131
|
return await promise;
|
|
@@ -13931,9 +14133,13 @@ function createHttpAcpBridge(opts) {
|
|
|
13931
14133
|
inFlightSpawns.delete(tracker);
|
|
13932
14134
|
}
|
|
13933
14135
|
},
|
|
13934
|
-
async sendPrompt(sessionId, req, signal) {
|
|
14136
|
+
async sendPrompt(sessionId, req, signal, context) {
|
|
13935
14137
|
const entry = byId.get(sessionId);
|
|
13936
14138
|
if (!entry) throw new SessionNotFoundError(sessionId);
|
|
14139
|
+
const originatorClientId = resolveTrustedClientId(
|
|
14140
|
+
entry,
|
|
14141
|
+
context?.clientId
|
|
14142
|
+
);
|
|
13937
14143
|
if (signal?.aborted) {
|
|
13938
14144
|
throw new DOMException("Prompt aborted", "AbortError");
|
|
13939
14145
|
}
|
|
@@ -13942,7 +14148,14 @@ function createHttpAcpBridge(opts) {
|
|
|
13942
14148
|
if (signal?.aborted) {
|
|
13943
14149
|
throw new DOMException("Prompt aborted", "AbortError");
|
|
13944
14150
|
}
|
|
13945
|
-
|
|
14151
|
+
if (originatorClientId === void 0) {
|
|
14152
|
+
delete entry.activePromptOriginatorClientId;
|
|
14153
|
+
} else {
|
|
14154
|
+
entry.activePromptOriginatorClientId = originatorClientId;
|
|
14155
|
+
}
|
|
14156
|
+
const promptPromise = entry.connection.prompt(normalized).finally(() => {
|
|
14157
|
+
delete entry.activePromptOriginatorClientId;
|
|
14158
|
+
});
|
|
13946
14159
|
const racedPromise = Promise.race([
|
|
13947
14160
|
promptPromise,
|
|
13948
14161
|
getTransportClosedReject(entry)
|
|
@@ -13969,9 +14182,10 @@ function createHttpAcpBridge(opts) {
|
|
|
13969
14182
|
);
|
|
13970
14183
|
return result;
|
|
13971
14184
|
},
|
|
13972
|
-
async cancelSession(sessionId, req) {
|
|
14185
|
+
async cancelSession(sessionId, req, context) {
|
|
13973
14186
|
const entry = byId.get(sessionId);
|
|
13974
14187
|
if (!entry) throw new SessionNotFoundError(sessionId);
|
|
14188
|
+
resolveTrustedClientId(entry, context?.clientId);
|
|
13975
14189
|
cancelPendingForSession(sessionId);
|
|
13976
14190
|
const notif = req ? { ...req, sessionId } : { sessionId };
|
|
13977
14191
|
await entry.connection.cancel(notif);
|
|
@@ -13981,65 +14195,259 @@ function createHttpAcpBridge(opts) {
|
|
|
13981
14195
|
if (!entry) throw new SessionNotFoundError(sessionId);
|
|
13982
14196
|
return entry.events.subscribe(subOpts);
|
|
13983
14197
|
},
|
|
13984
|
-
respondToPermission(requestId, response) {
|
|
14198
|
+
respondToPermission(requestId, response, context) {
|
|
14199
|
+
const pending = pendingPermissions.get(requestId);
|
|
14200
|
+
let originatorClientId;
|
|
14201
|
+
if (context?.clientId !== void 0 && !pending) {
|
|
14202
|
+
resolveAnyTrustedClientId(context.clientId);
|
|
14203
|
+
} else if (pending && context?.clientId !== void 0) {
|
|
14204
|
+
const entry = byId.get(pending.sessionId);
|
|
14205
|
+
if (entry) {
|
|
14206
|
+
originatorClientId = resolveTrustedClientId(entry, context.clientId);
|
|
14207
|
+
} else {
|
|
14208
|
+
resolveAnyTrustedClientId(context.clientId);
|
|
14209
|
+
}
|
|
14210
|
+
}
|
|
14211
|
+
if (!pending) {
|
|
14212
|
+
const record = resolvedPermissions.get(requestId);
|
|
14213
|
+
if (record) {
|
|
14214
|
+
publishPermissionAlreadyResolved(record);
|
|
14215
|
+
}
|
|
14216
|
+
return false;
|
|
14217
|
+
}
|
|
13985
14218
|
if (response.outcome.outcome === "selected") {
|
|
13986
|
-
|
|
13987
|
-
if (pending && !pending.allowedOptionIds.has(response.outcome.optionId)) {
|
|
14219
|
+
if (!pending.allowedOptionIds.has(response.outcome.optionId)) {
|
|
13988
14220
|
throw new InvalidPermissionOptionError(
|
|
13989
14221
|
requestId,
|
|
13990
14222
|
response.outcome.optionId
|
|
13991
14223
|
);
|
|
13992
14224
|
}
|
|
13993
14225
|
}
|
|
13994
|
-
return resolvePending(requestId, response);
|
|
14226
|
+
return resolvePending(requestId, response, originatorClientId);
|
|
13995
14227
|
},
|
|
13996
|
-
|
|
13997
|
-
|
|
13998
|
-
|
|
13999
|
-
|
|
14000
|
-
|
|
14001
|
-
|
|
14002
|
-
if (
|
|
14003
|
-
|
|
14004
|
-
|
|
14005
|
-
|
|
14006
|
-
|
|
14228
|
+
respondToSessionPermission(sessionId, requestId, response, context) {
|
|
14229
|
+
const entry = byId.get(sessionId);
|
|
14230
|
+
if (!entry) throw new SessionNotFoundError(sessionId);
|
|
14231
|
+
const pending = pendingPermissions.get(requestId);
|
|
14232
|
+
if (!pending) {
|
|
14233
|
+
const record = resolvedPermissions.get(requestId);
|
|
14234
|
+
if (record?.sessionId === sessionId) {
|
|
14235
|
+
resolveTrustedClientId(entry, context?.clientId);
|
|
14236
|
+
publishPermissionAlreadyResolved(record);
|
|
14237
|
+
} else if (record) {
|
|
14238
|
+
writeServeDebugLine(
|
|
14239
|
+
`rejected permission vote ${JSON.stringify(requestId)} for session ${JSON.stringify(sessionId)}; request belongs to session ${JSON.stringify(record.sessionId)}.`
|
|
14240
|
+
);
|
|
14007
14241
|
}
|
|
14242
|
+
return false;
|
|
14008
14243
|
}
|
|
14009
|
-
|
|
14244
|
+
if (pending.sessionId !== sessionId) {
|
|
14245
|
+
writeServeDebugLine(
|
|
14246
|
+
`rejected permission vote ${JSON.stringify(requestId)} for session ${JSON.stringify(sessionId)}; request belongs to session ${JSON.stringify(pending.sessionId)}.`
|
|
14247
|
+
);
|
|
14248
|
+
return false;
|
|
14249
|
+
}
|
|
14250
|
+
const originatorClientId = resolveTrustedClientId(
|
|
14251
|
+
entry,
|
|
14252
|
+
context?.clientId
|
|
14253
|
+
);
|
|
14254
|
+
if (response.outcome.outcome === "selected" && !pending.allowedOptionIds.has(response.outcome.optionId)) {
|
|
14255
|
+
throw new InvalidPermissionOptionError(
|
|
14256
|
+
requestId,
|
|
14257
|
+
response.outcome.optionId
|
|
14258
|
+
);
|
|
14259
|
+
}
|
|
14260
|
+
return resolvePending(requestId, response, originatorClientId);
|
|
14010
14261
|
},
|
|
14011
|
-
async
|
|
14262
|
+
async closeSession(sessionId, context) {
|
|
14012
14263
|
const entry = byId.get(sessionId);
|
|
14013
14264
|
if (!entry) throw new SessionNotFoundError(sessionId);
|
|
14014
|
-
|
|
14015
|
-
|
|
14016
|
-
|
|
14017
|
-
|
|
14018
|
-
|
|
14019
|
-
|
|
14020
|
-
conn.unstable_setSessionModel(normalized),
|
|
14021
|
-
initTimeoutMs,
|
|
14022
|
-
"setSessionModel"
|
|
14023
|
-
),
|
|
14024
|
-
transportClosed
|
|
14025
|
-
])
|
|
14026
|
-
);
|
|
14027
|
-
entry.modelChangeQueue = work.then(
|
|
14028
|
-
() => void 0,
|
|
14029
|
-
() => void 0
|
|
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)}` : "")
|
|
14030
14271
|
);
|
|
14031
|
-
|
|
14032
|
-
|
|
14033
|
-
|
|
14034
|
-
|
|
14035
|
-
|
|
14036
|
-
|
|
14037
|
-
|
|
14038
|
-
|
|
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
|
+
},
|
|
14342
|
+
listWorkspaceSessions(workspaceCwd) {
|
|
14343
|
+
if (!path.isAbsolute(workspaceCwd)) return [];
|
|
14344
|
+
const key = workspaceCwd === boundWorkspace ? boundWorkspace : canonicalizeWorkspace(workspaceCwd);
|
|
14345
|
+
if (key !== boundWorkspace) return [];
|
|
14346
|
+
const out = [];
|
|
14347
|
+
for (const entry of byId.values()) {
|
|
14348
|
+
if (entry.workspaceCwd === key) {
|
|
14349
|
+
out.push({
|
|
14350
|
+
sessionId: entry.sessionId,
|
|
14351
|
+
workspaceCwd: entry.workspaceCwd,
|
|
14352
|
+
createdAt: entry.createdAt,
|
|
14353
|
+
displayName: entry.displayName,
|
|
14354
|
+
clientCount: entry.clientIds.size,
|
|
14355
|
+
hasActivePrompt: entry.activePromptOriginatorClientId !== void 0
|
|
14356
|
+
});
|
|
14357
|
+
}
|
|
14358
|
+
}
|
|
14359
|
+
return out;
|
|
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
|
+
},
|
|
14414
|
+
async setSessionModel(sessionId, req, context) {
|
|
14415
|
+
const entry = byId.get(sessionId);
|
|
14416
|
+
if (!entry) throw new SessionNotFoundError(sessionId);
|
|
14417
|
+
const originatorClientId = resolveTrustedClientId(
|
|
14418
|
+
entry,
|
|
14419
|
+
context?.clientId
|
|
14420
|
+
);
|
|
14421
|
+
const normalized = { ...req, sessionId };
|
|
14422
|
+
const conn = entry.connection;
|
|
14423
|
+
const transportClosed = getTransportClosedReject(entry);
|
|
14424
|
+
const work = entry.modelChangeQueue.then(
|
|
14425
|
+
() => Promise.race([
|
|
14426
|
+
withTimeout(
|
|
14427
|
+
conn.unstable_setSessionModel(normalized),
|
|
14428
|
+
initTimeoutMs,
|
|
14429
|
+
"setSessionModel"
|
|
14430
|
+
),
|
|
14431
|
+
transportClosed
|
|
14432
|
+
])
|
|
14433
|
+
);
|
|
14434
|
+
entry.modelChangeQueue = work.then(
|
|
14435
|
+
() => void 0,
|
|
14436
|
+
() => void 0
|
|
14437
|
+
);
|
|
14438
|
+
let response;
|
|
14439
|
+
try {
|
|
14440
|
+
response = await work;
|
|
14441
|
+
} catch (err) {
|
|
14442
|
+
try {
|
|
14443
|
+
entry.events.publish({
|
|
14444
|
+
type: "model_switch_failed",
|
|
14445
|
+
data: {
|
|
14039
14446
|
sessionId: entry.sessionId,
|
|
14040
14447
|
requestedModelId: req.modelId,
|
|
14041
14448
|
error: err instanceof Error ? err.message : String(err)
|
|
14042
|
-
}
|
|
14449
|
+
},
|
|
14450
|
+
...originatorClientId ? { originatorClientId } : {}
|
|
14043
14451
|
});
|
|
14044
14452
|
} catch {
|
|
14045
14453
|
}
|
|
@@ -14048,7 +14456,8 @@ function createHttpAcpBridge(opts) {
|
|
|
14048
14456
|
try {
|
|
14049
14457
|
entry.events.publish({
|
|
14050
14458
|
type: "model_switched",
|
|
14051
|
-
data: { sessionId: entry.sessionId, modelId: req.modelId }
|
|
14459
|
+
data: { sessionId: entry.sessionId, modelId: req.modelId },
|
|
14460
|
+
...originatorClientId ? { originatorClientId } : {}
|
|
14052
14461
|
});
|
|
14053
14462
|
} catch {
|
|
14054
14463
|
}
|
|
@@ -14078,16 +14487,17 @@ function createHttpAcpBridge(opts) {
|
|
|
14078
14487
|
} catch {
|
|
14079
14488
|
}
|
|
14080
14489
|
entry.events.close();
|
|
14081
|
-
if (ci && ci.sessionIds.size === 0) {
|
|
14490
|
+
if (ci && ci.sessionIds.size === 0 && ci.pendingRestoreIds.size === 0) {
|
|
14082
14491
|
ci.isDying = true;
|
|
14083
14492
|
await ci.channel.kill().catch(() => {
|
|
14084
14493
|
});
|
|
14085
14494
|
}
|
|
14086
14495
|
},
|
|
14087
|
-
async detachClient(sessionId) {
|
|
14496
|
+
async detachClient(sessionId, clientId) {
|
|
14088
14497
|
const entry = byId.get(sessionId);
|
|
14089
14498
|
if (!entry) return;
|
|
14090
14499
|
if (entry.attachCount > 0) entry.attachCount--;
|
|
14500
|
+
unregisterClient(entry, clientId);
|
|
14091
14501
|
if (entry.spawnOwnerWantedKill && entry.attachCount === 0 && entry.events.subscriberCount === 0) {
|
|
14092
14502
|
await this.killSession(sessionId).catch(() => {
|
|
14093
14503
|
});
|
|
@@ -14135,6 +14545,12 @@ function createHttpAcpBridge(opts) {
|
|
|
14135
14545
|
() => void 0
|
|
14136
14546
|
)
|
|
14137
14547
|
);
|
|
14548
|
+
const inFlightRestoreAwaits = Array.from(inFlightRestores.values()).map(
|
|
14549
|
+
(restore) => restore.promise.then(
|
|
14550
|
+
() => void 0,
|
|
14551
|
+
() => void 0
|
|
14552
|
+
)
|
|
14553
|
+
);
|
|
14138
14554
|
const inFlightChannelAwait = inFlightChannelSpawn ? inFlightChannelSpawn.then(
|
|
14139
14555
|
() => void 0,
|
|
14140
14556
|
() => void 0
|
|
@@ -14143,6 +14559,7 @@ function createHttpAcpBridge(opts) {
|
|
|
14143
14559
|
...channels.map((ci) => ci.channel.kill().catch(() => {
|
|
14144
14560
|
})),
|
|
14145
14561
|
...inFlightSessionAwaits,
|
|
14562
|
+
...inFlightRestoreAwaits,
|
|
14146
14563
|
inFlightChannelAwait
|
|
14147
14564
|
]);
|
|
14148
14565
|
}
|
|
@@ -14328,20 +14745,101 @@ function killChild(child) {
|
|
|
14328
14745
|
}
|
|
14329
14746
|
__name(killChild, "killChild");
|
|
14330
14747
|
|
|
14748
|
+
// packages/cli/src/serve/capabilities.ts
|
|
14749
|
+
init_esbuild_shims();
|
|
14750
|
+
var SERVE_PROTOCOL_VERSION = "v1";
|
|
14751
|
+
var SUPPORTED_SERVE_PROTOCOL_VERSIONS = [
|
|
14752
|
+
SERVE_PROTOCOL_VERSION
|
|
14753
|
+
];
|
|
14754
|
+
var SERVE_CAPABILITY_REGISTRY = {
|
|
14755
|
+
health: { since: "v1" },
|
|
14756
|
+
capabilities: { since: "v1" },
|
|
14757
|
+
session_create: { since: "v1" },
|
|
14758
|
+
session_scope_override: { since: "v1" },
|
|
14759
|
+
session_load: { since: "v1" },
|
|
14760
|
+
// ACP backs this with `connection.unstable_resumeSession`. Surface
|
|
14761
|
+
// the unstable prefix so clients don't pin against a `v1` shape that
|
|
14762
|
+
// the underlying ACP method may still change.
|
|
14763
|
+
unstable_session_resume: { since: "v1" },
|
|
14764
|
+
session_list: { since: "v1" },
|
|
14765
|
+
session_prompt: { since: "v1" },
|
|
14766
|
+
session_cancel: { since: "v1" },
|
|
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" },
|
|
14778
|
+
session_set_model: { since: "v1" },
|
|
14779
|
+
client_identity: { since: "v1" },
|
|
14780
|
+
client_heartbeat: { since: "v1" },
|
|
14781
|
+
session_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" }
|
|
14799
|
+
};
|
|
14800
|
+
var CONDITIONAL_SERVE_FEATURES = /* @__PURE__ */ new Map([
|
|
14801
|
+
["require_auth", (toggles) => toggles.requireAuth === true]
|
|
14802
|
+
]);
|
|
14803
|
+
var SERVE_FEATURES = Object.freeze(
|
|
14804
|
+
Object.keys(SERVE_CAPABILITY_REGISTRY)
|
|
14805
|
+
);
|
|
14806
|
+
function serveProtocolVersionIndex(version) {
|
|
14807
|
+
return SUPPORTED_SERVE_PROTOCOL_VERSIONS.indexOf(version);
|
|
14808
|
+
}
|
|
14809
|
+
__name(serveProtocolVersionIndex, "serveProtocolVersionIndex");
|
|
14810
|
+
function isFeatureAvailableInProtocol(feature, protocolVersion) {
|
|
14811
|
+
return serveProtocolVersionIndex(SERVE_CAPABILITY_REGISTRY[feature].since) <= serveProtocolVersionIndex(protocolVersion);
|
|
14812
|
+
}
|
|
14813
|
+
__name(isFeatureAvailableInProtocol, "isFeatureAvailableInProtocol");
|
|
14814
|
+
function getRegisteredServeFeatures() {
|
|
14815
|
+
return [...SERVE_FEATURES];
|
|
14816
|
+
}
|
|
14817
|
+
__name(getRegisteredServeFeatures, "getRegisteredServeFeatures");
|
|
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
|
+
});
|
|
14825
|
+
}
|
|
14826
|
+
__name(getAdvertisedServeFeatures, "getAdvertisedServeFeatures");
|
|
14827
|
+
function getServeFeatures() {
|
|
14828
|
+
return getAdvertisedServeFeatures();
|
|
14829
|
+
}
|
|
14830
|
+
__name(getServeFeatures, "getServeFeatures");
|
|
14831
|
+
function getServeProtocolVersions() {
|
|
14832
|
+
return {
|
|
14833
|
+
current: SERVE_PROTOCOL_VERSION,
|
|
14834
|
+
supported: [...SUPPORTED_SERVE_PROTOCOL_VERSIONS]
|
|
14835
|
+
};
|
|
14836
|
+
}
|
|
14837
|
+
__name(getServeProtocolVersions, "getServeProtocolVersions");
|
|
14838
|
+
|
|
14331
14839
|
// packages/cli/src/serve/types.ts
|
|
14332
14840
|
init_esbuild_shims();
|
|
14333
14841
|
var CAPABILITIES_SCHEMA_VERSION = 1;
|
|
14334
|
-
var STAGE1_FEATURES =
|
|
14335
|
-
"health",
|
|
14336
|
-
"capabilities",
|
|
14337
|
-
"session_create",
|
|
14338
|
-
"session_list",
|
|
14339
|
-
"session_prompt",
|
|
14340
|
-
"session_cancel",
|
|
14341
|
-
"session_events",
|
|
14342
|
-
"session_set_model",
|
|
14343
|
-
"permission_vote"
|
|
14344
|
-
];
|
|
14842
|
+
var STAGE1_FEATURES = SERVE_FEATURES;
|
|
14345
14843
|
|
|
14346
14844
|
// packages/cli/src/serve/server.ts
|
|
14347
14845
|
function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
@@ -14349,6 +14847,11 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
14349
14847
|
const boundWorkspace = deps.boundWorkspace ?? canonicalizeWorkspace(opts.workspace ?? process.cwd());
|
|
14350
14848
|
const bridge = deps.bridge ?? createHttpAcpBridge({
|
|
14351
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 } : {},
|
|
14352
14855
|
boundWorkspace
|
|
14353
14856
|
});
|
|
14354
14857
|
app.use(denyBrowserOriginCors);
|
|
@@ -14374,19 +14877,31 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
14374
14877
|
}
|
|
14375
14878
|
}, "healthHandler");
|
|
14376
14879
|
const loopback = isLoopbackBind(opts.hostname);
|
|
14377
|
-
|
|
14880
|
+
const exposeHealthPreAuth = loopback && !opts.requireAuth;
|
|
14881
|
+
if (exposeHealthPreAuth) {
|
|
14378
14882
|
app.get("/health", healthHandler);
|
|
14379
14883
|
}
|
|
14380
14884
|
app.use(bearerAuth(opts.token));
|
|
14381
14885
|
app.use(import_express.default.json({ limit: "10mb" }));
|
|
14382
|
-
if (!
|
|
14886
|
+
if (!exposeHealthPreAuth) {
|
|
14383
14887
|
app.get("/health", healthHandler);
|
|
14384
14888
|
}
|
|
14889
|
+
const mutate = createMutationGate({
|
|
14890
|
+
tokenConfigured: opts.token !== void 0,
|
|
14891
|
+
requireAuth: opts.requireAuth === true
|
|
14892
|
+
});
|
|
14385
14893
|
app.get("/capabilities", (_req, res) => {
|
|
14386
14894
|
const envelope = {
|
|
14387
14895
|
v: CAPABILITIES_SCHEMA_VERSION,
|
|
14896
|
+
protocolVersions: getServeProtocolVersions(),
|
|
14388
14897
|
mode: opts.mode,
|
|
14389
|
-
|
|
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
|
+
}),
|
|
14390
14905
|
modelServices: [],
|
|
14391
14906
|
// #3803 §02: surface the bound workspace so clients can detect
|
|
14392
14907
|
// mismatch pre-flight and omit `cwd` on `POST /session`.
|
|
@@ -14394,7 +14909,28 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
14394
14909
|
};
|
|
14395
14910
|
res.status(200).json(envelope);
|
|
14396
14911
|
});
|
|
14397
|
-
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) => {
|
|
14398
14934
|
const body = safeBody(req);
|
|
14399
14935
|
const hasCwd = "cwd" in body;
|
|
14400
14936
|
if (hasCwd && typeof body["cwd"] !== "string") {
|
|
@@ -14413,17 +14949,33 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
14413
14949
|
return;
|
|
14414
14950
|
}
|
|
14415
14951
|
const modelServiceId = typeof body["modelServiceId"] === "string" ? body["modelServiceId"] : void 0;
|
|
14952
|
+
const rawSessionScope = body["sessionScope"];
|
|
14953
|
+
let sessionScope;
|
|
14954
|
+
if (rawSessionScope !== void 0) {
|
|
14955
|
+
if (rawSessionScope !== "single" && rawSessionScope !== "thread") {
|
|
14956
|
+
res.status(400).json({
|
|
14957
|
+
error: '`sessionScope` must be "single" or "thread" when provided',
|
|
14958
|
+
code: "invalid_session_scope"
|
|
14959
|
+
});
|
|
14960
|
+
return;
|
|
14961
|
+
}
|
|
14962
|
+
sessionScope = rawSessionScope;
|
|
14963
|
+
}
|
|
14964
|
+
const clientId = parseClientIdHeader(req, res);
|
|
14965
|
+
if (clientId === null) return;
|
|
14416
14966
|
try {
|
|
14417
14967
|
const session = await bridge.spawnOrAttach({
|
|
14418
14968
|
workspaceCwd: cwd,
|
|
14419
|
-
modelServiceId
|
|
14969
|
+
modelServiceId,
|
|
14970
|
+
...clientId !== void 0 ? { clientId } : {},
|
|
14971
|
+
...sessionScope !== void 0 ? { sessionScope } : {}
|
|
14420
14972
|
});
|
|
14421
14973
|
if (!res.writable) {
|
|
14422
14974
|
if (!session.attached) {
|
|
14423
14975
|
bridge.killSession(session.sessionId, { requireZeroAttaches: true }).catch(() => {
|
|
14424
14976
|
});
|
|
14425
14977
|
} else {
|
|
14426
|
-
bridge.detachClient(session.sessionId).catch(() => {
|
|
14978
|
+
bridge.detachClient(session.sessionId, session.clientId).catch(() => {
|
|
14427
14979
|
});
|
|
14428
14980
|
}
|
|
14429
14981
|
return;
|
|
@@ -14433,7 +14985,78 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
14433
14985
|
sendBridgeError(res, err, { route: "POST /session" });
|
|
14434
14986
|
}
|
|
14435
14987
|
});
|
|
14436
|
-
|
|
14988
|
+
const restoreSessionHandler = /* @__PURE__ */ __name((action) => async (req, res) => {
|
|
14989
|
+
const sessionId = req.params["id"];
|
|
14990
|
+
if (!sessionId) {
|
|
14991
|
+
res.status(400).json({ error: "`sessionId` route parameter is required" });
|
|
14992
|
+
return;
|
|
14993
|
+
}
|
|
14994
|
+
const body = safeBody(req);
|
|
14995
|
+
const cwd = parseOptionalWorkspaceCwd(body, boundWorkspace, res);
|
|
14996
|
+
if (cwd === void 0) return;
|
|
14997
|
+
const clientId = parseClientIdHeader(req, res);
|
|
14998
|
+
if (clientId === null) return;
|
|
14999
|
+
try {
|
|
15000
|
+
const session = action === "load" ? await bridge.loadSession({
|
|
15001
|
+
sessionId,
|
|
15002
|
+
workspaceCwd: cwd,
|
|
15003
|
+
...clientId !== void 0 ? { clientId } : {}
|
|
15004
|
+
}) : await bridge.resumeSession({
|
|
15005
|
+
sessionId,
|
|
15006
|
+
workspaceCwd: cwd,
|
|
15007
|
+
...clientId !== void 0 ? { clientId } : {}
|
|
15008
|
+
});
|
|
15009
|
+
if (!res.writable) {
|
|
15010
|
+
if (!session.attached) {
|
|
15011
|
+
bridge.killSession(session.sessionId, { requireZeroAttaches: true }).catch(() => {
|
|
15012
|
+
});
|
|
15013
|
+
} else {
|
|
15014
|
+
bridge.detachClient(session.sessionId, session.clientId).catch(() => {
|
|
15015
|
+
});
|
|
15016
|
+
}
|
|
15017
|
+
return;
|
|
15018
|
+
}
|
|
15019
|
+
res.status(200).json(session);
|
|
15020
|
+
} catch (err) {
|
|
15021
|
+
sendBridgeError(res, err, {
|
|
15022
|
+
route: `POST /session/:id/${action}`,
|
|
15023
|
+
sessionId
|
|
15024
|
+
});
|
|
15025
|
+
}
|
|
15026
|
+
}, "restoreSessionHandler");
|
|
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) => {
|
|
14437
15060
|
const sessionId = req.params["id"];
|
|
14438
15061
|
const body = safeBody(req);
|
|
14439
15062
|
const prompt = body["prompt"];
|
|
@@ -14464,6 +15087,11 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
14464
15087
|
if (!res.writableEnded) abort.abort();
|
|
14465
15088
|
}, "onResClose");
|
|
14466
15089
|
res.once("close", onResClose);
|
|
15090
|
+
const clientId = parseClientIdHeader(req, res);
|
|
15091
|
+
if (clientId === null) {
|
|
15092
|
+
res.off("close", onResClose);
|
|
15093
|
+
return;
|
|
15094
|
+
}
|
|
14467
15095
|
try {
|
|
14468
15096
|
const result = await bridge.sendPrompt(
|
|
14469
15097
|
sessionId,
|
|
@@ -14472,7 +15100,8 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
14472
15100
|
sessionId,
|
|
14473
15101
|
prompt
|
|
14474
15102
|
},
|
|
14475
|
-
abort.signal
|
|
15103
|
+
abort.signal,
|
|
15104
|
+
clientId !== void 0 ? { clientId } : void 0
|
|
14476
15105
|
);
|
|
14477
15106
|
res.status(200).json(result);
|
|
14478
15107
|
} catch (err) {
|
|
@@ -14487,14 +15116,41 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
14487
15116
|
res.off("close", onResClose);
|
|
14488
15117
|
}
|
|
14489
15118
|
});
|
|
14490
|
-
app.post("/session/:id/
|
|
15119
|
+
app.post("/session/:id/heartbeat", mutate(), (req, res) => {
|
|
14491
15120
|
const sessionId = req.params["id"];
|
|
14492
|
-
|
|
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;
|
|
14493
15127
|
try {
|
|
14494
|
-
|
|
14495
|
-
|
|
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",
|
|
14496
15136
|
sessionId
|
|
14497
15137
|
});
|
|
15138
|
+
}
|
|
15139
|
+
});
|
|
15140
|
+
app.post("/session/:id/cancel", mutate(), async (req, res) => {
|
|
15141
|
+
const sessionId = req.params["id"];
|
|
15142
|
+
const body = safeBody(req);
|
|
15143
|
+
const clientId = parseClientIdHeader(req, res);
|
|
15144
|
+
if (clientId === null) return;
|
|
15145
|
+
try {
|
|
15146
|
+
await bridge.cancelSession(
|
|
15147
|
+
sessionId,
|
|
15148
|
+
{
|
|
15149
|
+
...body,
|
|
15150
|
+
sessionId
|
|
15151
|
+
},
|
|
15152
|
+
clientId !== void 0 ? { clientId } : void 0
|
|
15153
|
+
);
|
|
14498
15154
|
res.status(204).end();
|
|
14499
15155
|
} catch (err) {
|
|
14500
15156
|
sendBridgeError(res, err, {
|
|
@@ -14503,6 +15159,51 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
14503
15159
|
});
|
|
14504
15160
|
}
|
|
14505
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
|
+
});
|
|
14506
15207
|
app.get("/workspace/:id/sessions", (req, res) => {
|
|
14507
15208
|
const workspaceCwd = req.params["id"] ?? "";
|
|
14508
15209
|
if (!path2.isAbsolute(workspaceCwd)) {
|
|
@@ -14522,7 +15223,7 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
14522
15223
|
const sessions = bridge.listWorkspaceSessions(workspaceCwd);
|
|
14523
15224
|
res.status(200).json({ sessions });
|
|
14524
15225
|
});
|
|
14525
|
-
app.post("/session/:id/model", async (req, res) => {
|
|
15226
|
+
app.post("/session/:id/model", mutate(), async (req, res) => {
|
|
14526
15227
|
const sessionId = req.params["id"];
|
|
14527
15228
|
const body = safeBody(req);
|
|
14528
15229
|
const modelId = body["modelId"];
|
|
@@ -14532,12 +15233,18 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
14532
15233
|
});
|
|
14533
15234
|
return;
|
|
14534
15235
|
}
|
|
15236
|
+
const clientId = parseClientIdHeader(req, res);
|
|
15237
|
+
if (clientId === null) return;
|
|
14535
15238
|
try {
|
|
14536
|
-
const response = await bridge.setSessionModel(
|
|
14537
|
-
...body,
|
|
15239
|
+
const response = await bridge.setSessionModel(
|
|
14538
15240
|
sessionId,
|
|
14539
|
-
|
|
14540
|
-
|
|
15241
|
+
{
|
|
15242
|
+
...body,
|
|
15243
|
+
sessionId,
|
|
15244
|
+
modelId
|
|
15245
|
+
},
|
|
15246
|
+
clientId !== void 0 ? { clientId } : void 0
|
|
15247
|
+
);
|
|
14541
15248
|
res.status(200).json(response);
|
|
14542
15249
|
} catch (err) {
|
|
14543
15250
|
sendBridgeError(res, err, {
|
|
@@ -14546,33 +15253,56 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
14546
15253
|
});
|
|
14547
15254
|
}
|
|
14548
15255
|
});
|
|
14549
|
-
app.post("/permission/:requestId", (req, res) => {
|
|
15256
|
+
app.post("/session/:id/permission/:requestId", mutate(), (req, res) => {
|
|
15257
|
+
const sessionId = req.params["id"];
|
|
14550
15258
|
const requestId = req.params["requestId"];
|
|
14551
|
-
const
|
|
14552
|
-
|
|
14553
|
-
|
|
14554
|
-
|
|
14555
|
-
|
|
15259
|
+
const response = parsePermissionVoteBody(req, res);
|
|
15260
|
+
if (response === void 0) return;
|
|
15261
|
+
const clientId = parseClientIdHeader(req, res);
|
|
15262
|
+
if (clientId === null) return;
|
|
15263
|
+
let accepted;
|
|
15264
|
+
try {
|
|
15265
|
+
accepted = bridge.respondToSessionPermission(
|
|
15266
|
+
sessionId,
|
|
15267
|
+
requestId,
|
|
15268
|
+
response,
|
|
15269
|
+
clientId !== void 0 ? { clientId } : void 0
|
|
15270
|
+
);
|
|
15271
|
+
} catch (err) {
|
|
15272
|
+
sendPermissionVoteError(res, err, {
|
|
15273
|
+
route: "POST /session/:id/permission/:requestId",
|
|
15274
|
+
sessionId
|
|
15275
|
+
});
|
|
15276
|
+
return;
|
|
15277
|
+
}
|
|
15278
|
+
if (!accepted) {
|
|
15279
|
+
res.status(404).json({
|
|
15280
|
+
error: "No pending permission request for session",
|
|
15281
|
+
sessionId,
|
|
15282
|
+
requestId
|
|
14556
15283
|
});
|
|
14557
15284
|
return;
|
|
14558
15285
|
}
|
|
15286
|
+
res.status(200).json({});
|
|
15287
|
+
});
|
|
15288
|
+
app.post("/permission/:requestId", mutate(), (req, res) => {
|
|
15289
|
+
const requestId = req.params["requestId"];
|
|
15290
|
+
const response = parsePermissionVoteBody(req, res);
|
|
15291
|
+
if (response === void 0) return;
|
|
15292
|
+
const clientId = parseClientIdHeader(req, res);
|
|
15293
|
+
if (clientId === null) return;
|
|
14559
15294
|
let accepted;
|
|
14560
15295
|
try {
|
|
14561
|
-
accepted = bridge.respondToPermission(
|
|
14562
|
-
|
|
14563
|
-
|
|
14564
|
-
|
|
15296
|
+
accepted = bridge.respondToPermission(
|
|
15297
|
+
requestId,
|
|
15298
|
+
response,
|
|
15299
|
+
clientId !== void 0 ? { clientId } : void 0
|
|
15300
|
+
);
|
|
14565
15301
|
} catch (err) {
|
|
14566
|
-
|
|
14567
|
-
|
|
14568
|
-
|
|
14569
|
-
|
|
14570
|
-
requestId: err.requestId,
|
|
14571
|
-
optionId: err.optionId
|
|
14572
|
-
});
|
|
14573
|
-
return;
|
|
14574
|
-
}
|
|
14575
|
-
throw err;
|
|
15302
|
+
sendPermissionVoteError(res, err, {
|
|
15303
|
+
route: "POST /permission/:requestId"
|
|
15304
|
+
});
|
|
15305
|
+
return;
|
|
14576
15306
|
}
|
|
14577
15307
|
if (!accepted) {
|
|
14578
15308
|
res.status(404).json({ error: "No pending permission request", requestId });
|
|
@@ -14583,12 +15313,15 @@ function createServeApp(opts, getPort = () => opts.port, deps = {}) {
|
|
|
14583
15313
|
app.get("/session/:id/events", (req, res) => {
|
|
14584
15314
|
const sessionId = req.params["id"];
|
|
14585
15315
|
const lastEventId = parseLastEventId(req.headers["last-event-id"]);
|
|
15316
|
+
const maxQueued = parseMaxQueuedQuery(req.query["maxQueued"], res);
|
|
15317
|
+
if (maxQueued === null) return;
|
|
14586
15318
|
let iter;
|
|
14587
15319
|
const abort = new AbortController();
|
|
14588
15320
|
try {
|
|
14589
15321
|
const iterable = bridge.subscribeEvents(sessionId, {
|
|
14590
15322
|
signal: abort.signal,
|
|
14591
|
-
lastEventId
|
|
15323
|
+
lastEventId,
|
|
15324
|
+
...maxQueued !== void 0 ? { maxQueued } : {}
|
|
14592
15325
|
});
|
|
14593
15326
|
iter = iterable[Symbol.asyncIterator]();
|
|
14594
15327
|
} catch (err) {
|
|
@@ -14728,6 +15461,10 @@ var PROTOTYPE_POLLUTION_KEYS = /* @__PURE__ */ new Set([
|
|
|
14728
15461
|
"constructor",
|
|
14729
15462
|
"prototype"
|
|
14730
15463
|
]);
|
|
15464
|
+
var CLIENT_ID_HEADER = "x-qwen-client-id";
|
|
15465
|
+
var MAX_CLIENT_ID_LENGTH = 128;
|
|
15466
|
+
var CLIENT_ID_RE = /^[A-Za-z0-9._:-]+$/;
|
|
15467
|
+
var INVALID_PERMISSION_OUTCOME_ERROR = '`outcome` must be `{ outcome: "cancelled" }` or `{ outcome: "selected", optionId: string }`';
|
|
14731
15468
|
function safeBody(req) {
|
|
14732
15469
|
const raw = req.body;
|
|
14733
15470
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
@@ -14741,6 +15478,52 @@ function safeBody(req) {
|
|
|
14741
15478
|
return out;
|
|
14742
15479
|
}
|
|
14743
15480
|
__name(safeBody, "safeBody");
|
|
15481
|
+
function parseOptionalWorkspaceCwd(body, boundWorkspace, res) {
|
|
15482
|
+
const hasCwd = "cwd" in body;
|
|
15483
|
+
if (hasCwd && typeof body["cwd"] !== "string") {
|
|
15484
|
+
res.status(400).json({ error: "`cwd` must be a string absolute path when provided" });
|
|
15485
|
+
return void 0;
|
|
15486
|
+
}
|
|
15487
|
+
if (hasCwd && body["cwd"].length > MAX_WORKSPACE_PATH_LENGTH) {
|
|
15488
|
+
res.status(400).json({
|
|
15489
|
+
error: `\`cwd\` exceeds the ${MAX_WORKSPACE_PATH_LENGTH}-character limit`
|
|
15490
|
+
});
|
|
15491
|
+
return void 0;
|
|
15492
|
+
}
|
|
15493
|
+
const cwd = hasCwd ? body["cwd"] : boundWorkspace;
|
|
15494
|
+
if (!path2.isAbsolute(cwd)) {
|
|
15495
|
+
res.status(400).json({ error: "`cwd` must be an absolute path when provided" });
|
|
15496
|
+
return void 0;
|
|
15497
|
+
}
|
|
15498
|
+
return cwd;
|
|
15499
|
+
}
|
|
15500
|
+
__name(parseOptionalWorkspaceCwd, "parseOptionalWorkspaceCwd");
|
|
15501
|
+
function parseClientIdHeader(req, res) {
|
|
15502
|
+
const raw = req.get(CLIENT_ID_HEADER);
|
|
15503
|
+
if (raw === void 0 || raw === "") return void 0;
|
|
15504
|
+
if (raw.length > MAX_CLIENT_ID_LENGTH || !CLIENT_ID_RE.test(raw)) {
|
|
15505
|
+
res.status(400).json({
|
|
15506
|
+
error: "`X-Qwen-Client-Id` must be a non-empty token of 128 characters or fewer",
|
|
15507
|
+
code: "invalid_client_id"
|
|
15508
|
+
});
|
|
15509
|
+
return null;
|
|
15510
|
+
}
|
|
15511
|
+
return raw;
|
|
15512
|
+
}
|
|
15513
|
+
__name(parseClientIdHeader, "parseClientIdHeader");
|
|
15514
|
+
function parsePermissionVoteBody(req, res) {
|
|
15515
|
+
const body = safeBody(req);
|
|
15516
|
+
const outcome = body["outcome"];
|
|
15517
|
+
if (!isValidOutcome(outcome)) {
|
|
15518
|
+
res.status(400).json({ error: INVALID_PERMISSION_OUTCOME_ERROR });
|
|
15519
|
+
return void 0;
|
|
15520
|
+
}
|
|
15521
|
+
return {
|
|
15522
|
+
...body,
|
|
15523
|
+
outcome
|
|
15524
|
+
};
|
|
15525
|
+
}
|
|
15526
|
+
__name(parsePermissionVoteBody, "parsePermissionVoteBody");
|
|
14744
15527
|
function isValidOutcome(raw) {
|
|
14745
15528
|
if (typeof raw !== "object" || raw === null) return false;
|
|
14746
15529
|
const obj = raw;
|
|
@@ -14748,11 +15531,43 @@ function isValidOutcome(raw) {
|
|
|
14748
15531
|
return obj["outcome"] === "selected" && typeof obj["optionId"] === "string" && obj["optionId"].length > 0;
|
|
14749
15532
|
}
|
|
14750
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");
|
|
14751
15566
|
function parseLastEventId(raw) {
|
|
14752
15567
|
if (typeof raw !== "string" || !/^\d+$/.test(raw)) {
|
|
14753
15568
|
if (typeof raw === "string" && raw.length > 0) {
|
|
14754
15569
|
writeStderrLine(
|
|
14755
|
-
`qwen serve: rejected Last-Event-ID
|
|
15570
|
+
`qwen serve: rejected Last-Event-ID ${safeLogValue(raw)} (not a decimal integer)`
|
|
14756
15571
|
);
|
|
14757
15572
|
}
|
|
14758
15573
|
return void 0;
|
|
@@ -14760,13 +15575,26 @@ function parseLastEventId(raw) {
|
|
|
14760
15575
|
const n = Number.parseInt(raw, 10);
|
|
14761
15576
|
if (!Number.isFinite(n) || n > Number.MAX_SAFE_INTEGER) {
|
|
14762
15577
|
writeStderrLine(
|
|
14763
|
-
`qwen serve: rejected Last-Event-ID
|
|
15578
|
+
`qwen serve: rejected Last-Event-ID ${safeLogValue(raw)} (exceeds Number.MAX_SAFE_INTEGER)`
|
|
14764
15579
|
);
|
|
14765
15580
|
return void 0;
|
|
14766
15581
|
}
|
|
14767
15582
|
return n;
|
|
14768
15583
|
}
|
|
14769
15584
|
__name(parseLastEventId, "parseLastEventId");
|
|
15585
|
+
function sendPermissionVoteError(res, err, ctx) {
|
|
15586
|
+
if (err instanceof InvalidPermissionOptionError) {
|
|
15587
|
+
res.status(400).json({
|
|
15588
|
+
error: err.message,
|
|
15589
|
+
code: "invalid_option_id",
|
|
15590
|
+
requestId: err.requestId,
|
|
15591
|
+
optionId: err.optionId
|
|
15592
|
+
});
|
|
15593
|
+
return;
|
|
15594
|
+
}
|
|
15595
|
+
sendBridgeError(res, err, ctx);
|
|
15596
|
+
}
|
|
15597
|
+
__name(sendPermissionVoteError, "sendPermissionVoteError");
|
|
14770
15598
|
function formatSseFrame(event) {
|
|
14771
15599
|
const dataJson = JSON.stringify(event);
|
|
14772
15600
|
const idLine = "id" in event && event.id !== void 0 ? `id: ${event.id}
|
|
@@ -14782,6 +15610,15 @@ function sendBridgeError(res, err, ctx) {
|
|
|
14782
15610
|
res.status(404).json({ error: err.message, sessionId: err.sessionId });
|
|
14783
15611
|
return;
|
|
14784
15612
|
}
|
|
15613
|
+
if (err instanceof InvalidClientIdError) {
|
|
15614
|
+
res.status(400).json({
|
|
15615
|
+
error: err.message,
|
|
15616
|
+
code: "invalid_client_id",
|
|
15617
|
+
sessionId: err.sessionId,
|
|
15618
|
+
clientId: err.clientId
|
|
15619
|
+
});
|
|
15620
|
+
return;
|
|
15621
|
+
}
|
|
14785
15622
|
if (err instanceof WorkspaceMismatchError) {
|
|
14786
15623
|
writeStderrLine(
|
|
14787
15624
|
`qwen serve: workspace_mismatch (POST /session): daemon bound to ${JSON.stringify(err.bound)}, rejected ${JSON.stringify(err.requested)}`
|
|
@@ -14794,6 +15631,21 @@ function sendBridgeError(res, err, ctx) {
|
|
|
14794
15631
|
});
|
|
14795
15632
|
return;
|
|
14796
15633
|
}
|
|
15634
|
+
if (err instanceof InvalidSessionScopeError) {
|
|
15635
|
+
res.status(400).json({
|
|
15636
|
+
error: err.message,
|
|
15637
|
+
code: "invalid_session_scope"
|
|
15638
|
+
});
|
|
15639
|
+
return;
|
|
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
|
+
}
|
|
14797
15649
|
if (err instanceof SessionLimitExceededError) {
|
|
14798
15650
|
res.set("Retry-After", "5");
|
|
14799
15651
|
res.status(503).json({
|
|
@@ -14803,6 +15655,17 @@ function sendBridgeError(res, err, ctx) {
|
|
|
14803
15655
|
});
|
|
14804
15656
|
return;
|
|
14805
15657
|
}
|
|
15658
|
+
if (err instanceof RestoreInProgressError) {
|
|
15659
|
+
res.set("Retry-After", "5");
|
|
15660
|
+
res.status(409).json({
|
|
15661
|
+
error: err.message,
|
|
15662
|
+
code: "restore_in_progress",
|
|
15663
|
+
sessionId: err.sessionId,
|
|
15664
|
+
activeAction: err.activeAction,
|
|
15665
|
+
requestedAction: err.requestedAction
|
|
15666
|
+
});
|
|
15667
|
+
return;
|
|
15668
|
+
}
|
|
14806
15669
|
const ctxParts = [
|
|
14807
15670
|
ctx?.route,
|
|
14808
15671
|
ctx?.sessionId ? `session=${ctx.sessionId}` : void 0
|
|
@@ -14870,6 +15733,11 @@ async function runQwenServe(optsIn, deps = {}) {
|
|
|
14870
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]).`
|
|
14871
15734
|
);
|
|
14872
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
|
+
}
|
|
14873
15741
|
const rawWorkspace = opts.workspace ?? process.cwd();
|
|
14874
15742
|
if (!path3.isAbsolute(rawWorkspace)) {
|
|
14875
15743
|
throw new Error(
|
|
@@ -14902,6 +15770,7 @@ async function runQwenServe(optsIn, deps = {}) {
|
|
|
14902
15770
|
const boundWorkspace = canonicalizeWorkspace(rawWorkspace);
|
|
14903
15771
|
const bridge = deps.bridge ?? createHttpAcpBridge({
|
|
14904
15772
|
maxSessions: opts.maxSessions,
|
|
15773
|
+
...opts.eventRingSize !== void 0 ? { eventRingSize: opts.eventRingSize } : {},
|
|
14905
15774
|
boundWorkspace
|
|
14906
15775
|
});
|
|
14907
15776
|
let actualPort = opts.port;
|
|
@@ -14950,6 +15819,10 @@ async function runQwenServe(optsIn, deps = {}) {
|
|
|
14950
15819
|
writeStderrLine(
|
|
14951
15820
|
`qwen serve: bearer auth disabled (loopback default). Set ${QWEN_SERVER_TOKEN_ENV} to enable.`
|
|
14952
15821
|
);
|
|
15822
|
+
} else if (opts.requireAuth) {
|
|
15823
|
+
writeStderrLine(
|
|
15824
|
+
"qwen serve: --require-auth enabled (bearer token mandatory on every route, including loopback /health)."
|
|
15825
|
+
);
|
|
14953
15826
|
}
|
|
14954
15827
|
let shuttingDown = false;
|
|
14955
15828
|
let closePromise;
|
|
@@ -15064,14 +15937,32 @@ function createInMemoryChannel() {
|
|
|
15064
15937
|
__name(createInMemoryChannel, "createInMemoryChannel");
|
|
15065
15938
|
export {
|
|
15066
15939
|
CAPABILITIES_SCHEMA_VERSION,
|
|
15940
|
+
CONDITIONAL_SERVE_FEATURES,
|
|
15067
15941
|
EVENT_SCHEMA_VERSION,
|
|
15068
15942
|
EventBus,
|
|
15943
|
+
SERVE_CAPABILITY_REGISTRY,
|
|
15944
|
+
SERVE_FEATURES,
|
|
15945
|
+
SERVE_PROTOCOL_VERSION,
|
|
15946
|
+
SERVE_STATUS_EXT_METHODS,
|
|
15069
15947
|
STAGE1_FEATURES,
|
|
15948
|
+
STATUS_SCHEMA_VERSION,
|
|
15949
|
+
SUPPORTED_SERVE_PROTOCOL_VERSIONS,
|
|
15070
15950
|
SessionNotFoundError,
|
|
15951
|
+
bearerAuth,
|
|
15071
15952
|
createHttpAcpBridge,
|
|
15953
|
+
createIdleWorkspaceMcpStatus,
|
|
15954
|
+
createIdleWorkspaceProvidersStatus,
|
|
15955
|
+
createIdleWorkspaceSkillsStatus,
|
|
15072
15956
|
createInMemoryChannel,
|
|
15957
|
+
createMutationGate,
|
|
15073
15958
|
createServeApp,
|
|
15074
15959
|
defaultSpawnChannelFactory,
|
|
15960
|
+
denyBrowserOriginCors,
|
|
15961
|
+
getAdvertisedServeFeatures,
|
|
15962
|
+
getRegisteredServeFeatures,
|
|
15963
|
+
getServeFeatures,
|
|
15964
|
+
getServeProtocolVersions,
|
|
15965
|
+
hostAllowlist,
|
|
15075
15966
|
runQwenServe
|
|
15076
15967
|
};
|
|
15077
15968
|
/**
|