scream-code 0.15.0 → 0.15.2

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.
@@ -4,7 +4,7 @@ import { dirname as __cjsShimDirname } from 'node:path';
4
4
  const __filename = __cjsShimFileURLToPath(import.meta.url);
5
5
  const __dirname = __cjsShimDirname(__filename);
6
6
  import "./suppress-sqlite-warning-C2VB0doZ.mjs";
7
- import { $t as isTodoItemShape, A as renderDiffLinesClustered, An as getInputHistoryFile, At as ENABLE_TERMINAL_FOCUS_REPORTING, B as BackgroundAgentStatusComponent, Bn as ScreamHarness, Bt as TERMINAL_THEME_LIGHT, Cn as PULSE_WAVE_FRAMES, Ct as createThemeStyles, D as toggleEmptySessionHint, Dn as saveTuiConfig, Dt as parseOsc11BackgroundTheme, E as isTurnElapsedEnabled, En as loadTuiConfig, Et as detectTerminalTheme, F as estimateTokens, Fn as CLI_USER_AGENT_PRODUCT, Ft as OSC11_RESPONSE_PREFIX_NO_ESC, G as getBreathingFrame, Gn as log, H as AgentGroupComponent, Hn as resolveScreamHome, Ht as isStreaming, I as getSharedSpeedTracker, In as PRODUCT_NAME, It as QUERY_TERMINAL_THEME, J as refineGoal, Jn as isOrphanedToolCallError, Jt as printableChar, K as resetBreathingClock, Kn as resolveGlobalLogPath, L as SkillActivationComponent, Ln as DEFAULT_CATALOG_URL, Lt as TERMINAL_FOCUS_IN, M as langFromPath, Mn as detectInstallSource, Mt as OSC11_QUERY, N as CachedContainer, Nn as CLI_COMMAND_NAME, Nt as OSC11_RESPONSE, O as ToolCallComponent, On as detectShellEnvironment, Ot as DISABLE_TERMINAL_FOCUS_REPORTING, P as ThinkingComponent, Pn as CLI_UI_MODE, Pt as OSC11_RESPONSE_PREFIX, Qt as formatErrorMessage, R as ReadGroupComponent, Rn as fetchCatalog, Rt as TERMINAL_FOCUS_OUT, S as getDaemonInstructions, Sn as PIXEL_PULSE_FRAMES, St as createMarkdownTheme, T as isEmptySessionHintDismissed, Tn as TuiLikePreferencesSchema, Tt as getColorPalette, U as WelcomeComponent, Un as MemoryMemoStore, Ut as FooterComponent, V as AssistantMessageComponent, Vt as isBusy, W as BREATHE_CYCLE_MS, Wn as flushDiagnosticLogs, Xn as SCREAM_ERROR_INFO, Xt as appendStreamingArgsPreview, Yn as ErrorCodes, Yt as STATUS_BULLET, Zt as argsRecord, _ as refreshUpdateCache, _n as isExperimentalFlagEnabled, _t as clearInfoPanelState, an as EMPTY_SESSION_HINT_URL, b as readJsonlFile, bn as isEmbeddingModelCached, bt as resolveThemeSync, c as hasDispose, cn as SESSION_TIPS, ct as handlePlanCommand, d as formatMemoryMemoForInjection, dn as getCtrlDHint, en as parseStreamingArgs, et as toTerminalHyperlink, fn as getLlmNotSetMessage, g as selectUpdateTarget, gn as sortSlashCommands, gt as supportsBalance, hn as BUILTIN_SLASH_COMMANDS, ht as refreshProviderBalance, i as buildRoleAdditionalText, in as CHARS_PER_TOKEN, j as highlightLines, jn as getLogDir, jt as ENABLE_TERMINAL_THEME_REPORTING, k as renderDiffLines, kn as getDataDir, kt as DISABLE_TERMINAL_THEME_REPORTING, l as isPlanExpandable, ln as TIP_ROTATION_INTERVAL_MS, mn as buildSkillSlashCommands, n as clearEvalPanelState, nn as stringValue, nt as getModelCycleLevel, on as EXIT_CONFIRM_WINDOW_MS, ot as handleFusionPlanCommand, pn as getNoActiveSessionMessage, q as clearGoalState, qn as isScreamError, r as openUrl, rn as truncateErrorMessage, s as disposeChildren, sn as MAIN_AGENT_ID, t as dispatchInput, tn as serializeToolResultOutput, tt as changeThinkingLevel, u as MoonLoader, un as getCtrlCHint, v as readUpdateCache, vn as setExperimentalFlags, w as UserMessageComponent, wn as TuiConfigParseError, wt as contrastTextHex, xn as startManualEmbeddingDownload, xt as createEditorTheme, y as appendJsonlLine, yn as getKnowledgeStore, z as parseReadGroupOutput, zn as saveCatalogCache, zt as TERMINAL_THEME_DARK } from "./dispatch-D8ovLbmr.mjs";
7
+ import { $t as isTodoItemShape, A as renderDiffLinesClustered, An as getInputHistoryFile, At as ENABLE_TERMINAL_FOCUS_REPORTING, B as BackgroundAgentStatusComponent, Bn as ScreamHarness, Bt as TERMINAL_THEME_LIGHT, Cn as PULSE_WAVE_FRAMES, Ct as createThemeStyles, D as toggleEmptySessionHint, Dn as saveTuiConfig, Dt as parseOsc11BackgroundTheme, E as isTurnElapsedEnabled, En as loadTuiConfig, Et as detectTerminalTheme, F as estimateTokens, Fn as CLI_USER_AGENT_PRODUCT, Ft as OSC11_RESPONSE_PREFIX_NO_ESC, G as getBreathingFrame, Gn as log, H as AgentGroupComponent, Hn as resolveScreamHome, Ht as isStreaming, I as getSharedSpeedTracker, In as PRODUCT_NAME, It as QUERY_TERMINAL_THEME, J as refineGoal, Jn as isOrphanedToolCallError, Jt as printableChar, K as resetBreathingClock, Kn as resolveGlobalLogPath, L as SkillActivationComponent, Ln as DEFAULT_CATALOG_URL, Lt as TERMINAL_FOCUS_IN, M as langFromPath, Mn as detectInstallSource, Mt as OSC11_QUERY, N as CachedContainer, Nn as CLI_COMMAND_NAME, Nt as OSC11_RESPONSE, O as ToolCallComponent, On as detectShellEnvironment, Ot as DISABLE_TERMINAL_FOCUS_REPORTING, P as ThinkingComponent, Pn as CLI_UI_MODE, Pt as OSC11_RESPONSE_PREFIX, Qt as formatErrorMessage, R as ReadGroupComponent, Rn as fetchCatalog, Rt as TERMINAL_FOCUS_OUT, S as getDaemonInstructions, Sn as PIXEL_PULSE_FRAMES, St as createMarkdownTheme, T as isEmptySessionHintDismissed, Tn as TuiLikePreferencesSchema, Tt as getColorPalette, U as WelcomeComponent, Un as MemoryMemoStore, Ut as FooterComponent, V as AssistantMessageComponent, Vt as isBusy, W as BREATHE_CYCLE_MS, Wn as flushDiagnosticLogs, Xn as SCREAM_ERROR_INFO, Xt as appendStreamingArgsPreview, Yn as ErrorCodes, Yt as STATUS_BULLET, Zt as argsRecord, _ as refreshUpdateCache, _n as isExperimentalFlagEnabled, _t as clearInfoPanelState, an as EMPTY_SESSION_HINT_URL, b as readJsonlFile, bn as isEmbeddingModelCached, bt as resolveThemeSync, c as hasDispose, cn as SESSION_TIPS, ct as handlePlanCommand, d as formatMemoryMemoForInjection, dn as getCtrlDHint, en as parseStreamingArgs, et as toTerminalHyperlink, fn as getLlmNotSetMessage, g as selectUpdateTarget, gn as sortSlashCommands, gt as supportsBalance, hn as BUILTIN_SLASH_COMMANDS, ht as refreshProviderBalance, i as buildRoleAdditionalText, in as CHARS_PER_TOKEN, j as highlightLines, jn as getLogDir, jt as ENABLE_TERMINAL_THEME_REPORTING, k as renderDiffLines, kn as getDataDir, kt as DISABLE_TERMINAL_THEME_REPORTING, l as isPlanExpandable, ln as TIP_ROTATION_INTERVAL_MS, mn as buildSkillSlashCommands, n as clearEvalPanelState, nn as stringValue, nt as getModelCycleLevel, on as EXIT_CONFIRM_WINDOW_MS, ot as handleFusionPlanCommand, pn as getNoActiveSessionMessage, q as clearGoalState, qn as isScreamError, r as openUrl, rn as truncateErrorMessage, s as disposeChildren, sn as MAIN_AGENT_ID, t as dispatchInput, tn as serializeToolResultOutput, tt as changeThinkingLevel, u as MoonLoader, un as getCtrlCHint, v as readUpdateCache, vn as setExperimentalFlags, w as UserMessageComponent, wn as TuiConfigParseError, wt as contrastTextHex, xn as startManualEmbeddingDownload, xt as createEditorTheme, y as appendJsonlLine, yn as getKnowledgeStore, z as parseReadGroupOutput, zn as saveCatalogCache, zt as TERMINAL_THEME_DARK } from "./dispatch-B_ViYZer.mjs";
8
8
  import { a as setLocale, o as t } from "./text-input-dialog-BUXAKycx.mjs";
9
9
  import { createRequire } from "node:module";
10
10
  import { randomBytes, randomUUID, scryptSync, timingSafeEqual } from "node:crypto";
@@ -32,7 +32,7 @@ function optionalBuildString(value) {
32
32
  return typeof value === "string" && value.length > 0 ? value : void 0;
33
33
  }
34
34
  const SCREAM_BUILD_INFO = {
35
- version: optionalBuildString("0.15.0"),
35
+ version: optionalBuildString("0.15.2"),
36
36
  channel: optionalBuildString(""),
37
37
  commit: optionalBuildString(""),
38
38
  buildTarget: optionalBuildString("darwin-arm64")
@@ -12397,7 +12397,7 @@ var ScreamTUI = class {
12397
12397
  const card = new OnboardingCardComponent({
12398
12398
  colors: this.state.theme.colors,
12399
12399
  onConfigure: () => {
12400
- import("./dispatch-BPcn43Vw.mjs").then(({ handleConnectCommand }) => handleConnectCommand(this, "")).finally(() => {
12400
+ import("./dispatch-Dur5jHVw.mjs").then(({ handleConnectCommand }) => handleConnectCommand(this, "")).finally(() => {
12401
12401
  this.restoreEditor();
12402
12402
  onDone?.();
12403
12403
  });
@@ -14527,7 +14527,14 @@ function toHttpError(error) {
14527
14527
  if (isScreamError(error)) switch (error.code) {
14528
14528
  case ErrorCodes.GOAL_OBJECTIVE_EMPTY:
14529
14529
  case ErrorCodes.GOAL_OBJECTIVE_TOO_LONG:
14530
- case ErrorCodes.REQUEST_INVALID: return new HttpError(400, error.message, error.code);
14530
+ case ErrorCodes.REQUEST_INVALID:
14531
+ case ErrorCodes.SESSION_PERMISSION_MODE_INVALID:
14532
+ case ErrorCodes.SESSION_PLAN_MODE_INVALID:
14533
+ case ErrorCodes.SESSION_WOLFPACK_MODE_INVALID:
14534
+ case ErrorCodes.SESSION_MODEL_EMPTY:
14535
+ case ErrorCodes.SESSION_THINKING_EMPTY:
14536
+ case ErrorCodes.SKILL_NAME_EMPTY:
14537
+ case ErrorCodes.BACKGROUND_TASK_ID_EMPTY: return new HttpError(400, error.message, error.code);
14531
14538
  case ErrorCodes.GOAL_NOT_FOUND:
14532
14539
  case ErrorCodes.SESSION_NOT_FOUND: return new HttpError(404, error.message, error.code);
14533
14540
  case ErrorCodes.GOAL_ALREADY_EXISTS:
@@ -14629,9 +14636,13 @@ async function appendJournalLine(homeDir, sessionId, line) {
14629
14636
  });
14630
14637
  }
14631
14638
  }
14639
+ let metaWriteSeq = 0;
14632
14640
  async function saveMetadata(homeDir, meta) {
14633
14641
  try {
14634
- await writeFile(getMetaPath(homeDir, meta.sessionId), JSON.stringify(meta, null, 2));
14642
+ const path = getMetaPath(homeDir, meta.sessionId);
14643
+ const tmp = `${path}.${process.pid}.${metaWriteSeq++}.tmp`;
14644
+ await writeFile(tmp, JSON.stringify(meta, null, 2));
14645
+ await rename(tmp, path);
14635
14646
  } catch (error) {
14636
14647
  log.warn("web: failed to save metadata", {
14637
14648
  sessionId: meta.sessionId,
@@ -15032,6 +15043,15 @@ var WebSession = class {
15032
15043
  });
15033
15044
  await appendJournalLine(this.homeDir, this.sessionId, line);
15034
15045
  }
15046
+ /** In-flight fire-and-forget persistence writes, drained by close(). */
15047
+ pendingWrites = /* @__PURE__ */ new Set();
15048
+ /** Register a background persistence promise so close() can await it. */
15049
+ trackPending(promise) {
15050
+ const tracked = promise.catch(() => {}).then(() => {
15051
+ this.pendingWrites.delete(tracked);
15052
+ });
15053
+ this.pendingWrites.add(tracked);
15054
+ }
15035
15055
  loadFromPersisted(entries) {
15036
15056
  for (const entry of entries) if ("type" in entry && entry.type === "user_message") {
15037
15057
  const um = entry;
@@ -15082,7 +15102,7 @@ var WebSession = class {
15082
15102
  payload
15083
15103
  };
15084
15104
  this.journal.push(entry);
15085
- if (!entry.volatile) this.persistEntry(entry);
15105
+ if (!entry.volatile) this.trackPending(this.persistEntry(entry));
15086
15106
  return entry;
15087
15107
  }
15088
15108
  /**
@@ -15098,7 +15118,7 @@ var WebSession = class {
15098
15118
  payload
15099
15119
  };
15100
15120
  this.journal.push(entry);
15101
- this.persistEntry(entry);
15121
+ this.trackPending(this.persistEntry(entry));
15102
15122
  return entry;
15103
15123
  }
15104
15124
  subscribeEvents() {
@@ -15148,7 +15168,7 @@ var WebSession = class {
15148
15168
  });
15149
15169
  }
15150
15170
  this.liveAssistant = null;
15151
- this.updateTitle();
15171
+ this.trackPending(this.updateTitle());
15152
15172
  } else if (event.type === "assistant.delta") {
15153
15173
  const live = this.liveAssistant;
15154
15174
  if (live) live.content += event.delta;
@@ -15336,7 +15356,7 @@ var WebSession = class {
15336
15356
  return;
15337
15357
  }
15338
15358
  this.busy = true;
15339
- this.persistUserMessage(text, this.nextSeq, clientMessageId);
15359
+ this.trackPending(this.persistUserMessage(text, this.nextSeq, clientMessageId));
15340
15360
  this.broadcast({
15341
15361
  type: "user_message",
15342
15362
  clientMessageId,
@@ -15683,6 +15703,49 @@ var WebSession = class {
15683
15703
  status: this.cachedStatus
15684
15704
  }, false);
15685
15705
  }
15706
+ /** Switch permission mode, then sync status + metadata (TUI /auto,/yes parity). */
15707
+ async switchPermission(mode) {
15708
+ if (!this.session) throw new HttpError(409, "会话已归档(只读),无法切换权限模式。");
15709
+ await this.session.setPermission(mode);
15710
+ this.permission = mode;
15711
+ await this.refreshStatus();
15712
+ this.broadcast({
15713
+ type: "status",
15714
+ status: this.cachedStatus
15715
+ }, false);
15716
+ if (this.homeDir) await saveMetadata(this.homeDir, this.getMetadata());
15717
+ }
15718
+ /** Toggle plan mode, then sync status (TUI /plan parity). */
15719
+ async switchPlanMode(enabled, strategy) {
15720
+ if (!this.session) throw new HttpError(409, "会话已归档(只读),无法切换计划模式。");
15721
+ await this.session.setPlanMode(enabled, strategy);
15722
+ await this.refreshStatus();
15723
+ this.broadcast({
15724
+ type: "status",
15725
+ status: this.cachedStatus
15726
+ }, false);
15727
+ }
15728
+ /** Toggle wolfpack mode, then sync status. */
15729
+ async switchWolfpackMode(enabled) {
15730
+ if (!this.session) throw new HttpError(409, "会话已归档(只读),无法切换 Wolfpack 模式。");
15731
+ await this.session.setWolfpackMode(enabled);
15732
+ await this.refreshStatus();
15733
+ this.broadcast({
15734
+ type: "status",
15735
+ status: this.cachedStatus
15736
+ }, false);
15737
+ }
15738
+ /** Toggle RLM persistent-python mode (+ optional max depth), then sync status. */
15739
+ async switchRlm(enabled, maxDepth) {
15740
+ if (!this.session) throw new HttpError(409, "会话已归档(只读),无法切换 RLM 模式。");
15741
+ await this.session.setRlmEnabled(enabled);
15742
+ if (maxDepth !== void 0) await this.session.setRlmMaxDepth(maxDepth);
15743
+ await this.refreshStatus();
15744
+ this.broadcast({
15745
+ type: "status",
15746
+ status: this.cachedStatus
15747
+ }, false);
15748
+ }
15686
15749
  getCoreSessionId() {
15687
15750
  return this.coreSessionId;
15688
15751
  }
@@ -15745,6 +15808,15 @@ var WebSession = class {
15745
15808
  await session.cancelGoal();
15746
15809
  }, true);
15747
15810
  }
15811
+ /**
15812
+ * Public handle to the live core Session for REST RPC forwarding.
15813
+ * Throws 409 for archived (read-only) sessions so handlers never call into a
15814
+ * closed core session instead of silently creating one.
15815
+ */
15816
+ requireLiveSession() {
15817
+ if (!this.session) throw new HttpError(409, "Session is archived (read-only)", ErrorCodes.SESSION_CLOSED);
15818
+ return this.session;
15819
+ }
15748
15820
  requireGoalSession(allowBusy = false) {
15749
15821
  if (!this.session) throw new HttpError(409, "Session is archived (read-only)", ErrorCodes.SESSION_CLOSED);
15750
15822
  if (!allowBusy && this.busy) throw new HttpError(409, "Session is busy", ErrorCodes.TURN_AGENT_BUSY);
@@ -15910,6 +15982,7 @@ var WebSession = class {
15910
15982
  });
15911
15983
  this.pendingApprovals.clear();
15912
15984
  this.unsubscribe?.();
15985
+ await Promise.allSettled(this.pendingWrites);
15913
15986
  for (const [ws] of this.connections) ws.terminate();
15914
15987
  this.connections.clear();
15915
15988
  if (this.session) {
@@ -16232,7 +16305,529 @@ var SessionManager = class {
16232
16305
  for (const [, ws] of this.sessions) await ws.close();
16233
16306
  this.sessions.clear();
16234
16307
  }
16308
+ /** Resolve a live session or throw 404/409; the single guard for all handlers. */
16309
+ getLiveSession(sessionId) {
16310
+ const ws = this.sessions.get(sessionId);
16311
+ if (!ws) throw new HttpError(404, "Session not found", ErrorCodes.SESSION_NOT_FOUND);
16312
+ return ws.requireLiveSession();
16313
+ }
16314
+ async getSessionStatus(sessionId) {
16315
+ return this.getLiveSession(sessionId).getStatus();
16316
+ }
16317
+ async getSessionUsage(sessionId) {
16318
+ return this.getLiveSession(sessionId).getUsage();
16319
+ }
16320
+ async getSessionContext(sessionId) {
16321
+ return this.getLiveSession(sessionId).getContext();
16322
+ }
16323
+ async getSessionPlan(sessionId) {
16324
+ return this.getLiveSession(sessionId).getPlan();
16325
+ }
16326
+ async setPermission(sessionId, mode) {
16327
+ const ws = this.sessions.get(sessionId);
16328
+ if (!ws) throw new HttpError(404, "Session not found", ErrorCodes.SESSION_NOT_FOUND);
16329
+ await ws.switchPermission(mode);
16330
+ }
16331
+ async setPlanMode(sessionId, enabled, strategy) {
16332
+ const ws = this.sessions.get(sessionId);
16333
+ if (!ws) throw new HttpError(404, "Session not found", ErrorCodes.SESSION_NOT_FOUND);
16334
+ await ws.switchPlanMode(enabled, strategy);
16335
+ }
16336
+ async clearPlan(sessionId) {
16337
+ await this.getLiveSession(sessionId).clearPlan();
16338
+ }
16339
+ async setWolfpackMode(sessionId, enabled) {
16340
+ const ws = this.sessions.get(sessionId);
16341
+ if (!ws) throw new HttpError(404, "Session not found", ErrorCodes.SESSION_NOT_FOUND);
16342
+ await ws.switchWolfpackMode(enabled);
16343
+ }
16344
+ async setRlm(sessionId, enabled, maxDepth) {
16345
+ const ws = this.sessions.get(sessionId);
16346
+ if (!ws) throw new HttpError(404, "Session not found", ErrorCodes.SESSION_NOT_FOUND);
16347
+ await ws.switchRlm(enabled, maxDepth);
16348
+ }
16349
+ async undoHistory(sessionId, count) {
16350
+ await this.getLiveSession(sessionId).undoHistory(count);
16351
+ }
16352
+ async compact(sessionId, instruction) {
16353
+ await this.getLiveSession(sessionId).compact(instruction ? { instruction } : {});
16354
+ }
16355
+ async listSkills(sessionId) {
16356
+ return this.getLiveSession(sessionId).listSkills();
16357
+ }
16358
+ async activateSkill(sessionId, name, args) {
16359
+ await this.getLiveSession(sessionId).activateSkill(name, args);
16360
+ }
16361
+ async removeSkill(sessionId, name) {
16362
+ await this.getLiveSession(sessionId).removeSkill(name);
16363
+ }
16364
+ async listPlugins(sessionId) {
16365
+ return this.getLiveSession(sessionId).listPlugins();
16366
+ }
16367
+ async getPluginInfo(sessionId, id) {
16368
+ return this.getLiveSession(sessionId).getPluginInfo(id);
16369
+ }
16370
+ async installPlugin(sessionId, source) {
16371
+ return this.getLiveSession(sessionId).installPlugin(source);
16372
+ }
16373
+ async setPluginEnabled(sessionId, id, enabled) {
16374
+ await this.getLiveSession(sessionId).setPluginEnabled(id, enabled);
16375
+ }
16376
+ async setPluginMcpServerEnabled(sessionId, id, server, enabled) {
16377
+ await this.getLiveSession(sessionId).setPluginMcpServerEnabled(id, server, enabled);
16378
+ }
16379
+ async removePlugin(sessionId, id) {
16380
+ await this.getLiveSession(sessionId).removePlugin(id);
16381
+ }
16382
+ async reloadPlugins(sessionId) {
16383
+ return this.getLiveSession(sessionId).reloadPlugins();
16384
+ }
16385
+ async activatePlugin(sessionId, id) {
16386
+ await this.getLiveSession(sessionId).activatePlugin(id);
16387
+ }
16388
+ async deactivatePlugin(sessionId, id) {
16389
+ await this.getLiveSession(sessionId).deactivatePlugin(id);
16390
+ }
16391
+ async injectPlugin(sessionId, id) {
16392
+ await this.getLiveSession(sessionId).injectPlugin(id);
16393
+ }
16394
+ async listMcpServers(sessionId) {
16395
+ return this.getLiveSession(sessionId).listMcpServers();
16396
+ }
16397
+ async getMcpStartupMetrics(sessionId) {
16398
+ return this.getLiveSession(sessionId).getMcpStartupMetrics();
16399
+ }
16400
+ async addMcpServer(sessionId, name, config) {
16401
+ await this.getLiveSession(sessionId).addMcpServer(name, config);
16402
+ }
16403
+ async reconnectMcpServer(sessionId, name) {
16404
+ await this.getLiveSession(sessionId).reconnectMcpServer(name);
16405
+ }
16406
+ async stopMcpServer(sessionId, name) {
16407
+ await this.getLiveSession(sessionId).stopMcpServer(name);
16408
+ }
16409
+ async removeMcpServer(sessionId, name) {
16410
+ await this.getLiveSession(sessionId).removeMcpServer(name);
16411
+ }
16412
+ async listBackgroundTasks(sessionId, activeOnly, limit) {
16413
+ return this.getLiveSession(sessionId).listBackgroundTasks({
16414
+ activeOnly,
16415
+ limit
16416
+ });
16417
+ }
16418
+ async getBackgroundTaskOutput(sessionId, taskId, tail) {
16419
+ return this.getLiveSession(sessionId).getBackgroundTaskOutput(taskId, tail !== void 0 ? { tail } : {});
16420
+ }
16421
+ async stopBackgroundTask(sessionId, taskId, reason) {
16422
+ await this.getLiveSession(sessionId).stopBackgroundTask(taskId, reason !== void 0 ? { reason } : {});
16423
+ }
16424
+ getConfig(options) {
16425
+ return this.harness.getConfig(options);
16426
+ }
16427
+ setConfig(patch) {
16428
+ return this.harness.setConfig(patch);
16429
+ }
16430
+ removeProvider(providerId) {
16431
+ return this.harness.removeProvider(providerId);
16432
+ }
16433
+ getExperimentalFlags() {
16434
+ return this.harness.getExperimentalFlags();
16435
+ }
16436
+ preflight() {
16437
+ return this.harness.preflight();
16438
+ }
16235
16439
  };
16440
+ async function handleSessionControlRoutes(req, res, url, method, manager) {
16441
+ const statusMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/status$`).exec(url);
16442
+ if (statusMatch && method === "GET") {
16443
+ try {
16444
+ sendJson(res, 200, await manager.getSessionStatus(decodeURIComponent(statusMatch[1])));
16445
+ } catch (error) {
16446
+ sendHttpError(res, error);
16447
+ }
16448
+ return true;
16449
+ }
16450
+ const usageMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/usage$`).exec(url);
16451
+ if (usageMatch && method === "GET") {
16452
+ try {
16453
+ sendJson(res, 200, await manager.getSessionUsage(decodeURIComponent(usageMatch[1])));
16454
+ } catch (error) {
16455
+ sendHttpError(res, error);
16456
+ }
16457
+ return true;
16458
+ }
16459
+ const contextMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/context$`).exec(url);
16460
+ if (contextMatch && method === "GET") {
16461
+ try {
16462
+ sendJson(res, 200, await manager.getSessionContext(decodeURIComponent(contextMatch[1])));
16463
+ } catch (error) {
16464
+ sendHttpError(res, error);
16465
+ }
16466
+ return true;
16467
+ }
16468
+ const planMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/plan$`).exec(url);
16469
+ if (planMatch && method === "GET") {
16470
+ try {
16471
+ sendJson(res, 200, await manager.getSessionPlan(decodeURIComponent(planMatch[1])));
16472
+ } catch (error) {
16473
+ sendHttpError(res, error);
16474
+ }
16475
+ return true;
16476
+ }
16477
+ const permissionMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/permission$`).exec(url);
16478
+ if (permissionMatch && method === "POST") {
16479
+ try {
16480
+ const mode = requiredString(await readJsonBody(req), "mode");
16481
+ await manager.setPermission(decodeURIComponent(permissionMatch[1]), mode);
16482
+ sendJson(res, 200, { ok: true });
16483
+ } catch (error) {
16484
+ sendHttpError(res, error);
16485
+ }
16486
+ return true;
16487
+ }
16488
+ const planModeMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/plan$`).exec(url);
16489
+ if (planModeMatch && method === "POST") {
16490
+ try {
16491
+ const body = await readJsonBody(req);
16492
+ const enabled = optionalBoolean(body, "enabled", true);
16493
+ const strategy = optionalString(body, "strategy");
16494
+ await manager.setPlanMode(decodeURIComponent(planModeMatch[1]), enabled, strategy);
16495
+ sendJson(res, 200, { ok: true });
16496
+ } catch (error) {
16497
+ sendHttpError(res, error);
16498
+ }
16499
+ return true;
16500
+ }
16501
+ const clearPlanMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/plan/clear$`).exec(url);
16502
+ if (clearPlanMatch && method === "POST") {
16503
+ try {
16504
+ await manager.clearPlan(decodeURIComponent(clearPlanMatch[1]));
16505
+ sendJson(res, 200, { ok: true });
16506
+ } catch (error) {
16507
+ sendHttpError(res, error);
16508
+ }
16509
+ return true;
16510
+ }
16511
+ const wolfpackMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/wolfpack$`).exec(url);
16512
+ if (wolfpackMatch && method === "POST") {
16513
+ try {
16514
+ const enabled = optionalBoolean(await readJsonBody(req), "enabled", true);
16515
+ await manager.setWolfpackMode(decodeURIComponent(wolfpackMatch[1]), enabled);
16516
+ sendJson(res, 200, { ok: true });
16517
+ } catch (error) {
16518
+ sendHttpError(res, error);
16519
+ }
16520
+ return true;
16521
+ }
16522
+ const rlmMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/rlm$`).exec(url);
16523
+ if (rlmMatch && method === "POST") {
16524
+ try {
16525
+ const body = await readJsonBody(req);
16526
+ const enabled = optionalBoolean(body, "enabled", true);
16527
+ const maxDepth = typeof body["maxDepth"] === "number" ? Number(body["maxDepth"]) : void 0;
16528
+ await manager.setRlm(decodeURIComponent(rlmMatch[1]), enabled, maxDepth);
16529
+ sendJson(res, 200, { ok: true });
16530
+ } catch (error) {
16531
+ sendHttpError(res, error);
16532
+ }
16533
+ return true;
16534
+ }
16535
+ const undoMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/undo$`).exec(url);
16536
+ if (undoMatch && method === "POST") {
16537
+ try {
16538
+ const body = await readJsonBody(req);
16539
+ const count = typeof body["count"] === "number" ? Number(body["count"]) : 1;
16540
+ await manager.undoHistory(decodeURIComponent(undoMatch[1]), count);
16541
+ sendJson(res, 200, { ok: true });
16542
+ } catch (error) {
16543
+ sendHttpError(res, error);
16544
+ }
16545
+ return true;
16546
+ }
16547
+ const compactMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/compact$`).exec(url);
16548
+ if (compactMatch && method === "POST") {
16549
+ try {
16550
+ const body = await readJsonBody(req);
16551
+ await manager.compact(decodeURIComponent(compactMatch[1]), optionalString(body, "instruction"));
16552
+ sendJson(res, 200, { ok: true });
16553
+ } catch (error) {
16554
+ sendHttpError(res, error);
16555
+ }
16556
+ return true;
16557
+ }
16558
+ return false;
16559
+ }
16560
+ async function handleResourceRoutes(req, res, url, method, manager) {
16561
+ const skillsMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/skills$`).exec(url);
16562
+ if (skillsMatch && method === "GET") {
16563
+ try {
16564
+ sendJson(res, 200, await manager.listSkills(decodeURIComponent(skillsMatch[1])));
16565
+ } catch (error) {
16566
+ sendHttpError(res, error);
16567
+ }
16568
+ return true;
16569
+ }
16570
+ const skillActivateMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/skills/([^/]+)/activate$`).exec(url);
16571
+ if (skillActivateMatch && method === "POST") {
16572
+ try {
16573
+ const body = await readJsonBody(req);
16574
+ await manager.activateSkill(decodeURIComponent(skillActivateMatch[1]), decodeURIComponent(skillActivateMatch[2]), optionalString(body, "args"));
16575
+ sendJson(res, 200, { ok: true });
16576
+ } catch (error) {
16577
+ sendHttpError(res, error);
16578
+ }
16579
+ return true;
16580
+ }
16581
+ const skillRemoveMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/skills/([^/]+)$`).exec(url);
16582
+ if (skillRemoveMatch && method === "DELETE") {
16583
+ try {
16584
+ await manager.removeSkill(decodeURIComponent(skillRemoveMatch[1]), decodeURIComponent(skillRemoveMatch[2]));
16585
+ sendJson(res, 200, { ok: true });
16586
+ } catch (error) {
16587
+ sendHttpError(res, error);
16588
+ }
16589
+ return true;
16590
+ }
16591
+ const pluginsMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/plugins$`).exec(url);
16592
+ if (pluginsMatch && method === "GET") {
16593
+ try {
16594
+ sendJson(res, 200, await manager.listPlugins(decodeURIComponent(pluginsMatch[1])));
16595
+ } catch (error) {
16596
+ sendHttpError(res, error);
16597
+ }
16598
+ return true;
16599
+ }
16600
+ const pluginInstallMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/plugins/install$`).exec(url);
16601
+ if (pluginInstallMatch && method === "POST") {
16602
+ try {
16603
+ const body = await readJsonBody(req);
16604
+ sendJson(res, 200, await manager.installPlugin(decodeURIComponent(pluginInstallMatch[1]), requiredString(body, "source")));
16605
+ } catch (error) {
16606
+ sendHttpError(res, error);
16607
+ }
16608
+ return true;
16609
+ }
16610
+ const pluginReloadMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/plugins/reload$`).exec(url);
16611
+ if (pluginReloadMatch && method === "POST") {
16612
+ try {
16613
+ sendJson(res, 200, await manager.reloadPlugins(decodeURIComponent(pluginReloadMatch[1])));
16614
+ } catch (error) {
16615
+ sendHttpError(res, error);
16616
+ }
16617
+ return true;
16618
+ }
16619
+ const pluginEnableMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/plugins/([^/]+)/enable$`).exec(url);
16620
+ if (pluginEnableMatch && method === "POST") {
16621
+ try {
16622
+ const body = await readJsonBody(req);
16623
+ await manager.setPluginEnabled(decodeURIComponent(pluginEnableMatch[1]), decodeURIComponent(pluginEnableMatch[2]), optionalBoolean(body, "enabled", true));
16624
+ sendJson(res, 200, { ok: true });
16625
+ } catch (error) {
16626
+ sendHttpError(res, error);
16627
+ }
16628
+ return true;
16629
+ }
16630
+ const pluginMcpEnableMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/plugins/([^/]+)/mcp/([^/]+)/enable$`).exec(url);
16631
+ if (pluginMcpEnableMatch && method === "POST") {
16632
+ try {
16633
+ const body = await readJsonBody(req);
16634
+ await manager.setPluginMcpServerEnabled(decodeURIComponent(pluginMcpEnableMatch[1]), decodeURIComponent(pluginMcpEnableMatch[2]), decodeURIComponent(pluginMcpEnableMatch[3]), optionalBoolean(body, "enabled", true));
16635
+ sendJson(res, 200, { ok: true });
16636
+ } catch (error) {
16637
+ sendHttpError(res, error);
16638
+ }
16639
+ return true;
16640
+ }
16641
+ const pluginActivateMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/plugins/([^/]+)/activate$`).exec(url);
16642
+ if (pluginActivateMatch && method === "POST") {
16643
+ try {
16644
+ await manager.activatePlugin(decodeURIComponent(pluginActivateMatch[1]), decodeURIComponent(pluginActivateMatch[2]));
16645
+ sendJson(res, 200, { ok: true });
16646
+ } catch (error) {
16647
+ sendHttpError(res, error);
16648
+ }
16649
+ return true;
16650
+ }
16651
+ const pluginDeactivateMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/plugins/([^/]+)/deactivate$`).exec(url);
16652
+ if (pluginDeactivateMatch && method === "POST") {
16653
+ try {
16654
+ await manager.deactivatePlugin(decodeURIComponent(pluginDeactivateMatch[1]), decodeURIComponent(pluginDeactivateMatch[2]));
16655
+ sendJson(res, 200, { ok: true });
16656
+ } catch (error) {
16657
+ sendHttpError(res, error);
16658
+ }
16659
+ return true;
16660
+ }
16661
+ const pluginInjectMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/plugins/([^/]+)/inject$`).exec(url);
16662
+ if (pluginInjectMatch && method === "POST") {
16663
+ try {
16664
+ await manager.injectPlugin(decodeURIComponent(pluginInjectMatch[1]), decodeURIComponent(pluginInjectMatch[2]));
16665
+ sendJson(res, 200, { ok: true });
16666
+ } catch (error) {
16667
+ sendHttpError(res, error);
16668
+ }
16669
+ return true;
16670
+ }
16671
+ const pluginInfoMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/plugins/(?!(?:install|reload)$)([^/]+)$`).exec(url);
16672
+ if (pluginInfoMatch && method === "GET") {
16673
+ try {
16674
+ sendJson(res, 200, await manager.getPluginInfo(decodeURIComponent(pluginInfoMatch[1]), decodeURIComponent(pluginInfoMatch[2])));
16675
+ } catch (error) {
16676
+ sendHttpError(res, error);
16677
+ }
16678
+ return true;
16679
+ }
16680
+ if (pluginInfoMatch && method === "DELETE") {
16681
+ try {
16682
+ await manager.removePlugin(decodeURIComponent(pluginInfoMatch[1]), decodeURIComponent(pluginInfoMatch[2]));
16683
+ sendJson(res, 200, { ok: true });
16684
+ } catch (error) {
16685
+ sendHttpError(res, error);
16686
+ }
16687
+ return true;
16688
+ }
16689
+ const mcpMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/mcp$`).exec(url);
16690
+ if (mcpMatch && method === "GET") {
16691
+ try {
16692
+ sendJson(res, 200, await manager.listMcpServers(decodeURIComponent(mcpMatch[1])));
16693
+ } catch (error) {
16694
+ sendHttpError(res, error);
16695
+ }
16696
+ return true;
16697
+ }
16698
+ const mcpMetricsMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/mcp/startup-metrics$`).exec(url);
16699
+ if (mcpMetricsMatch && method === "GET") {
16700
+ try {
16701
+ sendJson(res, 200, await manager.getMcpStartupMetrics(decodeURIComponent(mcpMetricsMatch[1])));
16702
+ } catch (error) {
16703
+ sendHttpError(res, error);
16704
+ }
16705
+ return true;
16706
+ }
16707
+ const mcpAddMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/mcp/add$`).exec(url);
16708
+ if (mcpAddMatch && method === "POST") {
16709
+ try {
16710
+ const body = await readJsonBody(req);
16711
+ await manager.addMcpServer(decodeURIComponent(mcpAddMatch[1]), requiredString(body, "name"), body["config"] ?? {});
16712
+ sendJson(res, 200, { ok: true });
16713
+ } catch (error) {
16714
+ sendHttpError(res, error);
16715
+ }
16716
+ return true;
16717
+ }
16718
+ const mcpReconnectMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/mcp/([^/]+)/reconnect$`).exec(url);
16719
+ if (mcpReconnectMatch && method === "POST") {
16720
+ try {
16721
+ await manager.reconnectMcpServer(decodeURIComponent(mcpReconnectMatch[1]), decodeURIComponent(mcpReconnectMatch[2]));
16722
+ sendJson(res, 200, { ok: true });
16723
+ } catch (error) {
16724
+ sendHttpError(res, error);
16725
+ }
16726
+ return true;
16727
+ }
16728
+ const mcpStopMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/mcp/([^/]+)/stop$`).exec(url);
16729
+ if (mcpStopMatch && method === "POST") {
16730
+ try {
16731
+ await manager.stopMcpServer(decodeURIComponent(mcpStopMatch[1]), decodeURIComponent(mcpStopMatch[2]));
16732
+ sendJson(res, 200, { ok: true });
16733
+ } catch (error) {
16734
+ sendHttpError(res, error);
16735
+ }
16736
+ return true;
16737
+ }
16738
+ const mcpRemoveMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/mcp/(?!(?:add|reconnect|stop|startup-metrics)$)([^/]+)$`).exec(url);
16739
+ if (mcpRemoveMatch && method === "DELETE") {
16740
+ try {
16741
+ await manager.removeMcpServer(decodeURIComponent(mcpRemoveMatch[1]), decodeURIComponent(mcpRemoveMatch[2]));
16742
+ sendJson(res, 200, { ok: true });
16743
+ } catch (error) {
16744
+ sendHttpError(res, error);
16745
+ }
16746
+ return true;
16747
+ }
16748
+ const tasksMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/tasks$`).exec(url);
16749
+ if (tasksMatch && method === "GET") {
16750
+ try {
16751
+ const query = new URLSearchParams(url.split("?")[1] ?? "");
16752
+ const activeOnly = query.get("activeOnly") === "true" ? true : query.get("activeOnly") === "false" ? false : void 0;
16753
+ const limit = query.get("limit") ? Number(query.get("limit")) : void 0;
16754
+ sendJson(res, 200, await manager.listBackgroundTasks(decodeURIComponent(tasksMatch[1]), activeOnly, limit));
16755
+ } catch (error) {
16756
+ sendHttpError(res, error);
16757
+ }
16758
+ return true;
16759
+ }
16760
+ const taskOutputMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/tasks/([^/]+)/output$`).exec(url);
16761
+ if (taskOutputMatch && method === "GET") {
16762
+ try {
16763
+ const query = new URLSearchParams(url.split("?")[1] ?? "");
16764
+ const tail = query.get("tail") ? Number(query.get("tail")) : void 0;
16765
+ sendJson(res, 200, { output: await manager.getBackgroundTaskOutput(decodeURIComponent(taskOutputMatch[1]), decodeURIComponent(taskOutputMatch[2]), tail) });
16766
+ } catch (error) {
16767
+ sendHttpError(res, error);
16768
+ }
16769
+ return true;
16770
+ }
16771
+ const taskStopMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/tasks/([^/]+)/stop$`).exec(url);
16772
+ if (taskStopMatch && method === "POST") {
16773
+ try {
16774
+ const body = await readJsonBody(req);
16775
+ await manager.stopBackgroundTask(decodeURIComponent(taskStopMatch[1]), decodeURIComponent(taskStopMatch[2]), optionalString(body, "reason"));
16776
+ sendJson(res, 200, { ok: true });
16777
+ } catch (error) {
16778
+ sendHttpError(res, error);
16779
+ }
16780
+ return true;
16781
+ }
16782
+ return false;
16783
+ }
16784
+ async function handleGlobalRoutes(req, res, url, method, manager) {
16785
+ if (url === `${API_PREFIX}/config` && method === "GET") {
16786
+ try {
16787
+ sendJson(res, 200, await manager.getConfig());
16788
+ } catch (error) {
16789
+ sendHttpError(res, error);
16790
+ }
16791
+ return true;
16792
+ }
16793
+ if (url === `${API_PREFIX}/config` && method === "POST") {
16794
+ try {
16795
+ const patch = (await readJsonBody(req))["patch"];
16796
+ if (patch === null || typeof patch !== "object" || Array.isArray(patch)) throw new HttpError(400, "Missing or invalid patch field", "request.invalid");
16797
+ sendJson(res, 200, await manager.setConfig(patch));
16798
+ } catch (error) {
16799
+ sendHttpError(res, error);
16800
+ }
16801
+ return true;
16802
+ }
16803
+ const providerMatch = new RegExp(`^${API_PREFIX}/config/providers/([^/]+)$`).exec(url);
16804
+ if (providerMatch && method === "DELETE") {
16805
+ try {
16806
+ sendJson(res, 200, await manager.removeProvider(decodeURIComponent(providerMatch[1])));
16807
+ } catch (error) {
16808
+ sendHttpError(res, error);
16809
+ }
16810
+ return true;
16811
+ }
16812
+ if (url === `${API_PREFIX}/experimental-flags` && method === "GET") {
16813
+ try {
16814
+ sendJson(res, 200, await manager.getExperimentalFlags());
16815
+ } catch (error) {
16816
+ sendHttpError(res, error);
16817
+ }
16818
+ return true;
16819
+ }
16820
+ if (url === `${API_PREFIX}/preflight` && method === "GET") {
16821
+ try {
16822
+ await manager.preflight();
16823
+ sendJson(res, 200, { ok: true });
16824
+ } catch (error) {
16825
+ sendHttpError(res, error);
16826
+ }
16827
+ return true;
16828
+ }
16829
+ return false;
16830
+ }
16236
16831
  async function runWebServer(opts) {
16237
16832
  const homeDir = resolveScreamHome();
16238
16833
  const workDir = opts.workDir;
@@ -16342,6 +16937,9 @@ async function runWebServer(opts) {
16342
16937
  return;
16343
16938
  }
16344
16939
  if (await handleGoalRoute(req, res, url, (sessionId) => manager.get(sessionId))) return;
16940
+ if (await handleSessionControlRoutes(req, res, url, method, manager)) return;
16941
+ if (await handleResourceRoutes(req, res, url, method, manager)) return;
16942
+ if (await handleGlobalRoutes(req, res, url, method, manager)) return;
16345
16943
  const snapshotMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/snapshot(\\?|$)`).exec(url);
16346
16944
  if (snapshotMatch && method === "GET") {
16347
16945
  const sessionId = decodeURIComponent(snapshotMatch[1]);