atomix-cli 1.2.0 → 1.3.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/dist/tui.mjs CHANGED
@@ -7941,7 +7941,7 @@ var require_react_reconciler_development = __commonJS({
7941
7941
  var HostPortal = 4;
7942
7942
  var HostComponent = 5;
7943
7943
  var HostText = 6;
7944
- var Fragment2 = 7;
7944
+ var Fragment = 7;
7945
7945
  var Mode = 8;
7946
7946
  var ContextConsumer = 9;
7947
7947
  var ContextProvider = 10;
@@ -8081,7 +8081,7 @@ var require_react_reconciler_development = __commonJS({
8081
8081
  return "DehydratedFragment";
8082
8082
  case ForwardRef:
8083
8083
  return getWrappedName$1(type, type.render, "ForwardRef");
8084
- case Fragment2:
8084
+ case Fragment:
8085
8085
  return "Fragment";
8086
8086
  case HostComponent:
8087
8087
  return type;
@@ -11215,7 +11215,7 @@ var require_react_reconciler_development = __commonJS({
11215
11215
  }
11216
11216
  }
11217
11217
  function updateFragment2(returnFiber, current2, fragment, lanes, key) {
11218
- if (current2 === null || current2.tag !== Fragment2) {
11218
+ if (current2 === null || current2.tag !== Fragment) {
11219
11219
  var created = createFiberFromFragment(fragment, returnFiber.mode, lanes, key);
11220
11220
  created.return = returnFiber;
11221
11221
  return created;
@@ -11618,7 +11618,7 @@ var require_react_reconciler_development = __commonJS({
11618
11618
  if (child.key === key) {
11619
11619
  var elementType = element.type;
11620
11620
  if (elementType === REACT_FRAGMENT_TYPE) {
11621
- if (child.tag === Fragment2) {
11621
+ if (child.tag === Fragment) {
11622
11622
  deleteRemainingChildren(returnFiber, child.sibling);
11623
11623
  var existing = useFiber(child, element.props.children);
11624
11624
  existing.return = returnFiber;
@@ -17109,7 +17109,7 @@ var require_react_reconciler_development = __commonJS({
17109
17109
  var _resolvedProps2 = workInProgress2.elementType === type ? _unresolvedProps2 : resolveDefaultProps(type, _unresolvedProps2);
17110
17110
  return updateForwardRef(current2, workInProgress2, type, _resolvedProps2, renderLanes2);
17111
17111
  }
17112
- case Fragment2:
17112
+ case Fragment:
17113
17113
  return updateFragment(current2, workInProgress2, renderLanes2);
17114
17114
  case Mode:
17115
17115
  return updateMode(current2, workInProgress2, renderLanes2);
@@ -17546,7 +17546,7 @@ var require_react_reconciler_development = __commonJS({
17546
17546
  case SimpleMemoComponent:
17547
17547
  case FunctionComponent:
17548
17548
  case ForwardRef:
17549
- case Fragment2:
17549
+ case Fragment:
17550
17550
  case Mode:
17551
17551
  case Profiler:
17552
17552
  case ContextConsumer:
@@ -22314,7 +22314,7 @@ var require_react_reconciler_development = __commonJS({
22314
22314
  return fiber;
22315
22315
  }
22316
22316
  function createFiberFromFragment(elements, mode, lanes, key) {
22317
- var fiber = createFiber(Fragment2, elements, key, mode);
22317
+ var fiber = createFiber(Fragment, elements, key, mode);
22318
22318
  fiber.lanes = lanes;
22319
22319
  return fiber;
22320
22320
  }
@@ -23358,7 +23358,7 @@ var require_permessage_deflate = __commonJS({
23358
23358
  acceptAsServer(offers) {
23359
23359
  const opts = this._options;
23360
23360
  const accepted = offers.find((params) => {
23361
- if (opts.serverNoContextTakeover === false && params.server_no_context_takeover || params.server_max_window_bits && (opts.serverMaxWindowBits === false || typeof opts.serverMaxWindowBits === "number" && opts.serverMaxWindowBits > params.server_max_window_bits) || typeof opts.clientMaxWindowBits === "number" && !params.client_max_window_bits) {
23361
+ if (opts.serverNoContextTakeover === false && params.server_no_context_takeover || params.server_max_window_bits && (opts.serverMaxWindowBits === false || typeof opts.serverMaxWindowBits === "number" && opts.serverMaxWindowBits > params.server_max_window_bits) || typeof opts.clientMaxWindowBits === "number" && (typeof params.client_max_window_bits === "number" ? opts.clientMaxWindowBits > params.client_max_window_bits : !params.client_max_window_bits)) {
23362
23362
  return false;
23363
23363
  }
23364
23364
  return true;
@@ -23878,6 +23878,7 @@ var require_receiver = __commonJS({
23878
23878
  this._opcode = 0;
23879
23879
  this._totalPayloadLength = 0;
23880
23880
  this._messageLength = 0;
23881
+ this._numFragments = 0;
23881
23882
  this._fragments = [];
23882
23883
  this._errored = false;
23883
23884
  this._loop = false;
@@ -24228,23 +24229,23 @@ var require_receiver = __commonJS({
24228
24229
  this.controlMessage(data, cb);
24229
24230
  return;
24230
24231
  }
24232
+ if (this._maxFragments > 0 && ++this._numFragments > this._maxFragments) {
24233
+ const error = this.createError(
24234
+ RangeError,
24235
+ "Too many message fragments",
24236
+ false,
24237
+ 1008,
24238
+ "WS_ERR_TOO_MANY_BUFFERED_PARTS"
24239
+ );
24240
+ cb(error);
24241
+ return;
24242
+ }
24231
24243
  if (this._compressed) {
24232
24244
  this._state = INFLATING;
24233
24245
  this.decompress(data, cb);
24234
24246
  return;
24235
24247
  }
24236
24248
  if (data.length) {
24237
- if (this._maxFragments > 0 && this._fragments.length >= this._maxFragments) {
24238
- const error = this.createError(
24239
- RangeError,
24240
- "Too many message fragments",
24241
- false,
24242
- 1008,
24243
- "WS_ERR_TOO_MANY_BUFFERED_PARTS"
24244
- );
24245
- cb(error);
24246
- return;
24247
- }
24248
24249
  this._messageLength = this._totalPayloadLength;
24249
24250
  this._fragments.push(data);
24250
24251
  }
@@ -24274,17 +24275,6 @@ var require_receiver = __commonJS({
24274
24275
  cb(error);
24275
24276
  return;
24276
24277
  }
24277
- if (this._maxFragments > 0 && this._fragments.length >= this._maxFragments) {
24278
- const error = this.createError(
24279
- RangeError,
24280
- "Too many message fragments",
24281
- false,
24282
- 1008,
24283
- "WS_ERR_TOO_MANY_BUFFERED_PARTS"
24284
- );
24285
- cb(error);
24286
- return;
24287
- }
24288
24278
  this._fragments.push(buf);
24289
24279
  }
24290
24280
  this.dataMessage(cb);
@@ -24307,6 +24297,7 @@ var require_receiver = __commonJS({
24307
24297
  this._totalPayloadLength = 0;
24308
24298
  this._messageLength = 0;
24309
24299
  this._fragmented = 0;
24300
+ this._numFragments = 0;
24310
24301
  this._fragments = [];
24311
24302
  if (this._opcode === 2) {
24312
24303
  let data;
@@ -25807,8 +25798,8 @@ var require_websocket = __commonJS({
25807
25798
  autoPong: true,
25808
25799
  closeTimeout: CLOSE_TIMEOUT,
25809
25800
  protocolVersion: protocolVersions[1],
25810
- maxBufferedChunks: 1024 * 1024,
25811
- maxFragments: 128 * 1024,
25801
+ maxBufferedChunks: 256 * 1024,
25802
+ maxFragments: 16 * 1024,
25812
25803
  maxPayload: 100 * 1024 * 1024,
25813
25804
  skipUTF8Validation: false,
25814
25805
  perMessageDeflate: true,
@@ -26395,9 +26386,9 @@ var require_websocket_server = __commonJS({
26395
26386
  * called
26396
26387
  * @param {Function} [options.handleProtocols] A hook to handle protocols
26397
26388
  * @param {String} [options.host] The hostname where to bind the server
26398
- * @param {Number} [options.maxBufferedChunks=1048576] The maximum number of
26389
+ * @param {Number} [options.maxBufferedChunks=262144] The maximum number of
26399
26390
  * buffered data chunks
26400
- * @param {Number} [options.maxFragments=131072] The maximum number of message
26391
+ * @param {Number} [options.maxFragments=16384] The maximum number of message
26401
26392
  * fragments
26402
26393
  * @param {Number} [options.maxPayload=104857600] The maximum allowed message
26403
26394
  * size
@@ -26420,8 +26411,8 @@ var require_websocket_server = __commonJS({
26420
26411
  options2 = {
26421
26412
  allowSynchronousEvents: true,
26422
26413
  autoPong: true,
26423
- maxBufferedChunks: 1024 * 1024,
26424
- maxFragments: 128 * 1024,
26414
+ maxBufferedChunks: 256 * 1024,
26415
+ maxFragments: 16 * 1024,
26425
26416
  maxPayload: 100 * 1024 * 1024,
26426
26417
  skipUTF8Validation: false,
26427
26418
  perMessageDeflate: false,
@@ -27772,7 +27763,7 @@ var require_config = __commonJS({
27772
27763
  exports2.LOG_DIR_PATH = "logs";
27773
27764
  exports2.SERVICE_LOG_FILES_RETAIN_COUNT = 7;
27774
27765
  exports2.LLM_LOG_DIR_PATH = "llm_logs";
27775
- exports2.LLM_LOG_FILES_RETAIN_COUNT = 10;
27766
+ exports2.LLM_LOG_FILES_RETAIN_COUNT = 50;
27776
27767
  exports2.LLM_LOG_CLEANUP_INTERVAL = 60 * 60 * 1e3;
27777
27768
  exports2.TRACKS_DIR_PATH = "tracks";
27778
27769
  exports2.TRACKS_FILES_RETAIN_COUNT = 30;
@@ -45120,9 +45111,11 @@ var require_logLLM = __commonJS({
45120
45111
  };
45121
45112
  })();
45122
45113
  Object.defineProperty(exports2, "__esModule", { value: true });
45123
- exports2.logEvent = exports2.logLLMResponse = exports2.logLLMRequest = void 0;
45114
+ exports2.logEvent = exports2.cleanupLLMLogFiles = exports2.logLLMResponse = exports2.logLLMRequest = exports2.readActiveLLMLogSessionsAcrossProcesses = exports2.getActiveLLMLogSessions = exports2.acquireActiveLLMLogSession = exports2.REGISTER_TIMEOUT_MS = exports2.holdLLMLogLockForTest = exports2.withLLMLogLock = void 0;
45124
45115
  var fs14 = __importStar(__require("fs"));
45125
45116
  var path15 = __importStar(__require("path"));
45117
+ var crypto_1 = __require("crypto");
45118
+ var child_process_1 = __require("child_process");
45126
45119
  var time_1 = require_time();
45127
45120
  var config_1 = require_config();
45128
45121
  var savePath_1 = require_savePath();
@@ -45131,6 +45124,280 @@ var require_logLLM = __commonJS({
45131
45124
  var StateManager_1 = require_StateManager();
45132
45125
  var lastLLMCleanupTime = 0;
45133
45126
  var lastEventCleanupTime = 0;
45127
+ var LOCK_DIR = ".lock";
45128
+ var LOCK_POLL_MS = 15;
45129
+ var getLockDir = () => path15.join((0, savePath_1.getLLMLogsDir)(), LOCK_DIR);
45130
+ var isPidAlive = (pid) => {
45131
+ if (!Number.isInteger(pid) || pid <= 0)
45132
+ return false;
45133
+ try {
45134
+ process.kill(pid, 0);
45135
+ return true;
45136
+ } catch (err) {
45137
+ return err.code !== "ESRCH";
45138
+ }
45139
+ };
45140
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
45141
+ var ticketPid = (name) => {
45142
+ const match = /^(\d+)-[0-9a-f-]+\.ticket$/.exec(name);
45143
+ return match ? Number(match[1]) : null;
45144
+ };
45145
+ var PID_REUSE_GRACE_MS = 2e3;
45146
+ var PID_START_CACHE_MS = 1e4;
45147
+ var pidStartCache = /* @__PURE__ */ new Map();
45148
+ var pidStartTimeMs = (pid) => {
45149
+ const cached = pidStartCache.get(pid);
45150
+ if (cached && Date.now() - cached.at < PID_START_CACHE_MS)
45151
+ return cached.startMs;
45152
+ let startMs = null;
45153
+ try {
45154
+ if (process.platform === "linux") {
45155
+ const stat = fs14.readFileSync(`/proc/${pid}/stat`, "utf8");
45156
+ const fields = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
45157
+ const startTicks = Number(fields[19]);
45158
+ const btime = Number(/^btime (\d+)$/m.exec(fs14.readFileSync("/proc/stat", "utf8"))?.[1]);
45159
+ if (Number.isFinite(startTicks) && Number.isFinite(btime))
45160
+ startMs = (btime + startTicks / 100) * 1e3;
45161
+ } else if (process.platform !== "win32") {
45162
+ const out = (0, child_process_1.execFileSync)("ps", ["-o", "lstart=", "-p", String(pid)], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 2e3 }).trim();
45163
+ const t = Date.parse(out);
45164
+ if (Number.isFinite(t))
45165
+ startMs = t;
45166
+ }
45167
+ } catch {
45168
+ }
45169
+ pidStartCache.set(pid, { startMs, at: Date.now() });
45170
+ return startMs;
45171
+ };
45172
+ var fileBirthMs = (file) => {
45173
+ const st = fs14.statSync(file);
45174
+ return st.birthtimeMs > 0 ? st.birthtimeMs : st.mtimeMs;
45175
+ };
45176
+ var isTicketOwnerDead = (pid, file) => {
45177
+ if (!isPidAlive(pid))
45178
+ return true;
45179
+ if (pid === process.pid)
45180
+ return false;
45181
+ const startMs = pidStartTimeMs(pid);
45182
+ if (startMs === null)
45183
+ return false;
45184
+ try {
45185
+ return startMs - fileBirthMs(file) > PID_REUSE_GRACE_MS;
45186
+ } catch {
45187
+ return false;
45188
+ }
45189
+ };
45190
+ var readTickets = (dir) => {
45191
+ const tickets = [];
45192
+ for (const name of fs14.readdirSync(dir)) {
45193
+ const pid = ticketPid(name);
45194
+ if (pid === null)
45195
+ continue;
45196
+ const file = path15.join(dir, name);
45197
+ if (isTicketOwnerDead(pid, file)) {
45198
+ fs14.rmSync(file, { force: true });
45199
+ continue;
45200
+ }
45201
+ try {
45202
+ const value = fs14.readFileSync(file, "utf8");
45203
+ tickets.push({ name, number: /^[1-9]\d*$/.test(value) ? BigInt(value) : null });
45204
+ } catch (err) {
45205
+ if (err.code !== "ENOENT")
45206
+ throw err;
45207
+ }
45208
+ }
45209
+ return tickets;
45210
+ };
45211
+ var acquireLock = async (timeoutMs, signal) => {
45212
+ if (signal?.aborted)
45213
+ throw new Error("\u767B\u8BB0\u5DF2\u53D6\u6D88");
45214
+ const dir = getLockDir();
45215
+ fs14.mkdirSync(dir, { recursive: true });
45216
+ const ticket = `${process.pid}-${(0, crypto_1.randomUUID)()}.ticket`;
45217
+ const mine = path15.join(dir, ticket);
45218
+ const staging = `${mine}.publishing`;
45219
+ fs14.closeSync(fs14.openSync(mine, "wx"));
45220
+ let acquired = false;
45221
+ const release = () => {
45222
+ fs14.rmSync(mine, { force: true });
45223
+ fs14.rmSync(staging, { force: true });
45224
+ };
45225
+ const started = process.hrtime.bigint();
45226
+ try {
45227
+ const number = readTickets(dir).reduce((max2, t) => t.number !== null && t.number > max2 ? t.number : max2, 0n) + 1n;
45228
+ fs14.writeFileSync(staging, String(number), { flag: "wx" });
45229
+ fs14.renameSync(staging, mine);
45230
+ for (; ; ) {
45231
+ if (signal?.aborted)
45232
+ throw new Error("\u767B\u8BB0\u5DF2\u53D6\u6D88");
45233
+ const tickets = readTickets(dir);
45234
+ if (!tickets.some((t) => t.name === ticket))
45235
+ throw new Error("LLM \u65E5\u5FD7\u9501\u7968\u636E\u4E22\u5931");
45236
+ const blocked = tickets.some((t) => t.name !== ticket && (t.number === null || t.number < number || t.number === number && t.name < ticket));
45237
+ if (!blocked) {
45238
+ acquired = true;
45239
+ return { ticket, release };
45240
+ }
45241
+ if (Number(process.hrtime.bigint() - started) / 1e6 >= timeoutMs)
45242
+ return null;
45243
+ await sleep(LOCK_POLL_MS);
45244
+ }
45245
+ } finally {
45246
+ if (!acquired)
45247
+ release();
45248
+ }
45249
+ };
45250
+ var withLLMLogLock = async (fn, timeoutMs = 5e3, signal) => {
45251
+ const lock = await acquireLock(timeoutMs, signal);
45252
+ if (!lock)
45253
+ return null;
45254
+ try {
45255
+ if (signal?.aborted)
45256
+ throw new Error("\u767B\u8BB0\u5DF2\u53D6\u6D88");
45257
+ return await fn();
45258
+ } finally {
45259
+ lock.release();
45260
+ }
45261
+ };
45262
+ exports2.withLLMLogLock = withLLMLogLock;
45263
+ var holdLLMLogLockForTest = async (timeoutMs = 1e3) => {
45264
+ const lock = await acquireLock(timeoutMs);
45265
+ return lock ? { ticketPath: path15.join(getLockDir(), lock.ticket), release: lock.release } : null;
45266
+ };
45267
+ exports2.holdLLMLogLockForTest = holdLLMLogLockForTest;
45268
+ var ACTIVE_MARK_DIR = ".active";
45269
+ exports2.REGISTER_TIMEOUT_MS = 3e4;
45270
+ var getActiveMarkDir = () => path15.join((0, savePath_1.getLLMLogsDir)(), ACTIVE_MARK_DIR);
45271
+ var encodeSessionId = (sessionId) => encodeURIComponent(sessionId.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD"));
45272
+ var decodeSessionId = (encoded) => {
45273
+ try {
45274
+ return decodeURIComponent(encoded);
45275
+ } catch {
45276
+ return null;
45277
+ }
45278
+ };
45279
+ var markFilePath = (sessionId) => path15.join(getActiveMarkDir(), `${encodeSessionId(sessionId)}.${process.pid}`);
45280
+ var registrations = /* @__PURE__ */ new Map();
45281
+ var removeMark = (sessionId) => {
45282
+ try {
45283
+ fs14.rmSync(markFilePath(sessionId), { force: true });
45284
+ } catch {
45285
+ }
45286
+ };
45287
+ var acquireActiveLLMLogSession = async (sessionId, opts = {}) => {
45288
+ if (opts.signal?.aborted)
45289
+ throw new Error("\u767B\u8BB0\u5DF2\u53D6\u6D88");
45290
+ let reg = registrations.get(sessionId);
45291
+ if (!reg) {
45292
+ const created = { refs: 0, state: "registering", ready: Promise.resolve(), abort: new AbortController() };
45293
+ created.ready = (async () => {
45294
+ const done = await (0, exports2.withLLMLogLock)(() => {
45295
+ if (created.refs === 0)
45296
+ return "abandoned";
45297
+ fs14.mkdirSync(getActiveMarkDir(), { recursive: true });
45298
+ try {
45299
+ fs14.writeFileSync(markFilePath(sessionId), String(process.pid), "utf8");
45300
+ } catch (err) {
45301
+ removeMark(sessionId);
45302
+ throw err;
45303
+ }
45304
+ created.state = "active";
45305
+ return "written";
45306
+ }, opts.timeoutMs ?? exports2.REGISTER_TIMEOUT_MS, created.abort.signal);
45307
+ if (done === null)
45308
+ throw new Error(`LLM \u65E5\u5FD7\u6E05\u7406\u9501\u7B49\u5F85\u8D85\u65F6(${opts.timeoutMs ?? exports2.REGISTER_TIMEOUT_MS}ms),\u4F1A\u8BDD ${sessionId} \u672A\u80FD\u767B\u8BB0\u4E3A\u6D3B\u8DC3`);
45309
+ if (done === "abandoned")
45310
+ return;
45311
+ })();
45312
+ void created.ready.then(() => {
45313
+ if (created.refs === 0 && registrations.get(sessionId) === created)
45314
+ registrations.delete(sessionId);
45315
+ }, () => {
45316
+ if (registrations.get(sessionId) === created)
45317
+ registrations.delete(sessionId);
45318
+ });
45319
+ registrations.set(sessionId, created);
45320
+ reg = created;
45321
+ }
45322
+ const mine = reg;
45323
+ mine.refs++;
45324
+ let released = false;
45325
+ const release = () => {
45326
+ if (released)
45327
+ return;
45328
+ released = true;
45329
+ mine.refs--;
45330
+ if (mine.refs > 0)
45331
+ return;
45332
+ mine.abort.abort();
45333
+ if (mine.state === "active")
45334
+ removeMark(sessionId);
45335
+ if (registrations.get(sessionId) === mine)
45336
+ registrations.delete(sessionId);
45337
+ };
45338
+ if (opts.signal?.aborted) {
45339
+ release();
45340
+ throw new Error("\u767B\u8BB0\u5DF2\u53D6\u6D88");
45341
+ }
45342
+ let rejectAbort;
45343
+ const aborted = new Promise((_, reject2) => {
45344
+ rejectAbort = reject2;
45345
+ });
45346
+ const onAbort = () => {
45347
+ release();
45348
+ rejectAbort(new Error("\u767B\u8BB0\u5DF2\u53D6\u6D88"));
45349
+ };
45350
+ opts.signal?.addEventListener("abort", onAbort, { once: true });
45351
+ try {
45352
+ await Promise.race([mine.ready, aborted]);
45353
+ } catch (err) {
45354
+ release();
45355
+ throw err;
45356
+ } finally {
45357
+ opts.signal?.removeEventListener("abort", onAbort);
45358
+ }
45359
+ if (released)
45360
+ throw new Error("\u767B\u8BB0\u5DF2\u53D6\u6D88");
45361
+ return { sessionId, release };
45362
+ };
45363
+ exports2.acquireActiveLLMLogSession = acquireActiveLLMLogSession;
45364
+ var getActiveLLMLogSessions = () => [...registrations.entries()].filter(([, r]) => r.state === "active").map(([id]) => id);
45365
+ exports2.getActiveLLMLogSessions = getActiveLLMLogSessions;
45366
+ var readActiveLLMLogSessionsAcrossProcesses = () => {
45367
+ const active = new Set((0, exports2.getActiveLLMLogSessions)());
45368
+ const dir = getActiveMarkDir();
45369
+ let names;
45370
+ try {
45371
+ names = fs14.readdirSync(dir);
45372
+ } catch (err) {
45373
+ if (err.code === "ENOENT")
45374
+ return active;
45375
+ throw err;
45376
+ }
45377
+ for (const name of names) {
45378
+ const dot = name.lastIndexOf(".");
45379
+ if (dot <= 0)
45380
+ continue;
45381
+ const sessionId = decodeSessionId(name.slice(0, dot));
45382
+ const pid = Number(name.slice(dot + 1));
45383
+ if (sessionId === null)
45384
+ continue;
45385
+ if (isPidAlive(pid)) {
45386
+ active.add(sessionId);
45387
+ } else {
45388
+ try {
45389
+ fs14.rmSync(path15.join(dir, name), { force: true });
45390
+ } catch {
45391
+ }
45392
+ }
45393
+ }
45394
+ return active;
45395
+ };
45396
+ exports2.readActiveLLMLogSessionsAcrossProcesses = readActiveLLMLogSessionsAcrossProcesses;
45397
+ var sessionIdOfLogFile = (name) => {
45398
+ const m = /^\d{4}-\d{2}-\d{2}_(.+)\.log$/.exec(name);
45399
+ return m ? m[1] : null;
45400
+ };
45134
45401
  var getProjectName = () => {
45135
45402
  try {
45136
45403
  const coreConfig = (0, ConfManager_1.getConfManager)().getCoreConfig();
@@ -45165,7 +45432,7 @@ var require_logLLM = __commonJS({
45165
45432
  if (nowTimestamp - lastLLMCleanupTime > config_1.LLM_LOG_CLEANUP_INTERVAL) {
45166
45433
  lastLLMCleanupTime = nowTimestamp;
45167
45434
  setImmediate(() => {
45168
- cleanupLLMLogFiles();
45435
+ void (0, exports2.cleanupLLMLogFiles)();
45169
45436
  });
45170
45437
  }
45171
45438
  } catch (err) {
@@ -45202,7 +45469,7 @@ var require_logLLM = __commonJS({
45202
45469
  if (nowTimestamp - lastLLMCleanupTime > config_1.LLM_LOG_CLEANUP_INTERVAL) {
45203
45470
  lastLLMCleanupTime = nowTimestamp;
45204
45471
  setImmediate(() => {
45205
- cleanupLLMLogFiles();
45472
+ void (0, exports2.cleanupLLMLogFiles)();
45206
45473
  });
45207
45474
  }
45208
45475
  } catch (err) {
@@ -45320,25 +45587,35 @@ var require_logLLM = __commonJS({
45320
45587
  } catch (err) {
45321
45588
  }
45322
45589
  };
45323
- var cleanupLLMLogFiles = () => {
45590
+ var cleanupLLMLogFiles = async () => {
45324
45591
  try {
45325
45592
  const llmLogsDir = (0, savePath_1.getLLMLogsDir)();
45326
45593
  if (!fs14.existsSync(llmLogsDir)) {
45327
45594
  return;
45328
45595
  }
45329
- const files = fs14.readdirSync(llmLogsDir).filter((file) => file.endsWith(".log")).map((file) => ({
45330
- name: file,
45331
- path: path15.join(llmLogsDir, file),
45332
- mtime: fs14.statSync(path15.join(llmLogsDir, file)).mtime
45333
- })).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
45334
- if (files.length > config_1.LLM_LOG_FILES_RETAIN_COUNT) {
45335
- const filesToArchive = files.slice(config_1.LLM_LOG_FILES_RETAIN_COUNT);
45336
- archiveLLMLogFiles(filesToArchive);
45337
- }
45596
+ const done = await (0, exports2.withLLMLogLock)(() => cleanupLLMLogFilesLocked(llmLogsDir));
45597
+ if (done === null)
45598
+ (0, log_1.logError)("LLM \u65E5\u5FD7\u6E05\u7406\u9501\u7B49\u5F85\u8D85\u65F6,\u672C\u8F6E\u8DF3\u8FC7");
45338
45599
  } catch (err) {
45339
45600
  (0, log_1.logError)(`\u6E05\u7406LLM\u65E5\u5FD7\u6587\u4EF6\u51FA\u9519: ${err}`);
45340
45601
  }
45341
45602
  };
45603
+ exports2.cleanupLLMLogFiles = cleanupLLMLogFiles;
45604
+ var cleanupLLMLogFilesLocked = (llmLogsDir) => {
45605
+ const active = (0, exports2.readActiveLLMLogSessionsAcrossProcesses)();
45606
+ const files = fs14.readdirSync(llmLogsDir).filter((file) => file.endsWith(".log")).filter((file) => {
45607
+ const sid = sessionIdOfLogFile(file);
45608
+ return sid === null || !active.has(sid);
45609
+ }).map((file) => ({
45610
+ name: file,
45611
+ path: path15.join(llmLogsDir, file),
45612
+ mtime: fs14.statSync(path15.join(llmLogsDir, file)).mtime
45613
+ })).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
45614
+ if (files.length > config_1.LLM_LOG_FILES_RETAIN_COUNT) {
45615
+ const filesToArchive = files.slice(config_1.LLM_LOG_FILES_RETAIN_COUNT);
45616
+ archiveLLMLogFiles(filesToArchive);
45617
+ }
45618
+ };
45342
45619
  var getEventLogFilePath = () => {
45343
45620
  const dateStr = (0, time_1.getDayTimeString)();
45344
45621
  const eventDir = (0, savePath_1.getEventDir)();
@@ -46014,9 +46291,13 @@ var require_StateManager = __commonJS({
46014
46291
  }
46015
46292
  /**
46016
46293
  * 清空所有状态数据
46294
+ * @param options.keepAgentStates 保留各代理的 processing/idle 状态记录(/clear 在处理输入的过程中调用:
46295
+ * 主代理此刻是 processing,若连状态一起清掉,重建出来的记录默认 idle,随后的 updateState('idle')
46296
+ * 因"状态未变"不再发 state:update,等 idle 的消费方(session 库 send、TUI 处理态)会挂住)
46017
46297
  */
46018
- clearAllState() {
46019
- this.statesMap.clear();
46298
+ clearAllState(options2 = {}) {
46299
+ if (!options2.keepAgentStates)
46300
+ this.statesMap.clear();
46020
46301
  this.messageHistoryMap.clear();
46021
46302
  this.readFileTimestampsMap.clear();
46022
46303
  this.todosMap.clear();
@@ -50795,12 +51076,12 @@ var require_frontmatter = __commonJS({
50795
51076
  function parseFrontmatter(text) {
50796
51077
  const metadata = {};
50797
51078
  const length = text.length;
50798
- let lineStart = 0;
51079
+ let lineStart2 = 0;
50799
51080
  for (let i = 0; i <= length; i++) {
50800
51081
  if (i === length || text[i] === "\n") {
50801
- if (i > lineStart) {
50802
- const lineEnd = i > 0 && text[i - 1] === "\r" ? i - 1 : i;
50803
- const line = text.slice(lineStart, lineEnd);
51082
+ if (i > lineStart2) {
51083
+ const lineEnd2 = i > 0 && text[i - 1] === "\r" ? i - 1 : i;
51084
+ const line = text.slice(lineStart2, lineEnd2);
50804
51085
  if (line && line[0] !== "#") {
50805
51086
  const colonIndex = line.indexOf(":");
50806
51087
  if (colonIndex !== -1) {
@@ -50835,7 +51116,7 @@ var require_frontmatter = __commonJS({
50835
51116
  }
50836
51117
  }
50837
51118
  }
50838
- lineStart = i + 1;
51119
+ lineStart2 = i + 1;
50839
51120
  }
50840
51121
  }
50841
51122
  return metadata;
@@ -90817,7 +91098,7 @@ var require_lib3 = __commonJS({
90817
91098
  } catch (e) {
90818
91099
  }
90819
91100
  var INTERNALS = /* @__PURE__ */ Symbol("Body internals");
90820
- var PassThrough2 = Stream2.PassThrough;
91101
+ var PassThrough3 = Stream2.PassThrough;
90821
91102
  function Body(body) {
90822
91103
  var _this = this;
90823
91104
  var _ref = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}, _ref$size = _ref.size;
@@ -91069,8 +91350,8 @@ var require_lib3 = __commonJS({
91069
91350
  throw new Error("cannot clone body after it is used");
91070
91351
  }
91071
91352
  if (body instanceof Stream2 && typeof body.getBoundary !== "function") {
91072
- p1 = new PassThrough2();
91073
- p2 = new PassThrough2();
91353
+ p1 = new PassThrough3();
91354
+ p2 = new PassThrough3();
91074
91355
  body.pipe(p1);
91075
91356
  body.pipe(p2);
91076
91357
  instance[INTERNALS].body = p1;
@@ -132070,10 +132351,10 @@ ${value}`, dataLines++;
132070
132351
  for (; searchIndex < chunk2.length; ) {
132071
132352
  const crIndex = chunk2.indexOf("\r", searchIndex), lfIndex = chunk2.indexOf(`
132072
132353
  `, searchIndex);
132073
- let lineEnd = -1;
132074
- if (crIndex !== -1 && lfIndex !== -1 ? lineEnd = crIndex < lfIndex ? crIndex : lfIndex : crIndex !== -1 ? crIndex === chunk2.length - 1 ? lineEnd = -1 : lineEnd = crIndex : lfIndex !== -1 && (lineEnd = lfIndex), lineEnd === -1)
132354
+ let lineEnd2 = -1;
132355
+ if (crIndex !== -1 && lfIndex !== -1 ? lineEnd2 = crIndex < lfIndex ? crIndex : lfIndex : crIndex !== -1 ? crIndex === chunk2.length - 1 ? lineEnd2 = -1 : lineEnd2 = crIndex : lfIndex !== -1 && (lineEnd2 = lfIndex), lineEnd2 === -1)
132075
132356
  break;
132076
- parseLine(chunk2, searchIndex, lineEnd), searchIndex = lineEnd + 1, chunk2.charCodeAt(searchIndex - 1) === CR && chunk2.charCodeAt(searchIndex) === LF && searchIndex++;
132357
+ parseLine(chunk2, searchIndex, lineEnd2), searchIndex = lineEnd2 + 1, chunk2.charCodeAt(searchIndex - 1) === CR && chunk2.charCodeAt(searchIndex) === LF && searchIndex++;
132077
132358
  }
132078
132359
  return chunk2.slice(searchIndex);
132079
132360
  }
@@ -134538,7 +134819,10 @@ var require_mcpShared = __commonJS({
134538
134819
  })();
134539
134820
  Object.defineProperty(exports2, "__esModule", { value: true });
134540
134821
  exports2.mergeServerConfigs = mergeServerConfigs;
134822
+ exports2.originalToolName = originalToolName;
134541
134823
  exports2.filterToolsByConfig = filterToolsByConfig;
134824
+ exports2.isServerScopedOut = isServerScopedOut;
134825
+ exports2.applyScopeToTools = applyScopeToTools;
134542
134826
  exports2.configHash = configHash;
134543
134827
  exports2.isValidServerConfig = isValidServerConfig;
134544
134828
  exports2.loadConfigFile = loadConfigFile;
@@ -134563,15 +134847,37 @@ var require_mcpShared = __commonJS({
134563
134847
  }
134564
134848
  return merged;
134565
134849
  }
134566
- function filterToolsByConfig(tools, useTools) {
134850
+ function originalToolName(fullName, serverName) {
134851
+ if (serverName) {
134852
+ const prefix = `mcp__${serverName}__`;
134853
+ if (fullName.startsWith(prefix))
134854
+ return fullName.slice(prefix.length);
134855
+ }
134856
+ const parts = fullName.split("__");
134857
+ return parts.length >= 3 ? parts.slice(2).join("__") : fullName;
134858
+ }
134859
+ function filterToolsByConfig(tools, useTools, serverName) {
134567
134860
  if (!useTools) {
134568
134861
  return tools;
134569
134862
  }
134570
- return tools.filter((tool) => {
134571
- const parts = tool.name.split("__");
134572
- const originalToolName = parts.length >= 3 ? parts.slice(2).join("__") : tool.name;
134573
- return useTools.includes(originalToolName);
134574
- });
134863
+ return tools.filter((tool) => useTools.includes(originalToolName(tool.name, serverName)));
134864
+ }
134865
+ function isServerScopedOut(serverName, scope) {
134866
+ if (!scope)
134867
+ return false;
134868
+ if (scope.servers !== null && !scope.servers.includes(serverName))
134869
+ return true;
134870
+ return scope.disabledServers?.includes(serverName) ?? false;
134871
+ }
134872
+ function applyScopeToTools(serverName, tools, scope) {
134873
+ if (!scope)
134874
+ return tools;
134875
+ if (isServerScopedOut(serverName, scope))
134876
+ return [];
134877
+ const allow = scope.tools?.[serverName];
134878
+ if (!allow)
134879
+ return tools;
134880
+ return filterToolsByConfig(tools, allow, serverName);
134575
134881
  }
134576
134882
  function configHash(config) {
134577
134883
  const identity2 = {
@@ -134736,6 +135042,7 @@ var require_MCPManager = __commonJS({
134736
135042
  this.clients = /* @__PURE__ */ new Map();
134737
135043
  this.connectingPromises = /* @__PURE__ */ new Map();
134738
135044
  this.serverInfoCache = null;
135045
+ this.scope = null;
134739
135046
  this.globalConfigPath = globalConfigPath;
134740
135047
  this.projectConfigPath = projectConfigPath;
134741
135048
  }
@@ -134872,7 +135179,7 @@ var require_MCPManager = __commonJS({
134872
135179
  const mergedConfigs = this.getMergedConfigs();
134873
135180
  for (const [serverName, tools] of this.toolsCache.toolsByServer) {
134874
135181
  const config = mergedConfigs.get(serverName);
134875
- const filteredTools = this.filterToolsByConfig(tools, config?.useTools);
135182
+ const filteredTools = this.filterToolsByConfig(tools, config?.useTools, serverName);
134876
135183
  toolStats[serverName] = filteredTools.length;
134877
135184
  }
134878
135185
  (0, log_1.logInfo)(`MCP \u5DE5\u5177\u7EDF\u8BA1 (\u8FC7\u6EE4\u540E): ${JSON.stringify(toolStats)}`);
@@ -134969,8 +135276,8 @@ var require_MCPManager = __commonJS({
134969
135276
  /**
134970
135277
  * 根据 useTools 配置过滤工具
134971
135278
  */
134972
- filterToolsByConfig(tools, useTools) {
134973
- return (0, mcpShared_1.filterToolsByConfig)(tools, useTools);
135279
+ filterToolsByConfig(tools, useTools, serverName) {
135280
+ return (0, mcpShared_1.filterToolsByConfig)(tools, useTools, serverName);
134974
135281
  }
134975
135282
  /**
134976
135283
  * 获取所有 MCP 工具(同步,返回缓存)
@@ -134983,11 +135290,19 @@ var require_MCPManager = __commonJS({
134983
135290
  const mergedConfigs = this.getMergedConfigs();
134984
135291
  for (const [serverName, tools] of this.toolsCache.toolsByServer) {
134985
135292
  const config = mergedConfigs.get(serverName);
134986
- const filteredTools = this.filterToolsByConfig(tools, config?.useTools);
134987
- result2.push(...filteredTools);
135293
+ const filteredTools = this.filterToolsByConfig(tools, config?.useTools, serverName);
135294
+ result2.push(...(0, mcpShared_1.applyScopeToTools)(serverName, filteredTools, this.scope));
134988
135295
  }
134989
135296
  return result2;
134990
135297
  }
135298
+ /**
135299
+ * 设置 / 清除(null)会话级 MCP 可见性 overlay(harness `mcp` 段)。
135300
+ * 不写 mcp.json、不断连接;ServerInfo 缓存里带 scopedOut,故一并失效。
135301
+ */
135302
+ setScope(scope) {
135303
+ this.scope = scope;
135304
+ this.serverInfoCache = null;
135305
+ }
134991
135306
  /**
134992
135307
  * 获取或创建客户端连接
134993
135308
  */
@@ -135067,7 +135382,8 @@ var require_MCPManager = __commonJS({
135067
135382
  status: client?.status ?? "disconnected",
135068
135383
  capabilities: client?.capabilities ?? void 0,
135069
135384
  error: void 0,
135070
- connectedAt: void 0
135385
+ connectedAt: void 0,
135386
+ scopedOut: config.enabled !== false && (0, mcpShared_1.isServerScopedOut)(name, this.scope)
135071
135387
  };
135072
135388
  }
135073
135389
  /**
@@ -135872,7 +136188,7 @@ var require_session = __commonJS({
135872
136188
  }
135873
136189
  }
135874
136190
  function generateSessionId() {
135875
- return crypto3.randomUUID().replace(/-/g, "").substring(0, 8);
136191
+ return crypto3.randomUUID();
135876
136192
  }
135877
136193
  function initializeSessionId(historyPath) {
135878
136194
  if (historyPath) {
@@ -136235,10 +136551,10 @@ var require_runCommand = __commonJS({
136235
136551
  var compact_1 = require_compact();
136236
136552
  var errors_1 = require_errors2();
136237
136553
  var customCommands_1 = require_customCommands();
136238
- async function handleSystemCommand(input) {
136554
+ async function handleSystemCommand(input, handlers) {
136239
136555
  switch (input) {
136240
136556
  case "/clear":
136241
- await handleClearCommand();
136557
+ await handlers.clear();
136242
136558
  return true;
136243
136559
  case "/compact":
136244
136560
  await handleCompactCommand();
@@ -136279,17 +136595,6 @@ var require_runCommand = __commonJS({
136279
136595
  return { processedInput: input, handled: false };
136280
136596
  }
136281
136597
  }
136282
- async function handleClearCommand() {
136283
- (0, log_1.logInfo)("\u6267\u884C\u6E05\u7A7A\u547D\u4EE4...");
136284
- const eventBus = EventSystem_1.EventBus.getInstance();
136285
- const stateManager = (0, StateManager_1.getStateManager)();
136286
- stateManager.setMessageHistory([]);
136287
- stateManager.updateState("idle");
136288
- eventBus.emit("session:cleared", {
136289
- sessionId: stateManager.getSessionId()
136290
- });
136291
- stateManager.clearAllState();
136292
- }
136293
136598
  async function handleCompactCommand() {
136294
136599
  (0, log_1.logInfo)("\u6267\u884C\u538B\u7F29\u547D\u4EE4...");
136295
136600
  const eventBus = EventSystem_1.EventBus.getInstance();
@@ -136434,12 +136739,53 @@ var require_HookManager = __commonJS({
136434
136739
  var require_SemaEngine = __commonJS({
136435
136740
  "../atomix-core/dist/core/SemaEngine.js"(exports2) {
136436
136741
  "use strict";
136742
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
136743
+ if (k2 === void 0) k2 = k;
136744
+ var desc = Object.getOwnPropertyDescriptor(m, k);
136745
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
136746
+ desc = { enumerable: true, get: function() {
136747
+ return m[k];
136748
+ } };
136749
+ }
136750
+ Object.defineProperty(o, k2, desc);
136751
+ }) : (function(o, m, k, k2) {
136752
+ if (k2 === void 0) k2 = k;
136753
+ o[k2] = m[k];
136754
+ }));
136755
+ var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? (function(o, v) {
136756
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
136757
+ }) : function(o, v) {
136758
+ o["default"] = v;
136759
+ });
136760
+ var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ (function() {
136761
+ var ownKeys = function(o) {
136762
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
136763
+ var ar = [];
136764
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
136765
+ return ar;
136766
+ };
136767
+ return ownKeys(o);
136768
+ };
136769
+ return function(mod) {
136770
+ if (mod && mod.__esModule) return mod;
136771
+ var result2 = {};
136772
+ if (mod != null) {
136773
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result2, mod, k[i]);
136774
+ }
136775
+ __setModuleDefault(result2, mod);
136776
+ return result2;
136777
+ };
136778
+ })();
136437
136779
  Object.defineProperty(exports2, "__esModule", { value: true });
136438
136780
  exports2.SemaEngine = void 0;
136439
136781
  var log_1 = require_log();
136782
+ var logLLM_1 = require_logLLM();
136783
+ var LLM_LOG_STRICT = process.env.ATOMIX_LLM_LOG_STRICT === "1";
136440
136784
  var session_1 = require_session();
136441
136785
  var tokens_1 = require_tokens();
136442
136786
  var history_1 = require_history();
136787
+ var savePath_1 = require_savePath();
136788
+ var fs14 = __importStar(__require("fs"));
136443
136789
  var topic_1 = require_topic();
136444
136790
  var fileReference_1 = require_fileReference();
136445
136791
  var message_1 = require_message2();
@@ -136466,6 +136812,9 @@ var require_SemaEngine = __commonJS({
136466
136812
  var SemaEngine = class {
136467
136813
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
136468
136814
  constructor(instanceId, config, mcpManager, kernel) {
136815
+ this.turnGeneration = 0;
136816
+ this.llmLogHandle = null;
136817
+ this.llmLogAbort = new AbortController();
136469
136818
  this.emit = (event, data) => {
136470
136819
  const store = this.getEngineStore();
136471
136820
  EngineContext_1.engineStorage.run(store, () => {
@@ -136603,6 +136952,7 @@ var require_SemaEngine = __commonJS({
136603
136952
  */
136604
136953
  processUserInput(input, originalInput) {
136605
136954
  (0, EngineContext_1.runWithEngine)(this.getEngineStore(), () => {
136955
+ const turnGeneration = ++this.turnGeneration;
136606
136956
  const stateManager = (0, StateManager_1.getStateManager)();
136607
136957
  const mainAgentState = stateManager.forAgent(StateManager_1.MAIN_AGENT_ID);
136608
136958
  mainAgentState.updateState("processing");
@@ -136635,7 +136985,7 @@ var require_SemaEngine = __commonJS({
136635
136985
  tools,
136636
136986
  model: "main"
136637
136987
  };
136638
- return this.processQuery(textInput, originalInput, agentContext, blocks);
136988
+ return this.processQuery(textInput, originalInput, agentContext, turnGeneration, blocks);
136639
136989
  }).catch((err) => (0, log_1.logInfo)(`[${this.instanceId}] processUserInput \u9876\u5C42\u9519\u8BEF: ${err}`));
136640
136990
  }
136641
136991
  /**
@@ -136643,7 +136993,7 @@ var require_SemaEngine = __commonJS({
136643
136993
  *
136644
136994
  * extraBlocks: blocks 路径下额外的非文本内容(如 image blocks),追加到当前 turn 的 user message。
136645
136995
  */
136646
- async processQuery(input, originalInput, agentContext, extraBlocks) {
136996
+ async processQuery(input, originalInput, agentContext, turnGeneration, extraBlocks) {
136647
136997
  const turnStartedAt = Date.now();
136648
136998
  const stateManager = (0, StateManager_1.getStateManager)();
136649
136999
  const mainAgentState = stateManager.forAgent(StateManager_1.MAIN_AGENT_ID);
@@ -136679,7 +137029,7 @@ var require_SemaEngine = __commonJS({
136679
137029
  }
136680
137030
  try {
136681
137031
  (0, ConfManager_1.getConfManager)().saveUserInputToHistory(originalInput || input);
136682
- const isSystemCommand = await (0, runCommand_1.handleSystemCommand)(input);
137032
+ const isSystemCommand = await (0, runCommand_1.handleSystemCommand)(input, { clear: () => this.clearSessionInternal(agentContext.abortController?.signal) });
136683
137033
  if (isSystemCommand) {
136684
137034
  return;
136685
137035
  }
@@ -136715,23 +137065,25 @@ var require_SemaEngine = __commonJS({
136715
137065
  (0, log_1.logDebug)("\u7528\u6237\u4E2D\u65AD\u64CD\u4F5C");
136716
137066
  }
136717
137067
  } finally {
136718
- stateManager.currentAbortController = null;
136719
- if (this.myHookManager?.hasHooksForEvent("Stop")) {
136720
- const stopHookInput = {
136721
- hook_event_name: "Stop",
136722
- session_id: stateManager.getSessionId() || "",
136723
- agent_id: StateManager_1.MAIN_AGENT_ID,
136724
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
136725
- cwd: this.initialConfig.workingDir || (0, cwd_1.getCwd)(),
136726
- turn_duration_ms: Date.now() - turnStartedAt
136727
- };
136728
- await (0, executeHooks_1.executeHooks)(this.myHookManager, "Stop", stopHookInput, {
136729
- env: this.initialConfig.hookEnv,
136730
- messages: mainAgentState.getMessageHistory()
136731
- });
136732
- }
136733
- if (stateManager.getCurrentState(StateManager_1.MAIN_AGENT_ID) !== "paused") {
136734
- mainAgentState.updateState("idle");
137068
+ if (turnGeneration === this.turnGeneration) {
137069
+ stateManager.currentAbortController = null;
137070
+ if (this.myHookManager?.hasHooksForEvent("Stop")) {
137071
+ const stopHookInput = {
137072
+ hook_event_name: "Stop",
137073
+ session_id: stateManager.getSessionId() || "",
137074
+ agent_id: StateManager_1.MAIN_AGENT_ID,
137075
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
137076
+ cwd: this.initialConfig.workingDir || (0, cwd_1.getCwd)(),
137077
+ turn_duration_ms: Date.now() - turnStartedAt
137078
+ };
137079
+ await (0, executeHooks_1.executeHooks)(this.myHookManager, "Stop", stopHookInput, {
137080
+ env: this.initialConfig.hookEnv,
137081
+ messages: mainAgentState.getMessageHistory()
137082
+ });
137083
+ }
137084
+ if (turnGeneration === this.turnGeneration && stateManager.getCurrentState(StateManager_1.MAIN_AGENT_ID) !== "paused") {
137085
+ mainAgentState.updateState("idle");
137086
+ }
136735
137087
  }
136736
137088
  }
136737
137089
  }
@@ -136762,6 +137114,7 @@ var require_SemaEngine = __commonJS({
136762
137114
  * 不更新状态,用于内部调用
136763
137115
  */
136764
137116
  abortCurrentRequest() {
137117
+ ++this.turnGeneration;
136765
137118
  const abortController = this.myStateManager.currentAbortController;
136766
137119
  if (abortController && !abortController.signal.aborted) {
136767
137120
  (0, log_1.logInfo)("\u901A\u8FC7 AbortController \u53D1\u9001\u4E2D\u65AD\u4FE1\u53F7");
@@ -136835,6 +137188,10 @@ var require_SemaEngine = __commonJS({
136835
137188
  cfg.temperatureOverrides = partial2.temperatureOverrides ?? null;
136836
137189
  if ("thinking" in partial2)
136837
137190
  cfg.thinking = partial2.thinking === true;
137191
+ if ("mcpScope" in partial2) {
137192
+ cfg.mcpScope = partial2.mcpScope ?? null;
137193
+ this.myMCPManager.setScope(cfg.mcpScope);
137194
+ }
136838
137195
  }
136839
137196
  /**
136840
137197
  * 当前 session 的 coreConfig 只读快照(= initialConfig,含运行时 mutation 结果)。
@@ -136889,12 +137246,130 @@ var require_SemaEngine = __commonJS({
136889
137246
  }
136890
137247
  }
136891
137248
  // 初始化系统
137249
+ /**
137250
+ * /clear:清空上下文并换新 sessionId(events/types.ts SessionClearedData 注释有语义说明)。
137251
+ * 三段式"准备 → 检查 → 同步提交":
137252
+ * 1. 准备:生成候选 id(完整 UUID),先做占用检查(当前 id / 项目历史文件 / 宿主回调 isSessionIdTaken),
137253
+ * 再为候选 id 登记 LLM 日志句柄(唯一可能失败、可能 await 的步骤)。此时旧 id、旧消息、旧句柄一概不动。
137254
+ * 2. 检查:await 回来后确认本轮未被中断、引擎未 dispose,并再查一次占用(等待期间宿主可能打开了同 id);
137255
+ * 冲突则释放候选句柄重新生成(有重试上限)。
137256
+ * 3. 提交:换 id → 清消息 / todos / 状态 → 装新句柄 → 发 session:cleared,中间不再 await——宿主(serve)在
137257
+ * 事件回调里同步 rekey,与最后一次检查处于同一同步段,其他 open 插不进来。随后释放旧句柄、进入 idle。
137258
+ * 准备失败或被取消:释放候选句柄,旧会话原样保留。严格模式(ATOMIX_LLM_LOG_STRICT=1)下登记失败发
137259
+ * session:error(session 库的 send 据此返回错误);缺省模式沿用"告警后无日志保护继续"。
137260
+ */
137261
+ async clearSessionInternal(turnSignal) {
137262
+ (0, log_1.logInfo)("\u6267\u884C\u6E05\u7A7A\u547D\u4EE4...");
137263
+ const stateManager = (0, StateManager_1.getStateManager)();
137264
+ const mainAgentState = stateManager.forAgent(StateManager_1.MAIN_AGENT_ID);
137265
+ const previousSessionId = stateManager.getSessionId();
137266
+ const workingDir = (0, ConfManager_1.getConfManager)().getCoreConfig()?.workingDir;
137267
+ const cancelled = () => !!turnSignal?.aborted || this.llmLogAbort.signal.aborted;
137268
+ const fail = (message) => {
137269
+ (0, log_1.logWarn)(`\u6E05\u7A7A\u5931\u8D25,\u4FDD\u7559\u539F\u4F1A\u8BDD ${previousSessionId}:${message}`);
137270
+ this.emit("session:error", { type: "clear_failed", error: { code: "CLEAR_FAILED", message } });
137271
+ mainAgentState.updateState("idle");
137272
+ };
137273
+ if (cancelled())
137274
+ return;
137275
+ const MAX_TRIES = 8;
137276
+ let newSessionId = null;
137277
+ let newHandle = null;
137278
+ for (let attempt2 = 1; attempt2 <= MAX_TRIES && newSessionId === null; attempt2++) {
137279
+ const candidate = (0, session_1.initializeSessionId)();
137280
+ if (this.isSessionIdTaken(candidate, previousSessionId, workingDir))
137281
+ continue;
137282
+ let handle;
137283
+ try {
137284
+ handle = await this.acquireLlmLogHandle(candidate);
137285
+ } catch (err) {
137286
+ if (cancelled())
137287
+ return;
137288
+ return fail(`LLM \u65E5\u5FD7\u6D3B\u8DC3\u767B\u8BB0\u5931\u8D25:${err instanceof Error ? err.message : String(err)}`);
137289
+ }
137290
+ if (cancelled()) {
137291
+ handle?.release();
137292
+ (0, log_1.logInfo)("\u6E05\u7A7A\u5728\u7B49\u5F85\u65E5\u5FD7\u767B\u8BB0\u671F\u95F4\u88AB\u53D6\u6D88,\u4FDD\u7559\u539F\u4F1A\u8BDD");
137293
+ return;
137294
+ }
137295
+ if (this.isSessionIdTaken(candidate, previousSessionId, workingDir)) {
137296
+ handle?.release();
137297
+ (0, log_1.logWarn)(`\u5019\u9009 sessionId ${candidate} \u5728\u767B\u8BB0\u671F\u95F4\u88AB\u5360\u7528,\u91CD\u65B0\u751F\u6210(\u7B2C ${attempt2} \u6B21)`);
137298
+ continue;
137299
+ }
137300
+ newSessionId = candidate;
137301
+ newHandle = handle;
137302
+ }
137303
+ if (newSessionId === null)
137304
+ return fail(`\u8FDE\u7EED ${MAX_TRIES} \u6B21\u751F\u6210\u7684 sessionId \u5747\u88AB\u5360\u7528`);
137305
+ const oldHandle = this.llmLogHandle;
137306
+ stateManager.setSessionId(newSessionId);
137307
+ stateManager.clearAllState({ keepAgentStates: true });
137308
+ mainAgentState.setMessageHistory([]);
137309
+ mainAgentState.setTodos([]);
137310
+ this.llmLogHandle = newHandle;
137311
+ (0, log_1.logInfo)(`\u4E0A\u4E0B\u6587\u5DF2\u6E05\u7A7A\uFF0C\u65B0\u4F1A\u8BDD ${newSessionId}\uFF08\u539F\u4F1A\u8BDD ${previousSessionId}\uFF09`);
137312
+ this.emit("session:cleared", {
137313
+ sessionId: newSessionId,
137314
+ previousSessionId,
137315
+ usage: (0, tokens_1.getTokens)([])
137316
+ });
137317
+ oldHandle?.release();
137318
+ mainAgentState.updateState("idle");
137319
+ }
137320
+ /** 候选 sessionId 是否已被占用:当前 id / 本项目已有历史文件 / 宿主回调(serve 进程内会话表等) */
137321
+ isSessionIdTaken(candidate, currentSessionId, workingDir) {
137322
+ if (candidate === currentSessionId)
137323
+ return true;
137324
+ try {
137325
+ if (fs14.existsSync((0, savePath_1.getHistoryFilePath)(candidate, workingDir)))
137326
+ return true;
137327
+ } catch {
137328
+ }
137329
+ try {
137330
+ return this.initialConfig.isSessionIdTaken?.(candidate) === true;
137331
+ } catch (err) {
137332
+ (0, log_1.logWarn)(`\u5BBF\u4E3B isSessionIdTaken \u56DE\u8C03\u629B\u9519,\u6309\u5DF2\u5360\u7528\u5904\u7406:${err instanceof Error ? err.message : String(err)}`);
137333
+ return true;
137334
+ }
137335
+ }
137336
+ /**
137337
+ * 为 sessionId 登记 LLM 日志活跃句柄(不动 this.llmLogHandle,由调用方决定何时装上/释放旧的)。
137338
+ * 登记为活跃会话:LLM 日志清理不动活跃会话的文件。resolve 即标记已在清理锁内落盘。engine 持句柄,换 id / dispose 只释放自己那一份。
137339
+ * 拿不到锁(如某张票据的 pid 被复用且平台上拿不到进程启动时间来判死):不能无锁写标记假装受保护;
137340
+ * 缺省告警后无保护继续(返回 null)——日志被归档的风险是原状,拒绝启动会话则是把清理的 fail-open 变成启动的 fail-closed,
137341
+ * 影响不对称;ATOMIX_LLM_LOG_STRICT=1 时改为抛出。dispose 中的取消照常抛出。
137342
+ */
137343
+ async acquireLlmLogHandle(sessionId) {
137344
+ let handle = null;
137345
+ try {
137346
+ handle = await (0, logLLM_1.acquireActiveLLMLogSession)(sessionId, { signal: this.llmLogAbort.signal });
137347
+ } catch (err) {
137348
+ if (this.llmLogAbort.signal.aborted || LLM_LOG_STRICT)
137349
+ throw err;
137350
+ (0, log_1.logWarn)(`LLM \u65E5\u5FD7\u6D3B\u8DC3\u767B\u8BB0\u5931\u8D25,\u672C\u4F1A\u8BDD\u65E5\u5FD7\u4E0D\u53D7\u6E05\u7406\u4FDD\u62A4(\u53EF\u80FD\u88AB\u5F52\u6863\u622A\u65AD):${err instanceof Error ? err.message : String(err)}`);
137351
+ }
137352
+ if (this.llmLogAbort.signal.aborted) {
137353
+ handle?.release();
137354
+ throw new Error("\u4F1A\u8BDD\u5DF2\u91CA\u653E");
137355
+ }
137356
+ return handle;
137357
+ }
137358
+ /** createSession 用:换绑到 sessionId(先登记新的,成功后释放旧的) */
137359
+ async bindLlmLogSession(sessionId) {
137360
+ if (this.llmLogHandle?.sessionId === sessionId)
137361
+ return;
137362
+ const handle = await this.acquireLlmLogHandle(sessionId);
137363
+ this.llmLogHandle?.release();
137364
+ this.llmLogHandle = handle;
137365
+ }
136892
137366
  async initialize(sessionId) {
136893
137367
  const coreConfig = (0, ConfManager_1.getConfManager)().getCoreConfig();
136894
137368
  (0, log_1.setLogLevel)(coreConfig?.logLevel || "info");
136895
137369
  const finalSessionId = sessionId || (0, session_1.initializeSessionId)();
136896
137370
  const stateManager = (0, StateManager_1.getStateManager)();
136897
137371
  stateManager.setSessionId(finalSessionId);
137372
+ await this.bindLlmLogSession(finalSessionId);
136898
137373
  try {
136899
137374
  const modelManager = (0, ModelManager_1.getModelManager)();
136900
137375
  const modelProfile = modelManager.getModel("main");
@@ -136923,6 +137398,9 @@ var require_SemaEngine = __commonJS({
136923
137398
  dispose() {
136924
137399
  (0, log_1.logInfo)("\u5F00\u59CB\u6E05\u7406 SemaEngine \u8D44\u6E90...");
136925
137400
  this.abortCurrentRequest();
137401
+ this.llmLogAbort.abort();
137402
+ this.llmLogHandle?.release();
137403
+ this.llmLogHandle = null;
136926
137404
  this.myStateManager.clearAllState();
136927
137405
  this.myEventBus.removeAllListeners();
136928
137406
  (0, log_1.logInfo)("SemaEngine \u8D44\u6E90\u6E05\u7406\u5B8C\u6210");
@@ -137049,6 +137527,8 @@ var require_SemaSession = __commonJS({
137049
137527
  this.initialWorkingDir = workingDir;
137050
137528
  const agentDataDir = resolvedConfig.agentDataDir || workingDir;
137051
137529
  this.instanceMCPManager = this.acquireMCPManager(agentDataDir);
137530
+ if (resolvedConfig.mcpScope !== void 0)
137531
+ this.instanceMCPManager.setScope(resolvedConfig.mcpScope ?? null);
137052
137532
  this.engine = new SemaEngine_1.SemaEngine(instanceId, resolvedConfig, this.instanceMCPManager, kernel);
137053
137533
  if (resolvedConfig.skipMCPInit) {
137054
137534
  this.configPromise = (0, ConfManager_1.getConfManager)().registerProjectConfig(resolvedConfig);
@@ -137219,6 +137699,10 @@ var require_MCPManagerView = __commonJS({
137219
137699
  var mcpShared_1 = require_mcpShared();
137220
137700
  var log_1 = require_log();
137221
137701
  var MCPManagerView = class {
137702
+ /** 设置 / 清除(null)本 session 的 MCP 可见性 overlay。 */
137703
+ setScope(scope) {
137704
+ this.scope = scope;
137705
+ }
137222
137706
  constructor(mux, globalConfigPath, projectConfigPath) {
137223
137707
  this.mux = mux;
137224
137708
  this.globalConfigPath = globalConfigPath;
@@ -137226,6 +137710,7 @@ var require_MCPManagerView = __commonJS({
137226
137710
  this.globalConfigs = /* @__PURE__ */ new Map();
137227
137711
  this.projectConfigs = /* @__PURE__ */ new Map();
137228
137712
  this.heldHashes = /* @__PURE__ */ new Set();
137713
+ this.scope = null;
137229
137714
  }
137230
137715
  // ==================== 生命周期 ====================
137231
137716
  /**
@@ -137269,7 +137754,7 @@ var require_MCPManagerView = __commonJS({
137269
137754
  if (!shared || shared.status !== "connected" || !shared.client)
137270
137755
  continue;
137271
137756
  const tools = shared.toolDefs.map((def) => (0, MCPToolAdapter_1.createMCPToolAdapter)(shared.client, serverName, def));
137272
- result2.push(...(0, mcpShared_1.filterToolsByConfig)(tools, config.useTools));
137757
+ result2.push(...(0, mcpShared_1.applyScopeToTools)(serverName, (0, mcpShared_1.filterToolsByConfig)(tools, config.useTools, serverName), this.scope));
137273
137758
  }
137274
137759
  return result2;
137275
137760
  }
@@ -137291,7 +137776,8 @@ var require_MCPManagerView = __commonJS({
137291
137776
  status: shared?.status === "connected" ? "connected" : shared?.status === "connecting" ? "connecting" : "disconnected",
137292
137777
  capabilities: shared?.client?.capabilities ?? void 0,
137293
137778
  error: void 0,
137294
- connectedAt: void 0
137779
+ connectedAt: void 0,
137780
+ scopedOut: config.enabled !== false && (0, mcpShared_1.isServerScopedOut)(config.name, this.scope)
137295
137781
  };
137296
137782
  }
137297
137783
  // ==================== 配置写入(本 session 的 mcp.json)====================
@@ -152927,6 +153413,11 @@ function recordAgentUniverse(core, names) {
152927
153413
  const rt = runtimeOf(core);
152928
153414
  if (rt) rt.fullAgentNames = names.slice();
152929
153415
  }
153416
+ function isServerScopedOutBy(server, scope) {
153417
+ if (!scope) return false;
153418
+ if (scope.servers !== null && !scope.servers.includes(server)) return true;
153419
+ return scope.disabledServers?.includes(server) ?? false;
153420
+ }
152930
153421
  function computeDisabledFrom(spec, allNames) {
152931
153422
  if (!spec) return /* @__PURE__ */ new Set();
152932
153423
  return spec.mode === "whitelist" ? new Set(allNames.filter((n) => !spec.enable.includes(n))) : new Set(spec.disable);
@@ -153101,6 +153592,37 @@ function loadHarnessDoc(name) {
153101
153592
  };
153102
153593
  const memory = parseToggle(raw.memory, "memory");
153103
153594
  const persona = parseToggle(raw.persona, "persona");
153595
+ let mcp = null;
153596
+ if (raw.mcp !== void 0 && raw.mcp !== null) {
153597
+ if (typeof raw.mcp !== "object" || Array.isArray(raw.mcp)) {
153598
+ toggleWarnings.push(`mcp \u6BB5\u53EA\u8BA4\u5BF9\u8C61 { mode, enable, disable, tools },\u5FFD\u7565\u975E\u6CD5\u503C ${JSON.stringify(raw.mcp)}`);
153599
+ } else {
153600
+ const m = raw.mcp;
153601
+ const tools = {};
153602
+ if (m.tools !== void 0 && m.tools !== null) {
153603
+ if (typeof m.tools !== "object" || Array.isArray(m.tools)) toggleWarnings.push("mcp.tools \u53EA\u8BA4 { <server>: [\u5DE5\u5177\u540D...] },\u5DF2\u5FFD\u7565");
153604
+ else for (const [server, v] of Object.entries(m.tools)) {
153605
+ if (Array.isArray(v)) tools[server] = asStrArr(v);
153606
+ else toggleWarnings.push(`mcp.tools.${server} \u53EA\u8BA4\u6570\u7EC4,\u5DF2\u5FFD\u7565`);
153607
+ }
153608
+ }
153609
+ if (m.mode !== void 0 && m.mode !== null && m.mode !== "whitelist" && m.mode !== "blacklist") {
153610
+ toggleWarnings.push(`mcp.mode \u53EA\u8BA4 whitelist / blacklist,\u6536\u5230 ${JSON.stringify(m.mode)},\u6309 blacklist \u5904\u7406`);
153611
+ }
153612
+ const strList = (key) => {
153613
+ const v = m[key];
153614
+ if (v === void 0 || v === null) return [];
153615
+ if (!Array.isArray(v)) {
153616
+ toggleWarnings.push(`mcp.${key} \u53EA\u8BA4\u6570\u7EC4,\u6536\u5230 ${JSON.stringify(v)},\u5DF2\u5FFD\u7565`);
153617
+ return [];
153618
+ }
153619
+ const bad = v.filter((x) => typeof x !== "string");
153620
+ if (bad.length) toggleWarnings.push(`mcp.${key} \u542B\u975E\u5B57\u7B26\u4E32\u9879 ${JSON.stringify(bad)},\u5DF2\u5FFD\u7565\u8FD9\u4E9B\u9879`);
153621
+ return asStrArr(v);
153622
+ };
153623
+ mcp = { mode: asMode(m.mode), enable: strList("enable"), disable: strList("disable"), tools };
153624
+ }
153625
+ }
153104
153626
  const parseModel = (v) => {
153105
153627
  if (v === void 0 || v === null) return null;
153106
153628
  if (typeof v === "string") {
@@ -153177,6 +153699,7 @@ function loadHarnessDoc(name) {
153177
153699
  },
153178
153700
  skills: { mode: asMode(sk.mode), disable: asStrArr(sk.disable), enable: asStrArr(sk.enable) },
153179
153701
  agents: { mode: asMode(ag.mode), disable: asStrArr(ag.disable), enable: asStrArr(ag.enable) },
153702
+ mcp,
153180
153703
  prompt: promptGroup.overrides,
153181
153704
  promptWarnings: promptGroup.warnings,
153182
153705
  memory,
@@ -153210,6 +153733,8 @@ function resolveBase(ctx) {
153210
153733
  thinking: ctx.baseline.thinking,
153211
153734
  skillsSpec: null,
153212
153735
  agentsSpec: null,
153736
+ mcpScope: null,
153737
+ mcpDeclared: null,
153213
153738
  skillsDisabled: /* @__PURE__ */ new Set(),
153214
153739
  agentsDisabled: /* @__PURE__ */ new Set()
153215
153740
  };
@@ -153290,9 +153815,33 @@ function resolveHarness(doc, ctx) {
153290
153815
  temperatureOverrides[slot] = t;
153291
153816
  }
153292
153817
  }
153818
+ let mcpScope = null;
153819
+ if (doc.mcp) {
153820
+ const known = new Set(ctx.mcpServerNames);
153821
+ const unknown = (names) => names.filter((n) => !known.has(n));
153822
+ if (doc.mcp.mode === "whitelist") {
153823
+ mcpScope = { servers: doc.mcp.enable.slice() };
153824
+ const u = unknown(doc.mcp.enable);
153825
+ if (u.length) warnings.push(`mcp.enable \u91CC\u7684 server \u4E0D\u5B58\u5728(\u672C\u9879\u76EE mcp.json \u672A\u914D\u7F6E\u6216\u5DF2\u7981\u7528):${u.join(", ")}`);
153826
+ } else {
153827
+ mcpScope = { servers: null, disabledServers: doc.mcp.disable.slice() };
153828
+ const u = unknown(doc.mcp.disable);
153829
+ if (u.length) warnings.push(`mcp.disable \u91CC\u7684 server \u4E0D\u5B58\u5728(\u672C\u9879\u76EE mcp.json \u672A\u914D\u7F6E\u6216\u5DF2\u7981\u7528):${u.join(", ")}`);
153830
+ }
153831
+ const toolEntries = Object.entries(doc.mcp.tools);
153832
+ if (toolEntries.length) {
153833
+ mcpScope.tools = Object.fromEntries(toolEntries.map(([k, v]) => [k, v.slice()]));
153834
+ const u = unknown(toolEntries.map(([k]) => k));
153835
+ if (u.length) warnings.push(`mcp.tools \u91CC\u7684 server \u4E0D\u5B58\u5728(\u672C\u9879\u76EE mcp.json \u672A\u914D\u7F6E\u6216\u5DF2\u7981\u7528):${u.join(", ")}`);
153836
+ const shadowed = toolEntries.map(([k]) => k).filter((k) => isServerScopedOutBy(k, mcpScope));
153837
+ if (shadowed.length) warnings.push(`mcp.tools \u5BF9\u5DF2\u88AB\u5C4F\u853D\u7684 server \u65E0\u6548:${shadowed.join(", ")}`);
153838
+ }
153839
+ }
153293
153840
  return {
153294
153841
  name: doc.name,
153295
153842
  dirName,
153843
+ mcpScope,
153844
+ mcpDeclared: doc.mcp,
153296
153845
  temperatureOverrides: Object.keys(temperatureOverrides).length ? temperatureOverrides : null,
153297
153846
  temperatureDeclared: doc.temperature,
153298
153847
  modelOverrides: Object.keys(modelOverrides).length ? modelOverrides : null,
@@ -153326,11 +153875,20 @@ function buildContext(core) {
153326
153875
  allToolNames: core.getToolInfos().map((t) => t.name),
153327
153876
  allSkillNames: rt.fullSkillNames ?? core.getSkillsInfo({ includeDisabled: true }).map((s) => s.name),
153328
153877
  allAgentNames: rt.fullAgentNames ?? core.getAgentsInfo().map((a) => a.name),
153878
+ mcpServerNames: mcpServerNamesOf(core),
153329
153879
  modelNames: core.getModelNames(),
153330
153880
  baseModels: core.getModelPointers(),
153331
153881
  modelProfiles: core.getModelProfiles()
153332
153882
  };
153333
153883
  }
153884
+ function mcpServerNamesOf(core) {
153885
+ const byScope = core.getMCPServerConfigs();
153886
+ const merged = /* @__PURE__ */ new Map();
153887
+ for (const scope of ["user", "project"]) {
153888
+ for (const s of byScope.get(scope) ?? []) merged.set(s.config.name, s.config.enabled !== false);
153889
+ }
153890
+ return [...merged.entries()].filter(([, on]) => on).map(([n]) => n);
153891
+ }
153334
153892
  function applyAssembly(core, rt, r, opts = {}) {
153335
153893
  core.updateAssemblyConfig({
153336
153894
  useTools: r.useTools,
@@ -153346,6 +153904,8 @@ function applyAssembly(core, rt, r, opts = {}) {
153346
153904
  // null = 回 model.conf 指针(base 清场)
153347
153905
  temperatureOverrides: r.temperatureOverrides,
153348
153906
  // null = 回 profile / 协议默认(base 清场)
153907
+ mcpScope: r.mcpScope,
153908
+ // null = 回 mcp.json 基线(base / 未声明都清场,不残留上一个 harness 的屏蔽)
153349
153909
  ...!opts.preserveThinking && r.thinking !== void 0 ? { thinking: r.thinking } : {}
153350
153910
  // 声明 ?? 基线;只在 use/reset/启动 下发,不做热切换
153351
153911
  });
@@ -153486,6 +154046,7 @@ function summarize(r, ctx) {
153486
154046
  const agentsDisabled = computeDisabledFrom(r.agentsSpec, ctx.allAgentNames);
153487
154047
  lines.push(`skill ${skillsDisabled.size ? `\u7981\u7528 ${[...skillsDisabled].join(", ")}` : "\u5168\u90E8\u53EF\u89C1"}`);
153488
154048
  lines.push(`agent ${agentsDisabled.size ? `\u7981\u7528 ${[...agentsDisabled].join(", ")}` : "\u5168\u90E8\u53EF\u89C1"}`);
154049
+ if (r.mcpDeclared) lines.push(`MCP ${describeMcpScope(r.mcpScope, ctx.mcpServerNames)}`);
153489
154050
  lines.push(`\u89C4\u5219\u6BB5 ${r.customRules ? `${r.customRules.split("\n").length} \u884C` : "\u65E0"}`);
153490
154051
  const p = r.promptOverrides;
153491
154052
  const promptDesc = p ? [
@@ -153521,6 +154082,18 @@ function summarize(r, ctx) {
153521
154082
  if (r.warnings.length) lines.push(...r.warnings.map((w) => `\u26A0 ${w}`));
153522
154083
  return lines.join("\n ");
153523
154084
  }
154085
+ function describeMcpScope(scope, allServers) {
154086
+ if (!scope) return "\u5168\u90E8\u53EF\u89C1";
154087
+ const blocked = allServers.filter((n) => isServerScopedOutBy(n, scope));
154088
+ const visible = allServers.filter((n) => !isServerScopedOutBy(n, scope));
154089
+ const parts = [];
154090
+ if (scope.servers !== null) parts.push(`\u767D\u540D\u5355 ${scope.servers.length ? scope.servers.join(", ") : "(\u7A7A:\u4E00\u4E2A server \u90FD\u4E0D\u7ED9)"}`);
154091
+ else if (scope.disabledServers?.length) parts.push(`\u5C4F\u853D ${scope.disabledServers.join(", ")}`);
154092
+ parts.push(`\u53EF\u89C1 ${visible.length}/${allServers.length}${blocked.length ? `(\u5C4F\u853D:${blocked.join(", ")})` : ""}`);
154093
+ const narrowed = Object.entries(scope.tools ?? {}).filter(([k]) => !isServerScopedOutBy(k, scope));
154094
+ if (narrowed.length) parts.push(`\u6536\u7A84 ${narrowed.map(([k, v]) => `${k}\u2192[${v.join(", ")}]`).join(" ")}`);
154095
+ return parts.join(";") + "(\u8FDE\u63A5\u4E0D\u91CA\u653E,\u5207\u8D70\u5373\u56DE mcp.json \u57FA\u7EBF)";
154096
+ }
153524
154097
  async function harnessCommand(core, args) {
153525
154098
  const [sub, ...rest2] = args;
153526
154099
  const arg = rest2.join(" ").trim() || void 0;
@@ -160716,7 +161289,8 @@ async function mcpCommand(core, args) {
160716
161289
  const name = s.config?.name ?? "?";
160717
161290
  const enabled = s.config?.enabled !== false;
160718
161291
  const inheritedTag = name.startsWith(INHERIT_PREFIX) ? "\uFF08\u7EE7\u627F\u81EA semaclaw\uFF09" : "";
160719
- lines.push(`${enabled ? "\u25CF" : "\u25CB"} ${name} [${scope}] ${s.status}${s.error ? ` \u2014 ${s.error}` : ""}${inheritedTag}`);
161292
+ const scopedTag = s.scopedOut ? "\uFF08harness \u5C4F\u853D\uFF0C\u5DE5\u5177\u4E0D\u53EF\u89C1\uFF09" : "";
161293
+ lines.push(`${enabled ? s.scopedOut ? "\u25CC" : "\u25CF" : "\u25CB"} ${name} [${scope}] ${s.status}${s.error ? ` \u2014 ${s.error}` : ""}${inheritedTag}${scopedTag}`);
160720
161294
  }
160721
161295
  }
160722
161296
  if (!lines.length) lines.push("\uFF08\u672A\u914D\u7F6E MCP \u670D\u52A1\u5668\uFF09");
@@ -161071,7 +161645,8 @@ function createSessionCore(opts) {
161071
161645
  // 交互模式下文件编辑必须过权限卡;无人值守靠 permissionMode(free-style 全放行,其余由自动应答器 fail-closed)
161072
161646
  skipFileEditPermission: false,
161073
161647
  multiSession: opts.multiSession ?? false,
161074
- systemPrompt: opts.systemPrompt ?? DEFAULT_SYSTEM_PROMPT
161648
+ systemPrompt: opts.systemPrompt ?? DEFAULT_SYSTEM_PROMPT,
161649
+ ...opts.isSessionIdTaken ? { isSessionIdTaken: opts.isSessionIdTaken } : {}
161075
161650
  });
161076
161651
  registerSessionDirs(core, dirs);
161077
161652
  const potential = buildPotentialContextFiles(opts.cwd, appConfig);
@@ -161084,7 +161659,7 @@ function createSessionCore(opts) {
161084
161659
  thinking: false
161085
161660
  // cli 构造 core 时 thinking 关;harness 未声明即回到这里
161086
161661
  };
161087
- return { [SESSION_HANDLE_BRAND]: true, core, cwd: opts.cwd, interactive, permissionMode, appConfig, contextFiles, baseline, notes };
161662
+ return { [SESSION_HANDLE_BRAND]: true, core, cwd: opts.cwd, interactive, permissionMode, appConfig, contextFiles, baseline, notes, permissionPolicy: opts.permissionPolicy, questionPolicy: opts.questionPolicy, formPolicy: opts.formPolicy };
161088
161663
  }
161089
161664
  async function discardSessionCore(handle) {
161090
161665
  const h = internalHandle(handle);
@@ -161116,29 +161691,100 @@ async function startSession(handle, opts = {}) {
161116
161691
  });
161117
161692
  const mcpNotes = await applyMcpHotUpdates(core).catch(() => []);
161118
161693
  notes.push(...mcpNotes.map((n) => `${n}\uFF08\u9879\u76EE\u7EA7 override\uFF09`));
161119
- const responder = h.interactive ? null : attachHeadlessResponder(core);
161694
+ const responder = h.interactive ? null : attachHeadlessResponder(core, { permission: h.permissionPolicy, question: h.questionPolicy, form: h.formPolicy });
161120
161695
  return new SessionImpl(h, ready, notes, responder);
161121
161696
  }
161122
- function attachHeadlessResponder(core) {
161697
+ function isPlainRecord(v) {
161698
+ return typeof v === "object" && v !== null && !Array.isArray(v);
161699
+ }
161700
+ function isQuestionAnswers(v) {
161701
+ return isPlainRecord(v) && Object.values(v).every((x) => typeof x === "string");
161702
+ }
161703
+ function isFormAnswer(v) {
161704
+ return isPlainRecord(v) && isPlainRecord(v.values) && typeof v.submitted === "boolean";
161705
+ }
161706
+ function attachHeadlessResponder(core, policies = {}) {
161707
+ const policy = policies.permission;
161123
161708
  const blocked = [];
161709
+ let gen = 0;
161124
161710
  const onPermission = (d) => {
161125
- blocked.push({ kind: "permission", name: d.toolName, title: d.title, agentId: d.agentId });
161126
- setImmediate(() => core.respondToToolPermission({ toolName: d.toolName, selected: "refuse", agentId: d.agentId }));
161711
+ const g = gen;
161712
+ const fresh = () => gen === g;
161713
+ const respond = (selected) => {
161714
+ setImmediate(() => {
161715
+ if (fresh()) core.respondToToolPermission({ toolName: d.toolName, selected, agentId: d.agentId });
161716
+ });
161717
+ };
161718
+ const deny = () => {
161719
+ if (!fresh()) return;
161720
+ blocked.push({ kind: "permission", name: d.toolName, title: d.title, agentId: d.agentId });
161721
+ respond("refuse");
161722
+ };
161723
+ if (!policy) return deny();
161724
+ let decided;
161725
+ try {
161726
+ decided = Promise.resolve(policy({ agentId: d.agentId, toolName: d.toolName, title: d.title, content: d.content, options: d.options }));
161727
+ } catch {
161728
+ decided = Promise.resolve("deny");
161729
+ }
161730
+ void decided.then(
161731
+ (decision) => {
161732
+ if (!fresh()) return;
161733
+ if (decision === "allow") respond("agree");
161734
+ else deny();
161735
+ },
161736
+ () => deny()
161737
+ );
161738
+ };
161739
+ const decide = (fn, valid, fallback, g, apply2) => {
161740
+ let decided;
161741
+ try {
161742
+ decided = fn ? Promise.resolve(fn()) : Promise.resolve(fallback);
161743
+ } catch {
161744
+ decided = Promise.resolve(fallback);
161745
+ }
161746
+ void decided.then(
161747
+ (v) => apply2(valid(v) ? v : fallback, gen === g),
161748
+ () => apply2(fallback, gen === g)
161749
+ );
161127
161750
  };
161128
161751
  const onQuestion = (d) => {
161129
- blocked.push({ kind: "question", name: "AskUserQuestion", title: d.questions.map((q) => q.question).join(" / "), agentId: d.agentId });
161130
- setImmediate(() => core.respondToAskQuestion({ agentId: d.agentId, answers: {} }));
161752
+ const g = gen;
161753
+ const q = policies.question;
161754
+ decide(q ? () => q({ agentId: d.agentId, questions: d.questions }) : void 0, isQuestionAnswers, {}, g, (answers, fresh) => {
161755
+ if (!fresh) return;
161756
+ const skipped = Object.keys(answers).length === 0;
161757
+ if (skipped) blocked.push({ kind: "question", name: "AskUserQuestion", title: d.questions.map((x) => x.question).join(" / "), agentId: d.agentId });
161758
+ setImmediate(() => {
161759
+ if (gen === g) core.respondToAskQuestion({ agentId: d.agentId, answers });
161760
+ });
161761
+ });
161131
161762
  };
161132
161763
  const onForm = (d) => {
161133
- blocked.push({ kind: "form", name: "FormUI", title: d.title, agentId: d.agentId });
161134
- setImmediate(() => core.respondToForm({ agentId: d.agentId, values: {}, submitted: false }));
161764
+ const g = gen;
161765
+ const f = policies.form;
161766
+ const fallback = { values: {}, submitted: false };
161767
+ decide(f ? () => f({ agentId: d.agentId, title: d.title, surface: d.surface, submitLabel: d.submitLabel, fields: d.fields }) : void 0, isFormAnswer, fallback, g, (a, fresh) => {
161768
+ if (!fresh) return;
161769
+ if (!a.submitted) blocked.push({ kind: "form", name: "FormUI", title: d.title, agentId: d.agentId });
161770
+ setImmediate(() => {
161771
+ if (gen === g) core.respondToForm({ agentId: d.agentId, values: a.values, submitted: a.submitted });
161772
+ });
161773
+ });
161135
161774
  };
161136
161775
  core.on("tool:permission:request", onPermission);
161137
161776
  core.on("ask:question:request", onQuestion);
161138
161777
  core.on("form:request", onForm);
161139
161778
  return {
161140
161779
  blocked,
161780
+ beginTurn() {
161781
+ gen++;
161782
+ },
161783
+ endTurn() {
161784
+ gen++;
161785
+ },
161141
161786
  detach() {
161787
+ gen++;
161142
161788
  core.off("tool:permission:request", onPermission);
161143
161789
  core.off("ask:question:request", onQuestion);
161144
161790
  core.off("form:request", onForm);
@@ -161198,6 +161844,7 @@ var SessionImpl = class {
161198
161844
  const core = this.core;
161199
161845
  const timeoutMs = opts.timeoutMs ?? DEFAULT_SEND_TIMEOUT_MS;
161200
161846
  const blockedStart = this.responder?.blocked.length ?? 0;
161847
+ this.responder?.beginTurn();
161201
161848
  return new Promise((resolve9) => {
161202
161849
  const texts = [];
161203
161850
  let usage2;
@@ -161228,6 +161875,7 @@ var SessionImpl = class {
161228
161875
  done = true;
161229
161876
  clearTimeout(timer);
161230
161877
  this.abortCurrent = null;
161878
+ this.responder?.endTurn();
161231
161879
  core.off("message:complete", onMessage);
161232
161880
  core.off("state:update", onState);
161233
161881
  core.off("session:error", onError);
@@ -161348,7 +161996,6 @@ var fancy = {
161348
161996
  refresh: "\u27F3",
161349
161997
  updown: "\u2191\u2193",
161350
161998
  permMode: "\u21E7\u21E5",
161351
- newline: "\u23CE",
161352
161999
  sBullet: "\u25CF",
161353
162000
  sToolDot: "\u23FA",
161354
162001
  sThink: "\u273B",
@@ -161382,7 +162029,6 @@ var ascii = {
161382
162029
  refresh: "~",
161383
162030
  updown: "\u4E0A\u4E0B\u952E",
161384
162031
  permMode: "[perm]",
161385
- newline: "\\n",
161386
162032
  sBullet: "\u25CF",
161387
162033
  sToolDot: "\u25CF",
161388
162034
  sThink: "\u203B",
@@ -161553,12 +162199,28 @@ function headByRows(text, maxRows, columns) {
161553
162199
  }
161554
162200
  return { text: lines.slice(0, i).join("\n"), hiddenLines: lines.length - i, truncated: true };
161555
162201
  }
162202
+ var isJoinAfter = (cp) => cp === 65039 || // VS16
162203
+ cp >= 127995 && cp <= 127999 || // 肤色修饰符
162204
+ cp >= 768 && cp <= 879 || // 组合附加符号
162205
+ cp >= 8400 && cp <= 8447;
162206
+ function clusterEnd(chars, i) {
162207
+ let j = i + 1;
162208
+ while (j < chars.length) {
162209
+ const cp = chars[j].codePointAt(0);
162210
+ if (cp === 8205 && j + 1 < chars.length) {
162211
+ j += 2;
162212
+ continue;
162213
+ }
162214
+ if (isJoinAfter(cp)) {
162215
+ j++;
162216
+ continue;
162217
+ }
162218
+ break;
162219
+ }
162220
+ return j;
162221
+ }
161556
162222
  function hardWrapByWidth(text, columns) {
161557
162223
  const cols = Math.max(1, columns);
161558
- const isJoinAfter = (cp) => cp === 65039 || // VS16
161559
- cp >= 127995 && cp <= 127999 || // 肤色修饰符
161560
- cp >= 768 && cp <= 879 || // 组合附加符号
161561
- cp >= 8400 && cp <= 8447;
161562
162224
  const out = [];
161563
162225
  for (const line of text.split("\n")) {
161564
162226
  if (!line.includes(" ") && estimateWidth(line) <= cols) {
@@ -161594,19 +162256,7 @@ function hardWrapByWidth(text, columns) {
161594
162256
  i++;
161595
162257
  continue;
161596
162258
  }
161597
- let j = i + 1;
161598
- while (j < chars.length) {
161599
- const cp = chars[j].codePointAt(0);
161600
- if (cp === 8205 && j + 1 < chars.length) {
161601
- j += 2;
161602
- continue;
161603
- }
161604
- if (isJoinAfter(cp)) {
161605
- j++;
161606
- continue;
161607
- }
161608
- break;
161609
- }
162259
+ const j = clusterEnd(chars, i);
161610
162260
  const cluster = chars.slice(i, j).join("");
161611
162261
  const cw = estimateWidth(cluster);
161612
162262
  if (w + cw > cols && w > 0) {
@@ -161628,30 +162278,6 @@ function clipToWidth(line, maxWidth) {
161628
162278
  if (estimateWidth(line) <= w) return line;
161629
162279
  return headOfLine(line, w - 1) + "\u2026";
161630
162280
  }
161631
- function windowAroundCursor(chars, cursor, budget, newlineGlyph) {
161632
- const glyph = (ch) => ch === "\n" ? newlineGlyph : ch;
161633
- const half = Math.floor(budget / 2);
161634
- let w = 0;
161635
- let lo = cursor;
161636
- while (lo > 0) {
161637
- const cw = estimateWidth(glyph(chars[lo - 1]));
161638
- if (w + cw > half) break;
161639
- w += cw;
161640
- lo--;
161641
- }
161642
- const at2 = cursor < chars.length ? glyph(chars[cursor]) : "";
161643
- let rest2 = budget - w - estimateWidth(at2);
161644
- let hi = Math.min(cursor + 1, chars.length);
161645
- while (hi < chars.length) {
161646
- const cw = estimateWidth(glyph(chars[hi]));
161647
- if (rest2 - cw < 0) break;
161648
- rest2 -= cw;
161649
- hi++;
161650
- }
161651
- const before2 = (lo > 0 ? "\u2026" : "") + chars.slice(lo, cursor).map(glyph).join("");
161652
- const after2 = chars.slice(Math.min(cursor + 1, chars.length), hi).map(glyph).join("") + (hi < chars.length ? "\u2026" : "");
161653
- return { before: before2, at: at2, after: after2 };
161654
- }
161655
162281
 
161656
162282
  // src/tui/bridge.ts
161657
162283
  var MAIN2 = "main";
@@ -161752,6 +162378,10 @@ var Bridge = class {
161752
162378
  setUsage(usage2) {
161753
162379
  this.set({ usage: usage2 });
161754
162380
  }
162381
+ /** 会话切换(/resume 的 session:ready、/clear 的 session:cleared)后同步 UI 镜像:新 sessionId/usage,清掉上一会话的 todos 与流式残留 */
162382
+ resetSession(d) {
162383
+ this.set({ sessionId: d.sessionId, usage: d.usage, todos: [], streamText: "", streamThinkingChars: 0 });
162384
+ }
161755
162385
  setThinking(enabled) {
161756
162386
  this.set({ thinkingEnabled: enabled });
161757
162387
  this.core.updateThinking(enabled);
@@ -161960,6 +162590,10 @@ var Bridge = class {
161960
162590
  core.on("session:interrupted", (d) => {
161961
162591
  if (d.agentId === MAIN2) this.notice("\u23F9 \u5DF2\u4E2D\u65AD", "warn");
161962
162592
  });
162593
+ core.on("session:cleared", (d) => {
162594
+ this.resetSession(d);
162595
+ this.notice(`\u4E0A\u4E0B\u6587\u5DF2\u6E05\u7A7A\uFF0C\u65B0\u4F1A\u8BDD ${d.sessionId}${d.previousSessionId ? `\uFF08\u539F\u4F1A\u8BDD ${d.previousSessionId} \u53EF\u7528 /resume \u6062\u590D\uFF09` : ""}`, "success");
162596
+ });
161963
162597
  core.on("session:error", (d) => {
161964
162598
  this.notice(`\u4F1A\u8BDD\u9519\u8BEF [${d.type}] ${d.error?.message ?? d.error?.code ?? ""}`, "error");
161965
162599
  });
@@ -162397,10 +163031,284 @@ function findAtToken(chars, cursor) {
162397
163031
  return { start, end, prefix: chars.slice(start + 1, cursor).join("") };
162398
163032
  }
162399
163033
 
163034
+ // src/tui/pasteText.ts
163035
+ var PASTE_COLLAPSE_CHARS = 200;
163036
+ var PLACEHOLDER_RE = /\[粘贴#\d+ [^[\]]*\]/g;
163037
+ var IMAGE_PLACEHOLDER_RE = /\[图片#\d+\]/g;
163038
+ var pastes = /* @__PURE__ */ new Map();
163039
+ var pasteSeq = 0;
163040
+ function sanitizePastedText(raw) {
163041
+ return raw.replace(/\r\n?/g, "\n").replace(/\t/g, " ").replace(/[\x00-\x08\x0b-\x1f\x7f]/g, "").replace(/\n+$/, "");
163042
+ }
163043
+ function collapsePaste(text) {
163044
+ const chars = [...text];
163045
+ if (chars.length <= PASTE_COLLAPSE_CHARS) return null;
163046
+ const lines = text.split("\n").length;
163047
+ const placeholder = `[\u7C98\u8D34#${++pasteSeq} ${lines > 1 ? `${lines}\u884C` : `${chars.length}\u5B57`}]`;
163048
+ pastes.set(placeholder, text);
163049
+ return placeholder;
163050
+ }
163051
+ function expandPastes(value) {
163052
+ return value.replace(PLACEHOLDER_RE, (m) => {
163053
+ const text = pastes.get(m);
163054
+ if (text === void 0) return m;
163055
+ pastes.delete(m);
163056
+ return text;
163057
+ });
163058
+ }
163059
+ function atomicSpans(value) {
163060
+ const spans = [];
163061
+ const collect = (re, accept) => {
163062
+ for (const m of value.matchAll(re)) {
163063
+ if (!accept(m[0])) continue;
163064
+ const start = [...value.slice(0, m.index)].length;
163065
+ spans.push({ start, end: start + [...m[0]].length });
163066
+ }
163067
+ };
163068
+ collect(PLACEHOLDER_RE, (t) => pastes.has(t));
163069
+ collect(IMAGE_PLACEHOLDER_RE, () => true);
163070
+ return spans.sort((a, b) => a.start - b.start);
163071
+ }
163072
+ function spanEndingAt(spans, i) {
163073
+ return spans.find((s) => s.end === i);
163074
+ }
163075
+ function spanStartingAt(spans, i) {
163076
+ return spans.find((s) => s.start === i);
163077
+ }
163078
+
163079
+ // src/tui/terminalInput.ts
163080
+ import { PassThrough as PassThrough2 } from "node:stream";
163081
+ import { emitKeypressEvents } from "node:readline";
163082
+ var BP_START = "\x1B[200~";
163083
+ var BP_END = "\x1B[201~";
163084
+ var SETTLE_MS = 30;
163085
+ var CHUNK_MIN = 256;
163086
+ function createTerminalInput(emit) {
163087
+ let bracketed = null;
163088
+ let fallback = "";
163089
+ let prefix = "";
163090
+ let timer;
163091
+ const keys2 = new PassThrough2();
163092
+ emitKeypressEvents(keys2);
163093
+ const onKey = (input, key) => {
163094
+ emit({
163095
+ kind: "key",
163096
+ input: key.ctrl ? key.name ?? "" : input ?? "",
163097
+ key: {
163098
+ upArrow: key.name === "up",
163099
+ downArrow: key.name === "down",
163100
+ leftArrow: key.name === "left",
163101
+ rightArrow: key.name === "right",
163102
+ pageUp: key.name === "pageup",
163103
+ pageDown: key.name === "pagedown",
163104
+ return: key.name === "return" || key.name === "enter",
163105
+ escape: key.name === "escape",
163106
+ tab: key.name === "tab",
163107
+ backspace: key.name === "backspace",
163108
+ delete: key.name === "delete",
163109
+ ctrl: !!key.ctrl,
163110
+ shift: !!key.shift,
163111
+ meta: !!key.meta
163112
+ }
163113
+ });
163114
+ };
163115
+ keys2.on("keypress", onKey);
163116
+ const flush = () => {
163117
+ if (!fallback) return;
163118
+ emit({ kind: "paste", text: fallback });
163119
+ fallback = "";
163120
+ };
163121
+ const ordinary = (data, asKeys = false) => {
163122
+ if (!data) return;
163123
+ if (data === "\x1B") {
163124
+ flush();
163125
+ onKey("", { name: "escape", sequence: data });
163126
+ return;
163127
+ }
163128
+ if (asKeys) {
163129
+ flush();
163130
+ keys2.write(data);
163131
+ return;
163132
+ }
163133
+ const singleControl = data.length === 1 && /[\x00-\x1f\x7f]/.test(data);
163134
+ if (!data.startsWith("\x1B") && !singleControl && (fallback || [...data].length >= CHUNK_MIN)) {
163135
+ fallback += data;
163136
+ } else {
163137
+ flush();
163138
+ if ([...data].length > 1 && !data.startsWith("\x1B")) emit({ kind: "paste", text: data });
163139
+ else keys2.write(data);
163140
+ }
163141
+ };
163142
+ const write = (chunk2) => {
163143
+ clearTimeout(timer);
163144
+ let data = prefix + chunk2;
163145
+ let afterBracket = false;
163146
+ prefix = "";
163147
+ while (data) {
163148
+ if (bracketed !== null) {
163149
+ const searchFrom = Math.max(0, bracketed.length - BP_END.length + 1);
163150
+ bracketed += data;
163151
+ const end = bracketed.indexOf(BP_END, searchFrom);
163152
+ if (end < 0) return;
163153
+ emit({ kind: "paste", text: bracketed.slice(0, end) });
163154
+ data = bracketed.slice(end + BP_END.length);
163155
+ bracketed = null;
163156
+ afterBracket = true;
163157
+ continue;
163158
+ }
163159
+ const start = data.indexOf(BP_START);
163160
+ if (start >= 0) {
163161
+ ordinary(data.slice(0, start), afterBracket);
163162
+ flush();
163163
+ bracketed = "";
163164
+ data = data.slice(start + BP_START.length);
163165
+ continue;
163166
+ }
163167
+ for (let n = Math.min(data.length, BP_START.length - 1); n > 0; n--) {
163168
+ if (data.endsWith(BP_START.slice(0, n))) {
163169
+ prefix = data.slice(-n);
163170
+ data = data.slice(0, -n);
163171
+ break;
163172
+ }
163173
+ }
163174
+ ordinary(data, afterBracket);
163175
+ break;
163176
+ }
163177
+ if (prefix || fallback) {
163178
+ timer = setTimeout(() => {
163179
+ const pending = prefix;
163180
+ prefix = "";
163181
+ ordinary(pending);
163182
+ flush();
163183
+ }, SETTLE_MS);
163184
+ }
163185
+ };
163186
+ return {
163187
+ write,
163188
+ dispose() {
163189
+ clearTimeout(timer);
163190
+ keys2.removeAllListeners("keypress");
163191
+ keys2.destroy();
163192
+ }
163193
+ };
163194
+ }
163195
+
163196
+ // src/tui/inputLayout.ts
163197
+ function layoutRows(chars, columns) {
163198
+ const cols = Math.max(1, columns);
163199
+ const rows = [];
163200
+ let rowStart = 0;
163201
+ let w = 0;
163202
+ let i = 0;
163203
+ while (i < chars.length) {
163204
+ if (chars[i] === "\n") {
163205
+ rows.push({ start: rowStart, end: i });
163206
+ i++;
163207
+ rowStart = i;
163208
+ w = 0;
163209
+ continue;
163210
+ }
163211
+ const j = clusterEnd(chars, i);
163212
+ const cw = estimateWidth(chars.slice(i, j).join(""));
163213
+ if (w + cw > cols && w > 0) {
163214
+ rows.push({ start: rowStart, end: i });
163215
+ rowStart = i;
163216
+ w = 0;
163217
+ }
163218
+ w += cw;
163219
+ i = j;
163220
+ }
163221
+ rows.push({ start: rowStart, end: chars.length });
163222
+ return rows;
163223
+ }
163224
+ function rowOfCursor(rows, cursor) {
163225
+ let r = 0;
163226
+ for (let i = 0; i < rows.length; i++) {
163227
+ if (rows[i].start <= cursor) r = i;
163228
+ else break;
163229
+ }
163230
+ return r;
163231
+ }
163232
+ function columnOf(chars, row, cursor) {
163233
+ return estimateWidth(chars.slice(row.start, Math.min(cursor, row.end)).join(""));
163234
+ }
163235
+ function indexAtColumn(chars, row, column) {
163236
+ let w = 0;
163237
+ let i = row.start;
163238
+ while (i < row.end) {
163239
+ const j = clusterEnd(chars, i);
163240
+ const cw = estimateWidth(chars.slice(i, j).join(""));
163241
+ if (w + cw > column) break;
163242
+ w += cw;
163243
+ i = j;
163244
+ }
163245
+ return i;
163246
+ }
163247
+ function moveVertical(chars, rows, cursor, delta) {
163248
+ const r = rowOfCursor(rows, cursor);
163249
+ const target = r + delta;
163250
+ if (target < 0 || target >= rows.length) return null;
163251
+ const row = rows[target];
163252
+ const index = indexAtColumn(chars, row, columnOf(chars, rows[r], cursor));
163253
+ if (index === row.end && rows[target + 1]?.start === row.end) {
163254
+ let last2 = row.start;
163255
+ for (let i = row.start; i < row.end; i = clusterEnd(chars, i)) last2 = i;
163256
+ return last2;
163257
+ }
163258
+ return index;
163259
+ }
163260
+ function lineStart(chars, cursor) {
163261
+ let i = cursor;
163262
+ while (i > 0 && chars[i - 1] !== "\n") i--;
163263
+ return i;
163264
+ }
163265
+ function lineEnd(chars, cursor) {
163266
+ let i = cursor;
163267
+ while (i < chars.length && chars[i] !== "\n") i++;
163268
+ return i;
163269
+ }
163270
+ function scrollTop(total, cursorRow, visible, prevTop) {
163271
+ const maxTop = Math.max(0, total - visible);
163272
+ let top = Math.min(Math.max(0, prevTop), maxTop);
163273
+ if (cursorRow < top) top = cursorRow;
163274
+ else if (cursorRow >= top + visible) top = cursorRow - visible + 1;
163275
+ return top;
163276
+ }
163277
+ function segmentRow(chars, row, spans, cursor) {
163278
+ const segs = [];
163279
+ const push = (text, kind) => {
163280
+ const last2 = segs[segs.length - 1];
163281
+ if (last2 && last2.kind === kind && kind !== "cursor") last2.text += text;
163282
+ else segs.push({ text, kind });
163283
+ };
163284
+ let s = 0;
163285
+ for (let i = row.start; i < row.end; i++) {
163286
+ while (s < spans.length && spans[s].end <= i) s++;
163287
+ const inSpan = s < spans.length && spans[s].start <= i && i < spans[s].end;
163288
+ if (i === cursor) push(chars[i], "cursor");
163289
+ else push(chars[i], inSpan ? "placeholder" : "text");
163290
+ }
163291
+ if (cursor === row.end) segs.push({ text: "", kind: "cursor" });
163292
+ return segs;
163293
+ }
163294
+
162400
163295
  // src/tui/components/LineInput.tsx
162401
163296
  var import_jsx_runtime3 = __toESM(require_jsx_runtime());
162402
163297
  var MENU_MAX = 8;
162403
- var FORWARD_DELETE_SEQ = "\x1B[3~";
163298
+ var BP_ENABLE = "\x1B[?2004h";
163299
+ var BP_DISABLE = "\x1B[?2004l";
163300
+ function insertAt({ value, cursor }, text, pad2) {
163301
+ const chars = [...value];
163302
+ let insert = text;
163303
+ if (pad2) {
163304
+ const spaceBefore = cursor > 0 && chars[cursor - 1] !== " " ? " " : "";
163305
+ const spaceAfter = chars[cursor] === " " || chars[cursor] === "\n" ? "" : " ";
163306
+ insert = spaceBefore + text + spaceAfter;
163307
+ }
163308
+ const ins = [...insert];
163309
+ chars.splice(cursor, 0, ...ins);
163310
+ return { value: chars.join(""), cursor: cursor + ins.length };
163311
+ }
162404
163312
  function LineInput({ prompt = G.prompt, placeholder = "", history = [], color = theme.accent, onSubmit, onPaste, commands = [], completePaths }) {
162405
163313
  const [state, setState] = (0, import_react22.useState)({ value: "", cursor: 0 });
162406
163314
  const [histIdx, setHistIdx] = (0, import_react22.useState)(-1);
@@ -162414,6 +163322,7 @@ function LineInput({ prompt = G.prompt, placeholder = "", history = [], color =
162414
163322
  const sel = menu.length > 0 ? Math.min(selIdx ?? 0, menu.length - 1) : 0;
162415
163323
  const histValueRef = (0, import_react22.useRef)(null);
162416
163324
  const browsingHistory = histIdx >= 0 && state.value === histValueRef.current;
163325
+ const draftRef = (0, import_react22.useRef)({ value: "", cursor: 0 });
162417
163326
  const arrowsToMenu = menuCapturesArrows(menu, isPathMenu && atToken ? "@" + atToken.prefix : state.value, browsingHistory);
162418
163327
  (0, import_react22.useEffect)(() => {
162419
163328
  setSelIdx(null);
@@ -162427,23 +163336,48 @@ function LineInput({ prompt = G.prompt, placeholder = "", history = [], color =
162427
163336
  }
162428
163337
  const pastingRef = (0, import_react22.useRef)(false);
162429
163338
  const [pasting, setPasting] = (0, import_react22.useState)(false);
162430
- const fwdDeleteRef = (0, import_react22.useRef)(false);
162431
- const { internal_eventEmitter } = use_stdin_default();
163339
+ const inputQueue = (0, import_react22.useRef)([]);
163340
+ const [inputVersion, wakeInput] = (0, import_react22.useState)(0);
163341
+ function insertPaste(raw) {
163342
+ const text = sanitizePastedText(raw);
163343
+ if (!text) return;
163344
+ const ph = collapsePaste(text);
163345
+ setState((s) => ph ? insertAt(s, ph, true) : insertAt(s, text, false));
163346
+ }
163347
+ const { internal_eventEmitter, setRawMode } = use_stdin_default();
163348
+ const { stdout } = use_stdout_default();
162432
163349
  (0, import_react22.useEffect)(() => {
162433
- const onRaw = (data) => {
162434
- fwdDeleteRef.current = data === FORWARD_DELETE_SEQ;
162435
- if (!fwdDeleteRef.current) return;
162436
- setState(({ value: value2, cursor: cursor2 }) => {
162437
- const chars2 = [...value2];
162438
- if (cursor2 >= chars2.length) return { value: value2, cursor: cursor2 };
162439
- chars2.splice(cursor2, 1);
162440
- return { value: chars2.join(""), cursor: cursor2 };
162441
- });
163350
+ const reader = createTerminalInput((event) => {
163351
+ inputQueue.current.push(event);
163352
+ wakeInput((n) => n + 1);
163353
+ });
163354
+ setRawMode(true);
163355
+ stdout?.write(BP_ENABLE);
163356
+ internal_eventEmitter?.on("input", reader.write);
163357
+ return () => {
163358
+ internal_eventEmitter?.off("input", reader.write);
163359
+ reader.dispose();
163360
+ inputQueue.current = [];
163361
+ stdout?.write(BP_DISABLE);
163362
+ setRawMode(false);
162442
163363
  };
162443
- internal_eventEmitter?.on("input", onRaw);
162444
- return () => internal_eventEmitter?.off("input", onRaw);
162445
- }, [internal_eventEmitter]);
162446
- use_input_default((input, key) => {
163364
+ }, [internal_eventEmitter, stdout, setRawMode]);
163365
+ const { value, cursor } = state;
163366
+ const chars = [...value];
163367
+ const termColumns = stdout?.columns ?? 80;
163368
+ const termRows = stdout?.rows ?? 24;
163369
+ const promptWidth = estimateWidth(prompt);
163370
+ const columns = Math.max(10, termColumns - 5 - promptWidth);
163371
+ const inputRows = Math.max(2, Math.min(6, termRows - 16));
163372
+ const rows = layoutRows(chars, columns);
163373
+ const curRow = rowOfCursor(rows, cursor);
163374
+ const clipped = rows.length > inputRows;
163375
+ const visible = clipped ? inputRows - 1 : inputRows;
163376
+ const topRef = (0, import_react22.useRef)(0);
163377
+ topRef.current = scrollTop(rows.length, curRow, visible, topRef.current);
163378
+ const top = topRef.current;
163379
+ const spans = atomicSpans(value);
163380
+ function handleKey(input, key) {
162447
163381
  if (key.return) {
162448
163382
  if (pastingRef.current) return;
162449
163383
  if (isPathMenu && selIdx !== null && menu.length > 0) {
@@ -162454,8 +163388,9 @@ function LineInput({ prompt = G.prompt, placeholder = "", history = [], color =
162454
163388
  setState({ value: "", cursor: 0 });
162455
163389
  setHistIdx(-1);
162456
163390
  histValueRef.current = null;
163391
+ draftRef.current = { value: "", cursor: 0 };
162457
163392
  setSelIdx(null);
162458
- onSubmit(v);
163393
+ onSubmit(expandPastes(v));
162459
163394
  return;
162460
163395
  }
162461
163396
  if (key.tab) {
@@ -162468,22 +163403,34 @@ function LineInput({ prompt = G.prompt, placeholder = "", history = [], color =
162468
163403
  }
162469
163404
  return;
162470
163405
  }
162471
- if (key.backspace || key.delete) {
162472
- if (fwdDeleteRef.current) return;
163406
+ if (key.delete) {
163407
+ setState(({ value: value2, cursor: cursor2 }) => {
163408
+ const chars2 = [...value2];
163409
+ const span = spanStartingAt(atomicSpans(value2), cursor2);
163410
+ chars2.splice(cursor2, span ? span.end - span.start : 1);
163411
+ return { value: chars2.join(""), cursor: cursor2 };
163412
+ });
163413
+ return;
163414
+ }
163415
+ if (key.backspace) {
162473
163416
  setState(({ value: value2, cursor: cursor2 }) => {
162474
163417
  if (cursor2 === 0) return { value: value2, cursor: cursor2 };
162475
163418
  const chars2 = [...value2];
162476
- chars2.splice(cursor2 - 1, 1);
162477
- return { value: chars2.join(""), cursor: cursor2 - 1 };
163419
+ const span = spanEndingAt(atomicSpans(value2), cursor2);
163420
+ const n = span ? span.end - span.start : 1;
163421
+ chars2.splice(cursor2 - n, n);
163422
+ return { value: chars2.join(""), cursor: cursor2 - n };
162478
163423
  });
162479
163424
  return;
162480
163425
  }
162481
163426
  if (key.leftArrow) {
162482
- setState(({ value: value2, cursor: cursor2 }) => ({ value: value2, cursor: Math.max(0, cursor2 - 1) }));
163427
+ const span = spanEndingAt(spans, cursor);
163428
+ setState(({ value: value2, cursor: cursor2 }) => ({ value: value2, cursor: span ? span.start : Math.max(0, cursor2 - 1) }));
162483
163429
  return;
162484
163430
  }
162485
163431
  if (key.rightArrow) {
162486
- setState(({ value: value2, cursor: cursor2 }) => ({ value: value2, cursor: Math.min([...value2].length, cursor2 + 1) }));
163432
+ const span = spanStartingAt(spans, cursor);
163433
+ setState(({ value: value2, cursor: cursor2 }) => ({ value: value2, cursor: span ? span.end : Math.min([...value2].length, cursor2 + 1) }));
162487
163434
  return;
162488
163435
  }
162489
163436
  if (key.upArrow) {
@@ -162491,8 +163438,15 @@ function LineInput({ prompt = G.prompt, placeholder = "", history = [], color =
162491
163438
  setSelIdx((sel - 1 + menu.length) % menu.length);
162492
163439
  return;
162493
163440
  }
163441
+ const moved = moveVertical(chars, rows, cursor, -1);
163442
+ if (moved !== null) {
163443
+ setState({ value, cursor: moved });
163444
+ return;
163445
+ }
162494
163446
  if (history.length === 0) return;
162495
- const next = Math.min(histIdx + 1, history.length - 1);
163447
+ const base = browsingHistory ? histIdx : -1;
163448
+ if (base === -1) draftRef.current = state;
163449
+ const next = Math.min(base + 1, history.length - 1);
162496
163450
  const v = history[next] ?? "";
162497
163451
  setHistIdx(next);
162498
163452
  histValueRef.current = v;
@@ -162504,10 +163458,16 @@ function LineInput({ prompt = G.prompt, placeholder = "", history = [], color =
162504
163458
  setSelIdx((sel + 1) % menu.length);
162505
163459
  return;
162506
163460
  }
163461
+ const moved = moveVertical(chars, rows, cursor, 1);
163462
+ if (moved !== null) {
163463
+ setState({ value, cursor: moved });
163464
+ return;
163465
+ }
163466
+ if (!browsingHistory) return;
162507
163467
  if (histIdx <= 0) {
162508
163468
  setHistIdx(-1);
162509
163469
  histValueRef.current = null;
162510
- setState({ value: "", cursor: 0 });
163470
+ setState(draftRef.current);
162511
163471
  return;
162512
163472
  }
162513
163473
  const next = histIdx - 1;
@@ -162518,11 +163478,11 @@ function LineInput({ prompt = G.prompt, placeholder = "", history = [], color =
162518
163478
  return;
162519
163479
  }
162520
163480
  if (key.ctrl && input === "a") {
162521
- setState(({ value: value2 }) => ({ value: value2, cursor: 0 }));
163481
+ setState(({ value: value2, cursor: cursor2 }) => ({ value: value2, cursor: lineStart([...value2], cursor2) }));
162522
163482
  return;
162523
163483
  }
162524
163484
  if (key.ctrl && input === "e") {
162525
- setState(({ value: value2 }) => ({ value: value2, cursor: [...value2].length }));
163485
+ setState(({ value: value2, cursor: cursor2 }) => ({ value: value2, cursor: lineEnd([...value2], cursor2) }));
162526
163486
  return;
162527
163487
  }
162528
163488
  if (key.ctrl && input === "v" && onPaste) {
@@ -162530,20 +163490,9 @@ function LineInput({ prompt = G.prompt, placeholder = "", history = [], color =
162530
163490
  pastingRef.current = true;
162531
163491
  setPasting(true);
162532
163492
  void onPaste().then((r) => {
162533
- if (r) {
162534
- setState(({ value: value2, cursor: cursor2 }) => {
162535
- const chars2 = [...value2];
162536
- let insert = r.insert;
162537
- if (r.pad) {
162538
- const spaceBefore = cursor2 > 0 && chars2[cursor2 - 1] !== " " ? " " : "";
162539
- const spaceAfter = chars2[cursor2] === " " ? "" : " ";
162540
- insert = spaceBefore + r.insert + spaceAfter;
162541
- }
162542
- const ins = [...insert];
162543
- chars2.splice(cursor2, 0, ...ins);
162544
- return { value: chars2.join(""), cursor: cursor2 + ins.length };
162545
- });
162546
- }
163493
+ if (!r) return;
163494
+ if (r.pad) setState((s) => insertAt(s, r.insert, true));
163495
+ else insertPaste(r.insert);
162547
163496
  }).catch(() => {
162548
163497
  }).finally(() => {
162549
163498
  pastingRef.current = false;
@@ -162555,39 +163504,40 @@ function LineInput({ prompt = G.prompt, placeholder = "", history = [], color =
162555
163504
  return;
162556
163505
  }
162557
163506
  if (input) {
162558
- setState(({ value: value2, cursor: cursor2 }) => {
162559
- const chars2 = [...value2];
162560
- const ins = [...input];
162561
- chars2.splice(cursor2, 0, ...ins);
162562
- return { value: chars2.join(""), cursor: cursor2 + ins.length };
162563
- });
163507
+ setState((s) => insertAt(s, input, false));
162564
163508
  }
162565
- });
162566
- const { value, cursor } = state;
162567
- const chars = [...value];
162568
- const { stdout } = use_stdout_default();
162569
- const termColumns = stdout?.columns ?? 80;
162570
- const termRows = stdout?.rows ?? 24;
162571
- const inputRows = Math.max(2, Math.min(4, termRows - 12));
162572
- const budget = Math.max(40, termColumns - 8) * inputRows;
162573
- const { before: before2, at: at2, after: after2 } = windowAroundCursor(chars, cursor, budget, G.newline);
163509
+ }
163510
+ (0, import_react22.useEffect)(() => {
163511
+ const event = inputQueue.current.shift();
163512
+ if (!event) return;
163513
+ if (event.kind === "paste") insertPaste(event.text);
163514
+ else handleKey(event.input, event.key);
163515
+ if (inputQueue.current.length) wakeInput((n) => n + 1);
163516
+ }, [inputVersion]);
163517
+ const pastingHint = pasting ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { dimColor: true, children: [
163518
+ " ",
163519
+ G.refresh,
163520
+ " \u7C98\u8D34\u4E2D\u2026"
163521
+ ] }) : null;
163522
+ const indent = " ".repeat(promptWidth);
162574
163523
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { flexDirection: "column", children: [
162575
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { children: [
163524
+ value ? rows.slice(top, top + visible).map((row, i) => {
163525
+ const r = top + i;
163526
+ const segs = segmentRow(chars, row, spans, r === curRow ? cursor : null);
163527
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { wrap: "truncate-end", children: [
163528
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color, children: r === 0 ? prompt : indent }),
163529
+ segs.map(
163530
+ (s, j) => s.kind === "cursor" ? s.text ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { inverse: true, children: s.text }, j) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color, children: G.cursor }, j) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: s.kind === "placeholder" ? color : void 0, children: s.text }, j)
163531
+ ),
163532
+ r === curRow ? pastingHint : null
163533
+ ] }, r);
163534
+ }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { children: [
162576
163535
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color, children: prompt }),
162577
- value ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
162578
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { children: before2 }),
162579
- at2 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { inverse: true, children: at2 }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color, children: G.cursor }),
162580
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { children: after2 })
162581
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
162582
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color, children: G.cursor }),
162583
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: placeholder })
162584
- ] }),
162585
- pasting ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { dimColor: true, children: [
162586
- " ",
162587
- G.refresh,
162588
- " \u7C98\u8D34\u4E2D\u2026"
162589
- ] }) : null
163536
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color, children: G.cursor }),
163537
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: placeholder }),
163538
+ pastingHint
162590
163539
  ] }),
163540
+ clipped ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: ` \u2026\u5171 ${rows.length} \u884C\uFF0C\u663E\u793A\u7B2C ${top + 1}-${top + visible} \u884C\uFF08${G.updown} \u79FB\u52A8\u5149\u6807\uFF09` }) : null,
162591
163541
  menu.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { color: i === sel ? color : void 0, dimColor: i !== sel, children: [
162592
163542
  i === sel ? G.menuPointer : " ",
162593
163543
  c
@@ -162645,8 +163595,8 @@ function SelectList({ options: options2, multi = false, color = theme.accent, on
162645
163595
  }
162646
163596
  });
162647
163597
  const { stdout } = use_stdout_default();
162648
- const maxVisible = Math.max(3, Math.min(options2.length, (stdout?.rows ?? 24) - 8));
162649
- const winStart = Math.min(Math.max(0, index - maxVisible + 1), options2.length - maxVisible);
163598
+ const maxVisible = Math.max(1, Math.min(options2.length, Math.max(3, (stdout?.rows ?? 24) - 8)));
163599
+ const winStart = Math.max(0, Math.min(index - maxVisible + 1, options2.length - maxVisible));
162650
163600
  const visible = options2.slice(winStart, winStart + maxVisible);
162651
163601
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Box_default, { flexDirection: "column", children: [
162652
163602
  winStart > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { dimColor: true, children: ` \u2026 \u4E0A\u65B9\u8FD8\u6709 ${winStart} \u9879` }) : null,
@@ -162872,7 +163822,7 @@ import { promisify as promisify2 } from "util";
162872
163822
  var execFileP2 = promisify2(execFile2);
162873
163823
  var IMAGE_EXT_RE = /\.(png|jpe?g|gif|webp)$/i;
162874
163824
  var pastedImages = /* @__PURE__ */ new Map();
162875
- var pasteSeq = 0;
163825
+ var pasteSeq2 = 0;
162876
163826
  var housekeepingDone = false;
162877
163827
  function ensureHousekeeping(dir) {
162878
163828
  if (housekeepingDone) return;
@@ -162944,11 +163894,11 @@ async function readClipboardText() {
162944
163894
  } catch {
162945
163895
  return null;
162946
163896
  }
162947
- const text = raw.replace(/\r\n?/g, "\n").replace(/\t/g, " ").replace(/[\x00-\x08\x0b-\x1f\x7f]/g, "").replace(/\n+$/, "");
163897
+ const text = sanitizePastedText(raw);
162948
163898
  return text || null;
162949
163899
  }
162950
163900
  async function capturePastedImage() {
162951
- const seq = ++pasteSeq;
163901
+ const seq = ++pasteSeq2;
162952
163902
  const file = await readClipboardImage(seq);
162953
163903
  if (!file) return null;
162954
163904
  const placeholder = `[\u56FE\u7247#${seq}]`;
@@ -163159,6 +164109,29 @@ async function runShellCommand(cmd, opts) {
163159
164109
  return finalize(killed ? "killed" : "exited");
163160
164110
  }
163161
164111
 
164112
+ // src/sessionSwitch.ts
164113
+ init_skills();
164114
+ init_agents();
164115
+ async function switchSession(core, sessionId) {
164116
+ let ready;
164117
+ const onReady = (d) => {
164118
+ ready = d;
164119
+ };
164120
+ core.once("session:ready", onReady);
164121
+ try {
164122
+ await core.createSession(sessionId);
164123
+ } finally {
164124
+ core.off("session:ready", onReady);
164125
+ }
164126
+ if (!ready) throw new Error("createSession \u5DF2\u8FD4\u56DE\u4F46\u672A\u6536\u5230 session:ready");
164127
+ const notes = [];
164128
+ applyDisabledSkills(core);
164129
+ await applyDisabledAgents(core).catch((e) => {
164130
+ notes.push(`subagent \u542F\u505C\u540D\u5355\u5E94\u7528\u5931\u8D25\uFF08harness agent \u53D6\u820D\u53EF\u80FD\u672A\u751F\u6548\uFF09\uFF1A${e instanceof Error ? e.message : String(e)}`);
164131
+ });
164132
+ return { ready, notes };
164133
+ }
164134
+
163162
164135
  // src/tui/App.tsx
163163
164136
  var import_jsx_runtime6 = __toESM(require_jsx_runtime());
163164
164137
  function ShellRunningLine({ cmd }) {
@@ -163179,7 +164152,7 @@ function ShellRunningLine({ cmd }) {
163179
164152
  }
163180
164153
  var HELP = [
163181
164154
  "/help \u672C\u5E2E\u52A9 \xB7 /model \u6A21\u578B\u914D\u7F6E \xB7 /skills skill \u542F\u505C \xB7 /agents \u4EBA\u8BBE\u542F\u505C \xB7 /mcp MCP \u5217\u8868 \xB7 /marketplace \u63D2\u4EF6\u5E02\u573A \xB7 /memory \u8BB0\u5FC6/\u4EBA\u8BBE\u6587\u4EF6 \xB7 /resume \u5207\u6362\u5386\u53F2\u4F1A\u8BDD \xB7 /status \u72B6\u6001 \xB7 /tools \u5DE5\u5177\u5217\u8868",
163182
- "/theme [\u540D\u79F0] \u4E3B\u9898\u5207\u6362 \xB7 /glyphs [auto|fancy|ascii] \u5B57\u5F62\u98CE\u683C\uFF08cmd \u9ED8\u8BA4\u964D\u7EA7 ASCII \u9632\u9519\u4F4D\uFF09 \xB7 /thinking [on|off] \u601D\u8003\u5F00\u5173 \xB7 /clear /compact \u4E0A\u4E0B\u6587 \xB7 /exit \u9000\u51FA",
164155
+ "/theme [\u540D\u79F0] \u4E3B\u9898\u5207\u6362 \xB7 /glyphs [auto|fancy|ascii] \u5B57\u5F62\u98CE\u683C\uFF08cmd \u9ED8\u8BA4\u964D\u7EA7 ASCII \u9632\u9519\u4F4D\uFF09 \xB7 /thinking [on|off] \u601D\u8003\u5F00\u5173 \xB7 /clear \u6E05\u7A7A\u4E0A\u4E0B\u6587\uFF08\u5F00\u65B0\u4F1A\u8BDD\uFF0C\u65E7\u4F1A\u8BDD\u53EF /resume\uFF09\xB7 /compact \u538B\u7F29\u4E0A\u4E0B\u6587 \xB7 /exit \u9000\u51FA",
163183
164156
  "/permissions [step-by-step|action-check|free-style] \u6743\u9650\u6A21\u5F0F\uFF08Shift+Tab \u5FAA\u73AF\u5207\u6362\uFF1Baction check \u53EA\u8BFB\u64CD\u4F5C\u514D\u786E\u8BA4\uFF0Cfree style \u5168\u90E8\u514D\u786E\u8BA4\uFF09",
163184
164157
  "\u8F93\u5165 / \u663E\u793A\u547D\u4EE4\u83DC\u5355\uFF1ATab \u8865\u5168 \xB7 \u2191\u2193 \u9009\u62E9 \xB7 \u552F\u4E00\u524D\u7F00\u53EF\u76F4\u63A5\u6267\u884C\uFF08\u5982 /st \u2192 /status\uFF09",
163185
164158
  "@\u6587\u4EF6\u8DEF\u5F84 \u5F15\u7528\u6587\u4EF6\uFF08\u8F93\u5165 @ \u5F39\u51FA\u8DEF\u5F84\u8865\u5168\uFF0CTab/\u2191\u2193 \u9009\u62E9\uFF1B\u56FE\u7247\u6269\u5C55\u540D\u81EA\u52A8\u8F6C\u591A\u6A21\u6001\uFF09 \xB7 Ctrl+V \u7C98\u8D34\u526A\u8D34\u677F\u56FE\u7247\u6216\u6587\u672C\uFF08\u56FE\u7247\u652F\u6301 macOS/Windows\uFF09 \xB7 Esc \u4E2D\u65AD\u5F53\u524D\u8F6E \xB7 \u2191\u2193 \u8F93\u5165\u5386\u53F2",
@@ -163320,17 +164293,9 @@ function App2({ bridge, cwd: cwd2, initialHistory, appConfig }) {
163320
164293
  ]);
163321
164294
  if (!picked) return;
163322
164295
  try {
163323
- await new Promise((resolve9, reject2) => {
163324
- bridge.core.once("session:ready", (d) => {
163325
- bridge.setSessionId(d.sessionId);
163326
- bridge.setUsage(d.usage);
163327
- resolve9();
163328
- });
163329
- bridge.core.createSession(picked).catch(reject2);
163330
- });
163331
- applyDisabledSkills(bridge.core);
163332
- await applyDisabledAgents(bridge.core).catch(() => {
163333
- });
164296
+ const { ready, notes } = await switchSession(bridge.core, picked);
164297
+ bridge.resetSession(ready);
164298
+ for (const n of notes) bridge.notice(n, "warn");
163334
164299
  bridge.seedReplay(loadReplay(cwd2, picked));
163335
164300
  bridge.notice(`\u5DF2\u5207\u6362\u5230\u4F1A\u8BDD ${picked}`, "success");
163336
164301
  } catch (e) {
@@ -163544,7 +164509,7 @@ function App2({ bridge, cwd: cwd2, initialHistory, appConfig }) {
163544
164509
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: theme[permMeta.tone], children: ` ${G.permMode} ${permMeta.label}` }),
163545
164510
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { dimColor: true, children: `${G.sep}Shift+Tab switch` })
163546
164511
  ] }),
163547
- showHint ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { dimColor: true, children: ` Enter \u53D1\u9001${G.sep}${G.updown} \u5386\u53F2${G.sep}/help \u547D\u4EE4\uFF08/ \u53EF Tab \u8865\u5168\uFF09${G.sep}Ctrl+C \u9000\u51FA` }) : null
164512
+ showHint ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { dimColor: true, children: ` Enter \u53D1\u9001${G.sep}${G.updown} \u884C/\u5386\u53F2${G.sep}/help \u547D\u4EE4\uFF08/ \u53EF Tab \u8865\u5168\uFF09${G.sep}Ctrl+C \u9000\u51FA` }) : null
163548
164513
  ] }) : null
163549
164514
  ] });
163550
164515
  }
@@ -163617,7 +164582,7 @@ async function startTui(cwd2, options2 = {}) {
163617
164582
  });
163618
164583
  };
163619
164584
  const GESTURE_GAP_MS = 300;
163620
- const SETTLE_MS = 150;
164585
+ const SETTLE_MS2 = 150;
163621
164586
  let lastResizeAt = 0;
163622
164587
  let resizeTimer = null;
163623
164588
  const onResize = () => {
@@ -163625,7 +164590,7 @@ async function startTui(cwd2, options2 = {}) {
163625
164590
  if (now2 - lastResizeAt > GESTURE_GAP_MS) scrollOutViewport();
163626
164591
  lastResizeAt = now2;
163627
164592
  if (resizeTimer) clearTimeout(resizeTimer);
163628
- resizeTimer = setTimeout(() => refreshTail(true), SETTLE_MS);
164593
+ resizeTimer = setTimeout(() => refreshTail(true), SETTLE_MS2);
163629
164594
  };
163630
164595
  process.stdout.on("resize", onResize);
163631
164596
  const guard = createScrollbackGuard(process.stdout, { onBurstEnd: () => refreshTail(false) });
@@ -163640,6 +164605,7 @@ async function startTui(cwd2, options2 = {}) {
163640
164605
  process.stdout.off("resize", onResize);
163641
164606
  if (resizeTimer) clearTimeout(resizeTimer);
163642
164607
  guard.flush();
164608
+ process.stdout.write(`${ESC4}[?2004l`);
163643
164609
  await core.dispose().catch(() => {
163644
164610
  });
163645
164611
  }