@charzhu/openjaw-agent 0.2.6 → 0.2.8

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
@@ -204,7 +204,7 @@ var init_config = __esm({
204
204
  "src/config.ts"() {
205
205
  "use strict";
206
206
  init_packageRoot();
207
- DEFAULT_COPILOT_OAUTH_CLIENT_ID = "Ov23li8tweQw6odWQebz";
207
+ DEFAULT_COPILOT_OAUTH_CLIENT_ID = "Iv1.b507a08c87ecfe98";
208
208
  DEFAULT_CONFIG = {
209
209
  llm: {
210
210
  provider: "anthropic",
@@ -1884,6 +1884,85 @@ var init_openai = __esm({
1884
1884
  }
1885
1885
  });
1886
1886
 
1887
+ // src/copilot-token.ts
1888
+ function normalizeCopilotEnterpriseDomain(value) {
1889
+ return value.replace(/^https?:\/\//, "").replace(/\/+$/, "");
1890
+ }
1891
+ function copilotApiBase(enterpriseUrl) {
1892
+ return enterpriseUrl ? `https://copilot-api.${normalizeCopilotEnterpriseDomain(enterpriseUrl)}` : "https://api.githubcopilot.com";
1893
+ }
1894
+ function copilotTokenEndpoint(enterpriseUrl) {
1895
+ if (!enterpriseUrl) return "https://api.github.com/copilot_internal/v2/token";
1896
+ const domain = normalizeCopilotEnterpriseDomain(enterpriseUrl);
1897
+ const apiDomain = domain.startsWith("api.") ? domain : `api.${domain}`;
1898
+ return `https://${apiDomain}/copilot_internal/v2/token`;
1899
+ }
1900
+ function decodeQueryComponent(value) {
1901
+ return decodeURIComponent(value.replace(/\+/g, "%20"));
1902
+ }
1903
+ function copilotApiBaseFromToken(token) {
1904
+ for (const part of token.split(";")) {
1905
+ const rawProxyEndpoint = part.startsWith("proxy-ep=") ? part.slice("proxy-ep=".length) : void 0;
1906
+ if (!rawProxyEndpoint) continue;
1907
+ const proxyEndpoint = decodeQueryComponent(rawProxyEndpoint).trim().replace(/\/+$/, "");
1908
+ if (!proxyEndpoint) return void 0;
1909
+ if (proxyEndpoint.startsWith("http://") || proxyEndpoint.startsWith("https://")) return proxyEndpoint;
1910
+ const host = proxyEndpoint.startsWith("proxy.") ? `api.${proxyEndpoint.slice("proxy.".length)}` : proxyEndpoint.startsWith("api.") ? proxyEndpoint : `api.${proxyEndpoint}`;
1911
+ return `https://${host}`;
1912
+ }
1913
+ return void 0;
1914
+ }
1915
+ async function exchangeGitHubTokenForCopilotToken(githubAccess, enterpriseUrl, signal) {
1916
+ const res = await fetch(copilotTokenEndpoint(enterpriseUrl), {
1917
+ headers: {
1918
+ "Accept": "application/json",
1919
+ "Authorization": `Bearer ${githubAccess}`,
1920
+ "User-Agent": COPILOT_USER_AGENT,
1921
+ "Editor-Version": COPILOT_EDITOR_VERSION,
1922
+ "Editor-Plugin-Version": COPILOT_EDITOR_PLUGIN_VERSION,
1923
+ "Copilot-Integration-Id": COPILOT_INTEGRATION_ID
1924
+ },
1925
+ signal
1926
+ });
1927
+ const text = await res.text();
1928
+ if (!res.ok) {
1929
+ let message = text;
1930
+ try {
1931
+ const parsed = JSON.parse(text);
1932
+ message = parsed.error_description || parsed.message || text;
1933
+ } catch {
1934
+ }
1935
+ throw new Error(`GitHub Copilot token request failed: ${res.status}${message ? ` ${message.slice(0, 160)}` : ""}`);
1936
+ }
1937
+ const body = JSON.parse(text);
1938
+ if (!body.token || typeof body.expires_at !== "number") {
1939
+ throw new Error("GitHub Copilot token response was missing token or expires_at.");
1940
+ }
1941
+ return {
1942
+ githubAccess,
1943
+ copilotAccess: body.token,
1944
+ copilotExpires: body.expires_at * 1e3,
1945
+ copilotApiBaseUrl: copilotApiBaseFromToken(body.token) ?? copilotApiBase(enterpriseUrl),
1946
+ enterpriseUrl
1947
+ };
1948
+ }
1949
+ var COPILOT_USER_AGENT, COPILOT_EDITOR_VERSION, COPILOT_EDITOR_PLUGIN_VERSION, COPILOT_INTEGRATION_ID;
1950
+ var init_copilot_token = __esm({
1951
+ "src/copilot-token.ts"() {
1952
+ "use strict";
1953
+ COPILOT_USER_AGENT = "GitHubCopilotChat/0.35.0";
1954
+ COPILOT_EDITOR_VERSION = "vscode/1.107.0";
1955
+ COPILOT_EDITOR_PLUGIN_VERSION = "copilot-chat/0.35.0";
1956
+ COPILOT_INTEGRATION_ID = "vscode-chat";
1957
+ __name(normalizeCopilotEnterpriseDomain, "normalizeCopilotEnterpriseDomain");
1958
+ __name(copilotApiBase, "copilotApiBase");
1959
+ __name(copilotTokenEndpoint, "copilotTokenEndpoint");
1960
+ __name(decodeQueryComponent, "decodeQueryComponent");
1961
+ __name(copilotApiBaseFromToken, "copilotApiBaseFromToken");
1962
+ __name(exchangeGitHubTokenForCopilotToken, "exchangeGitHubTokenForCopilotToken");
1963
+ }
1964
+ });
1965
+
1887
1966
  // src/provider-auth.ts
1888
1967
  import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
1889
1968
  import { join as join5 } from "node:path";
@@ -2024,12 +2103,6 @@ var init_types = __esm({
2024
2103
  });
2025
2104
 
2026
2105
  // src/providers/copilot.ts
2027
- function normalizeCopilotEnterpriseDomain(value) {
2028
- return value.replace(/^https?:\/\//, "").replace(/\/+$/, "");
2029
- }
2030
- function copilotApiBase(enterpriseUrl) {
2031
- return enterpriseUrl ? `https://copilot-api.${normalizeCopilotEnterpriseDomain(enterpriseUrl)}` : "https://api.githubcopilot.com";
2032
- }
2033
2106
  function shouldUseResponsesApi(modelID) {
2034
2107
  const normalized = modelID.toLowerCase();
2035
2108
  const match = /^gpt-(\d+)/.exec(normalized);
@@ -2079,6 +2152,12 @@ function toResponsesTool(tool) {
2079
2152
  parameters: normalizeObjectSchema2(tool.parameters)
2080
2153
  };
2081
2154
  }
2155
+ function endpointSupported(endpoints, target) {
2156
+ return endpoints?.some((endpoint) => endpoint.trim().toLowerCase() === target) ?? false;
2157
+ }
2158
+ function copilotSupportsResponsesWebSocket(modelInfo) {
2159
+ return endpointSupported(modelInfo?.supportedEndpoints, "ws:/responses");
2160
+ }
2082
2161
  function toAnthropicTool(tool) {
2083
2162
  assertToolName(tool.name);
2084
2163
  return {
@@ -2113,6 +2192,16 @@ function safeJsonParse(value) {
2113
2192
  return {};
2114
2193
  }
2115
2194
  }
2195
+ async function loadWebSocket() {
2196
+ const { createRequire } = await import("node:module");
2197
+ return createRequire(import.meta.url)("ws");
2198
+ }
2199
+ function responsesWebSocketUrl(baseUrl) {
2200
+ const url = new URL(`${baseUrl.replace(/\/+$/, "")}/responses`);
2201
+ if (url.protocol === "https:") url.protocol = "wss:";
2202
+ else if (url.protocol === "http:") url.protocol = "ws:";
2203
+ return url.toString();
2204
+ }
2116
2205
  function valueAtPath(value, path3) {
2117
2206
  let current = value;
2118
2207
  for (const key of path3) {
@@ -2121,6 +2210,24 @@ function valueAtPath(value, path3) {
2121
2210
  }
2122
2211
  return current;
2123
2212
  }
2213
+ function firstStringAtAny(value, paths2) {
2214
+ for (const path3 of paths2) {
2215
+ const candidate = valueAtPath(value, path3);
2216
+ if (typeof candidate === "string" && candidate.trim()) return candidate;
2217
+ }
2218
+ return void 0;
2219
+ }
2220
+ function valueContainsAny(value, needles) {
2221
+ if (typeof value === "string") {
2222
+ const normalized = value.toLowerCase();
2223
+ return needles.some((needle) => normalized.includes(needle));
2224
+ }
2225
+ if (Array.isArray(value)) return value.some((item) => valueContainsAny(item, needles));
2226
+ if (value && typeof value === "object") {
2227
+ return Object.values(value).some((item) => valueContainsAny(item, needles));
2228
+ }
2229
+ return false;
2230
+ }
2124
2231
  function firstPositiveIntegerAtAny(value, paths2) {
2125
2232
  for (const path3 of paths2) {
2126
2233
  const candidate = valueAtPath(value, path3);
@@ -2151,25 +2258,68 @@ function readCopilotOutputTokens(capabilities) {
2151
2258
  function readCopilotReasoningEfforts(capabilities) {
2152
2259
  return normalizeReasoningEfforts(valueAtPath(capabilities, ["supports", "reasoning_effort"]));
2153
2260
  }
2261
+ function copilotModelItems(body) {
2262
+ if (Array.isArray(body.data) && body.data.length > 0) return body.data;
2263
+ if (Array.isArray(body.models)) return body.models;
2264
+ return [];
2265
+ }
2266
+ function isDisabledPolicyValue(value) {
2267
+ return ["disabled", "blocked", "denied", "unavailable", "off"].includes(value.toLowerCase());
2268
+ }
2269
+ function policyAllows(policy) {
2270
+ if (policy === void 0 || policy === null) return true;
2271
+ if (typeof policy === "boolean") return policy;
2272
+ if (typeof policy === "string") return !isDisabledPolicyValue(policy);
2273
+ if (!policy || typeof policy !== "object" || Array.isArray(policy)) return true;
2274
+ const record = policy;
2275
+ if (record.enabled === false) return false;
2276
+ return !["state", "status", "result"].some((key) => {
2277
+ const value = record[key];
2278
+ return typeof value === "string" && isDisabledPolicyValue(value);
2279
+ });
2280
+ }
2281
+ function isChatCapable(model) {
2282
+ if (model.id?.toLowerCase().includes("embedding")) return false;
2283
+ if (!model.capabilities) return true;
2284
+ if (valueContainsAny(model.capabilities, ["embedding", "embeddings"])) return false;
2285
+ const kind = firstStringAtAny(model.capabilities, [["type"], ["kind"], ["family"]]);
2286
+ return !kind || !["embedding", "embeddings", "completion", "completions"].includes(kind.toLowerCase());
2287
+ }
2154
2288
  function buildReasoning(effort, modelInfo) {
2155
2289
  const supported = modelInfo?.supportedReasoningEfforts?.map((option) => option.effort) ?? [];
2156
2290
  const selected = effort && supported.includes(effort) ? effort : void 0;
2157
2291
  const effective = selected ?? modelInfo?.defaultReasoningEffort;
2158
2292
  return effective ? { effort: effective } : void 0;
2159
2293
  }
2294
+ function collectResponsesOutputItem(item, accumulator) {
2295
+ if (item.type === "message" && !accumulator.sawTextDelta) {
2296
+ for (const block of item.content ?? []) {
2297
+ if ((block.type === "output_text" || block.type === "text") && block.text) {
2298
+ accumulator.text = (accumulator.text ?? "") + block.text;
2299
+ }
2300
+ }
2301
+ } else if (item.type === "function_call" && item.name) {
2302
+ accumulator.toolCalls.push({
2303
+ id: item.call_id || item.id || `call_${accumulator.toolCalls.length}`,
2304
+ name: item.name,
2305
+ input: safeJsonParse(item.arguments)
2306
+ });
2307
+ }
2308
+ }
2160
2309
  function parseCopilotModels(body) {
2161
- const items = Array.isArray(body.data) ? body.data : [];
2310
+ const items = copilotModelItems(body);
2162
2311
  const models = [];
2163
2312
  for (const item of items) {
2164
- if (!item.id || item.model_picker_enabled === false || item.policy?.state === "disabled") continue;
2165
- const visionMedia = item.capabilities?.limits?.vision?.supported_media_types ?? [];
2313
+ if (!item.id || item.model_picker_enabled === false || !policyAllows(item.policy) || !isChatCapable(item)) continue;
2314
+ const visionMedia = valueAtPath(item.capabilities, ["limits", "vision", "supported_media_types"]);
2166
2315
  const supportedReasoning = readCopilotReasoningEfforts(item.capabilities);
2167
2316
  models.push({
2168
2317
  id: item.id,
2169
2318
  name: item.name,
2170
2319
  supportedEndpoints: item.supported_endpoints ?? [],
2171
- supportsVision: item.capabilities?.supports?.vision === true || visionMedia.some((media) => media.startsWith("image/")),
2172
- supportsToolCalls: item.capabilities?.supports?.tool_calls !== false,
2320
+ supportsResponsesWebSocket: copilotSupportsResponsesWebSocket({ supportedEndpoints: item.supported_endpoints ?? [] }),
2321
+ supportsVision: valueAtPath(item.capabilities, ["supports", "vision"]) === true || Array.isArray(visionMedia) && visionMedia.some((media) => typeof media === "string" && media.startsWith("image/")),
2322
+ supportsToolCalls: valueAtPath(item.capabilities, ["supports", "tool_calls"]) !== false,
2173
2323
  contextWindow: readCopilotContextWindow(item.capabilities),
2174
2324
  outputTokens: readCopilotOutputTokens(item.capabilities),
2175
2325
  supportedReasoningEfforts: supportedReasoning.map((effort) => ({ effort, description: effort })),
@@ -2178,32 +2328,46 @@ function parseCopilotModels(body) {
2178
2328
  }
2179
2329
  return models;
2180
2330
  }
2181
- var COPILOT_PROVIDER, USER_AGENT, OPENAI_TOOL_NAME2, CopilotProvider;
2331
+ var COPILOT_PROVIDER, USER_AGENT, OPENAI_TOOL_NAME2, COPILOT_TOKEN_EXPIRY_SAFETY_MARGIN_MS, RESPONSES_WEBSOCKET_CONNECT_TIMEOUT_MS, RESPONSES_WEBSOCKET_IDLE_TIMEOUT_MS, CopilotProvider;
2182
2332
  var init_copilot = __esm({
2183
2333
  "src/providers/copilot.ts"() {
2184
2334
  "use strict";
2335
+ init_copilot_token();
2185
2336
  init_provider_auth();
2186
2337
  init_types();
2338
+ init_copilot_token();
2187
2339
  COPILOT_PROVIDER = "github-copilot";
2188
2340
  USER_AGENT = "openjaw-agent/0.1.0";
2189
2341
  OPENAI_TOOL_NAME2 = /^[A-Za-z0-9_-]{1,64}$/;
2190
- __name(normalizeCopilotEnterpriseDomain, "normalizeCopilotEnterpriseDomain");
2191
- __name(copilotApiBase, "copilotApiBase");
2342
+ COPILOT_TOKEN_EXPIRY_SAFETY_MARGIN_MS = 6e4;
2343
+ RESPONSES_WEBSOCKET_CONNECT_TIMEOUT_MS = 1e4;
2344
+ RESPONSES_WEBSOCKET_IDLE_TIMEOUT_MS = 12e4;
2192
2345
  __name(shouldUseResponsesApi, "shouldUseResponsesApi");
2193
2346
  __name(normalizeObjectSchema2, "normalizeObjectSchema");
2194
2347
  __name(assertToolName, "assertToolName");
2195
2348
  __name(toChatTool, "toChatTool");
2196
2349
  __name(toResponsesTool, "toResponsesTool");
2350
+ __name(endpointSupported, "endpointSupported");
2351
+ __name(copilotSupportsResponsesWebSocket, "copilotSupportsResponsesWebSocket");
2197
2352
  __name(toAnthropicTool, "toAnthropicTool");
2198
2353
  __name(hasImageContent, "hasImageContent");
2199
2354
  __name(openAIUserContent, "openAIUserContent");
2200
2355
  __name(safeJsonParse, "safeJsonParse");
2356
+ __name(loadWebSocket, "loadWebSocket");
2357
+ __name(responsesWebSocketUrl, "responsesWebSocketUrl");
2201
2358
  __name(valueAtPath, "valueAtPath");
2359
+ __name(firstStringAtAny, "firstStringAtAny");
2360
+ __name(valueContainsAny, "valueContainsAny");
2202
2361
  __name(firstPositiveIntegerAtAny, "firstPositiveIntegerAtAny");
2203
2362
  __name(readCopilotContextWindow, "readCopilotContextWindow");
2204
2363
  __name(readCopilotOutputTokens, "readCopilotOutputTokens");
2205
2364
  __name(readCopilotReasoningEfforts, "readCopilotReasoningEfforts");
2365
+ __name(copilotModelItems, "copilotModelItems");
2366
+ __name(isDisabledPolicyValue, "isDisabledPolicyValue");
2367
+ __name(policyAllows, "policyAllows");
2368
+ __name(isChatCapable, "isChatCapable");
2206
2369
  __name(buildReasoning, "buildReasoning");
2370
+ __name(collectResponsesOutputItem, "collectResponsesOutputItem");
2207
2371
  __name(parseCopilotModels, "parseCopilotModels");
2208
2372
  CopilotProvider = class {
2209
2373
  static {
@@ -2212,6 +2376,7 @@ var init_copilot = __esm({
2212
2376
  name = COPILOT_PROVIDER;
2213
2377
  config;
2214
2378
  modelCache = null;
2379
+ responsesWebSocketDisabled = false;
2215
2380
  constructor(config) {
2216
2381
  this.config = config;
2217
2382
  }
@@ -2228,39 +2393,74 @@ var init_copilot = __esm({
2228
2393
  }
2229
2394
  async chat(options) {
2230
2395
  const modelInfo = await this.resolveModelInfo(this.config.model);
2231
- if (modelInfo?.supportedEndpoints.includes("/v1/messages") || this.config.model.toLowerCase().startsWith("claude-")) {
2232
- return this.chatAnthropicMessages(options);
2233
- }
2234
2396
  if (this.shouldRouteToResponses(modelInfo)) {
2235
2397
  return this.chatResponses(options, modelInfo);
2236
2398
  }
2399
+ if (this.shouldRouteToAnthropicMessages(modelInfo)) {
2400
+ return this.chatAnthropicMessages(options);
2401
+ }
2237
2402
  return this.chatCompletions(options);
2238
2403
  }
2239
2404
  shouldRouteToResponses(modelInfo) {
2240
2405
  if (this.config.use_responses_api === false) return false;
2241
2406
  if (modelInfo?.supportedEndpoints.length) {
2242
- return modelInfo.supportedEndpoints.includes("/responses");
2407
+ return endpointSupported(modelInfo.supportedEndpoints, "/responses");
2243
2408
  }
2244
2409
  return shouldUseResponsesApi(this.config.model);
2245
2410
  }
2411
+ shouldRouteToAnthropicMessages(modelInfo) {
2412
+ if (modelInfo?.supportedEndpoints.length) return false;
2413
+ return this.config.model.toLowerCase().startsWith("claude-");
2414
+ }
2415
+ copilotBaseUrl(credential) {
2416
+ if (this.config.base_url) return this.config.base_url.replace(/\/+$/, "");
2417
+ if (credential?.type === "oauth" && credential.copilotApiBaseUrl) return credential.copilotApiBaseUrl.replace(/\/+$/, "");
2418
+ const enterpriseUrl = this.config.copilot_enterprise_url || (credential?.type === "oauth" ? credential.enterpriseUrl : void 0);
2419
+ return copilotApiBase(enterpriseUrl);
2420
+ }
2421
+ async resolveCopilotAuth(signal) {
2422
+ if (this.config.api_key && this.config.api_key !== "proxy-token") {
2423
+ return { token: this.config.api_key, baseUrl: this.copilotBaseUrl() };
2424
+ }
2425
+ const credential = getProviderCredential(COPILOT_PROVIDER);
2426
+ if (credential?.type !== "oauth") {
2427
+ throw new Error("GitHub Copilot is not connected. Run /connect github-copilot first.");
2428
+ }
2429
+ if (credential.copilotAccess && credential.copilotExpires && credential.copilotExpires > Date.now() + COPILOT_TOKEN_EXPIRY_SAFETY_MARGIN_MS) {
2430
+ return { token: credential.copilotAccess, baseUrl: this.copilotBaseUrl(credential) };
2431
+ }
2432
+ const githubAccess = credential.githubAccess || credential.refresh || credential.access;
2433
+ const enterpriseUrl = this.config.copilot_enterprise_url || credential.enterpriseUrl;
2434
+ const exchanged = await exchangeGitHubTokenForCopilotToken(githubAccess, enterpriseUrl, signal);
2435
+ saveProviderCredential({
2436
+ ...credential,
2437
+ access: exchanged.copilotAccess,
2438
+ refresh: exchanged.githubAccess,
2439
+ expires: exchanged.copilotExpires,
2440
+ githubAccess: exchanged.githubAccess,
2441
+ copilotAccess: exchanged.copilotAccess,
2442
+ copilotExpires: exchanged.copilotExpires,
2443
+ copilotApiBaseUrl: exchanged.copilotApiBaseUrl,
2444
+ enterpriseUrl
2445
+ });
2446
+ return { token: exchanged.copilotAccess, baseUrl: this.config.base_url?.replace(/\/+$/, "") ?? exchanged.copilotApiBaseUrl };
2447
+ }
2246
2448
  resolveGitHubToken() {
2247
2449
  if (this.config.api_key && this.config.api_key !== "proxy-token") return this.config.api_key;
2248
2450
  const credential = getProviderCredential(COPILOT_PROVIDER);
2249
- if (credential?.type === "oauth") return credential.access || credential.refresh;
2451
+ if (credential?.type === "oauth") return credential.githubAccess || credential.refresh || credential.access;
2250
2452
  throw new Error("GitHub Copilot is not connected. Run /connect github-copilot first.");
2251
2453
  }
2252
- resolveCopilotToken() {
2253
- return this.resolveGitHubToken();
2454
+ async resolveCopilotToken(signal) {
2455
+ return (await this.resolveCopilotAuth(signal)).token;
2254
2456
  }
2255
- baseUrl() {
2256
- if (this.config.base_url) return this.config.base_url.replace(/\/+$/, "");
2257
- const credential = getProviderCredential(COPILOT_PROVIDER);
2258
- const enterpriseUrl = this.config.copilot_enterprise_url || (credential?.type === "oauth" ? credential.enterpriseUrl : void 0);
2259
- return copilotApiBase(enterpriseUrl);
2457
+ async baseUrl(signal) {
2458
+ return (await this.resolveCopilotAuth(signal)).baseUrl;
2260
2459
  }
2261
- async headers(options, extra) {
2460
+ async headers(options, extra, auth) {
2461
+ const resolvedAuth = auth ?? await this.resolveCopilotAuth(options.signal);
2262
2462
  const headers = {
2263
- "Authorization": `Bearer ${await this.resolveCopilotToken()}`,
2463
+ "Authorization": `Bearer ${resolvedAuth.token}`,
2264
2464
  "Content-Type": "application/json",
2265
2465
  "User-Agent": USER_AGENT,
2266
2466
  "Editor-Version": "OpenJaw/0.1.0",
@@ -2274,15 +2474,17 @@ var init_copilot = __esm({
2274
2474
  return headers;
2275
2475
  }
2276
2476
  async fetchModelInfo() {
2277
- const res = await fetch(`${this.baseUrl()}/models`, {
2477
+ const signal = AbortSignal.timeout(1e4);
2478
+ const auth = await this.resolveCopilotAuth(signal);
2479
+ const res = await fetch(`${auth.baseUrl}/models`, {
2278
2480
  headers: {
2279
- "Authorization": `Bearer ${await this.resolveCopilotToken()}`,
2481
+ "Authorization": `Bearer ${auth.token}`,
2280
2482
  "User-Agent": USER_AGENT,
2281
2483
  "Editor-Version": "OpenJaw/0.1.0",
2282
2484
  "Editor-Plugin-Version": "OpenJaw/0.1.0",
2283
2485
  "Copilot-Integration-Id": "vscode-chat"
2284
2486
  },
2285
- signal: AbortSignal.timeout(1e4)
2487
+ signal
2286
2488
  });
2287
2489
  if (!res.ok) {
2288
2490
  const detail = await res.text().catch(() => "");
@@ -2304,6 +2506,7 @@ var init_copilot = __esm({
2304
2506
  id: modelID,
2305
2507
  name: modelID,
2306
2508
  supportedEndpoints: ["/v1/messages"],
2509
+ supportsResponsesWebSocket: false,
2307
2510
  supportsVision: true,
2308
2511
  supportsToolCalls: true
2309
2512
  };
@@ -2311,6 +2514,102 @@ var init_copilot = __esm({
2311
2514
  return null;
2312
2515
  }
2313
2516
  }
2517
+ shouldUseResponsesWebSocket(modelInfo) {
2518
+ return !this.responsesWebSocketDisabled && copilotSupportsResponsesWebSocket(modelInfo);
2519
+ }
2520
+ disableResponsesWebSocket() {
2521
+ this.responsesWebSocketDisabled = true;
2522
+ }
2523
+ buildResponsesRequestBody(options, modelInfo) {
2524
+ const reasoning = buildReasoning(options.reasoningEffort, modelInfo);
2525
+ return {
2526
+ model: this.config.model,
2527
+ input: this.buildResponsesInput(options),
2528
+ instructions: Array.isArray(options.systemPrompt) ? options.systemPrompt.map((block) => block.text).join("\n\n") : options.systemPrompt,
2529
+ tools: options.tools.length > 0 ? options.tools.map(toResponsesTool) : void 0,
2530
+ ...reasoning && { reasoning, include: ["reasoning.encrypted_content"] }
2531
+ };
2532
+ }
2533
+ buildResponsesWebSocketRequest(requestBody) {
2534
+ return {
2535
+ type: "response.create",
2536
+ ...requestBody,
2537
+ tools: requestBody.tools ?? [],
2538
+ tool_choice: requestBody.tools?.length ? "auto" : "none",
2539
+ parallel_tool_calls: true,
2540
+ store: false,
2541
+ stream: true,
2542
+ include: requestBody.include ?? []
2543
+ };
2544
+ }
2545
+ async chatResponsesWebSocket(options, requestBody) {
2546
+ const auth = await this.resolveCopilotAuth(options.signal);
2547
+ const headers = await this.headers(options, void 0, auth);
2548
+ const WebSocket2 = await loadWebSocket();
2549
+ const ws = new WebSocket2(responsesWebSocketUrl(auth.baseUrl), {
2550
+ headers,
2551
+ handshakeTimeout: RESPONSES_WEBSOCKET_CONNECT_TIMEOUT_MS
2552
+ });
2553
+ const request = JSON.stringify(this.buildResponsesWebSocketRequest(requestBody));
2554
+ return new Promise((resolve5, reject) => {
2555
+ const accumulator = { sawTextDelta: false, text: null, toolCalls: [] };
2556
+ let settled = false;
2557
+ const timeout = setTimeout(() => {
2558
+ if (settled) return;
2559
+ settled = true;
2560
+ ws.close();
2561
+ reject(new Error("Responses WebSocket idle timeout"));
2562
+ }, RESPONSES_WEBSOCKET_IDLE_TIMEOUT_MS);
2563
+ const finish = /* @__PURE__ */ __name((value) => {
2564
+ if (settled) return;
2565
+ settled = true;
2566
+ clearTimeout(timeout);
2567
+ ws.close();
2568
+ resolve5(value);
2569
+ }, "finish");
2570
+ const fail = /* @__PURE__ */ __name((error) => {
2571
+ if (settled) return;
2572
+ settled = true;
2573
+ clearTimeout(timeout);
2574
+ ws.close();
2575
+ reject(error instanceof Error ? error : new Error(String(error)));
2576
+ }, "fail");
2577
+ options.signal?.addEventListener("abort", () => fail(options.signal?.reason ?? new DOMException("Aborted", "AbortError")), { once: true });
2578
+ ws.addEventListener("open", () => ws.send(request));
2579
+ ws.addEventListener("error", (event) => fail(event.error ?? event.message ?? "Responses WebSocket error"));
2580
+ ws.addEventListener("close", (event) => {
2581
+ if (!settled) fail(new Error(`Responses WebSocket closed before completion (${event.code ?? "unknown"})`));
2582
+ });
2583
+ ws.addEventListener("message", (event) => {
2584
+ try {
2585
+ const text = typeof event.data === "string" ? event.data : Buffer.isBuffer(event.data) ? event.data.toString("utf8") : String(event.data);
2586
+ const parsed = JSON.parse(text);
2587
+ const type = String(parsed.type ?? "");
2588
+ if (type === "response.output_text.delta" && typeof parsed.delta === "string") {
2589
+ accumulator.sawTextDelta = true;
2590
+ accumulator.text = (accumulator.text ?? "") + parsed.delta;
2591
+ } else if (type === "response.output_item.done" && parsed.item && typeof parsed.item === "object") {
2592
+ collectResponsesOutputItem(parsed.item, accumulator);
2593
+ } else if (type === "response.failed") {
2594
+ fail(new Error(JSON.stringify(parsed.response ?? parsed)));
2595
+ } else if (type === "response.incomplete") {
2596
+ fail(new Error("Responses WebSocket returned incomplete response"));
2597
+ } else if (type === "response.completed") {
2598
+ const response = parsed.response && typeof parsed.response === "object" ? parsed.response : {};
2599
+ const usage2 = response.usage && typeof response.usage === "object" ? response.usage : void 0;
2600
+ finish({
2601
+ text: accumulator.text,
2602
+ toolCalls: accumulator.toolCalls,
2603
+ stopReason: accumulator.toolCalls.length > 0 ? "tool_use" : "end",
2604
+ usage: this.usage(usage2?.input_tokens, usage2?.output_tokens)
2605
+ });
2606
+ }
2607
+ } catch (error) {
2608
+ fail(error);
2609
+ }
2610
+ });
2611
+ });
2612
+ }
2314
2613
  buildChatMessages(options) {
2315
2614
  const messages = [
2316
2615
  {
@@ -2362,7 +2661,7 @@ var init_copilot = __esm({
2362
2661
  tool_choice: options.tools.length > 0 ? "auto" : void 0,
2363
2662
  temperature: this.config.temperature
2364
2663
  };
2365
- const res = await fetch(`${this.baseUrl()}/chat/completions`, {
2664
+ const res = await fetch(`${await this.baseUrl(options.signal)}/chat/completions`, {
2366
2665
  method: "POST",
2367
2666
  headers: await this.headers(options),
2368
2667
  body: JSON.stringify(requestBody),
@@ -2406,15 +2705,16 @@ var init_copilot = __esm({
2406
2705
  return input;
2407
2706
  }
2408
2707
  async chatResponses(options, modelInfo) {
2409
- const reasoning = buildReasoning(options.reasoningEffort, modelInfo);
2410
- const requestBody = {
2411
- model: this.config.model,
2412
- input: this.buildResponsesInput(options),
2413
- instructions: Array.isArray(options.systemPrompt) ? options.systemPrompt.map((block) => block.text).join("\n\n") : options.systemPrompt,
2414
- tools: options.tools.length > 0 ? options.tools.map(toResponsesTool) : void 0,
2415
- ...reasoning && { reasoning, include: ["reasoning.encrypted_content"] }
2416
- };
2417
- const res = await fetch(`${this.baseUrl()}/responses`, {
2708
+ const requestBody = this.buildResponsesRequestBody(options, modelInfo);
2709
+ if (this.shouldUseResponsesWebSocket(modelInfo)) {
2710
+ try {
2711
+ return await this.chatResponsesWebSocket(options, requestBody);
2712
+ } catch (err) {
2713
+ if (options.signal?.aborted) throw err;
2714
+ this.disableResponsesWebSocket();
2715
+ }
2716
+ }
2717
+ const res = await fetch(`${await this.baseUrl(options.signal)}/responses`, {
2418
2718
  method: "POST",
2419
2719
  headers: await this.headers(options),
2420
2720
  body: JSON.stringify(requestBody),
@@ -2493,7 +2793,7 @@ var init_copilot = __esm({
2493
2793
  messages: this.buildAnthropicMessages(options),
2494
2794
  tools: options.tools.length > 0 ? options.tools.map(toAnthropicTool) : void 0
2495
2795
  };
2496
- const res = await fetch(`${this.baseUrl()}/v1/messages`, {
2796
+ const res = await fetch(`${await this.baseUrl(options.signal)}/v1/messages`, {
2497
2797
  method: "POST",
2498
2798
  headers: await this.headers(options, {
2499
2799
  "anthropic-version": "2023-06-01",
@@ -2803,6 +3103,34 @@ var init_cost_tracker = __esm({
2803
3103
  }
2804
3104
  });
2805
3105
 
3106
+ // src/domain/usage.ts
3107
+ var ZERO, contextPercent, formatContextPercent;
3108
+ var init_usage = __esm({
3109
+ "src/domain/usage.ts"() {
3110
+ "use strict";
3111
+ ZERO = { calls: 0, input: 0, output: 0, total: 0 };
3112
+ contextPercent = /* @__PURE__ */ __name((used, max) => {
3113
+ if (!Number.isFinite(used) || !Number.isFinite(max) || used <= 0 || max <= 0) {
3114
+ return 0;
3115
+ }
3116
+ const raw = used / max * 100;
3117
+ return raw < 10 ? Math.round(raw * 10) / 10 : Math.round(raw);
3118
+ }, "contextPercent");
3119
+ formatContextPercent = /* @__PURE__ */ __name((pct) => {
3120
+ if (pct == null || !Number.isFinite(pct) || pct <= 0) {
3121
+ return "0%";
3122
+ }
3123
+ if (pct < 0.1) {
3124
+ return "<0.1%";
3125
+ }
3126
+ if (pct < 10) {
3127
+ return `${pct.toFixed(1).replace(/\.0$/, "")}%`;
3128
+ }
3129
+ return `${Math.round(pct)}%`;
3130
+ }, "formatContextPercent");
3131
+ }
3132
+ });
3133
+
2806
3134
  // src/context-manager.ts
2807
3135
  function contextKey(provider, model) {
2808
3136
  return `${provider}\0${model}`;
@@ -2824,6 +3152,7 @@ var CONTEXT_WINDOWS, DEFAULT_CONTEXT_WINDOW, ContextManager;
2824
3152
  var init_context_manager = __esm({
2825
3153
  "src/context-manager.ts"() {
2826
3154
  "use strict";
3155
+ init_usage();
2827
3156
  CONTEXT_WINDOWS = {
2828
3157
  "claude-sonnet-4": 2e5,
2829
3158
  "claude-sonnet-4.5": 2e5,
@@ -2877,7 +3206,7 @@ var init_context_manager = __esm({
2877
3206
  const liveContextWindow = this.activeProvider ? this.liveModelContextWindows.get(contextKey(this.activeProvider, model)) : void 0;
2878
3207
  return getContextWindow(model, { contextWindow: liveContextWindow });
2879
3208
  }
2880
- /** Update from actual API response usage */
3209
+ /** Update latest effective context footprint from actual API response usage. */
2881
3210
  updateFromUsage(usage2) {
2882
3211
  this.lastTotalTokens = usage2.inputTokens + usage2.outputTokens + usage2.cacheReadTokens + usage2.cacheCreationTokens;
2883
3212
  }
@@ -2897,7 +3226,7 @@ var init_context_manager = __esm({
2897
3226
  /** Get context usage as percentage */
2898
3227
  getUsagePercent(model) {
2899
3228
  const limit = this.getContextWindow(model);
2900
- return limit > 0 ? Math.round(this.lastTotalTokens / limit * 100) : 0;
3229
+ return contextPercent(this.lastTotalTokens, limit);
2901
3230
  }
2902
3231
  /** Format context display string */
2903
3232
  formatContext(model) {
@@ -3796,13 +4125,18 @@ async function completeCopilotDeviceFlow(flow, signal) {
3796
4125
  }
3797
4126
  const body = await res.json();
3798
4127
  if (body.access_token) {
4128
+ const copilotToken = await exchangeGitHubTokenForCopilotToken(body.access_token, flow.enterpriseUrl, signal);
3799
4129
  const now2 = (/* @__PURE__ */ new Date()).toISOString();
3800
4130
  saveProviderCredential({
3801
4131
  type: "oauth",
3802
4132
  provider: "github-copilot",
3803
- access: body.access_token,
3804
- refresh: body.access_token,
3805
- expires: 0,
4133
+ access: copilotToken.copilotAccess,
4134
+ refresh: copilotToken.githubAccess,
4135
+ expires: copilotToken.copilotExpires,
4136
+ githubAccess: copilotToken.githubAccess,
4137
+ copilotAccess: copilotToken.copilotAccess,
4138
+ copilotExpires: copilotToken.copilotExpires,
4139
+ copilotApiBaseUrl: copilotToken.copilotApiBaseUrl,
3806
4140
  enterpriseUrl: flow.enterpriseUrl,
3807
4141
  createdAt: now2,
3808
4142
  updatedAt: now2
@@ -3810,7 +4144,7 @@ async function completeCopilotDeviceFlow(flow, signal) {
3810
4144
  return {
3811
4145
  provider: "github-copilot",
3812
4146
  enterpriseUrl: flow.enterpriseUrl,
3813
- baseUrl: copilotApiBase(flow.enterpriseUrl)
4147
+ baseUrl: copilotToken.copilotApiBaseUrl
3814
4148
  };
3815
4149
  }
3816
4150
  if (body.error === "authorization_pending") continue;
@@ -3826,7 +4160,7 @@ var init_copilot_auth = __esm({
3826
4160
  "src/copilot-auth.ts"() {
3827
4161
  "use strict";
3828
4162
  init_provider_auth();
3829
- init_copilot();
4163
+ init_copilot_token();
3830
4164
  OAUTH_POLLING_SAFETY_MARGIN_MS = 3e3;
3831
4165
  __name(isOAuthAbortError, "isOAuthAbortError");
3832
4166
  __name(oauthDomain, "oauthDomain");
@@ -8977,9 +9311,9 @@ var init_outlook_graph = __esm({
8977
9311
  const wellKnown = WELL_KNOWN_FOLDERS[lower];
8978
9312
  if (wellKnown)
8979
9313
  return wellKnown;
8980
- const cached8 = this.folderIdCache.get(lower);
8981
- if (cached8)
8982
- return cached8;
9314
+ const cached7 = this.folderIdCache.get(lower);
9315
+ if (cached7)
9316
+ return cached7;
8983
9317
  try {
8984
9318
  const result = await this.graphGet(`/me/mailFolders?$filter=displayName eq '${folderName.replace(/'/g, "''")}'&$top=1`);
8985
9319
  if (result.value.length > 0) {
@@ -9337,9 +9671,9 @@ var init_db = __esm({
9337
9671
  import { createHash as createHash2 } from "node:crypto";
9338
9672
  function encodeAtom(word, dim2 = HRR_DIM) {
9339
9673
  const cacheKey = `${word}:${dim2}`;
9340
- const cached8 = atomCache.get(cacheKey);
9341
- if (cached8)
9342
- return cached8;
9674
+ const cached7 = atomCache.get(cacheKey);
9675
+ if (cached7)
9676
+ return cached7;
9343
9677
  const phases = new Float64Array(dim2);
9344
9678
  const bytesNeeded = dim2 * 4;
9345
9679
  const chunks = [];
@@ -12555,9 +12889,9 @@ ${loopContent}` : loopContent;
12555
12889
  */
12556
12890
  async findChannelByName(searchTerm) {
12557
12891
  const searchLower = searchTerm.toLowerCase();
12558
- const cached8 = this.channelIdCache.get(searchLower);
12559
- if (cached8) {
12560
- return { ...cached8, displayName: searchTerm };
12892
+ const cached7 = this.channelIdCache.get(searchLower);
12893
+ if (cached7) {
12894
+ return { ...cached7, displayName: searchTerm };
12561
12895
  }
12562
12896
  try {
12563
12897
  const teamsData = await this.graphGet("/v1.0/me/joinedTeams");
@@ -12737,9 +13071,9 @@ ${loopContent}` : loopContent;
12737
13071
  * Results are cached per chat ID.
12738
13072
  */
12739
13073
  async getChatMembers(chatOrChannelId) {
12740
- const cached8 = this.chatMembersCache.get(chatOrChannelId);
12741
- if (cached8)
12742
- return cached8;
13074
+ const cached7 = this.chatMembersCache.get(chatOrChannelId);
13075
+ if (cached7)
13076
+ return cached7;
12743
13077
  try {
12744
13078
  let endpoint;
12745
13079
  if (this.contextType === "channel" && this.currentChannelTeamId && chatOrChannelId === this.currentChannelId) {
@@ -12977,9 +13311,9 @@ ${loopContent}` : loopContent;
12977
13311
  */
12978
13312
  async resolveChannelIds(teamName, channelName) {
12979
13313
  const cacheKey = `${teamName}/${channelName}`;
12980
- const cached8 = this.channelIdCache.get(cacheKey);
12981
- if (cached8)
12982
- return cached8;
13314
+ const cached7 = this.channelIdCache.get(cacheKey);
13315
+ if (cached7)
13316
+ return cached7;
12983
13317
  const teamsData = await this.graphGet("/v1.0/me/joinedTeams");
12984
13318
  const team = teamsData.value?.find((t) => t.displayName.toLowerCase() === teamName.toLowerCase());
12985
13319
  if (!team) {
@@ -19971,38 +20305,64 @@ var init_frontmatter = __esm({
19971
20305
  });
19972
20306
 
19973
20307
  // src/skills/registry.ts
19974
- import { existsSync as existsSync14, readFileSync as readFileSync14, readdirSync as readdirSync2 } from "node:fs";
20308
+ import { existsSync as existsSync14, readFileSync as readFileSync14, readdirSync as readdirSync2, statSync } from "node:fs";
19975
20309
  import { join as join22 } from "node:path";
19976
20310
  import { homedir as homedir12 } from "node:os";
20311
+ function skillRoots() {
20312
+ return rootsOverride ?? { bundledDir: packageSkillsDir(), userDir: DEFAULT_USER_DIR };
20313
+ }
19977
20314
  function discoverSkills() {
19978
- if (cachedSkills) return cachedSkills;
20315
+ const signature = buildRegistrySignature();
20316
+ if (cachedSkills?.signature === signature) return cachedSkills.skills;
20317
+ const roots = skillRoots();
19979
20318
  const byName2 = /* @__PURE__ */ new Map();
19980
- loadSkillsFromDir(packageSkillsDir(), "bundled", byName2);
19981
- loadSkillsFromDir(USER_DIR, "user", byName2);
19982
- cachedSkills = [...byName2.values()].sort((a, b) => a.name.localeCompare(b.name));
19983
- return cachedSkills;
20319
+ loadFlatSkillsFromDir(roots.bundledDir, "bundled", 0, byName2);
20320
+ loadFlatSkillsFromDir(roots.userDir, "user", 1, byName2);
20321
+ loadPackagedSkillsFromDir(roots.userDir, byName2);
20322
+ const skills = [...byName2.values()].map((entry) => entry.skill).sort((a, b) => a.name.localeCompare(b.name));
20323
+ cachedSkills = { signature, skills };
20324
+ return skills;
19984
20325
  }
19985
20326
  function loadSkillBody(skillName) {
19986
- const skills = discoverSkills();
19987
- const skill = skills.find((s) => s.name === skillName);
20327
+ const skill = findSkill(skillName);
19988
20328
  if (!skill) return null;
19989
20329
  try {
19990
20330
  const content = readFileSync14(skill.filePath, "utf-8").trim();
19991
- const parsed = parseSkillFile(content, skill.name + ".md");
20331
+ const parsed = parseSkillFile(content, `${skill.name}.md`);
19992
20332
  return parsed.body;
19993
20333
  } catch {
19994
20334
  return null;
19995
20335
  }
19996
20336
  }
20337
+ function loadSkillPrompt(skillName) {
20338
+ const skill = findSkill(skillName);
20339
+ const body = skill ? loadSkillBody(skill.name) : null;
20340
+ if (!skill || !body) return null;
20341
+ return `# Skill Runtime Context
20342
+
20343
+ Skill name: ${skill.name}
20344
+ Skill file: ${skill.filePath}
20345
+ Skill root directory: ${skill.rootDir}
20346
+
20347
+ ${body}`;
20348
+ }
19997
20349
  function findSkill(name) {
19998
20350
  const skills = discoverSkills();
19999
- const exact = skills.find((s) => s.name === name);
20351
+ const normalized = normalizeSkillName(name);
20352
+ const exact = skills.find((s) => s.name === normalized);
20000
20353
  if (exact) return exact;
20001
- const lower = name.toLowerCase();
20002
- const ci = skills.find((s) => s.name.toLowerCase() === lower);
20354
+ const ci = skills.find((s) => s.name.toLowerCase() === normalized.toLowerCase());
20003
20355
  if (ci) return ci;
20004
- const partial = skills.find((s) => s.name.startsWith(lower) || lower.startsWith(s.name));
20005
- return partial || null;
20356
+ const matches = findSkillMatches(normalized);
20357
+ return matches.length === 1 ? matches[0] : null;
20358
+ }
20359
+ function findSkillMatches(name) {
20360
+ const normalized = normalizeSkillName(name).toLowerCase();
20361
+ if (!normalized) return [];
20362
+ return discoverSkills().filter((skill) => {
20363
+ const skillName = skill.name.toLowerCase();
20364
+ return skillName.startsWith(normalized) || normalized.startsWith(skillName);
20365
+ });
20006
20366
  }
20007
20367
  function getSkillListing() {
20008
20368
  const skills = discoverSkills();
@@ -20017,52 +20377,145 @@ function getSkillListing() {
20017
20377
  function clearSkillRegistry() {
20018
20378
  cachedSkills = null;
20019
20379
  }
20020
- function loadSkillsFromDir(dir2, source, out) {
20021
- if (!existsSync14(dir2)) return;
20380
+ function normalizeSkillName(name) {
20381
+ return name.trim().replace(/^\/+/, "").toLowerCase();
20382
+ }
20383
+ function loadFlatSkillsFromDir(dir2, source, priority, out) {
20384
+ for (const entry of safeReadDir(dir2)) {
20385
+ if (!entry.isFile() || !isMarkdown(entry.name)) continue;
20386
+ const filePath = join22(dir2, entry.name);
20387
+ const skill = parseSkillAtPath(filePath, dir2, entry.name, source, entry.name);
20388
+ if (skill) putSkill(out, skill, priority);
20389
+ }
20390
+ }
20391
+ function loadPackagedSkillsFromDir(dir2, out) {
20392
+ for (const entry of safeReadDir(dir2)) {
20393
+ if (!entry.isDirectory()) continue;
20394
+ const rootDir = join22(dir2, entry.name);
20395
+ const entrypoint = findPackageEntrypoint(rootDir);
20396
+ if (!entrypoint) continue;
20397
+ const filePath = join22(rootDir, entrypoint);
20398
+ const skill = parseSkillAtPath(filePath, rootDir, entrypoint, "user", `${entry.name}.md`);
20399
+ if (skill) putSkill(out, skill, 2);
20400
+ }
20401
+ }
20402
+ function findPackageEntrypoint(dir2) {
20403
+ const entries = safeReadDir(dir2).filter((entry) => entry.isFile());
20404
+ const entrypoint = ENTRYPOINT_NAMES.find((name) => entries.some((entry) => entry.name.toLowerCase() === name.toLowerCase()));
20405
+ if (entrypoint) return entries.find((entry) => entry.name.toLowerCase() === entrypoint.toLowerCase())?.name ?? entrypoint;
20406
+ const readme = entries.find((entry) => entry.name.toLowerCase() === "readme.md");
20407
+ if (readme) return readme.name;
20408
+ const markdown = entries.filter((entry) => isMarkdown(entry.name));
20409
+ return markdown.length === 1 ? markdown[0].name : null;
20410
+ }
20411
+ function parseSkillAtPath(filePath, rootDir, entrypoint, source, fallbackFilename) {
20022
20412
  try {
20023
- const files = readdirSync2(dir2).filter((f) => f.endsWith(".md")).sort();
20024
- for (const file2 of files) {
20025
- const filePath = join22(dir2, file2);
20026
- try {
20027
- const content = readFileSync14(filePath, "utf-8").trim();
20028
- if (!content) continue;
20029
- const parsed = parseSkillFile(content, file2);
20030
- out.set(parsed.meta.name, {
20031
- name: parsed.meta.name,
20032
- meta: parsed.meta,
20033
- filePath,
20034
- source,
20035
- hasFrontmatter: parsed.hasFrontmatter
20036
- });
20037
- } catch {
20413
+ const content = readFileSync14(filePath, "utf-8").trim();
20414
+ if (!content) return null;
20415
+ const parsed = parseSkillFile(content, fallbackFilename);
20416
+ const name = normalizeSkillName(parsed.meta.name);
20417
+ if (!name) return null;
20418
+ return {
20419
+ name,
20420
+ meta: { ...parsed.meta, name },
20421
+ filePath,
20422
+ rootDir,
20423
+ entrypoint,
20424
+ source,
20425
+ hasFrontmatter: parsed.hasFrontmatter
20426
+ };
20427
+ } catch {
20428
+ return null;
20429
+ }
20430
+ }
20431
+ function putSkill(out, skill, priority) {
20432
+ const existing = out.get(skill.name);
20433
+ if (!existing || priority >= existing.priority) {
20434
+ out.set(skill.name, { priority, skill });
20435
+ }
20436
+ }
20437
+ function safeReadDir(dir2) {
20438
+ if (!existsSync14(dir2)) return [];
20439
+ try {
20440
+ return readdirSync2(dir2, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
20441
+ } catch {
20442
+ return [];
20443
+ }
20444
+ }
20445
+ function buildRegistrySignature() {
20446
+ const roots = skillRoots();
20447
+ return [signatureForDir(roots.bundledDir), signatureForDir(roots.userDir)].join("|");
20448
+ }
20449
+ function signatureForDir(dir2) {
20450
+ if (!existsSync14(dir2)) return `${dir2}:missing`;
20451
+ const parts = [];
20452
+ try {
20453
+ for (const entry of safeReadDir(dir2)) {
20454
+ const fullPath = join22(dir2, entry.name);
20455
+ if (entry.isFile()) {
20456
+ if (!isMarkdown(entry.name)) continue;
20457
+ parts.push(fileSignature(fullPath, `f:${entry.name}`));
20458
+ } else if (entry.isDirectory()) {
20459
+ parts.push(fileSignature(fullPath, `d:${entry.name}`));
20460
+ for (const child of safeReadDir(fullPath)) {
20461
+ if (child.isFile() && isMarkdown(child.name)) {
20462
+ parts.push(fileSignature(join22(fullPath, child.name), `d:${entry.name}/${child.name}`));
20463
+ }
20464
+ }
20038
20465
  }
20039
20466
  }
20040
20467
  } catch {
20468
+ return `${dir2}:unreadable`;
20469
+ }
20470
+ return `${dir2}:${parts.join(",")}`;
20471
+ }
20472
+ function isMarkdown(name) {
20473
+ return name.toLowerCase().endsWith(".md");
20474
+ }
20475
+ function fileSignature(path3, label) {
20476
+ try {
20477
+ const stat2 = statSync(path3);
20478
+ return `${label}:${stat2.mtimeMs}:${stat2.size}`;
20479
+ } catch {
20480
+ return `${label}:missing`;
20041
20481
  }
20042
20482
  }
20043
- var USER_DIR, cachedSkills;
20483
+ var DEFAULT_USER_DIR, ENTRYPOINT_NAMES, cachedSkills, rootsOverride;
20044
20484
  var init_registry2 = __esm({
20045
20485
  "src/skills/registry.ts"() {
20046
20486
  "use strict";
20047
20487
  init_frontmatter();
20048
20488
  init_packageRoot();
20049
- USER_DIR = join22(homedir12(), ".openjaw-agent", "skills");
20489
+ DEFAULT_USER_DIR = join22(homedir12(), ".openjaw-agent", "skills");
20490
+ ENTRYPOINT_NAMES = ["SKILL.md", "skill.md"];
20050
20491
  cachedSkills = null;
20492
+ rootsOverride = null;
20493
+ __name(skillRoots, "skillRoots");
20051
20494
  __name(discoverSkills, "discoverSkills");
20052
20495
  __name(loadSkillBody, "loadSkillBody");
20496
+ __name(loadSkillPrompt, "loadSkillPrompt");
20053
20497
  __name(findSkill, "findSkill");
20498
+ __name(findSkillMatches, "findSkillMatches");
20054
20499
  __name(getSkillListing, "getSkillListing");
20055
20500
  __name(clearSkillRegistry, "clearSkillRegistry");
20056
- __name(loadSkillsFromDir, "loadSkillsFromDir");
20501
+ __name(normalizeSkillName, "normalizeSkillName");
20502
+ __name(loadFlatSkillsFromDir, "loadFlatSkillsFromDir");
20503
+ __name(loadPackagedSkillsFromDir, "loadPackagedSkillsFromDir");
20504
+ __name(findPackageEntrypoint, "findPackageEntrypoint");
20505
+ __name(parseSkillAtPath, "parseSkillAtPath");
20506
+ __name(putSkill, "putSkill");
20507
+ __name(safeReadDir, "safeReadDir");
20508
+ __name(buildRegistrySignature, "buildRegistrySignature");
20509
+ __name(signatureForDir, "signatureForDir");
20510
+ __name(isMarkdown, "isMarkdown");
20511
+ __name(fileSignature, "fileSignature");
20057
20512
  }
20058
20513
  });
20059
20514
 
20060
20515
  // src/prompts/skills.ts
20061
20516
  function getSkillsSection() {
20062
- if (cached7 !== void 0) return cached7;
20063
20517
  const skills = discoverSkills();
20064
20518
  if (skills.length === 0) {
20065
- cached7 = null;
20066
20519
  return null;
20067
20520
  }
20068
20521
  const listing = getSkillListing();
@@ -20077,15 +20530,17 @@ New skills must be saved to \`~/.openjaw-agent/skills/\` (user skills directory)
20077
20530
  Do NOT write skills to the project's bundled skills/ directory.
20078
20531
 
20079
20532
  ${listing}`;
20080
- cached7 = content;
20081
- return cached7;
20533
+ return content;
20534
+ }
20535
+ function clearSkillsCache() {
20536
+ clearSkillRegistry();
20082
20537
  }
20083
- var cached7;
20084
20538
  var init_skills = __esm({
20085
20539
  "src/prompts/skills.ts"() {
20086
20540
  "use strict";
20087
20541
  init_registry2();
20088
20542
  __name(getSkillsSection, "getSkillsSection");
20543
+ __name(clearSkillsCache, "clearSkillsCache");
20089
20544
  }
20090
20545
  });
20091
20546
 
@@ -20776,8 +21231,8 @@ Options: ${chunk.choices.join(" | ")}` : "";
20776
21231
  if (fileName.startsWith("oj-") || fileName.endsWith(".log") || fileName.endsWith(".tmp")) continue;
20777
21232
  if (existsSync16(resolvedPath)) {
20778
21233
  try {
20779
- const { statSync: statSync4 } = await import("node:fs");
20780
- const stat2 = statSync4(resolvedPath);
21234
+ const { statSync: statSync5 } = await import("node:fs");
21235
+ const stat2 = statSync5(resolvedPath);
20781
21236
  if (stat2.isFile() && stat2.size < 50 * 1024 * 1024) {
20782
21237
  const ext = resolvedPath.split(".").pop()?.toLowerCase() || "";
20783
21238
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
@@ -21629,10 +22084,10 @@ var init_mcp_client = __esm({
21629
22084
  let tokenExpiry = 0;
21630
22085
  if (copilotTokenFile) {
21631
22086
  try {
21632
- const cached8 = JSON.parse(readFileSync17(copilotTokenFile, "utf-8"));
21633
- cachedToken = cached8.accessToken;
21634
- cachedRefreshToken = cached8.refreshToken;
21635
- tokenExpiry = cached8.expiresAt || 0;
22087
+ const cached7 = JSON.parse(readFileSync17(copilotTokenFile, "utf-8"));
22088
+ cachedToken = cached7.accessToken;
22089
+ cachedRefreshToken = cached7.refreshToken;
22090
+ tokenExpiry = cached7.expiresAt || 0;
21636
22091
  } catch {
21637
22092
  }
21638
22093
  }
@@ -21771,11 +22226,31 @@ async function runCopilotResponsesSearch(input, llm, startedAt) {
21771
22226
  if (!credential || credential.type !== "oauth") {
21772
22227
  throw new Error("GitHub Copilot is not connected. Run /connect github-copilot first.");
21773
22228
  }
21774
- const baseUrl = llm.base_url?.replace(/\/+$/, "") ?? copilotApiBase(credential.enterpriseUrl);
22229
+ let copilotAccess = credential.copilotAccess;
22230
+ let copilotApiBaseUrl = credential.copilotApiBaseUrl;
22231
+ if (!copilotAccess || !credential.copilotExpires || credential.copilotExpires <= Date.now() + COPILOT_TOKEN_EXPIRY_SAFETY_MARGIN_MS2) {
22232
+ const exchanged = await exchangeGitHubTokenForCopilotToken(
22233
+ credential.githubAccess || credential.refresh || credential.access,
22234
+ credential.enterpriseUrl
22235
+ );
22236
+ copilotAccess = exchanged.copilotAccess;
22237
+ copilotApiBaseUrl = exchanged.copilotApiBaseUrl;
22238
+ saveProviderCredential({
22239
+ ...credential,
22240
+ access: exchanged.copilotAccess,
22241
+ refresh: exchanged.githubAccess,
22242
+ expires: exchanged.copilotExpires,
22243
+ githubAccess: exchanged.githubAccess,
22244
+ copilotAccess: exchanged.copilotAccess,
22245
+ copilotExpires: exchanged.copilotExpires,
22246
+ copilotApiBaseUrl: exchanged.copilotApiBaseUrl
22247
+ });
22248
+ }
22249
+ const baseUrl = llm.base_url?.replace(/\/+$/, "") ?? copilotApiBaseUrl?.replace(/\/+$/, "") ?? copilotApiBase(credential.enterpriseUrl);
21775
22250
  const headers = {
21776
- "Authorization": `Bearer ${credential.access || credential.refresh}`,
22251
+ "Authorization": `Bearer ${copilotAccess}`,
21777
22252
  "Content-Type": "application/json",
21778
- "User-Agent": COPILOT_USER_AGENT,
22253
+ "User-Agent": COPILOT_USER_AGENT2,
21779
22254
  "Editor-Version": "OpenJaw/0.1.0",
21780
22255
  "Editor-Plugin-Version": "OpenJaw/0.1.0",
21781
22256
  "Copilot-Integration-Id": "vscode-chat",
@@ -22002,15 +22477,16 @@ function limitAndDedupe(results, max) {
22002
22477
  function truncate(text) {
22003
22478
  return text.length > 200 ? text.slice(0, 200) + "\u2026" : text;
22004
22479
  }
22005
- var COPILOT_USER_AGENT, DEFAULT_TIMEOUT_MS;
22480
+ var COPILOT_USER_AGENT2, DEFAULT_TIMEOUT_MS, COPILOT_TOKEN_EXPIRY_SAFETY_MARGIN_MS2;
22006
22481
  var init_web_search = __esm({
22007
22482
  "src/web-search.ts"() {
22008
22483
  "use strict";
22009
22484
  init_web_search_types();
22485
+ init_copilot_token();
22010
22486
  init_provider_auth();
22011
- init_copilot();
22012
- COPILOT_USER_AGENT = "openjaw-agent/0.1.0";
22487
+ COPILOT_USER_AGENT2 = "openjaw-agent/0.1.0";
22013
22488
  DEFAULT_TIMEOUT_MS = 45e3;
22489
+ COPILOT_TOKEN_EXPIRY_SAFETY_MARGIN_MS2 = 6e4;
22014
22490
  __name(createWebSearchExecutor, "createWebSearchExecutor");
22015
22491
  __name(runCopilotResponsesSearch, "runCopilotResponsesSearch");
22016
22492
  __name(runOpenAIResponsesSearch, "runOpenAIResponsesSearch");
@@ -26423,7 +26899,7 @@ function createSkillTool(config, toolRegistry, systemPromptFn) {
26423
26899
  };
26424
26900
  }
26425
26901
  async function executeSkill(skillName, args, config, toolRegistry, systemPromptFn) {
26426
- const body = loadSkillBody(skillName);
26902
+ const body = loadSkillPrompt(skillName);
26427
26903
  if (!body) {
26428
26904
  return { success: false, error: `Could not load skill content for "${skillName}"` };
26429
26905
  }
@@ -26786,8 +27262,8 @@ var init_teams = __esm({
26786
27262
  if (fileName.startsWith("oj-") || fileName.endsWith(".log") || fileName.endsWith(".tmp")) continue;
26787
27263
  if (existsSync25(filePath)) {
26788
27264
  try {
26789
- const { statSync: statSync4 } = await import("node:fs");
26790
- const stat2 = statSync4(filePath);
27265
+ const { statSync: statSync5 } = await import("node:fs");
27266
+ const stat2 = statSync5(filePath);
26791
27267
  if (stat2.isFile() && stat2.size < 50 * 1024 * 1024) {
26792
27268
  await this.sendFileToSelfChat(filePath);
26793
27269
  sentFiles.add(filePath);
@@ -27346,8 +27822,8 @@ var init_feishu = __esm({
27346
27822
  this.emit({ type: "system", content: `\u{1F50D} Found path: ${filePath} (exists: ${exists})` });
27347
27823
  if (exists) {
27348
27824
  try {
27349
- const { statSync: statSync4 } = await import("node:fs");
27350
- const stat2 = statSync4(filePath);
27825
+ const { statSync: statSync5 } = await import("node:fs");
27826
+ const stat2 = statSync5(filePath);
27351
27827
  if (stat2.isFile() && stat2.size < 30 * 1024 * 1024) {
27352
27828
  const fileType = this.getFeishuFileType(fileName);
27353
27829
  this.emit({ type: "system", content: `\u{1F4E4} Uploading to Feishu: ${fileName} (${(stat2.size / 1024).toFixed(0)}KB, type=${fileType})` });
@@ -27443,7 +27919,7 @@ __export(wechat_exports, {
27443
27919
  sniffMediaKind: () => sniffMediaKind,
27444
27920
  validateMediaForUpload: () => validateMediaForUpload
27445
27921
  });
27446
- import { existsSync as existsSync27, readFileSync as readFileSync26, writeFileSync as writeFileSync18, unlinkSync as unlinkSync7, statSync as statSync2 } from "node:fs";
27922
+ import { existsSync as existsSync27, readFileSync as readFileSync26, writeFileSync as writeFileSync18, unlinkSync as unlinkSync7, statSync as statSync3 } from "node:fs";
27447
27923
  import { mkdirSync as mkdirSync16 } from "node:fs";
27448
27924
  import { extname as extname3, join as join35 } from "node:path";
27449
27925
  import { homedir as homedir21 } from "node:os";
@@ -28314,7 +28790,7 @@ Scan URL manually: ${qrUrl}` });
28314
28790
  attemptedFiles.add(dedupKey);
28315
28791
  attempted++;
28316
28792
  try {
28317
- const stat2 = statSync2(filePath);
28793
+ const stat2 = statSync3(filePath);
28318
28794
  if (!stat2.isFile() || stat2.size > 30 * 1024 * 1024) continue;
28319
28795
  if (stat2.size === 0) {
28320
28796
  this.emit({ type: "system", content: `\u26A0 WeChat skipped empty file: ${fileName}` });
@@ -29314,12 +29790,40 @@ var init_bootstrap = __esm({
29314
29790
  }
29315
29791
  });
29316
29792
 
29793
+ // src/usageSnapshot.ts
29794
+ function usageSnapshot(agentLoop) {
29795
+ const cost = agentLoop.costTracker.getSessionCost();
29796
+ const contextMax = agentLoop.contextManager.getContextWindow(agentLoop.model);
29797
+ const contextUsed = agentLoop.contextManager.lastPromptTokens;
29798
+ return {
29799
+ cache_read: cost.totalCacheReadTokens,
29800
+ cache_write: cost.totalCacheCreationTokens,
29801
+ calls: cost.turns,
29802
+ context_max: contextMax,
29803
+ context_percent: contextPercent(contextUsed, contextMax),
29804
+ context_used: contextUsed,
29805
+ cost_status: "estimated",
29806
+ cost_usd: cost.totalCostUSD,
29807
+ input: cost.totalInputTokens,
29808
+ model: agentLoop.model,
29809
+ output: cost.totalOutputTokens,
29810
+ total: cost.totalInputTokens + cost.totalOutputTokens + cost.totalCacheReadTokens + cost.totalCacheCreationTokens
29811
+ };
29812
+ }
29813
+ var init_usageSnapshot = __esm({
29814
+ "src/usageSnapshot.ts"() {
29815
+ "use strict";
29816
+ init_usage();
29817
+ __name(usageSnapshot, "usageSnapshot");
29818
+ }
29819
+ });
29820
+
29317
29821
  // src/eventBridge.ts
29318
29822
  import { randomUUID as randomUUID11 } from "node:crypto";
29319
29823
  async function streamAgentRun(options) {
29320
29824
  const { agentLoop, bus, imageData, systemPrompt, text } = options;
29321
29825
  const sid = agentLoop.sessionId;
29322
- const state = newState();
29826
+ const state = newState(agentLoop);
29323
29827
  bus.emitEvent({
29324
29828
  payload: { kind: "thinking", text: "thinking\u2026" },
29325
29829
  session_id: sid,
@@ -29409,11 +29913,13 @@ function translateChunk(chunk, state, bus, sid) {
29409
29913
  }
29410
29914
  }
29411
29915
  }
29412
- var newState, ensureMessageOpen, closeMessage;
29916
+ var newState, ensureMessageOpen, closeMessage, safeUsageSnapshot;
29413
29917
  var init_eventBridge = __esm({
29414
29918
  "src/eventBridge.ts"() {
29415
29919
  "use strict";
29416
- newState = /* @__PURE__ */ __name(() => ({
29920
+ init_usageSnapshot();
29921
+ newState = /* @__PURE__ */ __name((agentLoop) => ({
29922
+ agentLoop,
29417
29923
  messageOpen: false,
29418
29924
  pendingAnswer: "",
29419
29925
  toolIds: /* @__PURE__ */ new Map()
@@ -29426,8 +29932,9 @@ var init_eventBridge = __esm({
29426
29932
  }, "ensureMessageOpen");
29427
29933
  closeMessage = /* @__PURE__ */ __name((state, bus, sid, text) => {
29428
29934
  if (state.messageOpen || text) {
29935
+ const usage2 = safeUsageSnapshot(state.agentLoop);
29429
29936
  bus.emitEvent({
29430
- payload: { text: text ?? state.pendingAnswer },
29937
+ payload: { text: text ?? state.pendingAnswer, ...usage2 ? { usage: usage2 } : {} },
29431
29938
  session_id: sid,
29432
29939
  type: "message.complete"
29433
29940
  });
@@ -29435,6 +29942,13 @@ var init_eventBridge = __esm({
29435
29942
  state.pendingAnswer = "";
29436
29943
  }
29437
29944
  }, "closeMessage");
29945
+ safeUsageSnapshot = /* @__PURE__ */ __name((agentLoop) => {
29946
+ try {
29947
+ return usageSnapshot(agentLoop);
29948
+ } catch {
29949
+ return void 0;
29950
+ }
29951
+ }, "safeUsageSnapshot");
29438
29952
  __name(streamAgentRun, "streamAgentRun");
29439
29953
  __name(translateChunk, "translateChunk");
29440
29954
  }
@@ -29514,15 +30028,6 @@ var init_env = __esm({
29514
30028
  }
29515
30029
  });
29516
30030
 
29517
- // src/domain/usage.ts
29518
- var ZERO;
29519
- var init_usage = __esm({
29520
- "src/domain/usage.ts"() {
29521
- "use strict";
29522
- ZERO = { calls: 0, input: 0, output: 0, total: 0 };
29523
- }
29524
- });
29525
-
29526
30031
  // src/theme.ts
29527
30032
  function parseHex(h) {
29528
30033
  const m = /^#?([0-9a-f]{6})$/i.exec(h);
@@ -29692,6 +30197,14 @@ function normalizeThemeForAnsiLightTerminal(theme, env2 = process.env, isLight =
29692
30197
  }
29693
30198
  return { ...theme, color };
29694
30199
  }
30200
+ function isBuiltinSkinName(value) {
30201
+ return BUILTIN_SKINS.some((skin) => skin.name === value);
30202
+ }
30203
+ function themeForBuiltinSkin(name) {
30204
+ if (name === "dark") return DARK_THEME;
30205
+ if (name === "light") return LIGHT_THEME;
30206
+ return DEFAULT_THEME;
30207
+ }
29695
30208
  function fromSkin(colors, branding, bannerLogo = "", bannerHero = "", toolPrefix = "", helpHeader = "") {
29696
30209
  const d = DEFAULT_THEME;
29697
30210
  const c = /* @__PURE__ */ __name((k) => colors[k], "c");
@@ -29749,7 +30262,7 @@ function fromSkin(colors, branding, bannerLogo = "", bannerHero = "", toolPrefix
29749
30262
  bannerHero
29750
30263
  }, process.env, DEFAULT_LIGHT_MODE);
29751
30264
  }
29752
- 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;
30265
+ 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;
29753
30266
  var init_theme = __esm({
29754
30267
  "src/theme.ts"() {
29755
30268
  "use strict";
@@ -29901,6 +30414,13 @@ var init_theme = __esm({
29901
30414
  process.env,
29902
30415
  DEFAULT_LIGHT_MODE
29903
30416
  );
30417
+ BUILTIN_SKINS = [
30418
+ { name: "default", description: "Auto-detect terminal light/dark preference" },
30419
+ { name: "dark", description: "OpenJaw dark coral theme" },
30420
+ { name: "light", description: "Light terminal theme" }
30421
+ ];
30422
+ __name(isBuiltinSkinName, "isBuiltinSkinName");
30423
+ __name(themeForBuiltinSkin, "themeForBuiltinSkin");
29904
30424
  __name(fromSkin, "fromSkin");
29905
30425
  }
29906
30426
  });
@@ -31669,11 +32189,11 @@ function sliceAnsi(str, start, end) {
31669
32189
  }
31670
32190
  if (end !== void 0) {
31671
32191
  const key = `${start}|${end}|${str}`;
31672
- const cached8 = sliceCache.get(key);
31673
- if (cached8 !== void 0) {
32192
+ const cached7 = sliceCache.get(key);
32193
+ if (cached7 !== void 0) {
31674
32194
  sliceCache.delete(key);
31675
- sliceCache.set(key, cached8);
31676
- return cached8;
32195
+ sliceCache.set(key, cached7);
32196
+ return cached7;
31677
32197
  }
31678
32198
  const result = computeSlice(str, start, end);
31679
32199
  if (sliceCache.size >= SLICE_CACHE_LIMIT) {
@@ -31728,11 +32248,11 @@ function computeSlice(str, start, end) {
31728
32248
  return result;
31729
32249
  }
31730
32250
  function lineWidth(line) {
31731
- const cached8 = cache.get(line);
31732
- if (cached8 !== void 0) {
32251
+ const cached7 = cache.get(line);
32252
+ if (cached7 !== void 0) {
31733
32253
  cache.delete(line);
31734
- cache.set(line, cached8);
31735
- return cached8;
32254
+ cache.set(line, cached7);
32255
+ return cached7;
31736
32256
  }
31737
32257
  const width = stringWidth(line);
31738
32258
  if (cache.size >= MAX_CACHE_SIZE) {
@@ -31749,11 +32269,11 @@ function evictLineWidthCache(keepRatio = 0) {
31749
32269
  }
31750
32270
  function memoizedWrap(text, maxWidth, wrapType) {
31751
32271
  const key = `${maxWidth}|${wrapType}|${text}`;
31752
- const cached8 = wrapCache.get(key);
31753
- if (cached8 !== void 0) {
32272
+ const cached7 = wrapCache.get(key);
32273
+ if (cached7 !== void 0) {
31754
32274
  wrapCache.delete(key);
31755
- wrapCache.set(key, cached8);
31756
- return cached8;
32275
+ wrapCache.set(key, cached7);
32276
+ return cached7;
31757
32277
  }
31758
32278
  const result = computeWrap(text, maxWidth, wrapType);
31759
32279
  if (wrapCache.size >= WRAP_CACHE_LIMIT) {
@@ -33713,9 +34233,9 @@ function collectRemovedRects(parent, removed, underAbsolute = false) {
33713
34233
  }
33714
34234
  const elem = removed;
33715
34235
  const isAbsolute2 = underAbsolute || elem.style.position === "absolute";
33716
- const cached8 = nodeCache.get(elem);
33717
- if (cached8) {
33718
- addPendingClear(parent, cached8, isAbsolute2);
34236
+ const cached7 = nodeCache.get(elem);
34237
+ if (cached7) {
34238
+ addPendingClear(parent, cached7, isAbsolute2);
33719
34239
  nodeCache.delete(elem);
33720
34240
  }
33721
34241
  for (const child of elem.childNodes) {
@@ -35835,31 +36355,31 @@ function renderNodeToOutput(node, output, {
35835
36355
  if (y < 0 && node.style.position === "absolute") {
35836
36356
  y = 0;
35837
36357
  }
35838
- const cached8 = nodeCache.get(node);
35839
- if (!node.dirty && !skipSelfBlit && node.pendingScrollDelta === void 0 && cached8 && cached8.x === x && cached8.y === y && cached8.width === width && cached8.height === height && prevScreen) {
36358
+ const cached7 = nodeCache.get(node);
36359
+ if (!node.dirty && !skipSelfBlit && node.pendingScrollDelta === void 0 && cached7 && cached7.x === x && cached7.y === y && cached7.width === width && cached7.height === height && prevScreen) {
35840
36360
  const fx = Math.floor(x);
35841
36361
  const fy = Math.floor(y);
35842
36362
  const fw = Math.floor(width);
35843
36363
  const fh = Math.floor(height);
35844
36364
  output.blit(prevScreen, fx, fy, fw, fh);
35845
36365
  if (node.style.position === "absolute") {
35846
- absoluteRectsCur.push(cached8);
36366
+ absoluteRectsCur.push(cached7);
35847
36367
  }
35848
36368
  blitEscapingAbsoluteDescendants(node, output, prevScreen, fx, fy, fw, fh);
35849
36369
  return;
35850
36370
  }
35851
- const positionChanged = cached8 !== void 0 && (cached8.x !== x || cached8.y !== y || cached8.width !== width || cached8.height !== height);
36371
+ const positionChanged = cached7 !== void 0 && (cached7.x !== x || cached7.y !== y || cached7.width !== width || cached7.height !== height);
35852
36372
  if (positionChanged) {
35853
36373
  layoutShifted = true;
35854
36374
  absoluteOverlayMoved ||= node.style.position === "absolute";
35855
36375
  }
35856
- if (cached8 && (node.dirty || positionChanged)) {
36376
+ if (cached7 && (node.dirty || positionChanged)) {
35857
36377
  output.clear(
35858
36378
  {
35859
- x: Math.floor(cached8.x),
35860
- y: Math.floor(cached8.y),
35861
- width: Math.floor(cached8.width),
35862
- height: Math.floor(cached8.height)
36379
+ x: Math.floor(cached7.x),
36380
+ y: Math.floor(cached7.y),
36381
+ width: Math.floor(cached7.width),
36382
+ height: Math.floor(cached7.height)
35863
36383
  },
35864
36384
  node.style.position === "absolute"
35865
36385
  );
@@ -36034,7 +36554,7 @@ function renderNodeToOutput(node, output, {
36034
36554
  const delta = contentCached.y - contentY;
36035
36555
  const regionTop = Math.floor(y + contentYoga.getComputedTop());
36036
36556
  const regionBottom = regionTop + innerHeight - 1;
36037
- if (cached8?.y === y && cached8.height === height && innerHeight > 0 && Math.abs(delta) < innerHeight) {
36557
+ if (cached7?.y === y && cached7.height === height && innerHeight > 0 && Math.abs(delta) < innerHeight) {
36038
36558
  hint = { top: regionTop, bottom: regionBottom, delta };
36039
36559
  scrollHint = hint;
36040
36560
  } else {
@@ -36334,13 +36854,13 @@ function blitEscapingAbsoluteDescendants(node, output, prevScreen, px, py, pw, p
36334
36854
  }
36335
36855
  const elem = child;
36336
36856
  if (elem.style.position === "absolute") {
36337
- const cached8 = nodeCache.get(elem);
36338
- if (cached8) {
36339
- absoluteRectsCur.push(cached8);
36340
- const cx = Math.floor(cached8.x);
36341
- const cy = Math.floor(cached8.y);
36342
- const cw = Math.floor(cached8.width);
36343
- const ch = Math.floor(cached8.height);
36857
+ const cached7 = nodeCache.get(elem);
36858
+ if (cached7) {
36859
+ absoluteRectsCur.push(cached7);
36860
+ const cx = Math.floor(cached7.x);
36861
+ const cy = Math.floor(cached7.y);
36862
+ const cw = Math.floor(cached7.width);
36863
+ const ch = Math.floor(cached7.height);
36344
36864
  if (cx < px || cy < py || cx + cw > pr || cy + ch > pb) {
36345
36865
  output.blit(prevScreen, cx, cy, cw, ch);
36346
36866
  }
@@ -36356,20 +36876,20 @@ function renderScrolledChildren(node, output, offsetX, offsetY, hasRemovedChild,
36356
36876
  const childElem = childNode;
36357
36877
  const cy = childElem.yogaNode;
36358
36878
  if (cy) {
36359
- const cached8 = nodeCache.get(childElem);
36879
+ const cached7 = nodeCache.get(childElem);
36360
36880
  let top;
36361
36881
  let height;
36362
- if (cached8?.top !== void 0 && !childElem.dirty && cumHeightShift === 0) {
36363
- top = cached8.top;
36364
- height = cached8.height;
36882
+ if (cached7?.top !== void 0 && !childElem.dirty && cumHeightShift === 0) {
36883
+ top = cached7.top;
36884
+ height = cached7.height;
36365
36885
  } else {
36366
36886
  top = cy.getComputedTop();
36367
36887
  height = cy.getComputedHeight();
36368
36888
  if (childElem.dirty) {
36369
- cumHeightShift += height - (cached8 ? cached8.height : 0);
36889
+ cumHeightShift += height - (cached7 ? cached7.height : 0);
36370
36890
  }
36371
- if (cached8) {
36372
- cached8.top = top;
36891
+ if (cached7) {
36892
+ cached7.top = top;
36373
36893
  }
36374
36894
  }
36375
36895
  const bottom = top + height;
@@ -38779,11 +39299,11 @@ var init_entry_exports = __esm({
38779
39299
  return rawStringWidth(str);
38780
39300
  }
38781
39301
  }
38782
- const cached8 = widthCache.get(str);
38783
- if (cached8 !== void 0) {
39302
+ const cached7 = widthCache.get(str);
39303
+ if (cached7 !== void 0) {
38784
39304
  widthCache.delete(str);
38785
- widthCache.set(str, cached8);
38786
- return cached8;
39305
+ widthCache.set(str, cached7);
39306
+ return cached7;
38787
39307
  }
38788
39308
  const w = rawStringWidth(str);
38789
39309
  if (widthCache.size >= WIDTH_CACHE_LIMIT) {
@@ -40888,9 +41408,9 @@ $ npm install --save-dev react-devtools-core
40888
41408
  if (char.length === 1) {
40889
41409
  const code = char.charCodeAt(0);
40890
41410
  if (code < 128) {
40891
- const cached8 = this.ascii[code];
40892
- if (cached8 !== -1) {
40893
- return cached8;
41411
+ const cached7 = this.ascii[code];
41412
+ if (cached7 !== -1) {
41413
+ return cached7;
40894
41414
  }
40895
41415
  const index2 = this.strings.length;
40896
41416
  this.strings.push(char);
@@ -45907,6 +46427,7 @@ var fmt, money, stub, eventLine, parseScheduleInput, showUsage, openjawCommands;
45907
46427
  var init_openjaw = __esm({
45908
46428
  "src/app/slash/commands/openjaw.ts"() {
45909
46429
  "use strict";
46430
+ init_usage();
45910
46431
  init_overlayStore();
45911
46432
  fmt = /* @__PURE__ */ __name((n) => (n ?? 0).toLocaleString(), "fmt");
45912
46433
  money = /* @__PURE__ */ __name((n) => `$${(n ?? 0).toFixed(4)}`, "money");
@@ -46154,7 +46675,7 @@ var init_openjaw = __esm({
46154
46675
  if (!r.context_max) {
46155
46676
  return ctx.transcript.sys("context usage unavailable");
46156
46677
  }
46157
- ctx.transcript.sys(`context: ${fmt(r.context_used)} / ${fmt(r.context_max)} (${r.context_percent ?? 0}%)`);
46678
+ ctx.transcript.sys(`context: ${fmt(r.context_used)} / ${fmt(r.context_max)} (${formatContextPercent(r.context_percent)})`);
46158
46679
  }), "run")
46159
46680
  },
46160
46681
  {
@@ -47057,6 +47578,8 @@ var init_session2 = __esm({
47057
47578
  init_slash();
47058
47579
  init_platform();
47059
47580
  init_text();
47581
+ init_theme();
47582
+ init_usage();
47060
47583
  init_interfaces();
47061
47584
  init_overlayStore();
47062
47585
  init_uiStore();
@@ -47275,10 +47798,18 @@ var init_session2 = __esm({
47275
47798
  help: "switch theme skin (fires skin.changed)",
47276
47799
  name: "skin",
47277
47800
  run: /* @__PURE__ */ __name((arg, ctx) => {
47278
- if (!arg) {
47279
- return ctx.gateway.rpc("config.get", { key: "skin" }).then(ctx.guarded((r) => ctx.transcript.sys(`skin: ${r.value || "default"}`)));
47801
+ const value = arg.trim().toLowerCase();
47802
+ if (!value || value === "list") {
47803
+ return ctx.transcript.panel("Skins", [
47804
+ { rows: BUILTIN_SKINS.map((skin) => [skin.name, skin.description]) },
47805
+ { text: "Use /skin <name> to apply a skin." }
47806
+ ]);
47807
+ }
47808
+ if (!isBuiltinSkinName(value)) {
47809
+ return ctx.transcript.sys(`usage: /skin <${BUILTIN_SKINS.map((s) => s.name).join("|")}>`);
47280
47810
  }
47281
- ctx.gateway.rpc("config.set", { key: "skin", value: arg }).then(ctx.guarded((r) => r.value && ctx.transcript.sys(`skin \u2192 ${r.value}`)));
47811
+ patchUiState({ theme: themeForBuiltinSkin(value) });
47812
+ ctx.gateway.rpc("config.set", { key: "skin", value }).then(ctx.guarded(() => ctx.transcript.sys(`skin \u2192 ${value}`)));
47282
47813
  }, "run")
47283
47814
  },
47284
47815
  {
@@ -47445,7 +47976,7 @@ var init_session2 = __esm({
47445
47976
  }
47446
47977
  const sections = [{ rows }];
47447
47978
  if (r.context_max) {
47448
- sections.push({ text: `Context: ${f(r.context_used)} / ${f(r.context_max)} (${r.context_percent}%)` });
47979
+ sections.push({ text: `Context: ${f(r.context_used)} / ${f(r.context_max)} (${formatContextPercent(r.context_percent)})` });
47449
47980
  }
47450
47981
  if (r.compressions) {
47451
47982
  sections.push({ text: `Compressions: ${r.compressions}` });
@@ -47569,6 +48100,14 @@ function buildSlashCompletions(text) {
47569
48100
  if (!text.startsWith("/")) {
47570
48101
  return { items: [], replace_from: 1 };
47571
48102
  }
48103
+ const skinArgMatch = /^\/skin\s+([^\s]*)$/i.exec(text);
48104
+ if (skinArgMatch) {
48105
+ const prefix = skinArgMatch[1]?.toLowerCase() ?? "";
48106
+ return {
48107
+ items: BUILTIN_SKINS.filter((skin) => skin.name.startsWith(prefix)).map((skin) => ({ display: skin.name, meta: skin.description, text: skin.name })),
48108
+ replace_from: text.length - (skinArgMatch[1]?.length ?? 0)
48109
+ };
48110
+ }
47572
48111
  const needle = text.slice(1).toLowerCase();
47573
48112
  const seen = /* @__PURE__ */ new Set();
47574
48113
  const items = [];
@@ -47587,6 +48126,19 @@ function buildSlashCompletions(text) {
47587
48126
  });
47588
48127
  }
47589
48128
  }
48129
+ if (needle.length > 0) {
48130
+ for (const skill of discoverSkills()) {
48131
+ if (!skill.name.toLowerCase().startsWith(needle)) continue;
48132
+ const command = `/${skill.name}`;
48133
+ if (seen.has(command)) continue;
48134
+ seen.add(command);
48135
+ items.push({
48136
+ display: command,
48137
+ meta: `skill \xB7 ${skill.meta.whenToUse || skill.meta.description || ""}`,
48138
+ text: command
48139
+ });
48140
+ }
48141
+ }
47590
48142
  items.sort((a, b) => {
47591
48143
  const aExact = a.text.slice(1).toLowerCase() === needle ? -1 : 0;
47592
48144
  const bExact = b.text.slice(1).toLowerCase() === needle ? -1 : 0;
@@ -47643,6 +48195,8 @@ var init_catalog = __esm({
47643
48195
  init_ops();
47644
48196
  init_session2();
47645
48197
  init_setup();
48198
+ init_registry2();
48199
+ init_theme();
47646
48200
  CATEGORIZED = [
47647
48201
  { category: "Core", commands: coreCommands },
47648
48202
  { category: "Session", commands: sessionCommands },
@@ -47663,7 +48217,7 @@ var init_catalog = __esm({
47663
48217
  // src/rpcHandlers.ts
47664
48218
  import { spawn as spawn7 } from "node:child_process";
47665
48219
  import { randomUUID as randomUUID13 } from "node:crypto";
47666
- import { existsSync as existsSync31, mkdirSync as mkdirSync17, readFileSync as readFileSync28, rmSync, statSync as statSync3, writeFileSync as writeFileSync19 } from "node:fs";
48220
+ import { existsSync as existsSync31, mkdirSync as mkdirSync17, readFileSync as readFileSync28, rmSync, statSync as statSync4, writeFileSync as writeFileSync19 } from "node:fs";
47667
48221
  import { homedir as homedir28 } from "node:os";
47668
48222
  import { basename as basename3, extname as extname4, join as join42 } from "node:path";
47669
48223
  function registerRpcHandlers(options) {
@@ -48356,8 +48910,7 @@ ${helpMessage}` : field.label;
48356
48910
  return { cancelled: true, slug: entry.slug };
48357
48911
  });
48358
48912
  bus.registerRpc("commands.catalog", () => {
48359
- const skillCount = toolRegistry.listTools().filter((t) => /^skill[:_-]/i.test(t.name)).length;
48360
- return buildCommandsCatalog({ skillCount });
48913
+ return buildCommandsCatalog({ skillCount: discoverSkills().length });
48361
48914
  });
48362
48915
  bus.registerRpc("completion.query", () => ({
48363
48916
  items: []
@@ -48365,14 +48918,9 @@ ${helpMessage}` : field.label;
48365
48918
  bus.registerRpc("complete.slash", (params) => buildSlashCompletions(String(params.text ?? "")));
48366
48919
  bus.registerRpc("complete.path", (params) => buildPathCompletions(String(params.word ?? "")));
48367
48920
  bus.registerRpc("skills.catalog", () => {
48368
- const tools = toolRegistry.listTools();
48369
- const skillTools = tools.filter((t) => /^skill[:_-]/i.test(t.name));
48370
48921
  return {
48371
- categories: ["skills"],
48372
- skills: skillTools.map((t) => ({
48373
- description: t.description,
48374
- name: t.name.replace(/^skill[:_-]/i, "")
48375
- }))
48922
+ categories: Object.keys(registrySkillsByCategory()).sort(),
48923
+ skills: listRegistrySkills()
48376
48924
  };
48377
48925
  });
48378
48926
  bus.registerRpc("tools.list", () => ({
@@ -48404,11 +48952,11 @@ ${helpMessage}` : field.label;
48404
48952
  return { output: lines.join("\n") };
48405
48953
  }
48406
48954
  if (head === "skills") {
48407
- const skills = toolRegistry.listTools().filter((t) => /^skill[:_-]/i.test(t.name)).map((t) => ({ description: t.description, name: t.name.replace(/^skill[:_-]/i, "") }));
48955
+ const skills = listRegistrySkills();
48408
48956
  if (!skills.length) {
48409
- return { output: "no skills registered" };
48957
+ return { output: "no skills available" };
48410
48958
  }
48411
- const lines = [`${skills.length} skills registered`, ""];
48959
+ const lines = [`${skills.length} skills available`, ""];
48412
48960
  for (const s of skills.sort((a, b) => a.name.localeCompare(b.name))) {
48413
48961
  const desc = s.description ? ` \u2014 ${s.description}` : "";
48414
48962
  lines.push(` ${s.name}${desc}`);
@@ -48684,7 +49232,7 @@ ${helpMessage}` : field.label;
48684
49232
  const meta = agentLoop.getSessionMeta();
48685
49233
  const tools = toolRegistry.listTools();
48686
49234
  const cost = `$${usage2.cost_usd.toFixed(4)}`;
48687
- const ctx = usage2.context_max > 0 ? `${usage2.context_percent}% (${usage2.context_used}/${usage2.context_max})` : "?";
49235
+ const ctx = usage2.context_max > 0 ? `${formatContextPercent(usage2.context_percent)} (${usage2.context_used}/${usage2.context_max})` : "?";
48688
49236
  const lines = [
48689
49237
  `Session: ${meta.id}`,
48690
49238
  `Title: ${meta.summary || "(untitled)"}`,
@@ -48814,7 +49362,7 @@ ${helpMessage}` : field.label;
48814
49362
  let fileSize = 0;
48815
49363
  try {
48816
49364
  buffer = readFileSync28(path3);
48817
- fileSize = statSync3(path3).size;
49365
+ fileSize = statSync4(path3).size;
48818
49366
  } catch (err) {
48819
49367
  throw new Error(`image.attach: ${err instanceof Error ? err.message : String(err)}`);
48820
49368
  }
@@ -48909,9 +49457,9 @@ ${helpMessage}` : field.label;
48909
49457
  bus.registerRpc("spawn_tree.load", () => ({ subagents: [] }));
48910
49458
  bus.registerRpc("skills.reload", async () => {
48911
49459
  try {
48912
- await mcpManager.connect();
48913
- const skillCount = toolRegistry.listTools().filter((t) => /^skill[:_-]/i.test(t.name)).length;
48914
- return { output: `Skills reloaded \xB7 ${skillCount} skills registered` };
49460
+ clearSkillsCache();
49461
+ const skillCount = discoverSkills().length;
49462
+ return { output: `Skills reloaded \xB7 ${skillCount} skills available` };
48915
49463
  } catch (err) {
48916
49464
  return { output: `skills reload failed: ${err instanceof Error ? err.message : String(err)}` };
48917
49465
  }
@@ -48919,18 +49467,28 @@ ${helpMessage}` : field.label;
48919
49467
  bus.registerRpc("skills.manage", (params) => {
48920
49468
  const action2 = String(params.action ?? "").toLowerCase();
48921
49469
  const query = String(params.query ?? "").trim().toLowerCase();
48922
- const skillTools = toolRegistry.listTools().filter((t) => /^skill[:_-]/i.test(t.name)).map((t) => ({ description: t.description, name: t.name.replace(/^skill[:_-]/i, "") }));
49470
+ const skills = listRegistrySkills();
48923
49471
  if (action2 === "list") {
48924
- return { skills: skillTools.length ? { installed: skillTools.map((s) => s.name) } : {} };
49472
+ return { skills: registrySkillsByCategory() };
48925
49473
  }
48926
49474
  if (action2 === "inspect") {
48927
- const hit = skillTools.find((s) => s.name.toLowerCase() === query);
49475
+ const hit = query ? findSkill(query) : null;
48928
49476
  if (!hit) return { info: null };
48929
- return { info: { category: "installed", description: hit.description, name: hit.name } };
49477
+ return {
49478
+ info: {
49479
+ category: hit.source,
49480
+ description: skillDescription(hit),
49481
+ entrypoint: hit.entrypoint,
49482
+ name: hit.name,
49483
+ path: hit.filePath,
49484
+ root_dir: hit.rootDir,
49485
+ source: hit.source
49486
+ }
49487
+ };
48930
49488
  }
48931
49489
  if (action2 === "search") {
48932
49490
  if (!query) return { results: [] };
48933
- const results = skillTools.filter((s) => s.name.toLowerCase().includes(query) || (s.description ?? "").toLowerCase().includes(query)).map((s) => ({ description: s.description, name: s.name }));
49491
+ const results = skills.filter((s) => s.name.toLowerCase().includes(query) || (s.description ?? "").toLowerCase().includes(query)).map((s) => ({ description: s.description, name: s.name }));
48934
49492
  return { results };
48935
49493
  }
48936
49494
  if (action2 === "install") {
@@ -48954,8 +49512,30 @@ ${helpMessage}` : field.label;
48954
49512
  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`]
48955
49513
  };
48956
49514
  });
48957
- bus.registerRpc("command.dispatch", (params) => {
49515
+ bus.registerRpc("command.dispatch", async (params) => {
48958
49516
  const name = String(params.name ?? "").trim();
49517
+ const arg = String(params.arg ?? "").trim();
49518
+ if (name) {
49519
+ const skill = findSkill(name);
49520
+ if (skill) {
49521
+ if (!toolRegistry.getTool("invoke_skill")) {
49522
+ return { output: "skill invocation is unavailable: invoke_skill tool is not registered", type: "exec" };
49523
+ }
49524
+ try {
49525
+ const result = await toolRegistry.execute("invoke_skill", { skill: skill.name, args: arg });
49526
+ return { output: formatSkillExecutionResult(result), type: "exec" };
49527
+ } catch (err) {
49528
+ return { output: `skill failed (${skill.name}): ${err instanceof Error ? err.message : String(err)}`, type: "exec" };
49529
+ }
49530
+ }
49531
+ const matches = findSkillMatches(name);
49532
+ if (matches.length > 1) {
49533
+ return {
49534
+ output: `ambiguous skill command /${name}: ${matches.slice(0, 8).map((s) => s.name).join(", ")}${matches.length > 8 ? ", \u2026" : ""}`,
49535
+ type: "exec"
49536
+ };
49537
+ }
49538
+ }
48959
49539
  return { output: name ? `unknown command: /${name}` : "(no command)", type: "exec" };
48960
49540
  });
48961
49541
  bus.registerRpc("delegation.status", () => ({ delegated: [], paused: false }));
@@ -48985,7 +49565,7 @@ ${helpMessage}` : field.label;
48985
49565
  }
48986
49566
  };
48987
49567
  }
48988
- var PROVIDERS2, PROVIDER_LABELS, PROVIDER_AUTH, isProviderAuthenticated, buildProviderOption, fetchLiveModels, parseReasoningEffortInput, reasoningEffortsForModel, resolveReasoningEffortForModel, BRIDGE_SOURCES, isBridgeSource, inferBridgeSource, bridgeLabels, bridgeEventLabels, stripBridgeGlyph, firstLogLine, bridgeUser, formatBridgeText, runProcess, contentToText, sessionMessageToMarkdown, usageSnapshot, sessionInfoSnapshot, MCP_TOOL_PREFIX, MAX_TOTAL_TOOLS, syncMcpTools;
49568
+ 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;
48989
49569
  var init_rpcHandlers = __esm({
48990
49570
  "src/rpcHandlers.ts"() {
48991
49571
  "use strict";
@@ -49004,6 +49584,10 @@ var init_rpcHandlers = __esm({
49004
49584
  init_models_static();
49005
49585
  init_providers();
49006
49586
  init_types();
49587
+ init_skills();
49588
+ init_registry2();
49589
+ init_usage();
49590
+ init_usageSnapshot();
49007
49591
  init_registry3();
49008
49592
  PROVIDERS2 = ["anthropic", "openai", "github-copilot"];
49009
49593
  PROVIDER_LABELS = {
@@ -49104,6 +49688,38 @@ var init_rpcHandlers = __esm({
49104
49688
  }
49105
49689
  return { clear: false, effort: normalized };
49106
49690
  }, "parseReasoningEffortInput");
49691
+ skillDescription = /* @__PURE__ */ __name((skill) => skill.meta.whenToUse || skill.meta.description || "", "skillDescription");
49692
+ listRegistrySkills = /* @__PURE__ */ __name(() => discoverSkills().map((skill) => ({
49693
+ category: skill.source,
49694
+ description: skillDescription(skill),
49695
+ entrypoint: skill.entrypoint,
49696
+ name: skill.name,
49697
+ path: skill.filePath,
49698
+ root_dir: skill.rootDir,
49699
+ source: skill.source
49700
+ })), "listRegistrySkills");
49701
+ registrySkillsByCategory = /* @__PURE__ */ __name(() => {
49702
+ const grouped = {};
49703
+ for (const skill of discoverSkills()) {
49704
+ const category = skill.source;
49705
+ grouped[category] ??= [];
49706
+ grouped[category].push(skill.name);
49707
+ }
49708
+ for (const skills of Object.values(grouped)) {
49709
+ skills.sort((a, b) => a.localeCompare(b));
49710
+ }
49711
+ return grouped;
49712
+ }, "registrySkillsByCategory");
49713
+ formatSkillExecutionResult = /* @__PURE__ */ __name((value) => {
49714
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
49715
+ return typeof value === "string" ? value : JSON.stringify(value ?? null);
49716
+ }
49717
+ const result = value;
49718
+ if (result.success === false) {
49719
+ return `skill failed${result.skill ? ` (${String(result.skill)})` : ""}: ${String(result.error ?? "unknown error")}`;
49720
+ }
49721
+ return typeof result.result === "string" ? result.result : JSON.stringify(result);
49722
+ }, "formatSkillExecutionResult");
49107
49723
  reasoningEffortsForModel = /* @__PURE__ */ __name((model) => model?.supportedReasoningEfforts?.map((option) => option.effort) ?? [], "reasoningEffortsForModel");
49108
49724
  resolveReasoningEffortForModel = /* @__PURE__ */ __name((model, requested) => {
49109
49725
  const supported = reasoningEffortsForModel(model);
@@ -49207,25 +49823,6 @@ ${raw}`;
49207
49823
  }
49208
49824
  return [];
49209
49825
  }, "sessionMessageToMarkdown");
49210
- usageSnapshot = /* @__PURE__ */ __name((agentLoop) => {
49211
- const cost = agentLoop.costTracker.getSessionCost();
49212
- const contextMax = agentLoop.contextManager.getContextWindow(agentLoop.model);
49213
- const contextUsed = agentLoop.contextManager.lastPromptTokens;
49214
- return {
49215
- cache_read: cost.totalCacheReadTokens,
49216
- cache_write: cost.totalCacheCreationTokens,
49217
- calls: cost.turns,
49218
- context_max: contextMax,
49219
- context_percent: contextMax > 0 ? Math.round(contextUsed / contextMax * 100) : 0,
49220
- context_used: contextUsed,
49221
- cost_status: "estimated",
49222
- cost_usd: cost.totalCostUSD,
49223
- input: cost.totalInputTokens,
49224
- model: agentLoop.model,
49225
- output: cost.totalOutputTokens,
49226
- total: cost.totalInputTokens + cost.totalOutputTokens + cost.totalCacheReadTokens + cost.totalCacheCreationTokens
49227
- };
49228
- }, "usageSnapshot");
49229
49826
  sessionInfoSnapshot = /* @__PURE__ */ __name((agentLoop, toolRegistry) => ({
49230
49827
  cwd: process.cwd(),
49231
49828
  model: agentLoop.model,
@@ -49483,7 +50080,7 @@ var init_gatewayContext = __esm({
49483
50080
  });
49484
50081
 
49485
50082
  // src/domain/paths.ts
49486
- var shortCwd, fmtCwdBranch;
50083
+ var shortCwd, fullCwdBranch;
49487
50084
  var init_paths = __esm({
49488
50085
  "src/domain/paths.ts"() {
49489
50086
  "use strict";
@@ -49492,13 +50089,7 @@ var init_paths = __esm({
49492
50089
  const p = h && cwd.startsWith(h) ? `~${cwd.slice(h.length)}` : cwd;
49493
50090
  return p.length <= max ? p : `\u2026${p.slice(-(max - 1))}`;
49494
50091
  }, "shortCwd");
49495
- fmtCwdBranch = /* @__PURE__ */ __name((cwd, branch, max = 40) => {
49496
- if (!branch) {
49497
- return shortCwd(cwd, max);
49498
- }
49499
- const tag = ` (${branch.length > 16 ? `\u2026${branch.slice(-15)}` : branch})`;
49500
- return `${shortCwd(cwd, Math.max(8, max - tag.length))}${tag}`;
49501
- }, "fmtCwdBranch");
50092
+ fullCwdBranch = /* @__PURE__ */ __name((cwd, branch) => branch ? `${cwd} (${branch})` : cwd, "fullCwdBranch");
49502
50093
  }
49503
50094
  });
49504
50095
 
@@ -49866,9 +50457,9 @@ var init_useVirtualHistory = __esm({
49866
50457
  viewportHeight
49867
50458
  }) => itemCount > 0 && viewportHeight > 0 && !sticky && !liveTailActive, "shouldSetVirtualClamp");
49868
50459
  ensureVirtualItemHeight = /* @__PURE__ */ __name((heights, key, index, estimate, estimateHeight) => {
49869
- const cached8 = heights.get(key);
49870
- if (cached8 !== void 0) {
49871
- return Math.max(1, Math.floor(cached8));
50460
+ const cached7 = heights.get(key);
50461
+ if (cached7 !== void 0) {
50462
+ return Math.max(1, Math.floor(cached7));
49872
50463
  }
49873
50464
  const seeded = Math.max(1, Math.floor(estimateHeight?.(index, key) ?? estimate));
49874
50465
  heights.set(key, seeded);
@@ -51800,7 +52391,7 @@ function createSlashHandler(ctx) {
51800
52391
  }
51801
52392
  }
51802
52393
  }
51803
- gw.request("slash.exec", { command: cmd.slice(1), session_id: sid }).then((r) => {
52394
+ const renderSlashExec = /* @__PURE__ */ __name((r) => {
51804
52395
  if (stale()) {
51805
52396
  return;
51806
52397
  }
@@ -51809,33 +52400,41 @@ function createSlashHandler(ctx) {
51809
52400
  ${body}` : body;
51810
52401
  const long = text.length > 180 || text.split("\n").filter(Boolean).length > 2;
51811
52402
  long ? page(text, parsed.name[0].toUpperCase() + parsed.name.slice(1)) : sys(text);
51812
- }).catch(() => {
51813
- gw.request("command.dispatch", { arg: parsed.arg, name: parsed.name, session_id: sid }).then((raw) => {
51814
- if (stale()) {
51815
- return;
51816
- }
51817
- const d = asCommandDispatch(raw);
51818
- if (!d) {
51819
- return sys("error: invalid response: command.dispatch");
51820
- }
51821
- if (d.type === "exec" || d.type === "plugin") {
51822
- return sys(d.output || "(no output)");
51823
- }
51824
- if (d.type === "alias") {
51825
- return handler(`/${d.target}${argTail}`);
51826
- }
51827
- if (d.type === "skill") {
52403
+ }, "renderSlashExec");
52404
+ const runSlashExec = /* @__PURE__ */ __name(() => {
52405
+ gw.request("slash.exec", { command: cmd.slice(1), session_id: sid }).then(renderSlashExec).catch(guardedErr);
52406
+ }, "runSlashExec");
52407
+ const skillHint = findSkill(parsed.name);
52408
+ if (skillHint) {
52409
+ sys(`\u26A1 loading skill: ${skillHint.name}`);
52410
+ }
52411
+ gw.request("command.dispatch", { arg: parsed.arg, name: parsed.name, session_id: sid }).then((raw) => {
52412
+ if (stale()) {
52413
+ return;
52414
+ }
52415
+ const d = asCommandDispatch(raw);
52416
+ if (!d) {
52417
+ return runSlashExec();
52418
+ }
52419
+ if (d.type === "exec" || d.type === "plugin") {
52420
+ return sys(d.output || "(no output)");
52421
+ }
52422
+ if (d.type === "alias") {
52423
+ return handler(`/${d.target}${argTail}`);
52424
+ }
52425
+ if (d.type === "skill") {
52426
+ if (!skillHint || skillHint.name !== d.name) {
51828
52427
  sys(`\u26A1 loading skill: ${d.name}`);
51829
- return d.message?.trim() ? send(d.message) : sys(`/${parsed.name}: skill payload missing message`);
51830
52428
  }
51831
- if (d.type === "send") {
51832
- if (d.notice?.trim()) {
51833
- sys(d.notice);
51834
- }
51835
- return d.message?.trim() ? send(d.message) : sys(`/${parsed.name}: empty message`);
52429
+ return d.message?.trim() ? send(d.message) : sys(`/${parsed.name}: skill payload missing message`);
52430
+ }
52431
+ if (d.type === "send") {
52432
+ if (d.notice?.trim()) {
52433
+ sys(d.notice);
51836
52434
  }
51837
- }).catch(guardedErr);
51838
- });
52435
+ return d.message?.trim() ? send(d.message) : sys(`/${parsed.name}: empty message`);
52436
+ }
52437
+ }).catch(runSlashExec);
51839
52438
  return true;
51840
52439
  }, "handler");
51841
52440
  return handler;
@@ -51845,6 +52444,7 @@ var init_createSlashHandler = __esm({
51845
52444
  "use strict";
51846
52445
  init_slash();
51847
52446
  init_rpc();
52447
+ init_registry2();
51848
52448
  init_registry4();
51849
52449
  init_uiStore();
51850
52450
  __name(createSlashHandler, "createSlashHandler");
@@ -53462,6 +54062,13 @@ var init_paste = __esm({
53462
54062
 
53463
54063
  // src/app/useSubmission.ts
53464
54064
  import { useCallback as useCallback9, useEffect as useEffect11, useRef as useRef12 } from "react";
54065
+ function resolveCompletionSubmit(value, row, replaceFrom) {
54066
+ if (!row?.text) return null;
54067
+ const text = value.startsWith("/") && row.text.startsWith("/") ? row.text.slice(1) : row.text;
54068
+ const next = value.slice(0, replaceFrom) + text;
54069
+ if (next === value) return null;
54070
+ return { next, submit: value.startsWith("/") && row.text.startsWith("/") && row.meta?.startsWith("skill \xB7") === true };
54071
+ }
53465
54072
  function useSubmission(opts) {
53466
54073
  const {
53467
54074
  appendMessage,
@@ -53686,12 +54293,9 @@ function useSubmission(opts) {
53686
54293
  (value) => {
53687
54294
  if (composerState.completions.length) {
53688
54295
  const row = composerState.completions[composerState.compIdx];
53689
- if (row?.text) {
53690
- const text = value.startsWith("/") && row.text.startsWith("/") ? row.text.slice(1) : row.text;
53691
- const next = value.slice(0, composerState.compReplace) + text;
53692
- if (next !== value) {
53693
- return composerActions.setInput(next);
53694
- }
54296
+ const completion = resolveCompletionSubmit(value, row, composerState.compReplace);
54297
+ if (completion) {
54298
+ return completion.submit ? dispatchSubmission(completion.next) : composerActions.setInput(completion.next);
53695
54299
  }
53696
54300
  }
53697
54301
  if (!value.trim() && !composerState.inputBuf.length) {
@@ -53748,6 +54352,7 @@ var init_useSubmission = __esm({
53748
54352
  return (value) => value.replace(PASTE_SNIPPET_RE, (tok) => byLabel.get(tok)?.shift() ?? tok);
53749
54353
  }, "expandSnips");
53750
54354
  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");
54355
+ __name(resolveCompletionSubmit, "resolveCompletionSubmit");
53751
54356
  __name(useSubmission, "useSubmission");
53752
54357
  }
53753
54358
  });
@@ -54332,7 +54937,7 @@ function useMainApp(gw) {
54332
54937
  const gitBranch = useGitBranch(cwd);
54333
54938
  const appStatus = useMemo7(
54334
54939
  () => ({
54335
- cwdLabel: fmtCwdBranch(cwd, gitBranch),
54940
+ cwdLabel: fullCwdBranch(cwd, gitBranch),
54336
54941
  goodVibesTick,
54337
54942
  sessionStartedAt: ui.sid ? sessionStartedAt : null,
54338
54943
  showStickyPrompt: !!stickyPrompt,
@@ -55402,7 +56007,7 @@ import { useStore as useStore5 } from "@nanostores/react";
55402
56007
  import { useEffect as useEffect14, useMemo as useMemo10, useRef as useRef15, useState as useState15 } from "react";
55403
56008
  import unicodeSpinners from "unicode-animations";
55404
56009
  import { Fragment as Fragment4, jsx as jsx19, jsxs as jsxs10 } from "react/jsx-runtime";
55405
- function FaceTicker({ color, startedAt }) {
56010
+ function useTickerText(startedAt) {
55406
56011
  const ui = useStore5($uiState);
55407
56012
  const style = ui.indicatorStyle;
55408
56013
  const [tick, setTick] = useState15(() => Math.floor(Math.random() * 1e3));
@@ -55425,11 +56030,7 @@ function FaceTicker({ color, startedAt }) {
55425
56030
  const verb = VERBS[verbTick % VERBS.length] ?? "";
55426
56031
  const verbSegment = showVerb ? ` ${padVerb(verb)}` : "";
55427
56032
  const durationSegment = startedAt ? ` \xB7 ${fmtDuration(now2 - startedAt)}` : "";
55428
- return /* @__PURE__ */ jsxs10(Text9, { color, children: [
55429
- frame,
55430
- verbSegment,
55431
- durationSegment
55432
- ] });
56033
+ return `${frame}${verbSegment}${durationSegment}`;
55433
56034
  }
55434
56035
  function ctxBarColor(pct, t) {
55435
56036
  if (pct == null) {
@@ -55451,6 +56052,27 @@ function ctxBar(pct, w = 10) {
55451
56052
  const filled = Math.round(p / 100 * w);
55452
56053
  return "\u2588".repeat(filled) + "\u2591".repeat(w - filled);
55453
56054
  }
56055
+ function fitVisible(text, width) {
56056
+ if (width <= 0) return "";
56057
+ if (stringWidth(text) <= width) return text + " ".repeat(width - stringWidth(text));
56058
+ let out = "";
56059
+ for (const ch of Array.from(text)) {
56060
+ if (stringWidth(out + ch + "\u2026") > width) break;
56061
+ out += ch;
56062
+ }
56063
+ return out + "\u2026" + " ".repeat(Math.max(0, width - stringWidth(out + "\u2026")));
56064
+ }
56065
+ function truncateTailVisible(text, width) {
56066
+ return fitVisible(text, width);
56067
+ }
56068
+ function cwdBranchWidth(cols) {
56069
+ const desired = Math.max(18, Math.floor(cols * 0.38));
56070
+ const maxByCols = Math.max(6, cols - 24);
56071
+ return Math.max(6, Math.min(64, desired, maxByCols));
56072
+ }
56073
+ function buildActivityText(status, width) {
56074
+ return fitVisible(` ${status}`, width);
56075
+ }
55454
56076
  function SpawnHud({ t }) {
55455
56077
  const delegation = useStore5($delegationState);
55456
56078
  const subagents = useTurnSelector((state) => state.subagents);
@@ -55515,12 +56137,14 @@ function GoodVibesHeart({ tick, t }) {
55515
56137
  }
55516
56138
  return /* @__PURE__ */ jsx19(Text9, { color, children: "\u2665" });
55517
56139
  }
56140
+ function ActivityStatusLine({ busy, cols, status, statusColor: statusColor2, turnStartedAt }) {
56141
+ const tickerStatus = useTickerText(busy ? turnStartedAt : null);
56142
+ const text = buildActivityText(busy ? tickerStatus : status, cols);
56143
+ return /* @__PURE__ */ jsx19(Box_default, { height: 1, width: cols, children: /* @__PURE__ */ jsx19(Text9, { color: statusColor2, children: text }) });
56144
+ }
55518
56145
  function StatusRule({
55519
56146
  cwdLabel,
55520
56147
  cols,
55521
- busy,
55522
- status,
55523
- statusColor: statusColor2,
55524
56148
  model,
55525
56149
  modelFast,
55526
56150
  modelReasoningEffort,
@@ -55528,7 +56152,6 @@ function StatusRule({
55528
56152
  bgCount,
55529
56153
  sessionStartedAt,
55530
56154
  showCost,
55531
- turnStartedAt,
55532
56155
  voiceLabel,
55533
56156
  t
55534
56157
  }) {
@@ -55536,29 +56159,15 @@ function StatusRule({
55536
56159
  const barColor = ctxBarColor(pct, t);
55537
56160
  const ctxLabel = usage2.context_max ? `${fmtK(usage2.context_used ?? 0)}/${fmtK(usage2.context_max)}` : usage2.total > 0 ? `${fmtK(usage2.total)} tok` : "";
55538
56161
  const bar = usage2.context_max ? ctxBar(pct) : "";
55539
- const leftWidth = Math.max(12, cols - cwdLabel.length - 3);
55540
- return /* @__PURE__ */ jsxs10(Box_default, { height: 1, children: [
55541
- /* @__PURE__ */ jsx19(Box_default, { flexShrink: 1, width: leftWidth, children: /* @__PURE__ */ jsxs10(Text9, { color: t.color.border, wrap: "truncate-end", children: [
55542
- "\u2500 ",
55543
- busy ? /* @__PURE__ */ jsx19(FaceTicker, { color: statusColor2, startedAt: turnStartedAt }) : /* @__PURE__ */ jsx19(Text9, { color: statusColor2, children: status }),
55544
- /* @__PURE__ */ jsxs10(Text9, { color: t.color.systemNote, children: [
55545
- " \u2502 ",
55546
- modelLabel(model, modelReasoningEffort, modelFast)
55547
- ] }),
55548
- ctxLabel ? /* @__PURE__ */ jsxs10(Text9, { color: t.color.systemNote, children: [
55549
- " \u2502 ",
55550
- ctxLabel
55551
- ] }) : null,
55552
- bar ? /* @__PURE__ */ jsxs10(Text9, { color: t.color.systemNote, children: [
55553
- " \u2502 ",
55554
- /* @__PURE__ */ jsxs10(Text9, { color: barColor, children: [
55555
- "[",
55556
- bar,
55557
- "]"
55558
- ] }),
55559
- " ",
55560
- /* @__PURE__ */ jsx19(Text9, { color: barColor, children: pct != null ? `${pct}%` : "" })
55561
- ] }) : null,
56162
+ const cwdWidth = cwdBranchWidth(cols);
56163
+ const modelText = modelLabel(model, modelReasoningEffort, modelFast);
56164
+ return /* @__PURE__ */ jsxs10(Box_default, { height: 1, width: cols, children: [
56165
+ /* @__PURE__ */ jsx19(Text9, { color: t.color.border, children: "\u2500 " }),
56166
+ /* @__PURE__ */ jsx19(Box_default, { flexShrink: 0, width: cwdWidth, children: /* @__PURE__ */ jsx19(Text9, { color: t.color.systemNote, children: truncateTailVisible(cwdLabel, cwdWidth) }) }),
56167
+ /* @__PURE__ */ jsx19(Box_default, { flexShrink: 1, children: /* @__PURE__ */ jsxs10(Text9, { color: t.color.systemNote, wrap: "truncate-end", children: [
56168
+ modelText ? ` \u2502 ${modelText}` : "",
56169
+ ctxLabel ? ` \u2502 ${ctxLabel}` : "",
56170
+ bar ? /* @__PURE__ */ jsx19(Text9, { color: barColor, children: statusContextMeter(bar, pct) }) : null,
55562
56171
  sessionStartedAt ? /* @__PURE__ */ jsxs10(Text9, { color: t.color.systemNote, children: [
55563
56172
  " \u2502 ",
55564
56173
  /* @__PURE__ */ jsx19(SessionDuration, { startedAt: sessionStartedAt })
@@ -55590,9 +56199,7 @@ function StatusRule({
55590
56199
  " \u2502 $",
55591
56200
  usage2.cost_usd.toFixed(4)
55592
56201
  ] }) : null
55593
- ] }) }),
55594
- /* @__PURE__ */ jsx19(Text9, { color: t.color.border, children: " \u2500 " }),
55595
- /* @__PURE__ */ jsx19(Text9, { color: t.color.systemNote, children: cwdLabel })
56202
+ ] }) })
55596
56203
  ] });
55597
56204
  }
55598
56205
  function FloatBox({ children, color }) {
@@ -55667,7 +56274,7 @@ function TranscriptScrollbar({ focused = false, scrollRef, t }) {
55667
56274
  }
55668
56275
  );
55669
56276
  }
55670
- var FACE_TICK_MS, VERB_PAD_LEN, padVerb, EMOJI_FRAMES, ASCII_FRAMES, SPINNER_TICK_MS, renderIndicator, effortLabel, shortModelLabel, modelLabel;
56277
+ var FACE_TICK_MS, VERB_PAD_LEN, padVerb, EMOJI_FRAMES, ASCII_FRAMES, SPINNER_TICK_MS, renderIndicator, statusContextPercent, statusContextMeter, effortLabel, shortModelLabel, modelLabel;
55671
56278
  var init_appChrome = __esm({
55672
56279
  "src/components/appChrome.tsx"() {
55673
56280
  "use strict";
@@ -55678,6 +56285,7 @@ var init_appChrome = __esm({
55678
56285
  init_faces();
55679
56286
  init_verbs();
55680
56287
  init_messages();
56288
+ init_usage();
55681
56289
  init_viewport();
55682
56290
  init_subagentTree();
55683
56291
  init_text();
@@ -55710,9 +56318,15 @@ var init_appChrome = __esm({
55710
56318
  const frame = spinner.frames[tick % spinner.frames.length] ?? "\u280B";
55711
56319
  return { frame, intervalMs: Math.max(SPINNER_TICK_MS, spinner.interval), showVerb: false };
55712
56320
  }, "renderIndicator");
55713
- __name(FaceTicker, "FaceTicker");
56321
+ __name(useTickerText, "useTickerText");
55714
56322
  __name(ctxBarColor, "ctxBarColor");
55715
56323
  __name(ctxBar, "ctxBar");
56324
+ statusContextPercent = /* @__PURE__ */ __name((pct) => formatContextPercent(pct).padStart(5), "statusContextPercent");
56325
+ statusContextMeter = /* @__PURE__ */ __name((bar, pct) => ` \u2502 [${bar}] ${statusContextPercent(pct)}`, "statusContextMeter");
56326
+ __name(fitVisible, "fitVisible");
56327
+ __name(truncateTailVisible, "truncateTailVisible");
56328
+ __name(cwdBranchWidth, "cwdBranchWidth");
56329
+ __name(buildActivityText, "buildActivityText");
55716
56330
  __name(SpawnHud, "SpawnHud");
55717
56331
  __name(SessionDuration, "SessionDuration");
55718
56332
  effortLabel = /* @__PURE__ */ __name((effort) => {
@@ -55722,6 +56336,7 @@ var init_appChrome = __esm({
55722
56336
  shortModelLabel = /* @__PURE__ */ __name((model) => model.split("/").pop().replace(/^claude[-_]/, "").replace(/^anthropic[-_]/, "").replace(/[-_]/g, " ").replace(/\b(\d+)\s+(\d+)\b/g, "$1.$2").trim(), "shortModelLabel");
55723
56337
  modelLabel = /* @__PURE__ */ __name((model, effort, fast) => [shortModelLabel(model), effortLabel(effort), fast ? "fast" : ""].filter(Boolean).join(" "), "modelLabel");
55724
56338
  __name(GoodVibesHeart, "GoodVibesHeart");
56339
+ __name(ActivityStatusLine, "ActivityStatusLine");
55725
56340
  __name(StatusRule, "StatusRule");
55726
56341
  __name(FloatBox, "FloatBox");
55727
56342
  __name(StickyPromptTracker, "StickyPromptTracker");
@@ -59890,9 +60505,9 @@ function MdImpl({ compact, t, text }) {
59890
60505
  const nodes = useMemo14(() => {
59891
60506
  const bucket = cacheBucket(t);
59892
60507
  const cacheKey = `${compact ? "1" : "0"}|${text}`;
59893
- const cached8 = cacheGet(bucket, cacheKey);
59894
- if (cached8) {
59895
- return cached8;
60508
+ const cached7 = cacheGet(bucket, cacheKey);
60509
+ if (cached7) {
60510
+ return cached7;
59896
60511
  }
59897
60512
  const lines = ensureEmojiPresentation(text).split("\n");
59898
60513
  const nodes2 = [];
@@ -61928,26 +62543,34 @@ var init_appLayout = __esm({
61928
62543
  if (ui.statusBar !== at) {
61929
62544
  return null;
61930
62545
  }
61931
- return /* @__PURE__ */ jsx41(Box_default, { marginTop: at === "top" ? 1 : 0, children: /* @__PURE__ */ jsx41(
61932
- StatusRule,
61933
- {
61934
- bgCount: ui.bgTasks.size,
61935
- busy: ui.busy,
61936
- cols: composer.cols,
61937
- cwdLabel: status.cwdLabel,
61938
- model: ui.info?.model ?? "",
61939
- modelFast: ui.info?.fast || ui.info?.service_tier === "priority",
61940
- modelReasoningEffort: ui.info?.reasoning_effort,
61941
- sessionStartedAt: status.sessionStartedAt,
61942
- showCost: ui.showCost,
61943
- status: ui.status,
61944
- statusColor: status.statusColor,
61945
- t: ui.theme,
61946
- turnStartedAt: status.turnStartedAt,
61947
- usage: ui.usage,
61948
- voiceLabel: status.voiceLabel
61949
- }
61950
- ) });
62546
+ return /* @__PURE__ */ jsxs28(Box_default, { flexDirection: "column", marginTop: at === "top" ? 1 : 0, children: [
62547
+ /* @__PURE__ */ jsx41(
62548
+ ActivityStatusLine,
62549
+ {
62550
+ busy: ui.busy,
62551
+ cols: composer.cols,
62552
+ status: ui.status,
62553
+ statusColor: status.statusColor,
62554
+ turnStartedAt: status.turnStartedAt
62555
+ }
62556
+ ),
62557
+ /* @__PURE__ */ jsx41(
62558
+ StatusRule,
62559
+ {
62560
+ bgCount: ui.bgTasks.size,
62561
+ cols: composer.cols,
62562
+ cwdLabel: status.cwdLabel,
62563
+ model: ui.info?.model ?? "",
62564
+ modelFast: ui.info?.fast || ui.info?.service_tier === "priority",
62565
+ modelReasoningEffort: ui.info?.reasoning_effort,
62566
+ sessionStartedAt: status.sessionStartedAt,
62567
+ showCost: ui.showCost,
62568
+ t: ui.theme,
62569
+ usage: ui.usage,
62570
+ voiceLabel: status.voiceLabel
62571
+ }
62572
+ )
62573
+ ] });
61951
62574
  }, "StatusRulePane"));
61952
62575
  AppLayout = memo7(/* @__PURE__ */ __name(function AppLayout2({
61953
62576
  actions,