@charzhu/openjaw-agent 0.2.5 → 0.2.7

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/main.js CHANGED
@@ -123,6 +123,7 @@ function loadAgentConfig() {
123
123
  temperature: parsedLlm?.temperature ?? DEFAULT_CONFIG.llm.temperature,
124
124
  computer_use: parsedLlm?.computer_use ?? DEFAULT_CONFIG.llm.computer_use,
125
125
  use_responses_api: parsedLlm?.use_responses_api ?? DEFAULT_CONFIG.llm.use_responses_api,
126
+ model_reasoning_effort: parsedLlm?.model_reasoning_effort,
126
127
  openai_tool_mode: parsedLlm?.openai_tool_mode ?? DEFAULT_CONFIG.llm.openai_tool_mode,
127
128
  openai_max_tools: parsedLlm?.openai_max_tools ?? DEFAULT_CONFIG.llm.openai_max_tools,
128
129
  openai_mcp_max_tools: parsedLlm?.openai_mcp_max_tools,
@@ -1973,6 +1974,55 @@ var init_provider_auth = __esm({
1973
1974
  }
1974
1975
  });
1975
1976
 
1977
+ // src/providers/types.ts
1978
+ function isReasoningEffort(value) {
1979
+ return REASONING_EFFORTS.includes(value);
1980
+ }
1981
+ function reasoningEffortLabel(effort) {
1982
+ switch (effort) {
1983
+ case "none":
1984
+ return "None";
1985
+ case "minimal":
1986
+ return "Minimal";
1987
+ case "low":
1988
+ return "Low";
1989
+ case "medium":
1990
+ return "Medium";
1991
+ case "high":
1992
+ return "High";
1993
+ case "xhigh":
1994
+ return "Extra high";
1995
+ }
1996
+ }
1997
+ function normalizeReasoningEfforts(values) {
1998
+ if (!Array.isArray(values)) return [];
1999
+ const efforts = [];
2000
+ for (const item of values) {
2001
+ if (typeof item !== "string" || !isReasoningEffort(item)) continue;
2002
+ if (!efforts.includes(item)) efforts.push(item);
2003
+ }
2004
+ return efforts;
2005
+ }
2006
+ function defaultReasoningEffort(efforts) {
2007
+ if (efforts.length === 0) return void 0;
2008
+ return efforts.includes("medium") ? "medium" : efforts[0];
2009
+ }
2010
+ function orderedReasoningEfforts(efforts) {
2011
+ return REASONING_EFFORTS.filter((effort) => efforts.includes(effort));
2012
+ }
2013
+ var REASONING_EFFORTS;
2014
+ var init_types = __esm({
2015
+ "src/providers/types.ts"() {
2016
+ "use strict";
2017
+ REASONING_EFFORTS = ["none", "minimal", "low", "medium", "high", "xhigh"];
2018
+ __name(isReasoningEffort, "isReasoningEffort");
2019
+ __name(reasoningEffortLabel, "reasoningEffortLabel");
2020
+ __name(normalizeReasoningEfforts, "normalizeReasoningEfforts");
2021
+ __name(defaultReasoningEffort, "defaultReasoningEffort");
2022
+ __name(orderedReasoningEfforts, "orderedReasoningEfforts");
2023
+ }
2024
+ });
2025
+
1976
2026
  // src/providers/copilot.ts
1977
2027
  function normalizeCopilotEnterpriseDomain(value) {
1978
2028
  return value.replace(/^https?:\/\//, "").replace(/\/+$/, "");
@@ -2063,20 +2113,67 @@ function safeJsonParse(value) {
2063
2113
  return {};
2064
2114
  }
2065
2115
  }
2066
- function parseModels(body) {
2116
+ function valueAtPath(value, path3) {
2117
+ let current = value;
2118
+ for (const key of path3) {
2119
+ if (!current || typeof current !== "object" || Array.isArray(current)) return void 0;
2120
+ current = current[key];
2121
+ }
2122
+ return current;
2123
+ }
2124
+ function firstPositiveIntegerAtAny(value, paths2) {
2125
+ for (const path3 of paths2) {
2126
+ const candidate = valueAtPath(value, path3);
2127
+ if (typeof candidate !== "number" || !Number.isFinite(candidate) || candidate <= 0) continue;
2128
+ return Math.trunc(candidate);
2129
+ }
2130
+ return void 0;
2131
+ }
2132
+ function readCopilotContextWindow(capabilities) {
2133
+ return firstPositiveIntegerAtAny(capabilities, [
2134
+ ["limits", "max_context_window_tokens"],
2135
+ ["limits", "maxContextWindowTokens"],
2136
+ ["limits", "max_prompt_tokens"],
2137
+ ["max_context_window_tokens"],
2138
+ ["maxContextWindowTokens"],
2139
+ ["context_window"],
2140
+ ["contextWindow"]
2141
+ ]);
2142
+ }
2143
+ function readCopilotOutputTokens(capabilities) {
2144
+ return firstPositiveIntegerAtAny(capabilities, [
2145
+ ["limits", "max_output_tokens"],
2146
+ ["limits", "maxOutputTokens"],
2147
+ ["max_output_tokens"],
2148
+ ["maxOutputTokens"]
2149
+ ]);
2150
+ }
2151
+ function readCopilotReasoningEfforts(capabilities) {
2152
+ return normalizeReasoningEfforts(valueAtPath(capabilities, ["supports", "reasoning_effort"]));
2153
+ }
2154
+ function buildReasoning(effort, modelInfo) {
2155
+ const supported = modelInfo?.supportedReasoningEfforts?.map((option) => option.effort) ?? [];
2156
+ const selected = effort && supported.includes(effort) ? effort : void 0;
2157
+ const effective = selected ?? modelInfo?.defaultReasoningEffort;
2158
+ return effective ? { effort: effective } : void 0;
2159
+ }
2160
+ function parseCopilotModels(body) {
2067
2161
  const items = Array.isArray(body.data) ? body.data : [];
2068
2162
  const models = [];
2069
2163
  for (const item of items) {
2070
2164
  if (!item.id || item.model_picker_enabled === false || item.policy?.state === "disabled") continue;
2071
2165
  const visionMedia = item.capabilities?.limits?.vision?.supported_media_types ?? [];
2166
+ const supportedReasoning = readCopilotReasoningEfforts(item.capabilities);
2072
2167
  models.push({
2073
2168
  id: item.id,
2074
2169
  name: item.name,
2075
2170
  supportedEndpoints: item.supported_endpoints ?? [],
2076
2171
  supportsVision: item.capabilities?.supports?.vision === true || visionMedia.some((media) => media.startsWith("image/")),
2077
2172
  supportsToolCalls: item.capabilities?.supports?.tool_calls !== false,
2078
- contextWindow: item.capabilities?.limits?.max_context_window_tokens,
2079
- outputTokens: item.capabilities?.limits?.max_output_tokens
2173
+ contextWindow: readCopilotContextWindow(item.capabilities),
2174
+ outputTokens: readCopilotOutputTokens(item.capabilities),
2175
+ supportedReasoningEfforts: supportedReasoning.map((effort) => ({ effort, description: effort })),
2176
+ defaultReasoningEffort: defaultReasoningEffort(supportedReasoning)
2080
2177
  });
2081
2178
  }
2082
2179
  return models;
@@ -2086,6 +2183,7 @@ var init_copilot = __esm({
2086
2183
  "src/providers/copilot.ts"() {
2087
2184
  "use strict";
2088
2185
  init_provider_auth();
2186
+ init_types();
2089
2187
  COPILOT_PROVIDER = "github-copilot";
2090
2188
  USER_AGENT = "openjaw-agent/0.1.0";
2091
2189
  OPENAI_TOOL_NAME2 = /^[A-Za-z0-9_-]{1,64}$/;
@@ -2100,7 +2198,13 @@ var init_copilot = __esm({
2100
2198
  __name(hasImageContent, "hasImageContent");
2101
2199
  __name(openAIUserContent, "openAIUserContent");
2102
2200
  __name(safeJsonParse, "safeJsonParse");
2103
- __name(parseModels, "parseModels");
2201
+ __name(valueAtPath, "valueAtPath");
2202
+ __name(firstPositiveIntegerAtAny, "firstPositiveIntegerAtAny");
2203
+ __name(readCopilotContextWindow, "readCopilotContextWindow");
2204
+ __name(readCopilotOutputTokens, "readCopilotOutputTokens");
2205
+ __name(readCopilotReasoningEfforts, "readCopilotReasoningEfforts");
2206
+ __name(buildReasoning, "buildReasoning");
2207
+ __name(parseCopilotModels, "parseCopilotModels");
2104
2208
  CopilotProvider = class {
2105
2209
  static {
2106
2210
  __name(this, "CopilotProvider");
@@ -2113,7 +2217,14 @@ var init_copilot = __esm({
2113
2217
  }
2114
2218
  async listModels() {
2115
2219
  const models = await this.fetchModelInfo();
2116
- return models.map((model) => ({ id: model.id, name: model.name ?? model.id }));
2220
+ return models.map((model) => ({
2221
+ id: model.id,
2222
+ name: model.name ?? model.id,
2223
+ contextWindow: model.contextWindow,
2224
+ outputTokens: model.outputTokens,
2225
+ supportedReasoningEfforts: model.supportedReasoningEfforts,
2226
+ defaultReasoningEffort: model.defaultReasoningEffort
2227
+ }));
2117
2228
  }
2118
2229
  async chat(options) {
2119
2230
  const modelInfo = await this.resolveModelInfo(this.config.model);
@@ -2121,7 +2232,7 @@ var init_copilot = __esm({
2121
2232
  return this.chatAnthropicMessages(options);
2122
2233
  }
2123
2234
  if (this.shouldRouteToResponses(modelInfo)) {
2124
- return this.chatResponses(options);
2235
+ return this.chatResponses(options, modelInfo);
2125
2236
  }
2126
2237
  return this.chatCompletions(options);
2127
2238
  }
@@ -2178,7 +2289,7 @@ var init_copilot = __esm({
2178
2289
  throw new Error(`GitHub Copilot models error: ${res.status}${detail ? ` ${detail.slice(0, 160)}` : ""}`);
2179
2290
  }
2180
2291
  const parsed = await res.json();
2181
- const models = parseModels(parsed);
2292
+ const models = parseCopilotModels(parsed);
2182
2293
  this.modelCache = new Map(models.map((model) => [model.id, model]));
2183
2294
  return models;
2184
2295
  }
@@ -2294,12 +2405,14 @@ var init_copilot = __esm({
2294
2405
  }
2295
2406
  return input;
2296
2407
  }
2297
- async chatResponses(options) {
2408
+ async chatResponses(options, modelInfo) {
2409
+ const reasoning = buildReasoning(options.reasoningEffort, modelInfo);
2298
2410
  const requestBody = {
2299
2411
  model: this.config.model,
2300
2412
  input: this.buildResponsesInput(options),
2301
2413
  instructions: Array.isArray(options.systemPrompt) ? options.systemPrompt.map((block) => block.text).join("\n\n") : options.systemPrompt,
2302
- tools: options.tools.length > 0 ? options.tools.map(toResponsesTool) : void 0
2414
+ tools: options.tools.length > 0 ? options.tools.map(toResponsesTool) : void 0,
2415
+ ...reasoning && { reasoning, include: ["reasoning.encrypted_content"] }
2303
2416
  };
2304
2417
  const res = await fetch(`${this.baseUrl()}/responses`, {
2305
2418
  method: "POST",
@@ -2690,8 +2803,45 @@ var init_cost_tracker = __esm({
2690
2803
  }
2691
2804
  });
2692
2805
 
2806
+ // src/domain/usage.ts
2807
+ var ZERO, contextPercent, formatContextPercent;
2808
+ var init_usage = __esm({
2809
+ "src/domain/usage.ts"() {
2810
+ "use strict";
2811
+ ZERO = { calls: 0, input: 0, output: 0, total: 0 };
2812
+ contextPercent = /* @__PURE__ */ __name((used, max) => {
2813
+ if (!Number.isFinite(used) || !Number.isFinite(max) || used <= 0 || max <= 0) {
2814
+ return 0;
2815
+ }
2816
+ const raw = used / max * 100;
2817
+ return raw < 10 ? Math.round(raw * 10) / 10 : Math.round(raw);
2818
+ }, "contextPercent");
2819
+ formatContextPercent = /* @__PURE__ */ __name((pct) => {
2820
+ if (pct == null || !Number.isFinite(pct) || pct <= 0) {
2821
+ return "0%";
2822
+ }
2823
+ if (pct < 0.1) {
2824
+ return "<0.1%";
2825
+ }
2826
+ if (pct < 10) {
2827
+ return `${pct.toFixed(1).replace(/\.0$/, "")}%`;
2828
+ }
2829
+ return `${Math.round(pct)}%`;
2830
+ }, "formatContextPercent");
2831
+ }
2832
+ });
2833
+
2693
2834
  // src/context-manager.ts
2694
- function getContextWindow(model) {
2835
+ function contextKey(provider, model) {
2836
+ return `${provider}\0${model}`;
2837
+ }
2838
+ function normalizeContextWindow(value) {
2839
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return void 0;
2840
+ return Math.trunc(value);
2841
+ }
2842
+ function getContextWindow(model, metadata) {
2843
+ const liveContextWindow = normalizeContextWindow(metadata?.contextWindow);
2844
+ if (liveContextWindow !== void 0) return liveContextWindow;
2695
2845
  if (CONTEXT_WINDOWS[model]) return CONTEXT_WINDOWS[model];
2696
2846
  for (const [key, size] of Object.entries(CONTEXT_WINDOWS)) {
2697
2847
  if (model.startsWith(key)) return size;
@@ -2702,6 +2852,7 @@ var CONTEXT_WINDOWS, DEFAULT_CONTEXT_WINDOW, ContextManager;
2702
2852
  var init_context_manager = __esm({
2703
2853
  "src/context-manager.ts"() {
2704
2854
  "use strict";
2855
+ init_usage();
2705
2856
  CONTEXT_WINDOWS = {
2706
2857
  "claude-sonnet-4": 2e5,
2707
2858
  "claude-sonnet-4.5": 2e5,
@@ -2716,13 +2867,46 @@ var init_context_manager = __esm({
2716
2867
  "gpt-4o": 128e3
2717
2868
  };
2718
2869
  DEFAULT_CONTEXT_WINDOW = 2e5;
2870
+ __name(contextKey, "contextKey");
2871
+ __name(normalizeContextWindow, "normalizeContextWindow");
2719
2872
  __name(getContextWindow, "getContextWindow");
2720
2873
  ContextManager = class {
2721
2874
  static {
2722
2875
  __name(this, "ContextManager");
2723
2876
  }
2724
2877
  lastTotalTokens = 0;
2725
- /** Update from actual API response usage */
2878
+ activeProvider = null;
2879
+ liveModelContextWindows = /* @__PURE__ */ new Map();
2880
+ setActiveProvider(provider) {
2881
+ this.activeProvider = provider;
2882
+ }
2883
+ setProviderModelMetadata(provider, models) {
2884
+ const prefix = `${provider}\0`;
2885
+ for (const key of [...this.liveModelContextWindows.keys()]) {
2886
+ if (key.startsWith(prefix)) this.liveModelContextWindows.delete(key);
2887
+ }
2888
+ for (const model of models ?? []) {
2889
+ const contextWindow = normalizeContextWindow(model.contextWindow);
2890
+ if (contextWindow !== void 0) {
2891
+ this.liveModelContextWindows.set(contextKey(provider, model.id), contextWindow);
2892
+ }
2893
+ }
2894
+ }
2895
+ clearProviderModelMetadata(provider) {
2896
+ if (!provider) {
2897
+ this.liveModelContextWindows.clear();
2898
+ return;
2899
+ }
2900
+ const prefix = `${provider}\0`;
2901
+ for (const key of [...this.liveModelContextWindows.keys()]) {
2902
+ if (key.startsWith(prefix)) this.liveModelContextWindows.delete(key);
2903
+ }
2904
+ }
2905
+ getContextWindow(model) {
2906
+ const liveContextWindow = this.activeProvider ? this.liveModelContextWindows.get(contextKey(this.activeProvider, model)) : void 0;
2907
+ return getContextWindow(model, { contextWindow: liveContextWindow });
2908
+ }
2909
+ /** Update latest effective context footprint from actual API response usage. */
2726
2910
  updateFromUsage(usage2) {
2727
2911
  this.lastTotalTokens = usage2.inputTokens + usage2.outputTokens + usage2.cacheReadTokens + usage2.cacheCreationTokens;
2728
2912
  }
@@ -2733,7 +2917,7 @@ var init_context_manager = __esm({
2733
2917
  /** Get warning level based on model context window */
2734
2918
  getWarningLevel(model, newContentChars = 0) {
2735
2919
  const estimated = this.getEstimatedTokens(newContentChars);
2736
- const limit = getContextWindow(model);
2920
+ const limit = this.getContextWindow(model);
2737
2921
  const ratio = estimated / limit;
2738
2922
  if (ratio > 0.95) return "critical";
2739
2923
  if (ratio > 0.85) return "warning";
@@ -2741,12 +2925,12 @@ var init_context_manager = __esm({
2741
2925
  }
2742
2926
  /** Get context usage as percentage */
2743
2927
  getUsagePercent(model) {
2744
- const limit = getContextWindow(model);
2745
- return limit > 0 ? Math.round(this.lastTotalTokens / limit * 100) : 0;
2928
+ const limit = this.getContextWindow(model);
2929
+ return contextPercent(this.lastTotalTokens, limit);
2746
2930
  }
2747
2931
  /** Format context display string */
2748
2932
  formatContext(model) {
2749
- const limit = getContextWindow(model);
2933
+ const limit = this.getContextWindow(model);
2750
2934
  const fmt2 = /* @__PURE__ */ __name((n) => n >= 1e3 ? `${(n / 1e3).toFixed(0)}K` : String(n), "fmt");
2751
2935
  const pct = this.getUsagePercent(model);
2752
2936
  return `${fmt2(this.lastTotalTokens)}/${fmt2(limit)} (${pct}%)`;
@@ -3096,7 +3280,6 @@ var init_context_compressor = __esm({
3096
3280
  "src/context-compressor.ts"() {
3097
3281
  "use strict";
3098
3282
  init_providers();
3099
- init_context_manager();
3100
3283
  DEFAULT_COMPRESSION_THRESHOLD = 0.7;
3101
3284
  PROTECTED_HEAD = 3;
3102
3285
  PROTECTED_TAIL_TOKENS = 2e4;
@@ -3188,7 +3371,7 @@ Rules:
3188
3371
  shouldCompress(threshold) {
3189
3372
  const t = threshold ?? this.config.llm.compression_threshold ?? DEFAULT_COMPRESSION_THRESHOLD;
3190
3373
  const model = this.config.llm.model;
3191
- const limit = getContextWindow(model);
3374
+ const limit = this.contextManager.getContextWindow(model);
3192
3375
  const current = this.contextManager.getEstimatedTokens();
3193
3376
  const ratio = current / limit;
3194
3377
  if (ratio < t) return false;
@@ -3281,7 +3464,7 @@ ${summary}
3281
3464
  async _summarize(middle, _systemPrompt) {
3282
3465
  const auxProvider = this._getAuxProvider();
3283
3466
  const middleText = buildMiddleText(middle);
3284
- const contextWindow = getContextWindow(this.config.llm.model);
3467
+ const contextWindow = this.contextManager.getContextWindow(this.config.llm.model);
3285
3468
  const targetTokens = Math.max(
3286
3469
  SUMMARY_MIN_TOKENS,
3287
3470
  Math.min(SUMMARY_MAX_TOKENS, Math.floor(contextWindow * SUMMARY_PCT))
@@ -4129,6 +4312,7 @@ var init_agent_loop = __esm({
4129
4312
  cacheMonitor;
4130
4313
  telemetry;
4131
4314
  contextCompressor;
4315
+ liveModelMetadata = /* @__PURE__ */ new Map();
4132
4316
  _compactedOnResume = false;
4133
4317
  _toolRoundsInRun = 0;
4134
4318
  _skillSuggested = false;
@@ -4146,6 +4330,7 @@ var init_agent_loop = __esm({
4146
4330
  this.provider = createProvider(config);
4147
4331
  this.costTracker = new CostTracker();
4148
4332
  this.contextManager = new ContextManager();
4333
+ this.contextManager.setActiveProvider(config.llm.provider);
4149
4334
  this.cacheMonitor = new CacheMonitor();
4150
4335
  this.telemetry = new Telemetry("pending");
4151
4336
  if (resumeSessionId) {
@@ -4162,6 +4347,7 @@ var init_agent_loop = __esm({
4162
4347
  this.telemetry = new Telemetry(this.session.id);
4163
4348
  this.telemetry.recordSessionStart(config.llm.model, config.llm.provider);
4164
4349
  this.contextCompressor = new ContextCompressor(config, this.contextManager, this.telemetry);
4350
+ void this.refreshActiveModelMetadata();
4165
4351
  }
4166
4352
  get tools() {
4167
4353
  return this.toolRegistry;
@@ -4191,6 +4377,9 @@ var init_agent_loop = __esm({
4191
4377
  }
4192
4378
  return this.config.llm.provider;
4193
4379
  }
4380
+ get reasoningEffort() {
4381
+ return this.config.llm.model_reasoning_effort;
4382
+ }
4194
4383
  /**
4195
4384
  * Provide the user's response to an ask_user question.
4196
4385
  * Called by Chat.tsx or bridges when the user answers.
@@ -4208,7 +4397,7 @@ var init_agent_loop = __esm({
4208
4397
  return this._waitingForAskUserFlag;
4209
4398
  }
4210
4399
  /** Switch provider and/or model at runtime */
4211
- switchModel(provider, model) {
4400
+ switchModel(provider, model, reasoningEffort) {
4212
4401
  let baseUrl = this.config.llm.base_url;
4213
4402
  if (baseUrl) {
4214
4403
  if (provider === "openai" && baseUrl.includes("/api/anthropic")) {
@@ -4223,14 +4412,26 @@ var init_agent_loop = __esm({
4223
4412
  }
4224
4413
  this.config = {
4225
4414
  ...this.config,
4226
- llm: { ...this.config.llm, provider, model, base_url: baseUrl }
4415
+ llm: { ...this.config.llm, provider, model, base_url: baseUrl, model_reasoning_effort: reasoningEffort }
4227
4416
  };
4228
4417
  this.provider = createProvider(this.config);
4418
+ this.contextManager.setActiveProvider(provider);
4229
4419
  this._toolExposureState = createToolExposureState();
4230
4420
  this.session.provider = provider;
4231
4421
  this.session.model = model;
4232
4422
  saveSession(this.session);
4233
4423
  saveAgentConfig(this.config);
4424
+ void this.refreshActiveModelMetadata();
4425
+ }
4426
+ updateReasoningEffort(reasoningEffort) {
4427
+ this.config = {
4428
+ ...this.config,
4429
+ llm: { ...this.config.llm, model_reasoning_effort: reasoningEffort }
4430
+ };
4431
+ this.session.provider = this.config.llm.provider;
4432
+ this.session.model = this.config.llm.model;
4433
+ saveSession(this.session);
4434
+ saveAgentConfig(this.config);
4234
4435
  }
4235
4436
  /** Apply provider config changes that are not expressible as a model-only switch. */
4236
4437
  updateProviderConfig(updates) {
@@ -4239,15 +4440,53 @@ var init_agent_loop = __esm({
4239
4440
  llm: { ...this.config.llm, ...updates }
4240
4441
  };
4241
4442
  this.provider = createProvider(this.config);
4443
+ this.contextManager.setActiveProvider(this.config.llm.provider);
4242
4444
  this._toolExposureState = createToolExposureState();
4243
4445
  this.session.provider = this.config.llm.provider;
4244
4446
  this.session.model = this.config.llm.model;
4245
4447
  saveSession(this.session);
4246
4448
  saveAgentConfig(this.config);
4449
+ void this.refreshActiveModelMetadata();
4247
4450
  }
4248
4451
  /** List available models from the current provider's API */
4249
4452
  async listModels() {
4250
- return this.provider.listModels?.() ?? null;
4453
+ const models = await this.provider.listModels?.() ?? null;
4454
+ if (models) this.setProviderModelMetadata(this.config.llm.provider, models);
4455
+ return models;
4456
+ }
4457
+ setProviderModelMetadata(provider, models) {
4458
+ const prefix = `${provider}\0`;
4459
+ for (const key of [...this.liveModelMetadata.keys()]) {
4460
+ if (key.startsWith(prefix)) this.liveModelMetadata.delete(key);
4461
+ }
4462
+ for (const model of models ?? []) {
4463
+ this.liveModelMetadata.set(`${provider}\0${model.id}`, model);
4464
+ }
4465
+ this.contextManager.setProviderModelMetadata(provider, models);
4466
+ }
4467
+ getActiveModelMetadata(model = this.config.llm.model) {
4468
+ return this.getModelMetadata(this.config.llm.provider, model);
4469
+ }
4470
+ getModelMetadata(provider, model) {
4471
+ return this.liveModelMetadata.get(`${provider}\0${model}`);
4472
+ }
4473
+ async refreshActiveModelMetadata() {
4474
+ const providerName = this.config.llm.provider;
4475
+ const provider = this.provider;
4476
+ this.contextManager.setActiveProvider(providerName);
4477
+ if (providerName !== "github-copilot") return;
4478
+ try {
4479
+ const models = await provider.listModels?.() ?? null;
4480
+ if (models) {
4481
+ this.setProviderModelMetadata(providerName, models);
4482
+ } else {
4483
+ this.setProviderModelMetadata(providerName, null);
4484
+ this.contextManager.clearProviderModelMetadata(providerName);
4485
+ }
4486
+ } catch {
4487
+ this.setProviderModelMetadata(providerName, null);
4488
+ this.contextManager.clearProviderModelMetadata(providerName);
4489
+ }
4251
4490
  }
4252
4491
  async listAllModels() {
4253
4492
  const { STATIC_MODELS: STATIC_MODELS2 } = await Promise.resolve().then(() => (init_models_static(), models_static_exports));
@@ -4278,13 +4517,16 @@ var init_agent_loop = __esm({
4278
4517
  const seen = /* @__PURE__ */ new Set();
4279
4518
  if (live && live.length > 0) {
4280
4519
  sources[currentProvider] = { source: "live" };
4520
+ this.setProviderModelMetadata(currentProvider, live);
4281
4521
  for (const m of live) {
4282
4522
  if (seen.has(m.id)) continue;
4283
4523
  seen.add(m.id);
4284
- models.push({ provider: currentProvider, id: m.id, name: m.name, source: "live" });
4524
+ models.push({ provider: currentProvider, id: m.id, name: m.name, contextWindow: m.contextWindow, outputTokens: m.outputTokens, source: "live" });
4285
4525
  }
4286
4526
  } else {
4287
4527
  sources[currentProvider] = { source: "static", error: lastError };
4528
+ this.setProviderModelMetadata(currentProvider, null);
4529
+ this.contextManager.clearProviderModelMetadata(currentProvider);
4288
4530
  for (const m of STATIC_MODELS2[currentProvider]) {
4289
4531
  if (seen.has(m.id)) continue;
4290
4532
  seen.add(m.id);
@@ -4328,13 +4570,16 @@ var init_agent_loop = __esm({
4328
4570
  const seen = new Set(models.filter((m) => m.provider === provider && !m.group).map((m) => m.id));
4329
4571
  if (live && live.length > 0) {
4330
4572
  sources[provider] = { source: "live" };
4573
+ this.setProviderModelMetadata(provider, live);
4331
4574
  for (const m of live) {
4332
4575
  if (seen.has(m.id)) continue;
4333
4576
  seen.add(m.id);
4334
- models.push({ provider, id: m.id, name: m.name, source: "live" });
4577
+ models.push({ provider, id: m.id, name: m.name, contextWindow: m.contextWindow, outputTokens: m.outputTokens, source: "live" });
4335
4578
  }
4336
4579
  } else {
4337
4580
  sources[provider] = { source: "static", error: lastError };
4581
+ this.setProviderModelMetadata(provider, null);
4582
+ this.contextManager.clearProviderModelMetadata(provider);
4338
4583
  for (const m of STATIC_MODELS2[provider]) {
4339
4584
  if (seen.has(m.id)) continue;
4340
4585
  seen.add(m.id);
@@ -4592,6 +4837,7 @@ ${summary}
4592
4837
  messages,
4593
4838
  tools,
4594
4839
  signal,
4840
+ reasoningEffort: this.config.llm.model_reasoning_effort,
4595
4841
  debug: {
4596
4842
  toolMode: exposure.mode,
4597
4843
  fullToolCount: exposure.fullToolCount,
@@ -8760,9 +9006,9 @@ var init_outlook_graph = __esm({
8760
9006
  const wellKnown = WELL_KNOWN_FOLDERS[lower];
8761
9007
  if (wellKnown)
8762
9008
  return wellKnown;
8763
- const cached8 = this.folderIdCache.get(lower);
8764
- if (cached8)
8765
- return cached8;
9009
+ const cached7 = this.folderIdCache.get(lower);
9010
+ if (cached7)
9011
+ return cached7;
8766
9012
  try {
8767
9013
  const result = await this.graphGet(`/me/mailFolders?$filter=displayName eq '${folderName.replace(/'/g, "''")}'&$top=1`);
8768
9014
  if (result.value.length > 0) {
@@ -9120,9 +9366,9 @@ var init_db = __esm({
9120
9366
  import { createHash as createHash2 } from "node:crypto";
9121
9367
  function encodeAtom(word, dim2 = HRR_DIM) {
9122
9368
  const cacheKey = `${word}:${dim2}`;
9123
- const cached8 = atomCache.get(cacheKey);
9124
- if (cached8)
9125
- return cached8;
9369
+ const cached7 = atomCache.get(cacheKey);
9370
+ if (cached7)
9371
+ return cached7;
9126
9372
  const phases = new Float64Array(dim2);
9127
9373
  const bytesNeeded = dim2 * 4;
9128
9374
  const chunks = [];
@@ -12338,9 +12584,9 @@ ${loopContent}` : loopContent;
12338
12584
  */
12339
12585
  async findChannelByName(searchTerm) {
12340
12586
  const searchLower = searchTerm.toLowerCase();
12341
- const cached8 = this.channelIdCache.get(searchLower);
12342
- if (cached8) {
12343
- return { ...cached8, displayName: searchTerm };
12587
+ const cached7 = this.channelIdCache.get(searchLower);
12588
+ if (cached7) {
12589
+ return { ...cached7, displayName: searchTerm };
12344
12590
  }
12345
12591
  try {
12346
12592
  const teamsData = await this.graphGet("/v1.0/me/joinedTeams");
@@ -12520,9 +12766,9 @@ ${loopContent}` : loopContent;
12520
12766
  * Results are cached per chat ID.
12521
12767
  */
12522
12768
  async getChatMembers(chatOrChannelId) {
12523
- const cached8 = this.chatMembersCache.get(chatOrChannelId);
12524
- if (cached8)
12525
- return cached8;
12769
+ const cached7 = this.chatMembersCache.get(chatOrChannelId);
12770
+ if (cached7)
12771
+ return cached7;
12526
12772
  try {
12527
12773
  let endpoint;
12528
12774
  if (this.contextType === "channel" && this.currentChannelTeamId && chatOrChannelId === this.currentChannelId) {
@@ -12760,9 +13006,9 @@ ${loopContent}` : loopContent;
12760
13006
  */
12761
13007
  async resolveChannelIds(teamName, channelName) {
12762
13008
  const cacheKey = `${teamName}/${channelName}`;
12763
- const cached8 = this.channelIdCache.get(cacheKey);
12764
- if (cached8)
12765
- return cached8;
13009
+ const cached7 = this.channelIdCache.get(cacheKey);
13010
+ if (cached7)
13011
+ return cached7;
12766
13012
  const teamsData = await this.graphGet("/v1.0/me/joinedTeams");
12767
13013
  const team = teamsData.value?.find((t) => t.displayName.toLowerCase() === teamName.toLowerCase());
12768
13014
  if (!team) {
@@ -19754,38 +20000,64 @@ var init_frontmatter = __esm({
19754
20000
  });
19755
20001
 
19756
20002
  // src/skills/registry.ts
19757
- import { existsSync as existsSync14, readFileSync as readFileSync14, readdirSync as readdirSync2 } from "node:fs";
20003
+ import { existsSync as existsSync14, readFileSync as readFileSync14, readdirSync as readdirSync2, statSync } from "node:fs";
19758
20004
  import { join as join22 } from "node:path";
19759
20005
  import { homedir as homedir12 } from "node:os";
20006
+ function skillRoots() {
20007
+ return rootsOverride ?? { bundledDir: packageSkillsDir(), userDir: DEFAULT_USER_DIR };
20008
+ }
19760
20009
  function discoverSkills() {
19761
- if (cachedSkills) return cachedSkills;
20010
+ const signature = buildRegistrySignature();
20011
+ if (cachedSkills?.signature === signature) return cachedSkills.skills;
20012
+ const roots = skillRoots();
19762
20013
  const byName2 = /* @__PURE__ */ new Map();
19763
- loadSkillsFromDir(packageSkillsDir(), "bundled", byName2);
19764
- loadSkillsFromDir(USER_DIR, "user", byName2);
19765
- cachedSkills = [...byName2.values()].sort((a, b) => a.name.localeCompare(b.name));
19766
- return cachedSkills;
20014
+ loadFlatSkillsFromDir(roots.bundledDir, "bundled", 0, byName2);
20015
+ loadFlatSkillsFromDir(roots.userDir, "user", 1, byName2);
20016
+ loadPackagedSkillsFromDir(roots.userDir, byName2);
20017
+ const skills = [...byName2.values()].map((entry) => entry.skill).sort((a, b) => a.name.localeCompare(b.name));
20018
+ cachedSkills = { signature, skills };
20019
+ return skills;
19767
20020
  }
19768
20021
  function loadSkillBody(skillName) {
19769
- const skills = discoverSkills();
19770
- const skill = skills.find((s) => s.name === skillName);
20022
+ const skill = findSkill(skillName);
19771
20023
  if (!skill) return null;
19772
20024
  try {
19773
20025
  const content = readFileSync14(skill.filePath, "utf-8").trim();
19774
- const parsed = parseSkillFile(content, skill.name + ".md");
20026
+ const parsed = parseSkillFile(content, `${skill.name}.md`);
19775
20027
  return parsed.body;
19776
20028
  } catch {
19777
20029
  return null;
19778
20030
  }
19779
20031
  }
20032
+ function loadSkillPrompt(skillName) {
20033
+ const skill = findSkill(skillName);
20034
+ const body = skill ? loadSkillBody(skill.name) : null;
20035
+ if (!skill || !body) return null;
20036
+ return `# Skill Runtime Context
20037
+
20038
+ Skill name: ${skill.name}
20039
+ Skill file: ${skill.filePath}
20040
+ Skill root directory: ${skill.rootDir}
20041
+
20042
+ ${body}`;
20043
+ }
19780
20044
  function findSkill(name) {
19781
20045
  const skills = discoverSkills();
19782
- const exact = skills.find((s) => s.name === name);
20046
+ const normalized = normalizeSkillName(name);
20047
+ const exact = skills.find((s) => s.name === normalized);
19783
20048
  if (exact) return exact;
19784
- const lower = name.toLowerCase();
19785
- const ci = skills.find((s) => s.name.toLowerCase() === lower);
20049
+ const ci = skills.find((s) => s.name.toLowerCase() === normalized.toLowerCase());
19786
20050
  if (ci) return ci;
19787
- const partial = skills.find((s) => s.name.startsWith(lower) || lower.startsWith(s.name));
19788
- return partial || null;
20051
+ const matches = findSkillMatches(normalized);
20052
+ return matches.length === 1 ? matches[0] : null;
20053
+ }
20054
+ function findSkillMatches(name) {
20055
+ const normalized = normalizeSkillName(name).toLowerCase();
20056
+ if (!normalized) return [];
20057
+ return discoverSkills().filter((skill) => {
20058
+ const skillName = skill.name.toLowerCase();
20059
+ return skillName.startsWith(normalized) || normalized.startsWith(skillName);
20060
+ });
19789
20061
  }
19790
20062
  function getSkillListing() {
19791
20063
  const skills = discoverSkills();
@@ -19800,52 +20072,145 @@ function getSkillListing() {
19800
20072
  function clearSkillRegistry() {
19801
20073
  cachedSkills = null;
19802
20074
  }
19803
- function loadSkillsFromDir(dir2, source, out) {
19804
- if (!existsSync14(dir2)) return;
20075
+ function normalizeSkillName(name) {
20076
+ return name.trim().replace(/^\/+/, "").toLowerCase();
20077
+ }
20078
+ function loadFlatSkillsFromDir(dir2, source, priority, out) {
20079
+ for (const entry of safeReadDir(dir2)) {
20080
+ if (!entry.isFile() || !isMarkdown(entry.name)) continue;
20081
+ const filePath = join22(dir2, entry.name);
20082
+ const skill = parseSkillAtPath(filePath, dir2, entry.name, source, entry.name);
20083
+ if (skill) putSkill(out, skill, priority);
20084
+ }
20085
+ }
20086
+ function loadPackagedSkillsFromDir(dir2, out) {
20087
+ for (const entry of safeReadDir(dir2)) {
20088
+ if (!entry.isDirectory()) continue;
20089
+ const rootDir = join22(dir2, entry.name);
20090
+ const entrypoint = findPackageEntrypoint(rootDir);
20091
+ if (!entrypoint) continue;
20092
+ const filePath = join22(rootDir, entrypoint);
20093
+ const skill = parseSkillAtPath(filePath, rootDir, entrypoint, "user", `${entry.name}.md`);
20094
+ if (skill) putSkill(out, skill, 2);
20095
+ }
20096
+ }
20097
+ function findPackageEntrypoint(dir2) {
20098
+ const entries = safeReadDir(dir2).filter((entry) => entry.isFile());
20099
+ const entrypoint = ENTRYPOINT_NAMES.find((name) => entries.some((entry) => entry.name.toLowerCase() === name.toLowerCase()));
20100
+ if (entrypoint) return entries.find((entry) => entry.name.toLowerCase() === entrypoint.toLowerCase())?.name ?? entrypoint;
20101
+ const readme = entries.find((entry) => entry.name.toLowerCase() === "readme.md");
20102
+ if (readme) return readme.name;
20103
+ const markdown = entries.filter((entry) => isMarkdown(entry.name));
20104
+ return markdown.length === 1 ? markdown[0].name : null;
20105
+ }
20106
+ function parseSkillAtPath(filePath, rootDir, entrypoint, source, fallbackFilename) {
19805
20107
  try {
19806
- const files = readdirSync2(dir2).filter((f) => f.endsWith(".md")).sort();
19807
- for (const file2 of files) {
19808
- const filePath = join22(dir2, file2);
19809
- try {
19810
- const content = readFileSync14(filePath, "utf-8").trim();
19811
- if (!content) continue;
19812
- const parsed = parseSkillFile(content, file2);
19813
- out.set(parsed.meta.name, {
19814
- name: parsed.meta.name,
19815
- meta: parsed.meta,
19816
- filePath,
19817
- source,
19818
- hasFrontmatter: parsed.hasFrontmatter
19819
- });
19820
- } catch {
20108
+ const content = readFileSync14(filePath, "utf-8").trim();
20109
+ if (!content) return null;
20110
+ const parsed = parseSkillFile(content, fallbackFilename);
20111
+ const name = normalizeSkillName(parsed.meta.name);
20112
+ if (!name) return null;
20113
+ return {
20114
+ name,
20115
+ meta: { ...parsed.meta, name },
20116
+ filePath,
20117
+ rootDir,
20118
+ entrypoint,
20119
+ source,
20120
+ hasFrontmatter: parsed.hasFrontmatter
20121
+ };
20122
+ } catch {
20123
+ return null;
20124
+ }
20125
+ }
20126
+ function putSkill(out, skill, priority) {
20127
+ const existing = out.get(skill.name);
20128
+ if (!existing || priority >= existing.priority) {
20129
+ out.set(skill.name, { priority, skill });
20130
+ }
20131
+ }
20132
+ function safeReadDir(dir2) {
20133
+ if (!existsSync14(dir2)) return [];
20134
+ try {
20135
+ return readdirSync2(dir2, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
20136
+ } catch {
20137
+ return [];
20138
+ }
20139
+ }
20140
+ function buildRegistrySignature() {
20141
+ const roots = skillRoots();
20142
+ return [signatureForDir(roots.bundledDir), signatureForDir(roots.userDir)].join("|");
20143
+ }
20144
+ function signatureForDir(dir2) {
20145
+ if (!existsSync14(dir2)) return `${dir2}:missing`;
20146
+ const parts = [];
20147
+ try {
20148
+ for (const entry of safeReadDir(dir2)) {
20149
+ const fullPath = join22(dir2, entry.name);
20150
+ if (entry.isFile()) {
20151
+ if (!isMarkdown(entry.name)) continue;
20152
+ parts.push(fileSignature(fullPath, `f:${entry.name}`));
20153
+ } else if (entry.isDirectory()) {
20154
+ parts.push(fileSignature(fullPath, `d:${entry.name}`));
20155
+ for (const child of safeReadDir(fullPath)) {
20156
+ if (child.isFile() && isMarkdown(child.name)) {
20157
+ parts.push(fileSignature(join22(fullPath, child.name), `d:${entry.name}/${child.name}`));
20158
+ }
20159
+ }
19821
20160
  }
19822
20161
  }
19823
20162
  } catch {
20163
+ return `${dir2}:unreadable`;
20164
+ }
20165
+ return `${dir2}:${parts.join(",")}`;
20166
+ }
20167
+ function isMarkdown(name) {
20168
+ return name.toLowerCase().endsWith(".md");
20169
+ }
20170
+ function fileSignature(path3, label) {
20171
+ try {
20172
+ const stat2 = statSync(path3);
20173
+ return `${label}:${stat2.mtimeMs}:${stat2.size}`;
20174
+ } catch {
20175
+ return `${label}:missing`;
19824
20176
  }
19825
20177
  }
19826
- var USER_DIR, cachedSkills;
20178
+ var DEFAULT_USER_DIR, ENTRYPOINT_NAMES, cachedSkills, rootsOverride;
19827
20179
  var init_registry2 = __esm({
19828
20180
  "src/skills/registry.ts"() {
19829
20181
  "use strict";
19830
20182
  init_frontmatter();
19831
20183
  init_packageRoot();
19832
- USER_DIR = join22(homedir12(), ".openjaw-agent", "skills");
20184
+ DEFAULT_USER_DIR = join22(homedir12(), ".openjaw-agent", "skills");
20185
+ ENTRYPOINT_NAMES = ["SKILL.md", "skill.md"];
19833
20186
  cachedSkills = null;
20187
+ rootsOverride = null;
20188
+ __name(skillRoots, "skillRoots");
19834
20189
  __name(discoverSkills, "discoverSkills");
19835
20190
  __name(loadSkillBody, "loadSkillBody");
20191
+ __name(loadSkillPrompt, "loadSkillPrompt");
19836
20192
  __name(findSkill, "findSkill");
20193
+ __name(findSkillMatches, "findSkillMatches");
19837
20194
  __name(getSkillListing, "getSkillListing");
19838
20195
  __name(clearSkillRegistry, "clearSkillRegistry");
19839
- __name(loadSkillsFromDir, "loadSkillsFromDir");
20196
+ __name(normalizeSkillName, "normalizeSkillName");
20197
+ __name(loadFlatSkillsFromDir, "loadFlatSkillsFromDir");
20198
+ __name(loadPackagedSkillsFromDir, "loadPackagedSkillsFromDir");
20199
+ __name(findPackageEntrypoint, "findPackageEntrypoint");
20200
+ __name(parseSkillAtPath, "parseSkillAtPath");
20201
+ __name(putSkill, "putSkill");
20202
+ __name(safeReadDir, "safeReadDir");
20203
+ __name(buildRegistrySignature, "buildRegistrySignature");
20204
+ __name(signatureForDir, "signatureForDir");
20205
+ __name(isMarkdown, "isMarkdown");
20206
+ __name(fileSignature, "fileSignature");
19840
20207
  }
19841
20208
  });
19842
20209
 
19843
20210
  // src/prompts/skills.ts
19844
20211
  function getSkillsSection() {
19845
- if (cached7 !== void 0) return cached7;
19846
20212
  const skills = discoverSkills();
19847
20213
  if (skills.length === 0) {
19848
- cached7 = null;
19849
20214
  return null;
19850
20215
  }
19851
20216
  const listing = getSkillListing();
@@ -19860,15 +20225,17 @@ New skills must be saved to \`~/.openjaw-agent/skills/\` (user skills directory)
19860
20225
  Do NOT write skills to the project's bundled skills/ directory.
19861
20226
 
19862
20227
  ${listing}`;
19863
- cached7 = content;
19864
- return cached7;
20228
+ return content;
20229
+ }
20230
+ function clearSkillsCache() {
20231
+ clearSkillRegistry();
19865
20232
  }
19866
- var cached7;
19867
20233
  var init_skills = __esm({
19868
20234
  "src/prompts/skills.ts"() {
19869
20235
  "use strict";
19870
20236
  init_registry2();
19871
20237
  __name(getSkillsSection, "getSkillsSection");
20238
+ __name(clearSkillsCache, "clearSkillsCache");
19872
20239
  }
19873
20240
  });
19874
20241
 
@@ -20559,8 +20926,8 @@ Options: ${chunk.choices.join(" | ")}` : "";
20559
20926
  if (fileName.startsWith("oj-") || fileName.endsWith(".log") || fileName.endsWith(".tmp")) continue;
20560
20927
  if (existsSync16(resolvedPath)) {
20561
20928
  try {
20562
- const { statSync: statSync4 } = await import("node:fs");
20563
- const stat2 = statSync4(resolvedPath);
20929
+ const { statSync: statSync5 } = await import("node:fs");
20930
+ const stat2 = statSync5(resolvedPath);
20564
20931
  if (stat2.isFile() && stat2.size < 50 * 1024 * 1024) {
20565
20932
  const ext = resolvedPath.split(".").pop()?.toLowerCase() || "";
20566
20933
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
@@ -21412,10 +21779,10 @@ var init_mcp_client = __esm({
21412
21779
  let tokenExpiry = 0;
21413
21780
  if (copilotTokenFile) {
21414
21781
  try {
21415
- const cached8 = JSON.parse(readFileSync17(copilotTokenFile, "utf-8"));
21416
- cachedToken = cached8.accessToken;
21417
- cachedRefreshToken = cached8.refreshToken;
21418
- tokenExpiry = cached8.expiresAt || 0;
21782
+ const cached7 = JSON.parse(readFileSync17(copilotTokenFile, "utf-8"));
21783
+ cachedToken = cached7.accessToken;
21784
+ cachedRefreshToken = cached7.refreshToken;
21785
+ tokenExpiry = cached7.expiresAt || 0;
21419
21786
  } catch {
21420
21787
  }
21421
21788
  }
@@ -26206,7 +26573,7 @@ function createSkillTool(config, toolRegistry, systemPromptFn) {
26206
26573
  };
26207
26574
  }
26208
26575
  async function executeSkill(skillName, args, config, toolRegistry, systemPromptFn) {
26209
- const body = loadSkillBody(skillName);
26576
+ const body = loadSkillPrompt(skillName);
26210
26577
  if (!body) {
26211
26578
  return { success: false, error: `Could not load skill content for "${skillName}"` };
26212
26579
  }
@@ -26569,8 +26936,8 @@ var init_teams = __esm({
26569
26936
  if (fileName.startsWith("oj-") || fileName.endsWith(".log") || fileName.endsWith(".tmp")) continue;
26570
26937
  if (existsSync25(filePath)) {
26571
26938
  try {
26572
- const { statSync: statSync4 } = await import("node:fs");
26573
- const stat2 = statSync4(filePath);
26939
+ const { statSync: statSync5 } = await import("node:fs");
26940
+ const stat2 = statSync5(filePath);
26574
26941
  if (stat2.isFile() && stat2.size < 50 * 1024 * 1024) {
26575
26942
  await this.sendFileToSelfChat(filePath);
26576
26943
  sentFiles.add(filePath);
@@ -27129,8 +27496,8 @@ var init_feishu = __esm({
27129
27496
  this.emit({ type: "system", content: `\u{1F50D} Found path: ${filePath} (exists: ${exists})` });
27130
27497
  if (exists) {
27131
27498
  try {
27132
- const { statSync: statSync4 } = await import("node:fs");
27133
- const stat2 = statSync4(filePath);
27499
+ const { statSync: statSync5 } = await import("node:fs");
27500
+ const stat2 = statSync5(filePath);
27134
27501
  if (stat2.isFile() && stat2.size < 30 * 1024 * 1024) {
27135
27502
  const fileType = this.getFeishuFileType(fileName);
27136
27503
  this.emit({ type: "system", content: `\u{1F4E4} Uploading to Feishu: ${fileName} (${(stat2.size / 1024).toFixed(0)}KB, type=${fileType})` });
@@ -27226,7 +27593,7 @@ __export(wechat_exports, {
27226
27593
  sniffMediaKind: () => sniffMediaKind,
27227
27594
  validateMediaForUpload: () => validateMediaForUpload
27228
27595
  });
27229
- import { existsSync as existsSync27, readFileSync as readFileSync26, writeFileSync as writeFileSync18, unlinkSync as unlinkSync7, statSync as statSync2 } from "node:fs";
27596
+ import { existsSync as existsSync27, readFileSync as readFileSync26, writeFileSync as writeFileSync18, unlinkSync as unlinkSync7, statSync as statSync3 } from "node:fs";
27230
27597
  import { mkdirSync as mkdirSync16 } from "node:fs";
27231
27598
  import { extname as extname3, join as join35 } from "node:path";
27232
27599
  import { homedir as homedir21 } from "node:os";
@@ -28097,7 +28464,7 @@ Scan URL manually: ${qrUrl}` });
28097
28464
  attemptedFiles.add(dedupKey);
28098
28465
  attempted++;
28099
28466
  try {
28100
- const stat2 = statSync2(filePath);
28467
+ const stat2 = statSync3(filePath);
28101
28468
  if (!stat2.isFile() || stat2.size > 30 * 1024 * 1024) continue;
28102
28469
  if (stat2.size === 0) {
28103
28470
  this.emit({ type: "system", content: `\u26A0 WeChat skipped empty file: ${fileName}` });
@@ -29097,12 +29464,40 @@ var init_bootstrap = __esm({
29097
29464
  }
29098
29465
  });
29099
29466
 
29467
+ // src/usageSnapshot.ts
29468
+ function usageSnapshot(agentLoop) {
29469
+ const cost = agentLoop.costTracker.getSessionCost();
29470
+ const contextMax = agentLoop.contextManager.getContextWindow(agentLoop.model);
29471
+ const contextUsed = agentLoop.contextManager.lastPromptTokens;
29472
+ return {
29473
+ cache_read: cost.totalCacheReadTokens,
29474
+ cache_write: cost.totalCacheCreationTokens,
29475
+ calls: cost.turns,
29476
+ context_max: contextMax,
29477
+ context_percent: contextPercent(contextUsed, contextMax),
29478
+ context_used: contextUsed,
29479
+ cost_status: "estimated",
29480
+ cost_usd: cost.totalCostUSD,
29481
+ input: cost.totalInputTokens,
29482
+ model: agentLoop.model,
29483
+ output: cost.totalOutputTokens,
29484
+ total: cost.totalInputTokens + cost.totalOutputTokens + cost.totalCacheReadTokens + cost.totalCacheCreationTokens
29485
+ };
29486
+ }
29487
+ var init_usageSnapshot = __esm({
29488
+ "src/usageSnapshot.ts"() {
29489
+ "use strict";
29490
+ init_usage();
29491
+ __name(usageSnapshot, "usageSnapshot");
29492
+ }
29493
+ });
29494
+
29100
29495
  // src/eventBridge.ts
29101
29496
  import { randomUUID as randomUUID11 } from "node:crypto";
29102
29497
  async function streamAgentRun(options) {
29103
29498
  const { agentLoop, bus, imageData, systemPrompt, text } = options;
29104
29499
  const sid = agentLoop.sessionId;
29105
- const state = newState();
29500
+ const state = newState(agentLoop);
29106
29501
  bus.emitEvent({
29107
29502
  payload: { kind: "thinking", text: "thinking\u2026" },
29108
29503
  session_id: sid,
@@ -29192,11 +29587,13 @@ function translateChunk(chunk, state, bus, sid) {
29192
29587
  }
29193
29588
  }
29194
29589
  }
29195
- var newState, ensureMessageOpen, closeMessage;
29590
+ var newState, ensureMessageOpen, closeMessage, safeUsageSnapshot;
29196
29591
  var init_eventBridge = __esm({
29197
29592
  "src/eventBridge.ts"() {
29198
29593
  "use strict";
29199
- newState = /* @__PURE__ */ __name(() => ({
29594
+ init_usageSnapshot();
29595
+ newState = /* @__PURE__ */ __name((agentLoop) => ({
29596
+ agentLoop,
29200
29597
  messageOpen: false,
29201
29598
  pendingAnswer: "",
29202
29599
  toolIds: /* @__PURE__ */ new Map()
@@ -29209,8 +29606,9 @@ var init_eventBridge = __esm({
29209
29606
  }, "ensureMessageOpen");
29210
29607
  closeMessage = /* @__PURE__ */ __name((state, bus, sid, text) => {
29211
29608
  if (state.messageOpen || text) {
29609
+ const usage2 = safeUsageSnapshot(state.agentLoop);
29212
29610
  bus.emitEvent({
29213
- payload: { text: text ?? state.pendingAnswer },
29611
+ payload: { text: text ?? state.pendingAnswer, ...usage2 ? { usage: usage2 } : {} },
29214
29612
  session_id: sid,
29215
29613
  type: "message.complete"
29216
29614
  });
@@ -29218,6 +29616,13 @@ var init_eventBridge = __esm({
29218
29616
  state.pendingAnswer = "";
29219
29617
  }
29220
29618
  }, "closeMessage");
29619
+ safeUsageSnapshot = /* @__PURE__ */ __name((agentLoop) => {
29620
+ try {
29621
+ return usageSnapshot(agentLoop);
29622
+ } catch {
29623
+ return void 0;
29624
+ }
29625
+ }, "safeUsageSnapshot");
29221
29626
  __name(streamAgentRun, "streamAgentRun");
29222
29627
  __name(translateChunk, "translateChunk");
29223
29628
  }
@@ -29297,15 +29702,6 @@ var init_env = __esm({
29297
29702
  }
29298
29703
  });
29299
29704
 
29300
- // src/domain/usage.ts
29301
- var ZERO;
29302
- var init_usage = __esm({
29303
- "src/domain/usage.ts"() {
29304
- "use strict";
29305
- ZERO = { calls: 0, input: 0, output: 0, total: 0 };
29306
- }
29307
- });
29308
-
29309
29705
  // src/theme.ts
29310
29706
  function parseHex(h) {
29311
29707
  const m = /^#?([0-9a-f]{6})$/i.exec(h);
@@ -29475,6 +29871,14 @@ function normalizeThemeForAnsiLightTerminal(theme, env2 = process.env, isLight =
29475
29871
  }
29476
29872
  return { ...theme, color };
29477
29873
  }
29874
+ function isBuiltinSkinName(value) {
29875
+ return BUILTIN_SKINS.some((skin) => skin.name === value);
29876
+ }
29877
+ function themeForBuiltinSkin(name) {
29878
+ if (name === "dark") return DARK_THEME;
29879
+ if (name === "light") return LIGHT_THEME;
29880
+ return DEFAULT_THEME;
29881
+ }
29478
29882
  function fromSkin(colors, branding, bannerLogo = "", bannerHero = "", toolPrefix = "", helpHeader = "") {
29479
29883
  const d = DEFAULT_THEME;
29480
29884
  const c = /* @__PURE__ */ __name((k) => colors[k], "c");
@@ -29532,7 +29936,7 @@ function fromSkin(colors, branding, bannerLogo = "", bannerHero = "", toolPrefix
29532
29936
  bannerHero
29533
29937
  }, process.env, DEFAULT_LIGHT_MODE);
29534
29938
  }
29535
- var XTERM_6_LEVELS, ANSI_LIGHT_MAX_LUMINANCE, ANSI_LIGHT_TARGET_LUMINANCE, ANSI_LIGHT_MIN_SATURATION, ANSI_MUTED_BUCKET, ANSI_NORMALIZED_FOREGROUNDS, ANSI_MUTED_FOREGROUNDS, BRAND, cleanPromptSymbol, DARK_THEME, LIGHT_THEME, TRUE_RE2, FALSE_RE2, LIGHT_DEFAULT_TERM_PROGRAMS, LUMA_LIGHT_THRESHOLD, HEX_3_RE, HEX_6_RE, DEFAULT_LIGHT_MODE, DEFAULT_THEME;
29939
+ var XTERM_6_LEVELS, ANSI_LIGHT_MAX_LUMINANCE, ANSI_LIGHT_TARGET_LUMINANCE, ANSI_LIGHT_MIN_SATURATION, ANSI_MUTED_BUCKET, ANSI_NORMALIZED_FOREGROUNDS, ANSI_MUTED_FOREGROUNDS, BRAND, cleanPromptSymbol, DARK_THEME, LIGHT_THEME, TRUE_RE2, FALSE_RE2, LIGHT_DEFAULT_TERM_PROGRAMS, LUMA_LIGHT_THRESHOLD, HEX_3_RE, HEX_6_RE, DEFAULT_LIGHT_MODE, DEFAULT_THEME, BUILTIN_SKINS;
29536
29940
  var init_theme = __esm({
29537
29941
  "src/theme.ts"() {
29538
29942
  "use strict";
@@ -29684,6 +30088,13 @@ var init_theme = __esm({
29684
30088
  process.env,
29685
30089
  DEFAULT_LIGHT_MODE
29686
30090
  );
30091
+ BUILTIN_SKINS = [
30092
+ { name: "default", description: "Auto-detect terminal light/dark preference" },
30093
+ { name: "dark", description: "OpenJaw dark coral theme" },
30094
+ { name: "light", description: "Light terminal theme" }
30095
+ ];
30096
+ __name(isBuiltinSkinName, "isBuiltinSkinName");
30097
+ __name(themeForBuiltinSkin, "themeForBuiltinSkin");
29687
30098
  __name(fromSkin, "fromSkin");
29688
30099
  }
29689
30100
  });
@@ -31452,11 +31863,11 @@ function sliceAnsi(str, start, end) {
31452
31863
  }
31453
31864
  if (end !== void 0) {
31454
31865
  const key = `${start}|${end}|${str}`;
31455
- const cached8 = sliceCache.get(key);
31456
- if (cached8 !== void 0) {
31866
+ const cached7 = sliceCache.get(key);
31867
+ if (cached7 !== void 0) {
31457
31868
  sliceCache.delete(key);
31458
- sliceCache.set(key, cached8);
31459
- return cached8;
31869
+ sliceCache.set(key, cached7);
31870
+ return cached7;
31460
31871
  }
31461
31872
  const result = computeSlice(str, start, end);
31462
31873
  if (sliceCache.size >= SLICE_CACHE_LIMIT) {
@@ -31511,11 +31922,11 @@ function computeSlice(str, start, end) {
31511
31922
  return result;
31512
31923
  }
31513
31924
  function lineWidth(line) {
31514
- const cached8 = cache.get(line);
31515
- if (cached8 !== void 0) {
31925
+ const cached7 = cache.get(line);
31926
+ if (cached7 !== void 0) {
31516
31927
  cache.delete(line);
31517
- cache.set(line, cached8);
31518
- return cached8;
31928
+ cache.set(line, cached7);
31929
+ return cached7;
31519
31930
  }
31520
31931
  const width = stringWidth(line);
31521
31932
  if (cache.size >= MAX_CACHE_SIZE) {
@@ -31532,11 +31943,11 @@ function evictLineWidthCache(keepRatio = 0) {
31532
31943
  }
31533
31944
  function memoizedWrap(text, maxWidth, wrapType) {
31534
31945
  const key = `${maxWidth}|${wrapType}|${text}`;
31535
- const cached8 = wrapCache.get(key);
31536
- if (cached8 !== void 0) {
31946
+ const cached7 = wrapCache.get(key);
31947
+ if (cached7 !== void 0) {
31537
31948
  wrapCache.delete(key);
31538
- wrapCache.set(key, cached8);
31539
- return cached8;
31949
+ wrapCache.set(key, cached7);
31950
+ return cached7;
31540
31951
  }
31541
31952
  const result = computeWrap(text, maxWidth, wrapType);
31542
31953
  if (wrapCache.size >= WRAP_CACHE_LIMIT) {
@@ -33496,9 +33907,9 @@ function collectRemovedRects(parent, removed, underAbsolute = false) {
33496
33907
  }
33497
33908
  const elem = removed;
33498
33909
  const isAbsolute2 = underAbsolute || elem.style.position === "absolute";
33499
- const cached8 = nodeCache.get(elem);
33500
- if (cached8) {
33501
- addPendingClear(parent, cached8, isAbsolute2);
33910
+ const cached7 = nodeCache.get(elem);
33911
+ if (cached7) {
33912
+ addPendingClear(parent, cached7, isAbsolute2);
33502
33913
  nodeCache.delete(elem);
33503
33914
  }
33504
33915
  for (const child of elem.childNodes) {
@@ -35618,31 +36029,31 @@ function renderNodeToOutput(node, output, {
35618
36029
  if (y < 0 && node.style.position === "absolute") {
35619
36030
  y = 0;
35620
36031
  }
35621
- const cached8 = nodeCache.get(node);
35622
- if (!node.dirty && !skipSelfBlit && node.pendingScrollDelta === void 0 && cached8 && cached8.x === x && cached8.y === y && cached8.width === width && cached8.height === height && prevScreen) {
36032
+ const cached7 = nodeCache.get(node);
36033
+ if (!node.dirty && !skipSelfBlit && node.pendingScrollDelta === void 0 && cached7 && cached7.x === x && cached7.y === y && cached7.width === width && cached7.height === height && prevScreen) {
35623
36034
  const fx = Math.floor(x);
35624
36035
  const fy = Math.floor(y);
35625
36036
  const fw = Math.floor(width);
35626
36037
  const fh = Math.floor(height);
35627
36038
  output.blit(prevScreen, fx, fy, fw, fh);
35628
36039
  if (node.style.position === "absolute") {
35629
- absoluteRectsCur.push(cached8);
36040
+ absoluteRectsCur.push(cached7);
35630
36041
  }
35631
36042
  blitEscapingAbsoluteDescendants(node, output, prevScreen, fx, fy, fw, fh);
35632
36043
  return;
35633
36044
  }
35634
- const positionChanged = cached8 !== void 0 && (cached8.x !== x || cached8.y !== y || cached8.width !== width || cached8.height !== height);
36045
+ const positionChanged = cached7 !== void 0 && (cached7.x !== x || cached7.y !== y || cached7.width !== width || cached7.height !== height);
35635
36046
  if (positionChanged) {
35636
36047
  layoutShifted = true;
35637
36048
  absoluteOverlayMoved ||= node.style.position === "absolute";
35638
36049
  }
35639
- if (cached8 && (node.dirty || positionChanged)) {
36050
+ if (cached7 && (node.dirty || positionChanged)) {
35640
36051
  output.clear(
35641
36052
  {
35642
- x: Math.floor(cached8.x),
35643
- y: Math.floor(cached8.y),
35644
- width: Math.floor(cached8.width),
35645
- height: Math.floor(cached8.height)
36053
+ x: Math.floor(cached7.x),
36054
+ y: Math.floor(cached7.y),
36055
+ width: Math.floor(cached7.width),
36056
+ height: Math.floor(cached7.height)
35646
36057
  },
35647
36058
  node.style.position === "absolute"
35648
36059
  );
@@ -35817,7 +36228,7 @@ function renderNodeToOutput(node, output, {
35817
36228
  const delta = contentCached.y - contentY;
35818
36229
  const regionTop = Math.floor(y + contentYoga.getComputedTop());
35819
36230
  const regionBottom = regionTop + innerHeight - 1;
35820
- if (cached8?.y === y && cached8.height === height && innerHeight > 0 && Math.abs(delta) < innerHeight) {
36231
+ if (cached7?.y === y && cached7.height === height && innerHeight > 0 && Math.abs(delta) < innerHeight) {
35821
36232
  hint = { top: regionTop, bottom: regionBottom, delta };
35822
36233
  scrollHint = hint;
35823
36234
  } else {
@@ -36117,13 +36528,13 @@ function blitEscapingAbsoluteDescendants(node, output, prevScreen, px, py, pw, p
36117
36528
  }
36118
36529
  const elem = child;
36119
36530
  if (elem.style.position === "absolute") {
36120
- const cached8 = nodeCache.get(elem);
36121
- if (cached8) {
36122
- absoluteRectsCur.push(cached8);
36123
- const cx = Math.floor(cached8.x);
36124
- const cy = Math.floor(cached8.y);
36125
- const cw = Math.floor(cached8.width);
36126
- const ch = Math.floor(cached8.height);
36531
+ const cached7 = nodeCache.get(elem);
36532
+ if (cached7) {
36533
+ absoluteRectsCur.push(cached7);
36534
+ const cx = Math.floor(cached7.x);
36535
+ const cy = Math.floor(cached7.y);
36536
+ const cw = Math.floor(cached7.width);
36537
+ const ch = Math.floor(cached7.height);
36127
36538
  if (cx < px || cy < py || cx + cw > pr || cy + ch > pb) {
36128
36539
  output.blit(prevScreen, cx, cy, cw, ch);
36129
36540
  }
@@ -36139,20 +36550,20 @@ function renderScrolledChildren(node, output, offsetX, offsetY, hasRemovedChild,
36139
36550
  const childElem = childNode;
36140
36551
  const cy = childElem.yogaNode;
36141
36552
  if (cy) {
36142
- const cached8 = nodeCache.get(childElem);
36553
+ const cached7 = nodeCache.get(childElem);
36143
36554
  let top;
36144
36555
  let height;
36145
- if (cached8?.top !== void 0 && !childElem.dirty && cumHeightShift === 0) {
36146
- top = cached8.top;
36147
- height = cached8.height;
36556
+ if (cached7?.top !== void 0 && !childElem.dirty && cumHeightShift === 0) {
36557
+ top = cached7.top;
36558
+ height = cached7.height;
36148
36559
  } else {
36149
36560
  top = cy.getComputedTop();
36150
36561
  height = cy.getComputedHeight();
36151
36562
  if (childElem.dirty) {
36152
- cumHeightShift += height - (cached8 ? cached8.height : 0);
36563
+ cumHeightShift += height - (cached7 ? cached7.height : 0);
36153
36564
  }
36154
- if (cached8) {
36155
- cached8.top = top;
36565
+ if (cached7) {
36566
+ cached7.top = top;
36156
36567
  }
36157
36568
  }
36158
36569
  const bottom = top + height;
@@ -38562,11 +38973,11 @@ var init_entry_exports = __esm({
38562
38973
  return rawStringWidth(str);
38563
38974
  }
38564
38975
  }
38565
- const cached8 = widthCache.get(str);
38566
- if (cached8 !== void 0) {
38976
+ const cached7 = widthCache.get(str);
38977
+ if (cached7 !== void 0) {
38567
38978
  widthCache.delete(str);
38568
- widthCache.set(str, cached8);
38569
- return cached8;
38979
+ widthCache.set(str, cached7);
38980
+ return cached7;
38570
38981
  }
38571
38982
  const w = rawStringWidth(str);
38572
38983
  if (widthCache.size >= WIDTH_CACHE_LIMIT) {
@@ -40671,9 +41082,9 @@ $ npm install --save-dev react-devtools-core
40671
41082
  if (char.length === 1) {
40672
41083
  const code = char.charCodeAt(0);
40673
41084
  if (code < 128) {
40674
- const cached8 = this.ascii[code];
40675
- if (cached8 !== -1) {
40676
- return cached8;
41085
+ const cached7 = this.ascii[code];
41086
+ if (cached7 !== -1) {
41087
+ return cached7;
40677
41088
  }
40678
41089
  const index2 = this.strings.length;
40679
41090
  this.strings.push(char);
@@ -45690,6 +46101,7 @@ var fmt, money, stub, eventLine, parseScheduleInput, showUsage, openjawCommands;
45690
46101
  var init_openjaw = __esm({
45691
46102
  "src/app/slash/commands/openjaw.ts"() {
45692
46103
  "use strict";
46104
+ init_usage();
45693
46105
  init_overlayStore();
45694
46106
  fmt = /* @__PURE__ */ __name((n) => (n ?? 0).toLocaleString(), "fmt");
45695
46107
  money = /* @__PURE__ */ __name((n) => `$${(n ?? 0).toFixed(4)}`, "money");
@@ -45937,7 +46349,7 @@ var init_openjaw = __esm({
45937
46349
  if (!r.context_max) {
45938
46350
  return ctx.transcript.sys("context usage unavailable");
45939
46351
  }
45940
- ctx.transcript.sys(`context: ${fmt(r.context_used)} / ${fmt(r.context_max)} (${r.context_percent ?? 0}%)`);
46352
+ ctx.transcript.sys(`context: ${fmt(r.context_used)} / ${fmt(r.context_max)} (${formatContextPercent(r.context_percent)})`);
45941
46353
  }), "run")
45942
46354
  },
45943
46355
  {
@@ -46840,6 +47252,8 @@ var init_session2 = __esm({
46840
47252
  init_slash();
46841
47253
  init_platform();
46842
47254
  init_text();
47255
+ init_theme();
47256
+ init_usage();
46843
47257
  init_interfaces();
46844
47258
  init_overlayStore();
46845
47259
  init_uiStore();
@@ -47058,10 +47472,18 @@ var init_session2 = __esm({
47058
47472
  help: "switch theme skin (fires skin.changed)",
47059
47473
  name: "skin",
47060
47474
  run: /* @__PURE__ */ __name((arg, ctx) => {
47061
- if (!arg) {
47062
- return ctx.gateway.rpc("config.get", { key: "skin" }).then(ctx.guarded((r) => ctx.transcript.sys(`skin: ${r.value || "default"}`)));
47475
+ const value = arg.trim().toLowerCase();
47476
+ if (!value || value === "list") {
47477
+ return ctx.transcript.panel("Skins", [
47478
+ { rows: BUILTIN_SKINS.map((skin) => [skin.name, skin.description]) },
47479
+ { text: "Use /skin <name> to apply a skin." }
47480
+ ]);
47481
+ }
47482
+ if (!isBuiltinSkinName(value)) {
47483
+ return ctx.transcript.sys(`usage: /skin <${BUILTIN_SKINS.map((s) => s.name).join("|")}>`);
47063
47484
  }
47064
- ctx.gateway.rpc("config.set", { key: "skin", value: arg }).then(ctx.guarded((r) => r.value && ctx.transcript.sys(`skin \u2192 ${r.value}`)));
47485
+ patchUiState({ theme: themeForBuiltinSkin(value) });
47486
+ ctx.gateway.rpc("config.set", { key: "skin", value }).then(ctx.guarded(() => ctx.transcript.sys(`skin \u2192 ${value}`)));
47065
47487
  }, "run")
47066
47488
  },
47067
47489
  {
@@ -47228,7 +47650,7 @@ var init_session2 = __esm({
47228
47650
  }
47229
47651
  const sections = [{ rows }];
47230
47652
  if (r.context_max) {
47231
- sections.push({ text: `Context: ${f(r.context_used)} / ${f(r.context_max)} (${r.context_percent}%)` });
47653
+ sections.push({ text: `Context: ${f(r.context_used)} / ${f(r.context_max)} (${formatContextPercent(r.context_percent)})` });
47232
47654
  }
47233
47655
  if (r.compressions) {
47234
47656
  sections.push({ text: `Compressions: ${r.compressions}` });
@@ -47352,6 +47774,14 @@ function buildSlashCompletions(text) {
47352
47774
  if (!text.startsWith("/")) {
47353
47775
  return { items: [], replace_from: 1 };
47354
47776
  }
47777
+ const skinArgMatch = /^\/skin\s+([^\s]*)$/i.exec(text);
47778
+ if (skinArgMatch) {
47779
+ const prefix = skinArgMatch[1]?.toLowerCase() ?? "";
47780
+ return {
47781
+ items: BUILTIN_SKINS.filter((skin) => skin.name.startsWith(prefix)).map((skin) => ({ display: skin.name, meta: skin.description, text: skin.name })),
47782
+ replace_from: text.length - (skinArgMatch[1]?.length ?? 0)
47783
+ };
47784
+ }
47355
47785
  const needle = text.slice(1).toLowerCase();
47356
47786
  const seen = /* @__PURE__ */ new Set();
47357
47787
  const items = [];
@@ -47370,6 +47800,19 @@ function buildSlashCompletions(text) {
47370
47800
  });
47371
47801
  }
47372
47802
  }
47803
+ if (needle.length > 0) {
47804
+ for (const skill of discoverSkills()) {
47805
+ if (!skill.name.toLowerCase().startsWith(needle)) continue;
47806
+ const command = `/${skill.name}`;
47807
+ if (seen.has(command)) continue;
47808
+ seen.add(command);
47809
+ items.push({
47810
+ display: command,
47811
+ meta: `skill \xB7 ${skill.meta.whenToUse || skill.meta.description || ""}`,
47812
+ text: command
47813
+ });
47814
+ }
47815
+ }
47373
47816
  items.sort((a, b) => {
47374
47817
  const aExact = a.text.slice(1).toLowerCase() === needle ? -1 : 0;
47375
47818
  const bExact = b.text.slice(1).toLowerCase() === needle ? -1 : 0;
@@ -47426,6 +47869,8 @@ var init_catalog = __esm({
47426
47869
  init_ops();
47427
47870
  init_session2();
47428
47871
  init_setup();
47872
+ init_registry2();
47873
+ init_theme();
47429
47874
  CATEGORIZED = [
47430
47875
  { category: "Core", commands: coreCommands },
47431
47876
  { category: "Session", commands: sessionCommands },
@@ -47446,7 +47891,7 @@ var init_catalog = __esm({
47446
47891
  // src/rpcHandlers.ts
47447
47892
  import { spawn as spawn7 } from "node:child_process";
47448
47893
  import { randomUUID as randomUUID13 } from "node:crypto";
47449
- import { existsSync as existsSync31, mkdirSync as mkdirSync17, readFileSync as readFileSync28, rmSync, statSync as statSync3, writeFileSync as writeFileSync19 } from "node:fs";
47894
+ import { existsSync as existsSync31, mkdirSync as mkdirSync17, readFileSync as readFileSync28, rmSync, statSync as statSync4, writeFileSync as writeFileSync19 } from "node:fs";
47450
47895
  import { homedir as homedir28 } from "node:os";
47451
47896
  import { basename as basename3, extname as extname4, join as join42 } from "node:path";
47452
47897
  function registerRpcHandlers(options) {
@@ -47628,6 +48073,12 @@ ${helpMessage}` : field.label;
47628
48073
  if (key === "mtime") {
47629
48074
  return { mtime: Date.now() };
47630
48075
  }
48076
+ if (key === "reasoning") {
48077
+ return {
48078
+ display: getUiState().showReasoning !== false ? "show" : "hide",
48079
+ value: agentLoop.reasoningEffort ?? "default"
48080
+ };
48081
+ }
47631
48082
  const ui = getUiState();
47632
48083
  return {
47633
48084
  config: {
@@ -47652,6 +48103,30 @@ ${helpMessage}` : field.label;
47652
48103
  if (typeof params?.key === "string") {
47653
48104
  const key = params.key;
47654
48105
  const rawValue = String(params.value ?? "").trim();
48106
+ if (key === "reasoning") {
48107
+ if (!rawValue) {
48108
+ return {
48109
+ display: getUiState().showReasoning !== false ? "show" : "hide",
48110
+ value: agentLoop.reasoningEffort ?? "default"
48111
+ };
48112
+ }
48113
+ if (rawValue === "hide" || rawValue === "show") {
48114
+ return { value: rawValue };
48115
+ }
48116
+ const parsed = parseReasoningEffortInput(rawValue);
48117
+ const modelInfo = agentLoop.getActiveModelMetadata();
48118
+ const nextEffort = parsed.clear ? void 0 : modelInfo ? resolveReasoningEffortForModel(modelInfo, parsed.effort) : parsed.effort;
48119
+ agentLoop.updateReasoningEffort(nextEffort);
48120
+ bus.emitEvent({
48121
+ payload: sessionInfoSnapshot(agentLoop, toolRegistry),
48122
+ session_id: agentLoop.sessionId,
48123
+ type: "session.info"
48124
+ });
48125
+ return {
48126
+ info: sessionInfoSnapshot(agentLoop, toolRegistry),
48127
+ value: nextEffort ?? "default"
48128
+ };
48129
+ }
47655
48130
  if (key === "model") {
47656
48131
  if (!rawValue) {
47657
48132
  return { value: agentLoop.model };
@@ -47665,6 +48140,15 @@ ${helpMessage}` : field.label;
47665
48140
  provider = flagsStripped[provIdx + 1];
47666
48141
  flagsStripped.splice(provIdx, 2);
47667
48142
  }
48143
+ let requestedReasoningEffort;
48144
+ let clearReasoningEffort = false;
48145
+ const effortIdx = flagsStripped.findIndex((t) => t === "--reasoning-effort" || t === "--effort");
48146
+ if (effortIdx >= 0) {
48147
+ const parsed = parseReasoningEffortInput(flagsStripped[effortIdx + 1] ?? "");
48148
+ requestedReasoningEffort = parsed.effort;
48149
+ clearReasoningEffort = parsed.clear;
48150
+ flagsStripped.splice(effortIdx, flagsStripped[effortIdx + 1] ? 2 : 1);
48151
+ }
47668
48152
  const model = flagsStripped.join(" ").trim();
47669
48153
  if (!model) {
47670
48154
  throw new Error("config.set model: missing model id");
@@ -47673,15 +48157,17 @@ ${helpMessage}` : field.label;
47673
48157
  throw new Error(`config.set model: unknown provider "${provider}"`);
47674
48158
  }
47675
48159
  const credential_warning = isProviderAuthenticated(provider) ? void 0 : `${PROVIDER_LABELS[provider]} is not connected \u2014 run /connect to add credentials`;
48160
+ const modelInfo = agentLoop.getModelMetadata(provider, model);
48161
+ const reasoningEffort = clearReasoningEffort ? void 0 : requestedReasoningEffort ? modelInfo ? resolveReasoningEffortForModel(modelInfo, requestedReasoningEffort) : requestedReasoningEffort : modelInfo ? resolveReasoningEffortForModel(modelInfo, agentLoop.reasoningEffort) : agentLoop.reasoningEffort;
47676
48162
  try {
47677
- agentLoop.switchModel(provider, model);
48163
+ agentLoop.switchModel(provider, model, reasoningEffort);
47678
48164
  } catch (err) {
47679
48165
  throw new Error(err instanceof Error ? err.message : String(err));
47680
48166
  }
47681
48167
  if (persistGlobal) {
47682
48168
  const next = {
47683
48169
  ...agentConfig,
47684
- llm: { ...agentConfig.llm, model, provider }
48170
+ llm: { ...agentConfig.llm, model, provider, model_reasoning_effort: reasoningEffort }
47685
48171
  };
47686
48172
  try {
47687
48173
  saveAgentConfig(next);
@@ -47698,6 +48184,7 @@ ${helpMessage}` : field.label;
47698
48184
  return {
47699
48185
  credential_warning,
47700
48186
  info: sessionInfoSnapshot(agentLoop, toolRegistry),
48187
+ reasoning_effort: reasoningEffort,
47701
48188
  value: model
47702
48189
  };
47703
48190
  }
@@ -47952,7 +48439,11 @@ ${helpMessage}` : field.label;
47952
48439
  bus.registerRpc("model.switch", (params) => {
47953
48440
  const provider = String(params.provider ?? agentLoop.providerName);
47954
48441
  const model = String(params.model ?? agentLoop.model);
47955
- agentLoop.switchModel(provider, model);
48442
+ const rawEffort = typeof params.reasoning_effort === "string" ? params.reasoning_effort : typeof params.effort === "string" ? params.effort : "";
48443
+ const parsedEffort = rawEffort ? parseReasoningEffortInput(rawEffort) : void 0;
48444
+ const modelInfo = agentLoop.getModelMetadata(provider, model);
48445
+ const reasoningEffort = parsedEffort?.clear ? void 0 : parsedEffort?.effort ? modelInfo ? resolveReasoningEffortForModel(modelInfo, parsedEffort.effort) : parsedEffort.effort : modelInfo ? resolveReasoningEffortForModel(modelInfo, agentLoop.reasoningEffort) : agentLoop.reasoningEffort;
48446
+ agentLoop.switchModel(provider, model, reasoningEffort);
47956
48447
  return {
47957
48448
  info: sessionInfoSnapshot(agentLoop, toolRegistry),
47958
48449
  ok: true
@@ -47964,12 +48455,20 @@ ${helpMessage}` : field.label;
47964
48455
  const liveResults = await Promise.all(
47965
48456
  PROVIDERS2.map((slug) => fetchLiveModels(slug, agentConfig, currentModel))
47966
48457
  );
48458
+ for (let i = 0; i < PROVIDERS2.length; i++) {
48459
+ const provider = PROVIDERS2[i];
48460
+ const models = liveResults[i]?.models;
48461
+ if (provider && models) {
48462
+ agentLoop.setProviderModelMetadata(provider, models);
48463
+ }
48464
+ }
47967
48465
  const providers = PROVIDERS2.map(
47968
48466
  (slug, i) => buildProviderOption(slug, currentProvider, currentModel, liveResults[i]?.models, liveResults[i]?.error)
47969
48467
  );
47970
48468
  return {
47971
48469
  model: currentModel,
47972
48470
  provider: currentProvider,
48471
+ reasoning_effort: agentLoop.reasoningEffort,
47973
48472
  providers
47974
48473
  };
47975
48474
  });
@@ -48085,8 +48584,7 @@ ${helpMessage}` : field.label;
48085
48584
  return { cancelled: true, slug: entry.slug };
48086
48585
  });
48087
48586
  bus.registerRpc("commands.catalog", () => {
48088
- const skillCount = toolRegistry.listTools().filter((t) => /^skill[:_-]/i.test(t.name)).length;
48089
- return buildCommandsCatalog({ skillCount });
48587
+ return buildCommandsCatalog({ skillCount: discoverSkills().length });
48090
48588
  });
48091
48589
  bus.registerRpc("completion.query", () => ({
48092
48590
  items: []
@@ -48094,14 +48592,9 @@ ${helpMessage}` : field.label;
48094
48592
  bus.registerRpc("complete.slash", (params) => buildSlashCompletions(String(params.text ?? "")));
48095
48593
  bus.registerRpc("complete.path", (params) => buildPathCompletions(String(params.word ?? "")));
48096
48594
  bus.registerRpc("skills.catalog", () => {
48097
- const tools = toolRegistry.listTools();
48098
- const skillTools = tools.filter((t) => /^skill[:_-]/i.test(t.name));
48099
48595
  return {
48100
- categories: ["skills"],
48101
- skills: skillTools.map((t) => ({
48102
- description: t.description,
48103
- name: t.name.replace(/^skill[:_-]/i, "")
48104
- }))
48596
+ categories: Object.keys(registrySkillsByCategory()).sort(),
48597
+ skills: listRegistrySkills()
48105
48598
  };
48106
48599
  });
48107
48600
  bus.registerRpc("tools.list", () => ({
@@ -48133,11 +48626,11 @@ ${helpMessage}` : field.label;
48133
48626
  return { output: lines.join("\n") };
48134
48627
  }
48135
48628
  if (head === "skills") {
48136
- const skills = toolRegistry.listTools().filter((t) => /^skill[:_-]/i.test(t.name)).map((t) => ({ description: t.description, name: t.name.replace(/^skill[:_-]/i, "") }));
48629
+ const skills = listRegistrySkills();
48137
48630
  if (!skills.length) {
48138
- return { output: "no skills registered" };
48631
+ return { output: "no skills available" };
48139
48632
  }
48140
- const lines = [`${skills.length} skills registered`, ""];
48633
+ const lines = [`${skills.length} skills available`, ""];
48141
48634
  for (const s of skills.sort((a, b) => a.name.localeCompare(b.name))) {
48142
48635
  const desc = s.description ? ` \u2014 ${s.description}` : "";
48143
48636
  lines.push(` ${s.name}${desc}`);
@@ -48413,7 +48906,7 @@ ${helpMessage}` : field.label;
48413
48906
  const meta = agentLoop.getSessionMeta();
48414
48907
  const tools = toolRegistry.listTools();
48415
48908
  const cost = `$${usage2.cost_usd.toFixed(4)}`;
48416
- const ctx = usage2.context_max > 0 ? `${usage2.context_percent}% (${usage2.context_used}/${usage2.context_max})` : "?";
48909
+ const ctx = usage2.context_max > 0 ? `${formatContextPercent(usage2.context_percent)} (${usage2.context_used}/${usage2.context_max})` : "?";
48417
48910
  const lines = [
48418
48911
  `Session: ${meta.id}`,
48419
48912
  `Title: ${meta.summary || "(untitled)"}`,
@@ -48543,7 +49036,7 @@ ${helpMessage}` : field.label;
48543
49036
  let fileSize = 0;
48544
49037
  try {
48545
49038
  buffer = readFileSync28(path3);
48546
- fileSize = statSync3(path3).size;
49039
+ fileSize = statSync4(path3).size;
48547
49040
  } catch (err) {
48548
49041
  throw new Error(`image.attach: ${err instanceof Error ? err.message : String(err)}`);
48549
49042
  }
@@ -48638,9 +49131,9 @@ ${helpMessage}` : field.label;
48638
49131
  bus.registerRpc("spawn_tree.load", () => ({ subagents: [] }));
48639
49132
  bus.registerRpc("skills.reload", async () => {
48640
49133
  try {
48641
- await mcpManager.connect();
48642
- const skillCount = toolRegistry.listTools().filter((t) => /^skill[:_-]/i.test(t.name)).length;
48643
- return { output: `Skills reloaded \xB7 ${skillCount} skills registered` };
49134
+ clearSkillsCache();
49135
+ const skillCount = discoverSkills().length;
49136
+ return { output: `Skills reloaded \xB7 ${skillCount} skills available` };
48644
49137
  } catch (err) {
48645
49138
  return { output: `skills reload failed: ${err instanceof Error ? err.message : String(err)}` };
48646
49139
  }
@@ -48648,18 +49141,28 @@ ${helpMessage}` : field.label;
48648
49141
  bus.registerRpc("skills.manage", (params) => {
48649
49142
  const action2 = String(params.action ?? "").toLowerCase();
48650
49143
  const query = String(params.query ?? "").trim().toLowerCase();
48651
- const skillTools = toolRegistry.listTools().filter((t) => /^skill[:_-]/i.test(t.name)).map((t) => ({ description: t.description, name: t.name.replace(/^skill[:_-]/i, "") }));
49144
+ const skills = listRegistrySkills();
48652
49145
  if (action2 === "list") {
48653
- return { skills: skillTools.length ? { installed: skillTools.map((s) => s.name) } : {} };
49146
+ return { skills: registrySkillsByCategory() };
48654
49147
  }
48655
49148
  if (action2 === "inspect") {
48656
- const hit = skillTools.find((s) => s.name.toLowerCase() === query);
49149
+ const hit = query ? findSkill(query) : null;
48657
49150
  if (!hit) return { info: null };
48658
- return { info: { category: "installed", description: hit.description, name: hit.name } };
49151
+ return {
49152
+ info: {
49153
+ category: hit.source,
49154
+ description: skillDescription(hit),
49155
+ entrypoint: hit.entrypoint,
49156
+ name: hit.name,
49157
+ path: hit.filePath,
49158
+ root_dir: hit.rootDir,
49159
+ source: hit.source
49160
+ }
49161
+ };
48659
49162
  }
48660
49163
  if (action2 === "search") {
48661
49164
  if (!query) return { results: [] };
48662
- const results = skillTools.filter((s) => s.name.toLowerCase().includes(query) || (s.description ?? "").toLowerCase().includes(query)).map((s) => ({ description: s.description, name: s.name }));
49165
+ const results = skills.filter((s) => s.name.toLowerCase().includes(query) || (s.description ?? "").toLowerCase().includes(query)).map((s) => ({ description: s.description, name: s.name }));
48663
49166
  return { results };
48664
49167
  }
48665
49168
  if (action2 === "install") {
@@ -48683,8 +49186,30 @@ ${helpMessage}` : field.label;
48683
49186
  unknown: names.length ? names : [`/tools ${action2 || "?"} is not yet wired in the new TUI \u2014 use the legacy --legacy-ui flag for full toolset toggles`]
48684
49187
  };
48685
49188
  });
48686
- bus.registerRpc("command.dispatch", (params) => {
49189
+ bus.registerRpc("command.dispatch", async (params) => {
48687
49190
  const name = String(params.name ?? "").trim();
49191
+ const arg = String(params.arg ?? "").trim();
49192
+ if (name) {
49193
+ const skill = findSkill(name);
49194
+ if (skill) {
49195
+ if (!toolRegistry.getTool("invoke_skill")) {
49196
+ return { output: "skill invocation is unavailable: invoke_skill tool is not registered", type: "exec" };
49197
+ }
49198
+ try {
49199
+ const result = await toolRegistry.execute("invoke_skill", { skill: skill.name, args: arg });
49200
+ return { output: formatSkillExecutionResult(result), type: "exec" };
49201
+ } catch (err) {
49202
+ return { output: `skill failed (${skill.name}): ${err instanceof Error ? err.message : String(err)}`, type: "exec" };
49203
+ }
49204
+ }
49205
+ const matches = findSkillMatches(name);
49206
+ if (matches.length > 1) {
49207
+ return {
49208
+ output: `ambiguous skill command /${name}: ${matches.slice(0, 8).map((s) => s.name).join(", ")}${matches.length > 8 ? ", \u2026" : ""}`,
49209
+ type: "exec"
49210
+ };
49211
+ }
49212
+ }
48688
49213
  return { output: name ? `unknown command: /${name}` : "(no command)", type: "exec" };
48689
49214
  });
48690
49215
  bus.registerRpc("delegation.status", () => ({ delegated: [], paused: false }));
@@ -48714,14 +49239,13 @@ ${helpMessage}` : field.label;
48714
49239
  }
48715
49240
  };
48716
49241
  }
48717
- var PROVIDERS2, PROVIDER_LABELS, PROVIDER_AUTH, isProviderAuthenticated, buildProviderOption, fetchLiveModels, BRIDGE_SOURCES, isBridgeSource, inferBridgeSource, bridgeLabels, bridgeEventLabels, stripBridgeGlyph, firstLogLine, bridgeUser, formatBridgeText, runProcess, contentToText, sessionMessageToMarkdown, usageSnapshot, sessionInfoSnapshot, MCP_TOOL_PREFIX, MAX_TOTAL_TOOLS, syncMcpTools;
49242
+ var PROVIDERS2, PROVIDER_LABELS, PROVIDER_AUTH, isProviderAuthenticated, buildProviderOption, fetchLiveModels, parseReasoningEffortInput, skillDescription, listRegistrySkills, registrySkillsByCategory, formatSkillExecutionResult, reasoningEffortsForModel, resolveReasoningEffortForModel, BRIDGE_SOURCES, isBridgeSource, inferBridgeSource, bridgeLabels, bridgeEventLabels, stripBridgeGlyph, firstLogLine, bridgeUser, formatBridgeText, runProcess, contentToText, sessionMessageToMarkdown, sessionInfoSnapshot, MCP_TOOL_PREFIX, MAX_TOTAL_TOOLS, syncMcpTools;
48718
49243
  var init_rpcHandlers = __esm({
48719
49244
  "src/rpcHandlers.ts"() {
48720
49245
  "use strict";
48721
49246
  init_connect();
48722
49247
  init_config();
48723
49248
  init_copilot_auth();
48724
- init_context_manager();
48725
49249
  init_eventBridge();
48726
49250
  init_fork();
48727
49251
  init_provider_auth();
@@ -48733,6 +49257,11 @@ var init_rpcHandlers = __esm({
48733
49257
  init_clipboard();
48734
49258
  init_models_static();
48735
49259
  init_providers();
49260
+ init_types();
49261
+ init_skills();
49262
+ init_registry2();
49263
+ init_usage();
49264
+ init_usageSnapshot();
48736
49265
  init_registry3();
48737
49266
  PROVIDERS2 = ["anthropic", "openai", "github-copilot"];
48738
49267
  PROVIDER_LABELS = {
@@ -48757,6 +49286,7 @@ var init_rpcHandlers = __esm({
48757
49286
  const auth = PROVIDER_AUTH[slug];
48758
49287
  const authenticated = isProviderAuthenticated(slug);
48759
49288
  let models = [];
49289
+ let modelOptions = [];
48760
49290
  let source = "static";
48761
49291
  if (authenticated) {
48762
49292
  if (liveModels && liveModels.length > 0) {
@@ -48765,10 +49295,12 @@ var init_rpcHandlers = __esm({
48765
49295
  if (seen.has(m.id)) continue;
48766
49296
  seen.add(m.id);
48767
49297
  models.push(m.id);
49298
+ modelOptions.push(m);
48768
49299
  }
48769
49300
  source = "live";
48770
49301
  } else {
48771
49302
  models = STATIC_MODELS[slug].map((m) => m.id);
49303
+ modelOptions = STATIC_MODELS[slug].map((m) => ({ id: m.id, name: m.name }));
48772
49304
  source = "static";
48773
49305
  }
48774
49306
  }
@@ -48784,6 +49316,7 @@ var init_rpcHandlers = __esm({
48784
49316
  is_current: slug === currentProvider,
48785
49317
  key_env: auth.key_env,
48786
49318
  models,
49319
+ model_options: modelOptions,
48787
49320
  name: PROVIDER_LABELS[slug],
48788
49321
  slug,
48789
49322
  source,
@@ -48819,6 +49352,54 @@ var init_rpcHandlers = __esm({
48819
49352
  return { error: err instanceof Error ? err.message : String(err), models: null };
48820
49353
  }
48821
49354
  }, "fetchLiveModels");
49355
+ parseReasoningEffortInput = /* @__PURE__ */ __name((value) => {
49356
+ const normalized = value.trim().toLowerCase();
49357
+ if (!normalized || normalized === "default" || normalized === "clear" || normalized === "auto") {
49358
+ return { clear: true };
49359
+ }
49360
+ if (!isReasoningEffort(normalized)) {
49361
+ throw new Error(`unknown reasoning effort: ${value}`);
49362
+ }
49363
+ return { clear: false, effort: normalized };
49364
+ }, "parseReasoningEffortInput");
49365
+ skillDescription = /* @__PURE__ */ __name((skill) => skill.meta.whenToUse || skill.meta.description || "", "skillDescription");
49366
+ listRegistrySkills = /* @__PURE__ */ __name(() => discoverSkills().map((skill) => ({
49367
+ category: skill.source,
49368
+ description: skillDescription(skill),
49369
+ entrypoint: skill.entrypoint,
49370
+ name: skill.name,
49371
+ path: skill.filePath,
49372
+ root_dir: skill.rootDir,
49373
+ source: skill.source
49374
+ })), "listRegistrySkills");
49375
+ registrySkillsByCategory = /* @__PURE__ */ __name(() => {
49376
+ const grouped = {};
49377
+ for (const skill of discoverSkills()) {
49378
+ const category = skill.source;
49379
+ grouped[category] ??= [];
49380
+ grouped[category].push(skill.name);
49381
+ }
49382
+ for (const skills of Object.values(grouped)) {
49383
+ skills.sort((a, b) => a.localeCompare(b));
49384
+ }
49385
+ return grouped;
49386
+ }, "registrySkillsByCategory");
49387
+ formatSkillExecutionResult = /* @__PURE__ */ __name((value) => {
49388
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
49389
+ return typeof value === "string" ? value : JSON.stringify(value ?? null);
49390
+ }
49391
+ const result = value;
49392
+ if (result.success === false) {
49393
+ return `skill failed${result.skill ? ` (${String(result.skill)})` : ""}: ${String(result.error ?? "unknown error")}`;
49394
+ }
49395
+ return typeof result.result === "string" ? result.result : JSON.stringify(result);
49396
+ }, "formatSkillExecutionResult");
49397
+ reasoningEffortsForModel = /* @__PURE__ */ __name((model) => model?.supportedReasoningEfforts?.map((option) => option.effort) ?? [], "reasoningEffortsForModel");
49398
+ resolveReasoningEffortForModel = /* @__PURE__ */ __name((model, requested) => {
49399
+ const supported = reasoningEffortsForModel(model);
49400
+ const selected = requested && supported.includes(requested) ? requested : void 0;
49401
+ return selected ?? model?.defaultReasoningEffort ?? defaultReasoningEffort(supported);
49402
+ }, "resolveReasoningEffortForModel");
48822
49403
  BRIDGE_SOURCES = ["telegram", "teams", "feishu", "wechat"];
48823
49404
  isBridgeSource = /* @__PURE__ */ __name((value) => typeof value === "string" && BRIDGE_SOURCES.includes(value), "isBridgeSource");
48824
49405
  inferBridgeSource = /* @__PURE__ */ __name((event) => {
@@ -48916,28 +49497,10 @@ ${raw}`;
48916
49497
  }
48917
49498
  return [];
48918
49499
  }, "sessionMessageToMarkdown");
48919
- usageSnapshot = /* @__PURE__ */ __name((agentLoop) => {
48920
- const cost = agentLoop.costTracker.getSessionCost();
48921
- const contextMax = getContextWindow(agentLoop.model);
48922
- const contextUsed = agentLoop.contextManager.lastPromptTokens;
48923
- return {
48924
- cache_read: cost.totalCacheReadTokens,
48925
- cache_write: cost.totalCacheCreationTokens,
48926
- calls: cost.turns,
48927
- context_max: contextMax,
48928
- context_percent: contextMax > 0 ? Math.round(contextUsed / contextMax * 100) : 0,
48929
- context_used: contextUsed,
48930
- cost_status: "estimated",
48931
- cost_usd: cost.totalCostUSD,
48932
- input: cost.totalInputTokens,
48933
- model: agentLoop.model,
48934
- output: cost.totalOutputTokens,
48935
- total: cost.totalInputTokens + cost.totalOutputTokens + cost.totalCacheReadTokens + cost.totalCacheCreationTokens
48936
- };
48937
- }, "usageSnapshot");
48938
49500
  sessionInfoSnapshot = /* @__PURE__ */ __name((agentLoop, toolRegistry) => ({
48939
49501
  cwd: process.cwd(),
48940
49502
  model: agentLoop.model,
49503
+ reasoning_effort: agentLoop.reasoningEffort,
48941
49504
  skills: {},
48942
49505
  system_prompt: "",
48943
49506
  tools: { [agentLoop.providerName]: toolRegistry.listTools().map((t) => t.name) },
@@ -49191,7 +49754,7 @@ var init_gatewayContext = __esm({
49191
49754
  });
49192
49755
 
49193
49756
  // src/domain/paths.ts
49194
- var shortCwd, fmtCwdBranch;
49757
+ var shortCwd, fullCwdBranch;
49195
49758
  var init_paths = __esm({
49196
49759
  "src/domain/paths.ts"() {
49197
49760
  "use strict";
@@ -49200,13 +49763,7 @@ var init_paths = __esm({
49200
49763
  const p = h && cwd.startsWith(h) ? `~${cwd.slice(h.length)}` : cwd;
49201
49764
  return p.length <= max ? p : `\u2026${p.slice(-(max - 1))}`;
49202
49765
  }, "shortCwd");
49203
- fmtCwdBranch = /* @__PURE__ */ __name((cwd, branch, max = 40) => {
49204
- if (!branch) {
49205
- return shortCwd(cwd, max);
49206
- }
49207
- const tag = ` (${branch.length > 16 ? `\u2026${branch.slice(-15)}` : branch})`;
49208
- return `${shortCwd(cwd, Math.max(8, max - tag.length))}${tag}`;
49209
- }, "fmtCwdBranch");
49766
+ fullCwdBranch = /* @__PURE__ */ __name((cwd, branch) => branch ? `${cwd} (${branch})` : cwd, "fullCwdBranch");
49210
49767
  }
49211
49768
  });
49212
49769
 
@@ -49574,9 +50131,9 @@ var init_useVirtualHistory = __esm({
49574
50131
  viewportHeight
49575
50132
  }) => itemCount > 0 && viewportHeight > 0 && !sticky && !liveTailActive, "shouldSetVirtualClamp");
49576
50133
  ensureVirtualItemHeight = /* @__PURE__ */ __name((heights, key, index, estimate, estimateHeight) => {
49577
- const cached8 = heights.get(key);
49578
- if (cached8 !== void 0) {
49579
- return Math.max(1, Math.floor(cached8));
50134
+ const cached7 = heights.get(key);
50135
+ if (cached7 !== void 0) {
50136
+ return Math.max(1, Math.floor(cached7));
49580
50137
  }
49581
50138
  const seeded = Math.max(1, Math.floor(estimateHeight?.(index, key) ?? estimate));
49582
50139
  heights.set(key, seeded);
@@ -51508,7 +52065,7 @@ function createSlashHandler(ctx) {
51508
52065
  }
51509
52066
  }
51510
52067
  }
51511
- gw.request("slash.exec", { command: cmd.slice(1), session_id: sid }).then((r) => {
52068
+ const renderSlashExec = /* @__PURE__ */ __name((r) => {
51512
52069
  if (stale()) {
51513
52070
  return;
51514
52071
  }
@@ -51517,33 +52074,41 @@ function createSlashHandler(ctx) {
51517
52074
  ${body}` : body;
51518
52075
  const long = text.length > 180 || text.split("\n").filter(Boolean).length > 2;
51519
52076
  long ? page(text, parsed.name[0].toUpperCase() + parsed.name.slice(1)) : sys(text);
51520
- }).catch(() => {
51521
- gw.request("command.dispatch", { arg: parsed.arg, name: parsed.name, session_id: sid }).then((raw) => {
51522
- if (stale()) {
51523
- return;
51524
- }
51525
- const d = asCommandDispatch(raw);
51526
- if (!d) {
51527
- return sys("error: invalid response: command.dispatch");
51528
- }
51529
- if (d.type === "exec" || d.type === "plugin") {
51530
- return sys(d.output || "(no output)");
51531
- }
51532
- if (d.type === "alias") {
51533
- return handler(`/${d.target}${argTail}`);
51534
- }
51535
- if (d.type === "skill") {
52077
+ }, "renderSlashExec");
52078
+ const runSlashExec = /* @__PURE__ */ __name(() => {
52079
+ gw.request("slash.exec", { command: cmd.slice(1), session_id: sid }).then(renderSlashExec).catch(guardedErr);
52080
+ }, "runSlashExec");
52081
+ const skillHint = findSkill(parsed.name);
52082
+ if (skillHint) {
52083
+ sys(`\u26A1 loading skill: ${skillHint.name}`);
52084
+ }
52085
+ gw.request("command.dispatch", { arg: parsed.arg, name: parsed.name, session_id: sid }).then((raw) => {
52086
+ if (stale()) {
52087
+ return;
52088
+ }
52089
+ const d = asCommandDispatch(raw);
52090
+ if (!d) {
52091
+ return runSlashExec();
52092
+ }
52093
+ if (d.type === "exec" || d.type === "plugin") {
52094
+ return sys(d.output || "(no output)");
52095
+ }
52096
+ if (d.type === "alias") {
52097
+ return handler(`/${d.target}${argTail}`);
52098
+ }
52099
+ if (d.type === "skill") {
52100
+ if (!skillHint || skillHint.name !== d.name) {
51536
52101
  sys(`\u26A1 loading skill: ${d.name}`);
51537
- return d.message?.trim() ? send(d.message) : sys(`/${parsed.name}: skill payload missing message`);
51538
52102
  }
51539
- if (d.type === "send") {
51540
- if (d.notice?.trim()) {
51541
- sys(d.notice);
51542
- }
51543
- return d.message?.trim() ? send(d.message) : sys(`/${parsed.name}: empty message`);
52103
+ return d.message?.trim() ? send(d.message) : sys(`/${parsed.name}: skill payload missing message`);
52104
+ }
52105
+ if (d.type === "send") {
52106
+ if (d.notice?.trim()) {
52107
+ sys(d.notice);
51544
52108
  }
51545
- }).catch(guardedErr);
51546
- });
52109
+ return d.message?.trim() ? send(d.message) : sys(`/${parsed.name}: empty message`);
52110
+ }
52111
+ }).catch(runSlashExec);
51547
52112
  return true;
51548
52113
  }, "handler");
51549
52114
  return handler;
@@ -51553,6 +52118,7 @@ var init_createSlashHandler = __esm({
51553
52118
  "use strict";
51554
52119
  init_slash();
51555
52120
  init_rpc();
52121
+ init_registry2();
51556
52122
  init_registry4();
51557
52123
  init_uiStore();
51558
52124
  __name(createSlashHandler, "createSlashHandler");
@@ -53170,6 +53736,13 @@ var init_paste = __esm({
53170
53736
 
53171
53737
  // src/app/useSubmission.ts
53172
53738
  import { useCallback as useCallback9, useEffect as useEffect11, useRef as useRef12 } from "react";
53739
+ function resolveCompletionSubmit(value, row, replaceFrom) {
53740
+ if (!row?.text) return null;
53741
+ const text = value.startsWith("/") && row.text.startsWith("/") ? row.text.slice(1) : row.text;
53742
+ const next = value.slice(0, replaceFrom) + text;
53743
+ if (next === value) return null;
53744
+ return { next, submit: value.startsWith("/") && row.text.startsWith("/") && row.meta?.startsWith("skill \xB7") === true };
53745
+ }
53173
53746
  function useSubmission(opts) {
53174
53747
  const {
53175
53748
  appendMessage,
@@ -53394,12 +53967,9 @@ function useSubmission(opts) {
53394
53967
  (value) => {
53395
53968
  if (composerState.completions.length) {
53396
53969
  const row = composerState.completions[composerState.compIdx];
53397
- if (row?.text) {
53398
- const text = value.startsWith("/") && row.text.startsWith("/") ? row.text.slice(1) : row.text;
53399
- const next = value.slice(0, composerState.compReplace) + text;
53400
- if (next !== value) {
53401
- return composerActions.setInput(next);
53402
- }
53970
+ const completion = resolveCompletionSubmit(value, row, composerState.compReplace);
53971
+ if (completion) {
53972
+ return completion.submit ? dispatchSubmission(completion.next) : composerActions.setInput(completion.next);
53403
53973
  }
53404
53974
  }
53405
53975
  if (!value.trim() && !composerState.inputBuf.length) {
@@ -53456,6 +54026,7 @@ var init_useSubmission = __esm({
53456
54026
  return (value) => value.replace(PASTE_SNIPPET_RE, (tok) => byLabel.get(tok)?.shift() ?? tok);
53457
54027
  }, "expandSnips");
53458
54028
  spliceMatches = /* @__PURE__ */ __name((text, matches, results) => matches.reduceRight((acc, m, i) => acc.slice(0, m.index) + results[i] + acc.slice(m.index + m[0].length), text), "spliceMatches");
54029
+ __name(resolveCompletionSubmit, "resolveCompletionSubmit");
53459
54030
  __name(useSubmission, "useSubmission");
53460
54031
  }
53461
54032
  });
@@ -54040,7 +54611,7 @@ function useMainApp(gw) {
54040
54611
  const gitBranch = useGitBranch(cwd);
54041
54612
  const appStatus = useMemo7(
54042
54613
  () => ({
54043
- cwdLabel: fmtCwdBranch(cwd, gitBranch),
54614
+ cwdLabel: fullCwdBranch(cwd, gitBranch),
54044
54615
  goodVibesTick,
54045
54616
  sessionStartedAt: ui.sid ? sessionStartedAt : null,
54046
54617
  showStickyPrompt: !!stickyPrompt,
@@ -55110,7 +55681,7 @@ import { useStore as useStore5 } from "@nanostores/react";
55110
55681
  import { useEffect as useEffect14, useMemo as useMemo10, useRef as useRef15, useState as useState15 } from "react";
55111
55682
  import unicodeSpinners from "unicode-animations";
55112
55683
  import { Fragment as Fragment4, jsx as jsx19, jsxs as jsxs10 } from "react/jsx-runtime";
55113
- function FaceTicker({ color, startedAt }) {
55684
+ function useTickerText(startedAt) {
55114
55685
  const ui = useStore5($uiState);
55115
55686
  const style = ui.indicatorStyle;
55116
55687
  const [tick, setTick] = useState15(() => Math.floor(Math.random() * 1e3));
@@ -55133,11 +55704,7 @@ function FaceTicker({ color, startedAt }) {
55133
55704
  const verb = VERBS[verbTick % VERBS.length] ?? "";
55134
55705
  const verbSegment = showVerb ? ` ${padVerb(verb)}` : "";
55135
55706
  const durationSegment = startedAt ? ` \xB7 ${fmtDuration(now2 - startedAt)}` : "";
55136
- return /* @__PURE__ */ jsxs10(Text9, { color, children: [
55137
- frame,
55138
- verbSegment,
55139
- durationSegment
55140
- ] });
55707
+ return `${frame}${verbSegment}${durationSegment}`;
55141
55708
  }
55142
55709
  function ctxBarColor(pct, t) {
55143
55710
  if (pct == null) {
@@ -55159,6 +55726,27 @@ function ctxBar(pct, w = 10) {
55159
55726
  const filled = Math.round(p / 100 * w);
55160
55727
  return "\u2588".repeat(filled) + "\u2591".repeat(w - filled);
55161
55728
  }
55729
+ function fitVisible(text, width) {
55730
+ if (width <= 0) return "";
55731
+ if (stringWidth(text) <= width) return text + " ".repeat(width - stringWidth(text));
55732
+ let out = "";
55733
+ for (const ch of Array.from(text)) {
55734
+ if (stringWidth(out + ch + "\u2026") > width) break;
55735
+ out += ch;
55736
+ }
55737
+ return out + "\u2026" + " ".repeat(Math.max(0, width - stringWidth(out + "\u2026")));
55738
+ }
55739
+ function truncateTailVisible(text, width) {
55740
+ return fitVisible(text, width);
55741
+ }
55742
+ function cwdBranchWidth(cols) {
55743
+ const desired = Math.max(18, Math.floor(cols * 0.38));
55744
+ const maxByCols = Math.max(6, cols - 24);
55745
+ return Math.max(6, Math.min(64, desired, maxByCols));
55746
+ }
55747
+ function buildActivityText(status, width) {
55748
+ return fitVisible(` ${status}`, width);
55749
+ }
55162
55750
  function SpawnHud({ t }) {
55163
55751
  const delegation = useStore5($delegationState);
55164
55752
  const subagents = useTurnSelector((state) => state.subagents);
@@ -55223,12 +55811,14 @@ function GoodVibesHeart({ tick, t }) {
55223
55811
  }
55224
55812
  return /* @__PURE__ */ jsx19(Text9, { color, children: "\u2665" });
55225
55813
  }
55814
+ function ActivityStatusLine({ busy, cols, status, statusColor: statusColor2, turnStartedAt }) {
55815
+ const tickerStatus = useTickerText(busy ? turnStartedAt : null);
55816
+ const text = buildActivityText(busy ? tickerStatus : status, cols);
55817
+ return /* @__PURE__ */ jsx19(Box_default, { height: 1, width: cols, children: /* @__PURE__ */ jsx19(Text9, { color: statusColor2, children: text }) });
55818
+ }
55226
55819
  function StatusRule({
55227
55820
  cwdLabel,
55228
55821
  cols,
55229
- busy,
55230
- status,
55231
- statusColor: statusColor2,
55232
55822
  model,
55233
55823
  modelFast,
55234
55824
  modelReasoningEffort,
@@ -55236,7 +55826,6 @@ function StatusRule({
55236
55826
  bgCount,
55237
55827
  sessionStartedAt,
55238
55828
  showCost,
55239
- turnStartedAt,
55240
55829
  voiceLabel,
55241
55830
  t
55242
55831
  }) {
@@ -55244,29 +55833,15 @@ function StatusRule({
55244
55833
  const barColor = ctxBarColor(pct, t);
55245
55834
  const ctxLabel = usage2.context_max ? `${fmtK(usage2.context_used ?? 0)}/${fmtK(usage2.context_max)}` : usage2.total > 0 ? `${fmtK(usage2.total)} tok` : "";
55246
55835
  const bar = usage2.context_max ? ctxBar(pct) : "";
55247
- const leftWidth = Math.max(12, cols - cwdLabel.length - 3);
55248
- return /* @__PURE__ */ jsxs10(Box_default, { height: 1, children: [
55249
- /* @__PURE__ */ jsx19(Box_default, { flexShrink: 1, width: leftWidth, children: /* @__PURE__ */ jsxs10(Text9, { color: t.color.border, wrap: "truncate-end", children: [
55250
- "\u2500 ",
55251
- busy ? /* @__PURE__ */ jsx19(FaceTicker, { color: statusColor2, startedAt: turnStartedAt }) : /* @__PURE__ */ jsx19(Text9, { color: statusColor2, children: status }),
55252
- /* @__PURE__ */ jsxs10(Text9, { color: t.color.systemNote, children: [
55253
- " \u2502 ",
55254
- modelLabel(model, modelReasoningEffort, modelFast)
55255
- ] }),
55256
- ctxLabel ? /* @__PURE__ */ jsxs10(Text9, { color: t.color.systemNote, children: [
55257
- " \u2502 ",
55258
- ctxLabel
55259
- ] }) : null,
55260
- bar ? /* @__PURE__ */ jsxs10(Text9, { color: t.color.systemNote, children: [
55261
- " \u2502 ",
55262
- /* @__PURE__ */ jsxs10(Text9, { color: barColor, children: [
55263
- "[",
55264
- bar,
55265
- "]"
55266
- ] }),
55267
- " ",
55268
- /* @__PURE__ */ jsx19(Text9, { color: barColor, children: pct != null ? `${pct}%` : "" })
55269
- ] }) : null,
55836
+ const cwdWidth = cwdBranchWidth(cols);
55837
+ const modelText = modelLabel(model, modelReasoningEffort, modelFast);
55838
+ return /* @__PURE__ */ jsxs10(Box_default, { height: 1, width: cols, children: [
55839
+ /* @__PURE__ */ jsx19(Text9, { color: t.color.border, children: "\u2500 " }),
55840
+ /* @__PURE__ */ jsx19(Box_default, { flexShrink: 0, width: cwdWidth, children: /* @__PURE__ */ jsx19(Text9, { color: t.color.systemNote, children: truncateTailVisible(cwdLabel, cwdWidth) }) }),
55841
+ /* @__PURE__ */ jsx19(Box_default, { flexShrink: 1, children: /* @__PURE__ */ jsxs10(Text9, { color: t.color.systemNote, wrap: "truncate-end", children: [
55842
+ modelText ? ` \u2502 ${modelText}` : "",
55843
+ ctxLabel ? ` \u2502 ${ctxLabel}` : "",
55844
+ bar ? /* @__PURE__ */ jsx19(Text9, { color: barColor, children: statusContextMeter(bar, pct) }) : null,
55270
55845
  sessionStartedAt ? /* @__PURE__ */ jsxs10(Text9, { color: t.color.systemNote, children: [
55271
55846
  " \u2502 ",
55272
55847
  /* @__PURE__ */ jsx19(SessionDuration, { startedAt: sessionStartedAt })
@@ -55298,9 +55873,7 @@ function StatusRule({
55298
55873
  " \u2502 $",
55299
55874
  usage2.cost_usd.toFixed(4)
55300
55875
  ] }) : null
55301
- ] }) }),
55302
- /* @__PURE__ */ jsx19(Text9, { color: t.color.border, children: " \u2500 " }),
55303
- /* @__PURE__ */ jsx19(Text9, { color: t.color.systemNote, children: cwdLabel })
55876
+ ] }) })
55304
55877
  ] });
55305
55878
  }
55306
55879
  function FloatBox({ children, color }) {
@@ -55375,7 +55948,7 @@ function TranscriptScrollbar({ focused = false, scrollRef, t }) {
55375
55948
  }
55376
55949
  );
55377
55950
  }
55378
- var FACE_TICK_MS, VERB_PAD_LEN, padVerb, EMOJI_FRAMES, ASCII_FRAMES, SPINNER_TICK_MS, renderIndicator, effortLabel, shortModelLabel, modelLabel;
55951
+ var FACE_TICK_MS, VERB_PAD_LEN, padVerb, EMOJI_FRAMES, ASCII_FRAMES, SPINNER_TICK_MS, renderIndicator, statusContextPercent, statusContextMeter, effortLabel, shortModelLabel, modelLabel;
55379
55952
  var init_appChrome = __esm({
55380
55953
  "src/components/appChrome.tsx"() {
55381
55954
  "use strict";
@@ -55386,6 +55959,7 @@ var init_appChrome = __esm({
55386
55959
  init_faces();
55387
55960
  init_verbs();
55388
55961
  init_messages();
55962
+ init_usage();
55389
55963
  init_viewport();
55390
55964
  init_subagentTree();
55391
55965
  init_text();
@@ -55418,9 +55992,15 @@ var init_appChrome = __esm({
55418
55992
  const frame = spinner.frames[tick % spinner.frames.length] ?? "\u280B";
55419
55993
  return { frame, intervalMs: Math.max(SPINNER_TICK_MS, spinner.interval), showVerb: false };
55420
55994
  }, "renderIndicator");
55421
- __name(FaceTicker, "FaceTicker");
55995
+ __name(useTickerText, "useTickerText");
55422
55996
  __name(ctxBarColor, "ctxBarColor");
55423
55997
  __name(ctxBar, "ctxBar");
55998
+ statusContextPercent = /* @__PURE__ */ __name((pct) => formatContextPercent(pct).padStart(5), "statusContextPercent");
55999
+ statusContextMeter = /* @__PURE__ */ __name((bar, pct) => ` \u2502 [${bar}] ${statusContextPercent(pct)}`, "statusContextMeter");
56000
+ __name(fitVisible, "fitVisible");
56001
+ __name(truncateTailVisible, "truncateTailVisible");
56002
+ __name(cwdBranchWidth, "cwdBranchWidth");
56003
+ __name(buildActivityText, "buildActivityText");
55424
56004
  __name(SpawnHud, "SpawnHud");
55425
56005
  __name(SessionDuration, "SessionDuration");
55426
56006
  effortLabel = /* @__PURE__ */ __name((effort) => {
@@ -55430,6 +56010,7 @@ var init_appChrome = __esm({
55430
56010
  shortModelLabel = /* @__PURE__ */ __name((model) => model.split("/").pop().replace(/^claude[-_]/, "").replace(/^anthropic[-_]/, "").replace(/[-_]/g, " ").replace(/\b(\d+)\s+(\d+)\b/g, "$1.$2").trim(), "shortModelLabel");
55431
56011
  modelLabel = /* @__PURE__ */ __name((model, effort, fast) => [shortModelLabel(model), effortLabel(effort), fast ? "fast" : ""].filter(Boolean).join(" "), "modelLabel");
55432
56012
  __name(GoodVibesHeart, "GoodVibesHeart");
56013
+ __name(ActivityStatusLine, "ActivityStatusLine");
55433
56014
  __name(StatusRule, "StatusRule");
55434
56015
  __name(FloatBox, "FloatBox");
55435
56016
  __name(StickyPromptTracker, "StickyPromptTracker");
@@ -56602,12 +57183,15 @@ import { Fragment as Fragment5, jsx as jsx25, jsxs as jsxs13 } from "react/jsx-r
56602
57183
  function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t }) {
56603
57184
  const [providers, setProviders] = useState19([]);
56604
57185
  const [currentModel, setCurrentModel] = useState19("");
57186
+ const [currentReasoningEffort, setCurrentReasoningEffort] = useState19();
56605
57187
  const [err, setErr] = useState19("");
56606
57188
  const [loading, setLoading] = useState19(true);
56607
57189
  const [persistGlobal, setPersistGlobal] = useState19(false);
56608
57190
  const [providerIdx, setProviderIdx] = useState19(0);
56609
57191
  const [modelIdx, setModelIdx] = useState19(0);
57192
+ const [effortIdx, setEffortIdx] = useState19(0);
56610
57193
  const [stage, setStage] = useState19("provider");
57194
+ const [selectedModelForEffort, setSelectedModelForEffort] = useState19(null);
56611
57195
  const [keyInput, setKeyInput] = useState19("");
56612
57196
  const [keySaving, setKeySaving] = useState19(false);
56613
57197
  const [keyError, setKeyError] = useState19("");
@@ -56633,6 +57217,7 @@ function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t })
56633
57217
  const next = r.providers ?? [];
56634
57218
  setProviders(next);
56635
57219
  setCurrentModel(String(r.model ?? ""));
57220
+ setCurrentReasoningEffort(r.reasoning_effort);
56636
57221
  setProviderIdx(
56637
57222
  Math.max(
56638
57223
  0,
@@ -56640,6 +57225,8 @@ function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t })
56640
57225
  )
56641
57226
  );
56642
57227
  setModelIdx(0);
57228
+ setEffortIdx(0);
57229
+ setSelectedModelForEffort(null);
56643
57230
  setStage("provider");
56644
57231
  setErr("");
56645
57232
  setLoading(false);
@@ -56650,8 +57237,34 @@ function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t })
56650
57237
  });
56651
57238
  }, [gw, sessionId]);
56652
57239
  const provider = providers[providerIdx];
56653
- const models = provider?.models ?? [];
57240
+ const modelOptions = useMemo12(() => {
57241
+ if (!provider) return [];
57242
+ if (provider.model_options?.length) return provider.model_options;
57243
+ return (provider.models ?? []).map((id) => ({ id }));
57244
+ }, [provider]);
57245
+ const models = useMemo12(() => modelOptions.map((model) => model.id), [modelOptions]);
57246
+ const effortChoices = useMemo12(() => modelReasoningEfforts(selectedModelForEffort), [selectedModelForEffort]);
57247
+ const providerHasReasoningStage = modelOptions.some((model) => modelReasoningEfforts(model).length > 1);
56654
57248
  const names = useMemo12(() => providerDisplayNames(providers), [providers]);
57249
+ const selectionScopeFlag = /* @__PURE__ */ __name(() => persistGlobal ? " --global" : ` ${TUI_SESSION_MODEL_FLAG}`, "selectionScopeFlag");
57250
+ const selectModel = /* @__PURE__ */ __name((model, effort) => {
57251
+ if (!provider) return;
57252
+ onSelect(`${model.id} --provider ${provider.slug} --reasoning-effort ${effort}${selectionScopeFlag()}`);
57253
+ }, "selectModel");
57254
+ const chooseModel = /* @__PURE__ */ __name((model) => {
57255
+ if (!model) {
57256
+ setStage("provider");
57257
+ return;
57258
+ }
57259
+ const efforts = modelReasoningEfforts(model);
57260
+ if (efforts.length > 1) {
57261
+ setSelectedModelForEffort(model);
57262
+ setEffortIdx(initialEffortIndex(efforts, currentReasoningEffort, defaultReasoningForModel(model, efforts)));
57263
+ setStage("effort");
57264
+ return;
57265
+ }
57266
+ selectModel(model, efforts[0] ?? "default");
57267
+ }, "chooseModel");
56655
57268
  useEffect17(() => {
56656
57269
  mountedRef.current = true;
56657
57270
  return () => {
@@ -56672,6 +57285,7 @@ function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t })
56672
57285
  }
56673
57286
  setProviders(r.providers ?? []);
56674
57287
  setCurrentModel(String(r.model ?? ""));
57288
+ setCurrentReasoningEffort(r.reasoning_effort);
56675
57289
  }).catch(() => {
56676
57290
  }), "refreshProviders");
56677
57291
  const cancelActiveOAuth = /* @__PURE__ */ __name(() => {
@@ -56767,9 +57381,17 @@ function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t })
56767
57381
  setOauthError("");
56768
57382
  return;
56769
57383
  }
57384
+ if (stage === "effort") {
57385
+ setStage("model");
57386
+ setEffortIdx(0);
57387
+ setSelectedModelForEffort(null);
57388
+ return;
57389
+ }
56770
57390
  if (stage === "model" || stage === "key" || stage === "disconnect") {
56771
57391
  setStage("provider");
56772
57392
  setModelIdx(0);
57393
+ setEffortIdx(0);
57394
+ setSelectedModelForEffort(null);
56773
57395
  setKeyInput("");
56774
57396
  setKeyError("");
56775
57397
  setKeySaving(false);
@@ -56867,7 +57489,7 @@ function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t })
56867
57489
  if (r?.disconnected) {
56868
57490
  setProviders(
56869
57491
  (prev) => prev.map(
56870
- (p) => p.slug === provider.slug ? { ...p, authenticated: false, models: [], total_models: 0, warning: p.key_env ? `paste ${p.key_env} to activate` : "run `hermes model` to configure" } : p
57492
+ (p) => p.slug === provider.slug ? { ...p, authenticated: false, models: [], model_options: [], total_models: 0, warning: p.key_env ? `paste ${p.key_env} to activate` : "run `hermes model` to configure" } : p
56871
57493
  )
56872
57494
  );
56873
57495
  }
@@ -56885,9 +57507,9 @@ function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t })
56885
57507
  }
56886
57508
  return;
56887
57509
  }
56888
- const count = stage === "provider" ? providers.length : models.length;
56889
- const sel = stage === "provider" ? providerIdx : modelIdx;
56890
- const setSel = stage === "provider" ? setProviderIdx : setModelIdx;
57510
+ const count = stage === "provider" ? providers.length : stage === "effort" ? effortChoices.length : models.length;
57511
+ const sel = stage === "provider" ? providerIdx : stage === "effort" ? effortIdx : modelIdx;
57512
+ const setSel = stage === "provider" ? setProviderIdx : stage === "effort" ? setEffortIdx : setModelIdx;
56891
57513
  if (key.upArrow && sel > 0) {
56892
57514
  setSel((v) => v - 1);
56893
57515
  return;
@@ -56922,12 +57544,16 @@ function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t })
56922
57544
  setModelIdx(0);
56923
57545
  return;
56924
57546
  }
56925
- const model = models[modelIdx];
56926
- if (provider && model) {
56927
- onSelect(`${model} --provider ${provider.slug}${persistGlobal ? " --global" : ` ${TUI_SESSION_MODEL_FLAG}`}`);
56928
- } else {
56929
- setStage("provider");
57547
+ if (stage === "effort") {
57548
+ const effort = effortChoices[effortIdx];
57549
+ if (selectedModelForEffort && effort) {
57550
+ selectModel(selectedModelForEffort, effort);
57551
+ } else {
57552
+ setStage("model");
57553
+ }
57554
+ return;
56930
57555
  }
57556
+ chooseModel(modelOptions[modelIdx]);
56931
57557
  return;
56932
57558
  }
56933
57559
  if (ch.toLowerCase() === "g") {
@@ -57090,7 +57716,11 @@ function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t })
57090
57716
  );
57091
57717
  const { items: items2, offset: offset2 } = windowItems(rows, providerIdx, VISIBLE2);
57092
57718
  return /* @__PURE__ */ jsxs13(Box_default, { flexDirection: "column", width, children: [
57093
- /* @__PURE__ */ jsx25(Text9, { bold: true, color: t.color.accent, wrap: "truncate-end", children: "Select provider (step 1/2)" }),
57719
+ /* @__PURE__ */ jsxs13(Text9, { bold: true, color: t.color.accent, wrap: "truncate-end", children: [
57720
+ "Select provider (step 1/",
57721
+ providerHasReasoningStage ? "3" : "2",
57722
+ ")"
57723
+ ] }),
57094
57724
  /* @__PURE__ */ jsx25(Text9, { color: t.color.muted, wrap: "truncate-end", children: "Full model IDs on the next step \xB7 Enter to continue" }),
57095
57725
  /* @__PURE__ */ jsxs13(Text9, { color: t.color.muted, wrap: "truncate-end", children: [
57096
57726
  "Current: ",
@@ -57129,9 +57759,61 @@ function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t })
57129
57759
  /* @__PURE__ */ jsx25(OverlayHint, { t, children: "\u2191/\u2193 select \xB7 Enter choose \xB7 d disconnect \xB7 Esc/q cancel" })
57130
57760
  ] });
57131
57761
  }
57762
+ if (stage === "effort") {
57763
+ const defaultEffort = defaultReasoningForModel(selectedModelForEffort, effortChoices);
57764
+ const warningEffort = effortChoices.includes("xhigh") ? "xhigh" : effortChoices.includes("high") ? "high" : void 0;
57765
+ const warningText = warningEffort ? `\u26A0 ${reasoningEffortLabel(warningEffort)} reasoning can quickly consume rate limits.` : " ";
57766
+ const { items: items2, offset: offset2 } = windowItems(effortChoices, effortIdx, VISIBLE2);
57767
+ return /* @__PURE__ */ jsxs13(Box_default, { flexDirection: "column", width, children: [
57768
+ /* @__PURE__ */ jsx25(Text9, { bold: true, color: t.color.accent, wrap: "truncate-end", children: "Select reasoning level (step 3/3)" }),
57769
+ /* @__PURE__ */ jsxs13(Text9, { color: t.color.muted, wrap: "truncate-end", children: [
57770
+ modelOptionID(selectedModelForEffort ?? void 0) || "(unknown model)",
57771
+ " \xB7 Esc back"
57772
+ ] }),
57773
+ /* @__PURE__ */ jsx25(Text9, { color: t.color.label, wrap: "truncate-end", children: warningText }),
57774
+ /* @__PURE__ */ jsx25(Text9, { color: t.color.muted, wrap: "truncate-end", children: offset2 > 0 ? ` \u2191 ${offset2} more` : " " }),
57775
+ Array.from({ length: VISIBLE2 }, (_, i) => {
57776
+ const effort = items2[i];
57777
+ const idx = offset2 + i;
57778
+ if (!effort) {
57779
+ return !effortChoices.length && i === 0 ? /* @__PURE__ */ jsx25(Text9, { color: t.color.muted, wrap: "truncate-end", children: "no reasoning levels listed for this model" }, "empty-effort") : /* @__PURE__ */ jsx25(Text9, { color: t.color.muted, wrap: "truncate-end", children: " " }, `pad-effort-${i}`);
57780
+ }
57781
+ const option = selectedModelForEffort?.supportedReasoningEfforts?.find((o) => o.effort === effort);
57782
+ const description = option?.description && option.description !== effort ? ` \xB7 ${option.description}` : "";
57783
+ const label = `${reasoningEffortLabel(effort)}${effort === defaultEffort ? " (default)" : ""}${description}`;
57784
+ return /* @__PURE__ */ jsxs13(
57785
+ Text9,
57786
+ {
57787
+ bold: effortIdx === idx,
57788
+ color: effortIdx === idx ? t.color.accent : t.color.muted,
57789
+ inverse: effortIdx === idx,
57790
+ wrap: "truncate-end",
57791
+ children: [
57792
+ effortIdx === idx ? "\u25B8 " : currentReasoningEffort === effort ? "* " : " ",
57793
+ idx + 1,
57794
+ ". ",
57795
+ label
57796
+ ]
57797
+ },
57798
+ `${modelOptionID(selectedModelForEffort ?? void 0)}:${effort}`
57799
+ );
57800
+ }),
57801
+ /* @__PURE__ */ jsx25(Text9, { color: t.color.muted, wrap: "truncate-end", children: offset2 + VISIBLE2 < effortChoices.length ? ` \u2193 ${effortChoices.length - offset2 - VISIBLE2} more` : " " }),
57802
+ /* @__PURE__ */ jsxs13(Text9, { color: t.color.muted, wrap: "truncate-end", children: [
57803
+ "persist: ",
57804
+ persistGlobal ? "global" : "session",
57805
+ " \xB7 g toggle"
57806
+ ] }),
57807
+ /* @__PURE__ */ jsx25(OverlayHint, { t, children: "\u2191/\u2193 select \xB7 Enter switch \xB7 Esc back \xB7 q close" })
57808
+ ] });
57809
+ }
57132
57810
  const { items, offset } = windowItems(models, modelIdx, VISIBLE2);
57133
57811
  return /* @__PURE__ */ jsxs13(Box_default, { flexDirection: "column", width, children: [
57134
- /* @__PURE__ */ jsx25(Text9, { bold: true, color: t.color.accent, wrap: "truncate-end", children: "Select model (step 2/2)" }),
57812
+ /* @__PURE__ */ jsxs13(Text9, { bold: true, color: t.color.accent, wrap: "truncate-end", children: [
57813
+ "Select model (step 2/",
57814
+ providerHasReasoningStage ? "3" : "2",
57815
+ ")"
57816
+ ] }),
57135
57817
  /* @__PURE__ */ jsxs13(Text9, { color: t.color.muted, wrap: "truncate-end", children: [
57136
57818
  names[providerIdx] || "(unknown provider)",
57137
57819
  " \xB7 Esc back"
@@ -57171,7 +57853,7 @@ function ModelPicker2({ gw, mode = "switch", onCancel, onSelect, sessionId, t })
57171
57853
  /* @__PURE__ */ jsx25(OverlayHint, { t, children: models.length ? "\u2191/\u2193 select \xB7 Enter switch \xB7 Esc back \xB7 q close" : "Enter/Esc back \xB7 q close" })
57172
57854
  ] });
57173
57855
  }
57174
- var VISIBLE2, MIN_WIDTH2, MAX_WIDTH2;
57856
+ var VISIBLE2, MIN_WIDTH2, MAX_WIDTH2, modelOptionID, modelReasoningEfforts, defaultReasoningForModel, initialEffortIndex;
57175
57857
  var init_modelPicker = __esm({
57176
57858
  "src/components/modelPicker.tsx"() {
57177
57859
  "use strict";
@@ -57179,10 +57861,28 @@ var init_modelPicker = __esm({
57179
57861
  init_providers2();
57180
57862
  init_slash();
57181
57863
  init_rpc();
57864
+ init_types();
57182
57865
  init_overlayControls();
57183
57866
  VISIBLE2 = 12;
57184
57867
  MIN_WIDTH2 = 40;
57185
57868
  MAX_WIDTH2 = 90;
57869
+ modelOptionID = /* @__PURE__ */ __name((model) => model?.id ?? "", "modelOptionID");
57870
+ modelReasoningEfforts = /* @__PURE__ */ __name((model) => {
57871
+ const efforts = model?.supportedReasoningEfforts?.map((option) => option.effort) ?? [];
57872
+ return orderedReasoningEfforts(efforts);
57873
+ }, "modelReasoningEfforts");
57874
+ defaultReasoningForModel = /* @__PURE__ */ __name((model, efforts = modelReasoningEfforts(model)) => {
57875
+ const configured = model?.defaultReasoningEffort;
57876
+ if (configured && efforts.includes(configured)) {
57877
+ return configured;
57878
+ }
57879
+ return efforts[0];
57880
+ }, "defaultReasoningForModel");
57881
+ initialEffortIndex = /* @__PURE__ */ __name((efforts, current, fallback) => {
57882
+ const selected = current && efforts.includes(current) ? current : fallback;
57883
+ const idx = selected ? efforts.indexOf(selected) : -1;
57884
+ return idx >= 0 ? idx : 0;
57885
+ }, "initialEffortIndex");
57186
57886
  __name(ModelPicker2, "ModelPicker");
57187
57887
  }
57188
57888
  });
@@ -59479,9 +60179,9 @@ function MdImpl({ compact, t, text }) {
59479
60179
  const nodes = useMemo14(() => {
59480
60180
  const bucket = cacheBucket(t);
59481
60181
  const cacheKey = `${compact ? "1" : "0"}|${text}`;
59482
- const cached8 = cacheGet(bucket, cacheKey);
59483
- if (cached8) {
59484
- return cached8;
60182
+ const cached7 = cacheGet(bucket, cacheKey);
60183
+ if (cached7) {
60184
+ return cached7;
59485
60185
  }
59486
60186
  const lines = ensureEmojiPresentation(text).split("\n");
59487
60187
  const nodes2 = [];
@@ -61517,26 +62217,34 @@ var init_appLayout = __esm({
61517
62217
  if (ui.statusBar !== at) {
61518
62218
  return null;
61519
62219
  }
61520
- return /* @__PURE__ */ jsx41(Box_default, { marginTop: at === "top" ? 1 : 0, children: /* @__PURE__ */ jsx41(
61521
- StatusRule,
61522
- {
61523
- bgCount: ui.bgTasks.size,
61524
- busy: ui.busy,
61525
- cols: composer.cols,
61526
- cwdLabel: status.cwdLabel,
61527
- model: ui.info?.model ?? "",
61528
- modelFast: ui.info?.fast || ui.info?.service_tier === "priority",
61529
- modelReasoningEffort: ui.info?.reasoning_effort,
61530
- sessionStartedAt: status.sessionStartedAt,
61531
- showCost: ui.showCost,
61532
- status: ui.status,
61533
- statusColor: status.statusColor,
61534
- t: ui.theme,
61535
- turnStartedAt: status.turnStartedAt,
61536
- usage: ui.usage,
61537
- voiceLabel: status.voiceLabel
61538
- }
61539
- ) });
62220
+ return /* @__PURE__ */ jsxs28(Box_default, { flexDirection: "column", marginTop: at === "top" ? 1 : 0, children: [
62221
+ /* @__PURE__ */ jsx41(
62222
+ ActivityStatusLine,
62223
+ {
62224
+ busy: ui.busy,
62225
+ cols: composer.cols,
62226
+ status: ui.status,
62227
+ statusColor: status.statusColor,
62228
+ turnStartedAt: status.turnStartedAt
62229
+ }
62230
+ ),
62231
+ /* @__PURE__ */ jsx41(
62232
+ StatusRule,
62233
+ {
62234
+ bgCount: ui.bgTasks.size,
62235
+ cols: composer.cols,
62236
+ cwdLabel: status.cwdLabel,
62237
+ model: ui.info?.model ?? "",
62238
+ modelFast: ui.info?.fast || ui.info?.service_tier === "priority",
62239
+ modelReasoningEffort: ui.info?.reasoning_effort,
62240
+ sessionStartedAt: status.sessionStartedAt,
62241
+ showCost: ui.showCost,
62242
+ t: ui.theme,
62243
+ usage: ui.usage,
62244
+ voiceLabel: status.voiceLabel
62245
+ }
62246
+ )
62247
+ ] });
61540
62248
  }, "StatusRulePane"));
61541
62249
  AppLayout = memo7(/* @__PURE__ */ __name(function AppLayout2({
61542
62250
  actions,