@charzhu/openjaw-agent 0.2.6 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -2803,6 +2803,34 @@ var init_cost_tracker = __esm({
2803
2803
  }
2804
2804
  });
2805
2805
 
2806
+ // src/domain/usage.ts
2807
+ var ZERO, contextPercent, formatContextPercent;
2808
+ var init_usage = __esm({
2809
+ "src/domain/usage.ts"() {
2810
+ "use strict";
2811
+ ZERO = { calls: 0, input: 0, output: 0, total: 0 };
2812
+ contextPercent = /* @__PURE__ */ __name((used, max) => {
2813
+ if (!Number.isFinite(used) || !Number.isFinite(max) || used <= 0 || max <= 0) {
2814
+ return 0;
2815
+ }
2816
+ const raw = used / max * 100;
2817
+ return raw < 10 ? Math.round(raw * 10) / 10 : Math.round(raw);
2818
+ }, "contextPercent");
2819
+ formatContextPercent = /* @__PURE__ */ __name((pct) => {
2820
+ if (pct == null || !Number.isFinite(pct) || pct <= 0) {
2821
+ return "0%";
2822
+ }
2823
+ if (pct < 0.1) {
2824
+ return "<0.1%";
2825
+ }
2826
+ if (pct < 10) {
2827
+ return `${pct.toFixed(1).replace(/\.0$/, "")}%`;
2828
+ }
2829
+ return `${Math.round(pct)}%`;
2830
+ }, "formatContextPercent");
2831
+ }
2832
+ });
2833
+
2806
2834
  // src/context-manager.ts
2807
2835
  function contextKey(provider, model) {
2808
2836
  return `${provider}\0${model}`;
@@ -2824,6 +2852,7 @@ var CONTEXT_WINDOWS, DEFAULT_CONTEXT_WINDOW, ContextManager;
2824
2852
  var init_context_manager = __esm({
2825
2853
  "src/context-manager.ts"() {
2826
2854
  "use strict";
2855
+ init_usage();
2827
2856
  CONTEXT_WINDOWS = {
2828
2857
  "claude-sonnet-4": 2e5,
2829
2858
  "claude-sonnet-4.5": 2e5,
@@ -2877,7 +2906,7 @@ var init_context_manager = __esm({
2877
2906
  const liveContextWindow = this.activeProvider ? this.liveModelContextWindows.get(contextKey(this.activeProvider, model)) : void 0;
2878
2907
  return getContextWindow(model, { contextWindow: liveContextWindow });
2879
2908
  }
2880
- /** Update from actual API response usage */
2909
+ /** Update latest effective context footprint from actual API response usage. */
2881
2910
  updateFromUsage(usage2) {
2882
2911
  this.lastTotalTokens = usage2.inputTokens + usage2.outputTokens + usage2.cacheReadTokens + usage2.cacheCreationTokens;
2883
2912
  }
@@ -2897,7 +2926,7 @@ var init_context_manager = __esm({
2897
2926
  /** Get context usage as percentage */
2898
2927
  getUsagePercent(model) {
2899
2928
  const limit = this.getContextWindow(model);
2900
- return limit > 0 ? Math.round(this.lastTotalTokens / limit * 100) : 0;
2929
+ return contextPercent(this.lastTotalTokens, limit);
2901
2930
  }
2902
2931
  /** Format context display string */
2903
2932
  formatContext(model) {
@@ -8977,9 +9006,9 @@ var init_outlook_graph = __esm({
8977
9006
  const wellKnown = WELL_KNOWN_FOLDERS[lower];
8978
9007
  if (wellKnown)
8979
9008
  return wellKnown;
8980
- const cached8 = this.folderIdCache.get(lower);
8981
- if (cached8)
8982
- return cached8;
9009
+ const cached7 = this.folderIdCache.get(lower);
9010
+ if (cached7)
9011
+ return cached7;
8983
9012
  try {
8984
9013
  const result = await this.graphGet(`/me/mailFolders?$filter=displayName eq '${folderName.replace(/'/g, "''")}'&$top=1`);
8985
9014
  if (result.value.length > 0) {
@@ -9337,9 +9366,9 @@ var init_db = __esm({
9337
9366
  import { createHash as createHash2 } from "node:crypto";
9338
9367
  function encodeAtom(word, dim2 = HRR_DIM) {
9339
9368
  const cacheKey = `${word}:${dim2}`;
9340
- const cached8 = atomCache.get(cacheKey);
9341
- if (cached8)
9342
- return cached8;
9369
+ const cached7 = atomCache.get(cacheKey);
9370
+ if (cached7)
9371
+ return cached7;
9343
9372
  const phases = new Float64Array(dim2);
9344
9373
  const bytesNeeded = dim2 * 4;
9345
9374
  const chunks = [];
@@ -12555,9 +12584,9 @@ ${loopContent}` : loopContent;
12555
12584
  */
12556
12585
  async findChannelByName(searchTerm) {
12557
12586
  const searchLower = searchTerm.toLowerCase();
12558
- const cached8 = this.channelIdCache.get(searchLower);
12559
- if (cached8) {
12560
- return { ...cached8, displayName: searchTerm };
12587
+ const cached7 = this.channelIdCache.get(searchLower);
12588
+ if (cached7) {
12589
+ return { ...cached7, displayName: searchTerm };
12561
12590
  }
12562
12591
  try {
12563
12592
  const teamsData = await this.graphGet("/v1.0/me/joinedTeams");
@@ -12737,9 +12766,9 @@ ${loopContent}` : loopContent;
12737
12766
  * Results are cached per chat ID.
12738
12767
  */
12739
12768
  async getChatMembers(chatOrChannelId) {
12740
- const cached8 = this.chatMembersCache.get(chatOrChannelId);
12741
- if (cached8)
12742
- return cached8;
12769
+ const cached7 = this.chatMembersCache.get(chatOrChannelId);
12770
+ if (cached7)
12771
+ return cached7;
12743
12772
  try {
12744
12773
  let endpoint;
12745
12774
  if (this.contextType === "channel" && this.currentChannelTeamId && chatOrChannelId === this.currentChannelId) {
@@ -12977,9 +13006,9 @@ ${loopContent}` : loopContent;
12977
13006
  */
12978
13007
  async resolveChannelIds(teamName, channelName) {
12979
13008
  const cacheKey = `${teamName}/${channelName}`;
12980
- const cached8 = this.channelIdCache.get(cacheKey);
12981
- if (cached8)
12982
- return cached8;
13009
+ const cached7 = this.channelIdCache.get(cacheKey);
13010
+ if (cached7)
13011
+ return cached7;
12983
13012
  const teamsData = await this.graphGet("/v1.0/me/joinedTeams");
12984
13013
  const team = teamsData.value?.find((t) => t.displayName.toLowerCase() === teamName.toLowerCase());
12985
13014
  if (!team) {
@@ -19971,38 +20000,64 @@ var init_frontmatter = __esm({
19971
20000
  });
19972
20001
 
19973
20002
  // src/skills/registry.ts
19974
- import { existsSync as existsSync14, readFileSync as readFileSync14, readdirSync as readdirSync2 } from "node:fs";
20003
+ import { existsSync as existsSync14, readFileSync as readFileSync14, readdirSync as readdirSync2, statSync } from "node:fs";
19975
20004
  import { join as join22 } from "node:path";
19976
20005
  import { homedir as homedir12 } from "node:os";
20006
+ function skillRoots() {
20007
+ return rootsOverride ?? { bundledDir: packageSkillsDir(), userDir: DEFAULT_USER_DIR };
20008
+ }
19977
20009
  function discoverSkills() {
19978
- if (cachedSkills) return cachedSkills;
20010
+ const signature = buildRegistrySignature();
20011
+ if (cachedSkills?.signature === signature) return cachedSkills.skills;
20012
+ const roots = skillRoots();
19979
20013
  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;
20014
+ loadFlatSkillsFromDir(roots.bundledDir, "bundled", 0, byName2);
20015
+ loadFlatSkillsFromDir(roots.userDir, "user", 1, byName2);
20016
+ loadPackagedSkillsFromDir(roots.userDir, byName2);
20017
+ const skills = [...byName2.values()].map((entry) => entry.skill).sort((a, b) => a.name.localeCompare(b.name));
20018
+ cachedSkills = { signature, skills };
20019
+ return skills;
19984
20020
  }
19985
20021
  function loadSkillBody(skillName) {
19986
- const skills = discoverSkills();
19987
- const skill = skills.find((s) => s.name === skillName);
20022
+ const skill = findSkill(skillName);
19988
20023
  if (!skill) return null;
19989
20024
  try {
19990
20025
  const content = readFileSync14(skill.filePath, "utf-8").trim();
19991
- const parsed = parseSkillFile(content, skill.name + ".md");
20026
+ const parsed = parseSkillFile(content, `${skill.name}.md`);
19992
20027
  return parsed.body;
19993
20028
  } catch {
19994
20029
  return null;
19995
20030
  }
19996
20031
  }
20032
+ function loadSkillPrompt(skillName) {
20033
+ const skill = findSkill(skillName);
20034
+ const body = skill ? loadSkillBody(skill.name) : null;
20035
+ if (!skill || !body) return null;
20036
+ return `# Skill Runtime Context
20037
+
20038
+ Skill name: ${skill.name}
20039
+ Skill file: ${skill.filePath}
20040
+ Skill root directory: ${skill.rootDir}
20041
+
20042
+ ${body}`;
20043
+ }
19997
20044
  function findSkill(name) {
19998
20045
  const skills = discoverSkills();
19999
- const exact = skills.find((s) => s.name === name);
20046
+ const normalized = normalizeSkillName(name);
20047
+ const exact = skills.find((s) => s.name === normalized);
20000
20048
  if (exact) return exact;
20001
- const lower = name.toLowerCase();
20002
- const ci = skills.find((s) => s.name.toLowerCase() === lower);
20049
+ const ci = skills.find((s) => s.name.toLowerCase() === normalized.toLowerCase());
20003
20050
  if (ci) return ci;
20004
- const partial = skills.find((s) => s.name.startsWith(lower) || lower.startsWith(s.name));
20005
- return partial || null;
20051
+ const matches = findSkillMatches(normalized);
20052
+ return matches.length === 1 ? matches[0] : null;
20053
+ }
20054
+ function findSkillMatches(name) {
20055
+ const normalized = normalizeSkillName(name).toLowerCase();
20056
+ if (!normalized) return [];
20057
+ return discoverSkills().filter((skill) => {
20058
+ const skillName = skill.name.toLowerCase();
20059
+ return skillName.startsWith(normalized) || normalized.startsWith(skillName);
20060
+ });
20006
20061
  }
20007
20062
  function getSkillListing() {
20008
20063
  const skills = discoverSkills();
@@ -20017,52 +20072,145 @@ function getSkillListing() {
20017
20072
  function clearSkillRegistry() {
20018
20073
  cachedSkills = null;
20019
20074
  }
20020
- function loadSkillsFromDir(dir2, source, out) {
20021
- if (!existsSync14(dir2)) return;
20075
+ function normalizeSkillName(name) {
20076
+ return name.trim().replace(/^\/+/, "").toLowerCase();
20077
+ }
20078
+ function loadFlatSkillsFromDir(dir2, source, priority, out) {
20079
+ for (const entry of safeReadDir(dir2)) {
20080
+ if (!entry.isFile() || !isMarkdown(entry.name)) continue;
20081
+ const filePath = join22(dir2, entry.name);
20082
+ const skill = parseSkillAtPath(filePath, dir2, entry.name, source, entry.name);
20083
+ if (skill) putSkill(out, skill, priority);
20084
+ }
20085
+ }
20086
+ function loadPackagedSkillsFromDir(dir2, out) {
20087
+ for (const entry of safeReadDir(dir2)) {
20088
+ if (!entry.isDirectory()) continue;
20089
+ const rootDir = join22(dir2, entry.name);
20090
+ const entrypoint = findPackageEntrypoint(rootDir);
20091
+ if (!entrypoint) continue;
20092
+ const filePath = join22(rootDir, entrypoint);
20093
+ const skill = parseSkillAtPath(filePath, rootDir, entrypoint, "user", `${entry.name}.md`);
20094
+ if (skill) putSkill(out, skill, 2);
20095
+ }
20096
+ }
20097
+ function findPackageEntrypoint(dir2) {
20098
+ const entries = safeReadDir(dir2).filter((entry) => entry.isFile());
20099
+ const entrypoint = ENTRYPOINT_NAMES.find((name) => entries.some((entry) => entry.name.toLowerCase() === name.toLowerCase()));
20100
+ if (entrypoint) return entries.find((entry) => entry.name.toLowerCase() === entrypoint.toLowerCase())?.name ?? entrypoint;
20101
+ const readme = entries.find((entry) => entry.name.toLowerCase() === "readme.md");
20102
+ if (readme) return readme.name;
20103
+ const markdown = entries.filter((entry) => isMarkdown(entry.name));
20104
+ return markdown.length === 1 ? markdown[0].name : null;
20105
+ }
20106
+ function parseSkillAtPath(filePath, rootDir, entrypoint, source, fallbackFilename) {
20022
20107
  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 {
20108
+ const content = readFileSync14(filePath, "utf-8").trim();
20109
+ if (!content) return null;
20110
+ const parsed = parseSkillFile(content, fallbackFilename);
20111
+ const name = normalizeSkillName(parsed.meta.name);
20112
+ if (!name) return null;
20113
+ return {
20114
+ name,
20115
+ meta: { ...parsed.meta, name },
20116
+ filePath,
20117
+ rootDir,
20118
+ entrypoint,
20119
+ source,
20120
+ hasFrontmatter: parsed.hasFrontmatter
20121
+ };
20122
+ } catch {
20123
+ return null;
20124
+ }
20125
+ }
20126
+ function putSkill(out, skill, priority) {
20127
+ const existing = out.get(skill.name);
20128
+ if (!existing || priority >= existing.priority) {
20129
+ out.set(skill.name, { priority, skill });
20130
+ }
20131
+ }
20132
+ function safeReadDir(dir2) {
20133
+ if (!existsSync14(dir2)) return [];
20134
+ try {
20135
+ return readdirSync2(dir2, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
20136
+ } catch {
20137
+ return [];
20138
+ }
20139
+ }
20140
+ function buildRegistrySignature() {
20141
+ const roots = skillRoots();
20142
+ return [signatureForDir(roots.bundledDir), signatureForDir(roots.userDir)].join("|");
20143
+ }
20144
+ function signatureForDir(dir2) {
20145
+ if (!existsSync14(dir2)) return `${dir2}:missing`;
20146
+ const parts = [];
20147
+ try {
20148
+ for (const entry of safeReadDir(dir2)) {
20149
+ const fullPath = join22(dir2, entry.name);
20150
+ if (entry.isFile()) {
20151
+ if (!isMarkdown(entry.name)) continue;
20152
+ parts.push(fileSignature(fullPath, `f:${entry.name}`));
20153
+ } else if (entry.isDirectory()) {
20154
+ parts.push(fileSignature(fullPath, `d:${entry.name}`));
20155
+ for (const child of safeReadDir(fullPath)) {
20156
+ if (child.isFile() && isMarkdown(child.name)) {
20157
+ parts.push(fileSignature(join22(fullPath, child.name), `d:${entry.name}/${child.name}`));
20158
+ }
20159
+ }
20038
20160
  }
20039
20161
  }
20040
20162
  } catch {
20163
+ return `${dir2}:unreadable`;
20164
+ }
20165
+ return `${dir2}:${parts.join(",")}`;
20166
+ }
20167
+ function isMarkdown(name) {
20168
+ return name.toLowerCase().endsWith(".md");
20169
+ }
20170
+ function fileSignature(path3, label) {
20171
+ try {
20172
+ const stat2 = statSync(path3);
20173
+ return `${label}:${stat2.mtimeMs}:${stat2.size}`;
20174
+ } catch {
20175
+ return `${label}:missing`;
20041
20176
  }
20042
20177
  }
20043
- var USER_DIR, cachedSkills;
20178
+ var DEFAULT_USER_DIR, ENTRYPOINT_NAMES, cachedSkills, rootsOverride;
20044
20179
  var init_registry2 = __esm({
20045
20180
  "src/skills/registry.ts"() {
20046
20181
  "use strict";
20047
20182
  init_frontmatter();
20048
20183
  init_packageRoot();
20049
- USER_DIR = join22(homedir12(), ".openjaw-agent", "skills");
20184
+ DEFAULT_USER_DIR = join22(homedir12(), ".openjaw-agent", "skills");
20185
+ ENTRYPOINT_NAMES = ["SKILL.md", "skill.md"];
20050
20186
  cachedSkills = null;
20187
+ rootsOverride = null;
20188
+ __name(skillRoots, "skillRoots");
20051
20189
  __name(discoverSkills, "discoverSkills");
20052
20190
  __name(loadSkillBody, "loadSkillBody");
20191
+ __name(loadSkillPrompt, "loadSkillPrompt");
20053
20192
  __name(findSkill, "findSkill");
20193
+ __name(findSkillMatches, "findSkillMatches");
20054
20194
  __name(getSkillListing, "getSkillListing");
20055
20195
  __name(clearSkillRegistry, "clearSkillRegistry");
20056
- __name(loadSkillsFromDir, "loadSkillsFromDir");
20196
+ __name(normalizeSkillName, "normalizeSkillName");
20197
+ __name(loadFlatSkillsFromDir, "loadFlatSkillsFromDir");
20198
+ __name(loadPackagedSkillsFromDir, "loadPackagedSkillsFromDir");
20199
+ __name(findPackageEntrypoint, "findPackageEntrypoint");
20200
+ __name(parseSkillAtPath, "parseSkillAtPath");
20201
+ __name(putSkill, "putSkill");
20202
+ __name(safeReadDir, "safeReadDir");
20203
+ __name(buildRegistrySignature, "buildRegistrySignature");
20204
+ __name(signatureForDir, "signatureForDir");
20205
+ __name(isMarkdown, "isMarkdown");
20206
+ __name(fileSignature, "fileSignature");
20057
20207
  }
20058
20208
  });
20059
20209
 
20060
20210
  // src/prompts/skills.ts
20061
20211
  function getSkillsSection() {
20062
- if (cached7 !== void 0) return cached7;
20063
20212
  const skills = discoverSkills();
20064
20213
  if (skills.length === 0) {
20065
- cached7 = null;
20066
20214
  return null;
20067
20215
  }
20068
20216
  const listing = getSkillListing();
@@ -20077,15 +20225,17 @@ New skills must be saved to \`~/.openjaw-agent/skills/\` (user skills directory)
20077
20225
  Do NOT write skills to the project's bundled skills/ directory.
20078
20226
 
20079
20227
  ${listing}`;
20080
- cached7 = content;
20081
- return cached7;
20228
+ return content;
20229
+ }
20230
+ function clearSkillsCache() {
20231
+ clearSkillRegistry();
20082
20232
  }
20083
- var cached7;
20084
20233
  var init_skills = __esm({
20085
20234
  "src/prompts/skills.ts"() {
20086
20235
  "use strict";
20087
20236
  init_registry2();
20088
20237
  __name(getSkillsSection, "getSkillsSection");
20238
+ __name(clearSkillsCache, "clearSkillsCache");
20089
20239
  }
20090
20240
  });
20091
20241
 
@@ -20776,8 +20926,8 @@ Options: ${chunk.choices.join(" | ")}` : "";
20776
20926
  if (fileName.startsWith("oj-") || fileName.endsWith(".log") || fileName.endsWith(".tmp")) continue;
20777
20927
  if (existsSync16(resolvedPath)) {
20778
20928
  try {
20779
- const { statSync: statSync4 } = await import("node:fs");
20780
- const stat2 = statSync4(resolvedPath);
20929
+ const { statSync: statSync5 } = await import("node:fs");
20930
+ const stat2 = statSync5(resolvedPath);
20781
20931
  if (stat2.isFile() && stat2.size < 50 * 1024 * 1024) {
20782
20932
  const ext = resolvedPath.split(".").pop()?.toLowerCase() || "";
20783
20933
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
@@ -21629,10 +21779,10 @@ var init_mcp_client = __esm({
21629
21779
  let tokenExpiry = 0;
21630
21780
  if (copilotTokenFile) {
21631
21781
  try {
21632
- const cached8 = JSON.parse(readFileSync17(copilotTokenFile, "utf-8"));
21633
- cachedToken = cached8.accessToken;
21634
- cachedRefreshToken = cached8.refreshToken;
21635
- tokenExpiry = cached8.expiresAt || 0;
21782
+ const cached7 = JSON.parse(readFileSync17(copilotTokenFile, "utf-8"));
21783
+ cachedToken = cached7.accessToken;
21784
+ cachedRefreshToken = cached7.refreshToken;
21785
+ tokenExpiry = cached7.expiresAt || 0;
21636
21786
  } catch {
21637
21787
  }
21638
21788
  }
@@ -26423,7 +26573,7 @@ function createSkillTool(config, toolRegistry, systemPromptFn) {
26423
26573
  };
26424
26574
  }
26425
26575
  async function executeSkill(skillName, args, config, toolRegistry, systemPromptFn) {
26426
- const body = loadSkillBody(skillName);
26576
+ const body = loadSkillPrompt(skillName);
26427
26577
  if (!body) {
26428
26578
  return { success: false, error: `Could not load skill content for "${skillName}"` };
26429
26579
  }
@@ -26786,8 +26936,8 @@ var init_teams = __esm({
26786
26936
  if (fileName.startsWith("oj-") || fileName.endsWith(".log") || fileName.endsWith(".tmp")) continue;
26787
26937
  if (existsSync25(filePath)) {
26788
26938
  try {
26789
- const { statSync: statSync4 } = await import("node:fs");
26790
- const stat2 = statSync4(filePath);
26939
+ const { statSync: statSync5 } = await import("node:fs");
26940
+ const stat2 = statSync5(filePath);
26791
26941
  if (stat2.isFile() && stat2.size < 50 * 1024 * 1024) {
26792
26942
  await this.sendFileToSelfChat(filePath);
26793
26943
  sentFiles.add(filePath);
@@ -27346,8 +27496,8 @@ var init_feishu = __esm({
27346
27496
  this.emit({ type: "system", content: `\u{1F50D} Found path: ${filePath} (exists: ${exists})` });
27347
27497
  if (exists) {
27348
27498
  try {
27349
- const { statSync: statSync4 } = await import("node:fs");
27350
- const stat2 = statSync4(filePath);
27499
+ const { statSync: statSync5 } = await import("node:fs");
27500
+ const stat2 = statSync5(filePath);
27351
27501
  if (stat2.isFile() && stat2.size < 30 * 1024 * 1024) {
27352
27502
  const fileType = this.getFeishuFileType(fileName);
27353
27503
  this.emit({ type: "system", content: `\u{1F4E4} Uploading to Feishu: ${fileName} (${(stat2.size / 1024).toFixed(0)}KB, type=${fileType})` });
@@ -27443,7 +27593,7 @@ __export(wechat_exports, {
27443
27593
  sniffMediaKind: () => sniffMediaKind,
27444
27594
  validateMediaForUpload: () => validateMediaForUpload
27445
27595
  });
27446
- import { existsSync as existsSync27, readFileSync as readFileSync26, writeFileSync as writeFileSync18, unlinkSync as unlinkSync7, statSync as statSync2 } from "node:fs";
27596
+ import { existsSync as existsSync27, readFileSync as readFileSync26, writeFileSync as writeFileSync18, unlinkSync as unlinkSync7, statSync as statSync3 } from "node:fs";
27447
27597
  import { mkdirSync as mkdirSync16 } from "node:fs";
27448
27598
  import { extname as extname3, join as join35 } from "node:path";
27449
27599
  import { homedir as homedir21 } from "node:os";
@@ -28314,7 +28464,7 @@ Scan URL manually: ${qrUrl}` });
28314
28464
  attemptedFiles.add(dedupKey);
28315
28465
  attempted++;
28316
28466
  try {
28317
- const stat2 = statSync2(filePath);
28467
+ const stat2 = statSync3(filePath);
28318
28468
  if (!stat2.isFile() || stat2.size > 30 * 1024 * 1024) continue;
28319
28469
  if (stat2.size === 0) {
28320
28470
  this.emit({ type: "system", content: `\u26A0 WeChat skipped empty file: ${fileName}` });
@@ -29314,12 +29464,40 @@ var init_bootstrap = __esm({
29314
29464
  }
29315
29465
  });
29316
29466
 
29467
+ // src/usageSnapshot.ts
29468
+ function usageSnapshot(agentLoop) {
29469
+ const cost = agentLoop.costTracker.getSessionCost();
29470
+ const contextMax = agentLoop.contextManager.getContextWindow(agentLoop.model);
29471
+ const contextUsed = agentLoop.contextManager.lastPromptTokens;
29472
+ return {
29473
+ cache_read: cost.totalCacheReadTokens,
29474
+ cache_write: cost.totalCacheCreationTokens,
29475
+ calls: cost.turns,
29476
+ context_max: contextMax,
29477
+ context_percent: contextPercent(contextUsed, contextMax),
29478
+ context_used: contextUsed,
29479
+ cost_status: "estimated",
29480
+ cost_usd: cost.totalCostUSD,
29481
+ input: cost.totalInputTokens,
29482
+ model: agentLoop.model,
29483
+ output: cost.totalOutputTokens,
29484
+ total: cost.totalInputTokens + cost.totalOutputTokens + cost.totalCacheReadTokens + cost.totalCacheCreationTokens
29485
+ };
29486
+ }
29487
+ var init_usageSnapshot = __esm({
29488
+ "src/usageSnapshot.ts"() {
29489
+ "use strict";
29490
+ init_usage();
29491
+ __name(usageSnapshot, "usageSnapshot");
29492
+ }
29493
+ });
29494
+
29317
29495
  // src/eventBridge.ts
29318
29496
  import { randomUUID as randomUUID11 } from "node:crypto";
29319
29497
  async function streamAgentRun(options) {
29320
29498
  const { agentLoop, bus, imageData, systemPrompt, text } = options;
29321
29499
  const sid = agentLoop.sessionId;
29322
- const state = newState();
29500
+ const state = newState(agentLoop);
29323
29501
  bus.emitEvent({
29324
29502
  payload: { kind: "thinking", text: "thinking\u2026" },
29325
29503
  session_id: sid,
@@ -29409,11 +29587,13 @@ function translateChunk(chunk, state, bus, sid) {
29409
29587
  }
29410
29588
  }
29411
29589
  }
29412
- var newState, ensureMessageOpen, closeMessage;
29590
+ var newState, ensureMessageOpen, closeMessage, safeUsageSnapshot;
29413
29591
  var init_eventBridge = __esm({
29414
29592
  "src/eventBridge.ts"() {
29415
29593
  "use strict";
29416
- newState = /* @__PURE__ */ __name(() => ({
29594
+ init_usageSnapshot();
29595
+ newState = /* @__PURE__ */ __name((agentLoop) => ({
29596
+ agentLoop,
29417
29597
  messageOpen: false,
29418
29598
  pendingAnswer: "",
29419
29599
  toolIds: /* @__PURE__ */ new Map()
@@ -29426,8 +29606,9 @@ var init_eventBridge = __esm({
29426
29606
  }, "ensureMessageOpen");
29427
29607
  closeMessage = /* @__PURE__ */ __name((state, bus, sid, text) => {
29428
29608
  if (state.messageOpen || text) {
29609
+ const usage2 = safeUsageSnapshot(state.agentLoop);
29429
29610
  bus.emitEvent({
29430
- payload: { text: text ?? state.pendingAnswer },
29611
+ payload: { text: text ?? state.pendingAnswer, ...usage2 ? { usage: usage2 } : {} },
29431
29612
  session_id: sid,
29432
29613
  type: "message.complete"
29433
29614
  });
@@ -29435,6 +29616,13 @@ var init_eventBridge = __esm({
29435
29616
  state.pendingAnswer = "";
29436
29617
  }
29437
29618
  }, "closeMessage");
29619
+ safeUsageSnapshot = /* @__PURE__ */ __name((agentLoop) => {
29620
+ try {
29621
+ return usageSnapshot(agentLoop);
29622
+ } catch {
29623
+ return void 0;
29624
+ }
29625
+ }, "safeUsageSnapshot");
29438
29626
  __name(streamAgentRun, "streamAgentRun");
29439
29627
  __name(translateChunk, "translateChunk");
29440
29628
  }
@@ -29514,15 +29702,6 @@ var init_env = __esm({
29514
29702
  }
29515
29703
  });
29516
29704
 
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
29705
  // src/theme.ts
29527
29706
  function parseHex(h) {
29528
29707
  const m = /^#?([0-9a-f]{6})$/i.exec(h);
@@ -29692,6 +29871,14 @@ function normalizeThemeForAnsiLightTerminal(theme, env2 = process.env, isLight =
29692
29871
  }
29693
29872
  return { ...theme, color };
29694
29873
  }
29874
+ function isBuiltinSkinName(value) {
29875
+ return BUILTIN_SKINS.some((skin) => skin.name === value);
29876
+ }
29877
+ function themeForBuiltinSkin(name) {
29878
+ if (name === "dark") return DARK_THEME;
29879
+ if (name === "light") return LIGHT_THEME;
29880
+ return DEFAULT_THEME;
29881
+ }
29695
29882
  function fromSkin(colors, branding, bannerLogo = "", bannerHero = "", toolPrefix = "", helpHeader = "") {
29696
29883
  const d = DEFAULT_THEME;
29697
29884
  const c = /* @__PURE__ */ __name((k) => colors[k], "c");
@@ -29749,7 +29936,7 @@ function fromSkin(colors, branding, bannerLogo = "", bannerHero = "", toolPrefix
29749
29936
  bannerHero
29750
29937
  }, process.env, DEFAULT_LIGHT_MODE);
29751
29938
  }
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;
29939
+ var XTERM_6_LEVELS, ANSI_LIGHT_MAX_LUMINANCE, ANSI_LIGHT_TARGET_LUMINANCE, ANSI_LIGHT_MIN_SATURATION, ANSI_MUTED_BUCKET, ANSI_NORMALIZED_FOREGROUNDS, ANSI_MUTED_FOREGROUNDS, BRAND, cleanPromptSymbol, DARK_THEME, LIGHT_THEME, TRUE_RE2, FALSE_RE2, LIGHT_DEFAULT_TERM_PROGRAMS, LUMA_LIGHT_THRESHOLD, HEX_3_RE, HEX_6_RE, DEFAULT_LIGHT_MODE, DEFAULT_THEME, BUILTIN_SKINS;
29753
29940
  var init_theme = __esm({
29754
29941
  "src/theme.ts"() {
29755
29942
  "use strict";
@@ -29901,6 +30088,13 @@ var init_theme = __esm({
29901
30088
  process.env,
29902
30089
  DEFAULT_LIGHT_MODE
29903
30090
  );
30091
+ BUILTIN_SKINS = [
30092
+ { name: "default", description: "Auto-detect terminal light/dark preference" },
30093
+ { name: "dark", description: "OpenJaw dark coral theme" },
30094
+ { name: "light", description: "Light terminal theme" }
30095
+ ];
30096
+ __name(isBuiltinSkinName, "isBuiltinSkinName");
30097
+ __name(themeForBuiltinSkin, "themeForBuiltinSkin");
29904
30098
  __name(fromSkin, "fromSkin");
29905
30099
  }
29906
30100
  });
@@ -31669,11 +31863,11 @@ function sliceAnsi(str, start, end) {
31669
31863
  }
31670
31864
  if (end !== void 0) {
31671
31865
  const key = `${start}|${end}|${str}`;
31672
- const cached8 = sliceCache.get(key);
31673
- if (cached8 !== void 0) {
31866
+ const cached7 = sliceCache.get(key);
31867
+ if (cached7 !== void 0) {
31674
31868
  sliceCache.delete(key);
31675
- sliceCache.set(key, cached8);
31676
- return cached8;
31869
+ sliceCache.set(key, cached7);
31870
+ return cached7;
31677
31871
  }
31678
31872
  const result = computeSlice(str, start, end);
31679
31873
  if (sliceCache.size >= SLICE_CACHE_LIMIT) {
@@ -31728,11 +31922,11 @@ function computeSlice(str, start, end) {
31728
31922
  return result;
31729
31923
  }
31730
31924
  function lineWidth(line) {
31731
- const cached8 = cache.get(line);
31732
- if (cached8 !== void 0) {
31925
+ const cached7 = cache.get(line);
31926
+ if (cached7 !== void 0) {
31733
31927
  cache.delete(line);
31734
- cache.set(line, cached8);
31735
- return cached8;
31928
+ cache.set(line, cached7);
31929
+ return cached7;
31736
31930
  }
31737
31931
  const width = stringWidth(line);
31738
31932
  if (cache.size >= MAX_CACHE_SIZE) {
@@ -31749,11 +31943,11 @@ function evictLineWidthCache(keepRatio = 0) {
31749
31943
  }
31750
31944
  function memoizedWrap(text, maxWidth, wrapType) {
31751
31945
  const key = `${maxWidth}|${wrapType}|${text}`;
31752
- const cached8 = wrapCache.get(key);
31753
- if (cached8 !== void 0) {
31946
+ const cached7 = wrapCache.get(key);
31947
+ if (cached7 !== void 0) {
31754
31948
  wrapCache.delete(key);
31755
- wrapCache.set(key, cached8);
31756
- return cached8;
31949
+ wrapCache.set(key, cached7);
31950
+ return cached7;
31757
31951
  }
31758
31952
  const result = computeWrap(text, maxWidth, wrapType);
31759
31953
  if (wrapCache.size >= WRAP_CACHE_LIMIT) {
@@ -33713,9 +33907,9 @@ function collectRemovedRects(parent, removed, underAbsolute = false) {
33713
33907
  }
33714
33908
  const elem = removed;
33715
33909
  const isAbsolute2 = underAbsolute || elem.style.position === "absolute";
33716
- const cached8 = nodeCache.get(elem);
33717
- if (cached8) {
33718
- addPendingClear(parent, cached8, isAbsolute2);
33910
+ const cached7 = nodeCache.get(elem);
33911
+ if (cached7) {
33912
+ addPendingClear(parent, cached7, isAbsolute2);
33719
33913
  nodeCache.delete(elem);
33720
33914
  }
33721
33915
  for (const child of elem.childNodes) {
@@ -35835,31 +36029,31 @@ function renderNodeToOutput(node, output, {
35835
36029
  if (y < 0 && node.style.position === "absolute") {
35836
36030
  y = 0;
35837
36031
  }
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) {
36032
+ const cached7 = nodeCache.get(node);
36033
+ if (!node.dirty && !skipSelfBlit && node.pendingScrollDelta === void 0 && cached7 && cached7.x === x && cached7.y === y && cached7.width === width && cached7.height === height && prevScreen) {
35840
36034
  const fx = Math.floor(x);
35841
36035
  const fy = Math.floor(y);
35842
36036
  const fw = Math.floor(width);
35843
36037
  const fh = Math.floor(height);
35844
36038
  output.blit(prevScreen, fx, fy, fw, fh);
35845
36039
  if (node.style.position === "absolute") {
35846
- absoluteRectsCur.push(cached8);
36040
+ absoluteRectsCur.push(cached7);
35847
36041
  }
35848
36042
  blitEscapingAbsoluteDescendants(node, output, prevScreen, fx, fy, fw, fh);
35849
36043
  return;
35850
36044
  }
35851
- const positionChanged = cached8 !== void 0 && (cached8.x !== x || cached8.y !== y || cached8.width !== width || cached8.height !== height);
36045
+ const positionChanged = cached7 !== void 0 && (cached7.x !== x || cached7.y !== y || cached7.width !== width || cached7.height !== height);
35852
36046
  if (positionChanged) {
35853
36047
  layoutShifted = true;
35854
36048
  absoluteOverlayMoved ||= node.style.position === "absolute";
35855
36049
  }
35856
- if (cached8 && (node.dirty || positionChanged)) {
36050
+ if (cached7 && (node.dirty || positionChanged)) {
35857
36051
  output.clear(
35858
36052
  {
35859
- x: Math.floor(cached8.x),
35860
- y: Math.floor(cached8.y),
35861
- width: Math.floor(cached8.width),
35862
- height: Math.floor(cached8.height)
36053
+ x: Math.floor(cached7.x),
36054
+ y: Math.floor(cached7.y),
36055
+ width: Math.floor(cached7.width),
36056
+ height: Math.floor(cached7.height)
35863
36057
  },
35864
36058
  node.style.position === "absolute"
35865
36059
  );
@@ -36034,7 +36228,7 @@ function renderNodeToOutput(node, output, {
36034
36228
  const delta = contentCached.y - contentY;
36035
36229
  const regionTop = Math.floor(y + contentYoga.getComputedTop());
36036
36230
  const regionBottom = regionTop + innerHeight - 1;
36037
- if (cached8?.y === y && cached8.height === height && innerHeight > 0 && Math.abs(delta) < innerHeight) {
36231
+ if (cached7?.y === y && cached7.height === height && innerHeight > 0 && Math.abs(delta) < innerHeight) {
36038
36232
  hint = { top: regionTop, bottom: regionBottom, delta };
36039
36233
  scrollHint = hint;
36040
36234
  } else {
@@ -36334,13 +36528,13 @@ function blitEscapingAbsoluteDescendants(node, output, prevScreen, px, py, pw, p
36334
36528
  }
36335
36529
  const elem = child;
36336
36530
  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);
36531
+ const cached7 = nodeCache.get(elem);
36532
+ if (cached7) {
36533
+ absoluteRectsCur.push(cached7);
36534
+ const cx = Math.floor(cached7.x);
36535
+ const cy = Math.floor(cached7.y);
36536
+ const cw = Math.floor(cached7.width);
36537
+ const ch = Math.floor(cached7.height);
36344
36538
  if (cx < px || cy < py || cx + cw > pr || cy + ch > pb) {
36345
36539
  output.blit(prevScreen, cx, cy, cw, ch);
36346
36540
  }
@@ -36356,20 +36550,20 @@ function renderScrolledChildren(node, output, offsetX, offsetY, hasRemovedChild,
36356
36550
  const childElem = childNode;
36357
36551
  const cy = childElem.yogaNode;
36358
36552
  if (cy) {
36359
- const cached8 = nodeCache.get(childElem);
36553
+ const cached7 = nodeCache.get(childElem);
36360
36554
  let top;
36361
36555
  let height;
36362
- if (cached8?.top !== void 0 && !childElem.dirty && cumHeightShift === 0) {
36363
- top = cached8.top;
36364
- height = cached8.height;
36556
+ if (cached7?.top !== void 0 && !childElem.dirty && cumHeightShift === 0) {
36557
+ top = cached7.top;
36558
+ height = cached7.height;
36365
36559
  } else {
36366
36560
  top = cy.getComputedTop();
36367
36561
  height = cy.getComputedHeight();
36368
36562
  if (childElem.dirty) {
36369
- cumHeightShift += height - (cached8 ? cached8.height : 0);
36563
+ cumHeightShift += height - (cached7 ? cached7.height : 0);
36370
36564
  }
36371
- if (cached8) {
36372
- cached8.top = top;
36565
+ if (cached7) {
36566
+ cached7.top = top;
36373
36567
  }
36374
36568
  }
36375
36569
  const bottom = top + height;
@@ -38779,11 +38973,11 @@ var init_entry_exports = __esm({
38779
38973
  return rawStringWidth(str);
38780
38974
  }
38781
38975
  }
38782
- const cached8 = widthCache.get(str);
38783
- if (cached8 !== void 0) {
38976
+ const cached7 = widthCache.get(str);
38977
+ if (cached7 !== void 0) {
38784
38978
  widthCache.delete(str);
38785
- widthCache.set(str, cached8);
38786
- return cached8;
38979
+ widthCache.set(str, cached7);
38980
+ return cached7;
38787
38981
  }
38788
38982
  const w = rawStringWidth(str);
38789
38983
  if (widthCache.size >= WIDTH_CACHE_LIMIT) {
@@ -40888,9 +41082,9 @@ $ npm install --save-dev react-devtools-core
40888
41082
  if (char.length === 1) {
40889
41083
  const code = char.charCodeAt(0);
40890
41084
  if (code < 128) {
40891
- const cached8 = this.ascii[code];
40892
- if (cached8 !== -1) {
40893
- return cached8;
41085
+ const cached7 = this.ascii[code];
41086
+ if (cached7 !== -1) {
41087
+ return cached7;
40894
41088
  }
40895
41089
  const index2 = this.strings.length;
40896
41090
  this.strings.push(char);
@@ -45907,6 +46101,7 @@ var fmt, money, stub, eventLine, parseScheduleInput, showUsage, openjawCommands;
45907
46101
  var init_openjaw = __esm({
45908
46102
  "src/app/slash/commands/openjaw.ts"() {
45909
46103
  "use strict";
46104
+ init_usage();
45910
46105
  init_overlayStore();
45911
46106
  fmt = /* @__PURE__ */ __name((n) => (n ?? 0).toLocaleString(), "fmt");
45912
46107
  money = /* @__PURE__ */ __name((n) => `$${(n ?? 0).toFixed(4)}`, "money");
@@ -46154,7 +46349,7 @@ var init_openjaw = __esm({
46154
46349
  if (!r.context_max) {
46155
46350
  return ctx.transcript.sys("context usage unavailable");
46156
46351
  }
46157
- ctx.transcript.sys(`context: ${fmt(r.context_used)} / ${fmt(r.context_max)} (${r.context_percent ?? 0}%)`);
46352
+ ctx.transcript.sys(`context: ${fmt(r.context_used)} / ${fmt(r.context_max)} (${formatContextPercent(r.context_percent)})`);
46158
46353
  }), "run")
46159
46354
  },
46160
46355
  {
@@ -47057,6 +47252,8 @@ var init_session2 = __esm({
47057
47252
  init_slash();
47058
47253
  init_platform();
47059
47254
  init_text();
47255
+ init_theme();
47256
+ init_usage();
47060
47257
  init_interfaces();
47061
47258
  init_overlayStore();
47062
47259
  init_uiStore();
@@ -47275,10 +47472,18 @@ var init_session2 = __esm({
47275
47472
  help: "switch theme skin (fires skin.changed)",
47276
47473
  name: "skin",
47277
47474
  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"}`)));
47475
+ const value = arg.trim().toLowerCase();
47476
+ if (!value || value === "list") {
47477
+ return ctx.transcript.panel("Skins", [
47478
+ { rows: BUILTIN_SKINS.map((skin) => [skin.name, skin.description]) },
47479
+ { text: "Use /skin <name> to apply a skin." }
47480
+ ]);
47280
47481
  }
47281
- ctx.gateway.rpc("config.set", { key: "skin", value: arg }).then(ctx.guarded((r) => r.value && ctx.transcript.sys(`skin \u2192 ${r.value}`)));
47482
+ if (!isBuiltinSkinName(value)) {
47483
+ return ctx.transcript.sys(`usage: /skin <${BUILTIN_SKINS.map((s) => s.name).join("|")}>`);
47484
+ }
47485
+ patchUiState({ theme: themeForBuiltinSkin(value) });
47486
+ ctx.gateway.rpc("config.set", { key: "skin", value }).then(ctx.guarded(() => ctx.transcript.sys(`skin \u2192 ${value}`)));
47282
47487
  }, "run")
47283
47488
  },
47284
47489
  {
@@ -47445,7 +47650,7 @@ var init_session2 = __esm({
47445
47650
  }
47446
47651
  const sections = [{ rows }];
47447
47652
  if (r.context_max) {
47448
- sections.push({ text: `Context: ${f(r.context_used)} / ${f(r.context_max)} (${r.context_percent}%)` });
47653
+ sections.push({ text: `Context: ${f(r.context_used)} / ${f(r.context_max)} (${formatContextPercent(r.context_percent)})` });
47449
47654
  }
47450
47655
  if (r.compressions) {
47451
47656
  sections.push({ text: `Compressions: ${r.compressions}` });
@@ -47569,6 +47774,14 @@ function buildSlashCompletions(text) {
47569
47774
  if (!text.startsWith("/")) {
47570
47775
  return { items: [], replace_from: 1 };
47571
47776
  }
47777
+ const skinArgMatch = /^\/skin\s+([^\s]*)$/i.exec(text);
47778
+ if (skinArgMatch) {
47779
+ const prefix = skinArgMatch[1]?.toLowerCase() ?? "";
47780
+ return {
47781
+ items: BUILTIN_SKINS.filter((skin) => skin.name.startsWith(prefix)).map((skin) => ({ display: skin.name, meta: skin.description, text: skin.name })),
47782
+ replace_from: text.length - (skinArgMatch[1]?.length ?? 0)
47783
+ };
47784
+ }
47572
47785
  const needle = text.slice(1).toLowerCase();
47573
47786
  const seen = /* @__PURE__ */ new Set();
47574
47787
  const items = [];
@@ -47587,6 +47800,19 @@ function buildSlashCompletions(text) {
47587
47800
  });
47588
47801
  }
47589
47802
  }
47803
+ if (needle.length > 0) {
47804
+ for (const skill of discoverSkills()) {
47805
+ if (!skill.name.toLowerCase().startsWith(needle)) continue;
47806
+ const command = `/${skill.name}`;
47807
+ if (seen.has(command)) continue;
47808
+ seen.add(command);
47809
+ items.push({
47810
+ display: command,
47811
+ meta: `skill \xB7 ${skill.meta.whenToUse || skill.meta.description || ""}`,
47812
+ text: command
47813
+ });
47814
+ }
47815
+ }
47590
47816
  items.sort((a, b) => {
47591
47817
  const aExact = a.text.slice(1).toLowerCase() === needle ? -1 : 0;
47592
47818
  const bExact = b.text.slice(1).toLowerCase() === needle ? -1 : 0;
@@ -47643,6 +47869,8 @@ var init_catalog = __esm({
47643
47869
  init_ops();
47644
47870
  init_session2();
47645
47871
  init_setup();
47872
+ init_registry2();
47873
+ init_theme();
47646
47874
  CATEGORIZED = [
47647
47875
  { category: "Core", commands: coreCommands },
47648
47876
  { category: "Session", commands: sessionCommands },
@@ -47663,7 +47891,7 @@ var init_catalog = __esm({
47663
47891
  // src/rpcHandlers.ts
47664
47892
  import { spawn as spawn7 } from "node:child_process";
47665
47893
  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";
47894
+ import { existsSync as existsSync31, mkdirSync as mkdirSync17, readFileSync as readFileSync28, rmSync, statSync as statSync4, writeFileSync as writeFileSync19 } from "node:fs";
47667
47895
  import { homedir as homedir28 } from "node:os";
47668
47896
  import { basename as basename3, extname as extname4, join as join42 } from "node:path";
47669
47897
  function registerRpcHandlers(options) {
@@ -48356,8 +48584,7 @@ ${helpMessage}` : field.label;
48356
48584
  return { cancelled: true, slug: entry.slug };
48357
48585
  });
48358
48586
  bus.registerRpc("commands.catalog", () => {
48359
- const skillCount = toolRegistry.listTools().filter((t) => /^skill[:_-]/i.test(t.name)).length;
48360
- return buildCommandsCatalog({ skillCount });
48587
+ return buildCommandsCatalog({ skillCount: discoverSkills().length });
48361
48588
  });
48362
48589
  bus.registerRpc("completion.query", () => ({
48363
48590
  items: []
@@ -48365,14 +48592,9 @@ ${helpMessage}` : field.label;
48365
48592
  bus.registerRpc("complete.slash", (params) => buildSlashCompletions(String(params.text ?? "")));
48366
48593
  bus.registerRpc("complete.path", (params) => buildPathCompletions(String(params.word ?? "")));
48367
48594
  bus.registerRpc("skills.catalog", () => {
48368
- const tools = toolRegistry.listTools();
48369
- const skillTools = tools.filter((t) => /^skill[:_-]/i.test(t.name));
48370
48595
  return {
48371
- categories: ["skills"],
48372
- skills: skillTools.map((t) => ({
48373
- description: t.description,
48374
- name: t.name.replace(/^skill[:_-]/i, "")
48375
- }))
48596
+ categories: Object.keys(registrySkillsByCategory()).sort(),
48597
+ skills: listRegistrySkills()
48376
48598
  };
48377
48599
  });
48378
48600
  bus.registerRpc("tools.list", () => ({
@@ -48404,11 +48626,11 @@ ${helpMessage}` : field.label;
48404
48626
  return { output: lines.join("\n") };
48405
48627
  }
48406
48628
  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, "") }));
48629
+ const skills = listRegistrySkills();
48408
48630
  if (!skills.length) {
48409
- return { output: "no skills registered" };
48631
+ return { output: "no skills available" };
48410
48632
  }
48411
- const lines = [`${skills.length} skills registered`, ""];
48633
+ const lines = [`${skills.length} skills available`, ""];
48412
48634
  for (const s of skills.sort((a, b) => a.name.localeCompare(b.name))) {
48413
48635
  const desc = s.description ? ` \u2014 ${s.description}` : "";
48414
48636
  lines.push(` ${s.name}${desc}`);
@@ -48684,7 +48906,7 @@ ${helpMessage}` : field.label;
48684
48906
  const meta = agentLoop.getSessionMeta();
48685
48907
  const tools = toolRegistry.listTools();
48686
48908
  const cost = `$${usage2.cost_usd.toFixed(4)}`;
48687
- const ctx = usage2.context_max > 0 ? `${usage2.context_percent}% (${usage2.context_used}/${usage2.context_max})` : "?";
48909
+ const ctx = usage2.context_max > 0 ? `${formatContextPercent(usage2.context_percent)} (${usage2.context_used}/${usage2.context_max})` : "?";
48688
48910
  const lines = [
48689
48911
  `Session: ${meta.id}`,
48690
48912
  `Title: ${meta.summary || "(untitled)"}`,
@@ -48814,7 +49036,7 @@ ${helpMessage}` : field.label;
48814
49036
  let fileSize = 0;
48815
49037
  try {
48816
49038
  buffer = readFileSync28(path3);
48817
- fileSize = statSync3(path3).size;
49039
+ fileSize = statSync4(path3).size;
48818
49040
  } catch (err) {
48819
49041
  throw new Error(`image.attach: ${err instanceof Error ? err.message : String(err)}`);
48820
49042
  }
@@ -48909,9 +49131,9 @@ ${helpMessage}` : field.label;
48909
49131
  bus.registerRpc("spawn_tree.load", () => ({ subagents: [] }));
48910
49132
  bus.registerRpc("skills.reload", async () => {
48911
49133
  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` };
49134
+ clearSkillsCache();
49135
+ const skillCount = discoverSkills().length;
49136
+ return { output: `Skills reloaded \xB7 ${skillCount} skills available` };
48915
49137
  } catch (err) {
48916
49138
  return { output: `skills reload failed: ${err instanceof Error ? err.message : String(err)}` };
48917
49139
  }
@@ -48919,18 +49141,28 @@ ${helpMessage}` : field.label;
48919
49141
  bus.registerRpc("skills.manage", (params) => {
48920
49142
  const action2 = String(params.action ?? "").toLowerCase();
48921
49143
  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, "") }));
49144
+ const skills = listRegistrySkills();
48923
49145
  if (action2 === "list") {
48924
- return { skills: skillTools.length ? { installed: skillTools.map((s) => s.name) } : {} };
49146
+ return { skills: registrySkillsByCategory() };
48925
49147
  }
48926
49148
  if (action2 === "inspect") {
48927
- const hit = skillTools.find((s) => s.name.toLowerCase() === query);
49149
+ const hit = query ? findSkill(query) : null;
48928
49150
  if (!hit) return { info: null };
48929
- return { info: { category: "installed", description: hit.description, name: hit.name } };
49151
+ return {
49152
+ info: {
49153
+ category: hit.source,
49154
+ description: skillDescription(hit),
49155
+ entrypoint: hit.entrypoint,
49156
+ name: hit.name,
49157
+ path: hit.filePath,
49158
+ root_dir: hit.rootDir,
49159
+ source: hit.source
49160
+ }
49161
+ };
48930
49162
  }
48931
49163
  if (action2 === "search") {
48932
49164
  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 }));
49165
+ const results = skills.filter((s) => s.name.toLowerCase().includes(query) || (s.description ?? "").toLowerCase().includes(query)).map((s) => ({ description: s.description, name: s.name }));
48934
49166
  return { results };
48935
49167
  }
48936
49168
  if (action2 === "install") {
@@ -48954,8 +49186,30 @@ ${helpMessage}` : field.label;
48954
49186
  unknown: names.length ? names : [`/tools ${action2 || "?"} is not yet wired in the new TUI \u2014 use the legacy --legacy-ui flag for full toolset toggles`]
48955
49187
  };
48956
49188
  });
48957
- bus.registerRpc("command.dispatch", (params) => {
49189
+ bus.registerRpc("command.dispatch", async (params) => {
48958
49190
  const name = String(params.name ?? "").trim();
49191
+ const arg = String(params.arg ?? "").trim();
49192
+ if (name) {
49193
+ const skill = findSkill(name);
49194
+ if (skill) {
49195
+ if (!toolRegistry.getTool("invoke_skill")) {
49196
+ return { output: "skill invocation is unavailable: invoke_skill tool is not registered", type: "exec" };
49197
+ }
49198
+ try {
49199
+ const result = await toolRegistry.execute("invoke_skill", { skill: skill.name, args: arg });
49200
+ return { output: formatSkillExecutionResult(result), type: "exec" };
49201
+ } catch (err) {
49202
+ return { output: `skill failed (${skill.name}): ${err instanceof Error ? err.message : String(err)}`, type: "exec" };
49203
+ }
49204
+ }
49205
+ const matches = findSkillMatches(name);
49206
+ if (matches.length > 1) {
49207
+ return {
49208
+ output: `ambiguous skill command /${name}: ${matches.slice(0, 8).map((s) => s.name).join(", ")}${matches.length > 8 ? ", \u2026" : ""}`,
49209
+ type: "exec"
49210
+ };
49211
+ }
49212
+ }
48959
49213
  return { output: name ? `unknown command: /${name}` : "(no command)", type: "exec" };
48960
49214
  });
48961
49215
  bus.registerRpc("delegation.status", () => ({ delegated: [], paused: false }));
@@ -48985,7 +49239,7 @@ ${helpMessage}` : field.label;
48985
49239
  }
48986
49240
  };
48987
49241
  }
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;
49242
+ var PROVIDERS2, PROVIDER_LABELS, PROVIDER_AUTH, isProviderAuthenticated, buildProviderOption, fetchLiveModels, parseReasoningEffortInput, skillDescription, listRegistrySkills, registrySkillsByCategory, formatSkillExecutionResult, reasoningEffortsForModel, resolveReasoningEffortForModel, BRIDGE_SOURCES, isBridgeSource, inferBridgeSource, bridgeLabels, bridgeEventLabels, stripBridgeGlyph, firstLogLine, bridgeUser, formatBridgeText, runProcess, contentToText, sessionMessageToMarkdown, sessionInfoSnapshot, MCP_TOOL_PREFIX, MAX_TOTAL_TOOLS, syncMcpTools;
48989
49243
  var init_rpcHandlers = __esm({
48990
49244
  "src/rpcHandlers.ts"() {
48991
49245
  "use strict";
@@ -49004,6 +49258,10 @@ var init_rpcHandlers = __esm({
49004
49258
  init_models_static();
49005
49259
  init_providers();
49006
49260
  init_types();
49261
+ init_skills();
49262
+ init_registry2();
49263
+ init_usage();
49264
+ init_usageSnapshot();
49007
49265
  init_registry3();
49008
49266
  PROVIDERS2 = ["anthropic", "openai", "github-copilot"];
49009
49267
  PROVIDER_LABELS = {
@@ -49104,6 +49362,38 @@ var init_rpcHandlers = __esm({
49104
49362
  }
49105
49363
  return { clear: false, effort: normalized };
49106
49364
  }, "parseReasoningEffortInput");
49365
+ skillDescription = /* @__PURE__ */ __name((skill) => skill.meta.whenToUse || skill.meta.description || "", "skillDescription");
49366
+ listRegistrySkills = /* @__PURE__ */ __name(() => discoverSkills().map((skill) => ({
49367
+ category: skill.source,
49368
+ description: skillDescription(skill),
49369
+ entrypoint: skill.entrypoint,
49370
+ name: skill.name,
49371
+ path: skill.filePath,
49372
+ root_dir: skill.rootDir,
49373
+ source: skill.source
49374
+ })), "listRegistrySkills");
49375
+ registrySkillsByCategory = /* @__PURE__ */ __name(() => {
49376
+ const grouped = {};
49377
+ for (const skill of discoverSkills()) {
49378
+ const category = skill.source;
49379
+ grouped[category] ??= [];
49380
+ grouped[category].push(skill.name);
49381
+ }
49382
+ for (const skills of Object.values(grouped)) {
49383
+ skills.sort((a, b) => a.localeCompare(b));
49384
+ }
49385
+ return grouped;
49386
+ }, "registrySkillsByCategory");
49387
+ formatSkillExecutionResult = /* @__PURE__ */ __name((value) => {
49388
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
49389
+ return typeof value === "string" ? value : JSON.stringify(value ?? null);
49390
+ }
49391
+ const result = value;
49392
+ if (result.success === false) {
49393
+ return `skill failed${result.skill ? ` (${String(result.skill)})` : ""}: ${String(result.error ?? "unknown error")}`;
49394
+ }
49395
+ return typeof result.result === "string" ? result.result : JSON.stringify(result);
49396
+ }, "formatSkillExecutionResult");
49107
49397
  reasoningEffortsForModel = /* @__PURE__ */ __name((model) => model?.supportedReasoningEfforts?.map((option) => option.effort) ?? [], "reasoningEffortsForModel");
49108
49398
  resolveReasoningEffortForModel = /* @__PURE__ */ __name((model, requested) => {
49109
49399
  const supported = reasoningEffortsForModel(model);
@@ -49207,25 +49497,6 @@ ${raw}`;
49207
49497
  }
49208
49498
  return [];
49209
49499
  }, "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
49500
  sessionInfoSnapshot = /* @__PURE__ */ __name((agentLoop, toolRegistry) => ({
49230
49501
  cwd: process.cwd(),
49231
49502
  model: agentLoop.model,
@@ -49483,7 +49754,7 @@ var init_gatewayContext = __esm({
49483
49754
  });
49484
49755
 
49485
49756
  // src/domain/paths.ts
49486
- var shortCwd, fmtCwdBranch;
49757
+ var shortCwd, fullCwdBranch;
49487
49758
  var init_paths = __esm({
49488
49759
  "src/domain/paths.ts"() {
49489
49760
  "use strict";
@@ -49492,13 +49763,7 @@ var init_paths = __esm({
49492
49763
  const p = h && cwd.startsWith(h) ? `~${cwd.slice(h.length)}` : cwd;
49493
49764
  return p.length <= max ? p : `\u2026${p.slice(-(max - 1))}`;
49494
49765
  }, "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");
49766
+ fullCwdBranch = /* @__PURE__ */ __name((cwd, branch) => branch ? `${cwd} (${branch})` : cwd, "fullCwdBranch");
49502
49767
  }
49503
49768
  });
49504
49769
 
@@ -49866,9 +50131,9 @@ var init_useVirtualHistory = __esm({
49866
50131
  viewportHeight
49867
50132
  }) => itemCount > 0 && viewportHeight > 0 && !sticky && !liveTailActive, "shouldSetVirtualClamp");
49868
50133
  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));
50134
+ const cached7 = heights.get(key);
50135
+ if (cached7 !== void 0) {
50136
+ return Math.max(1, Math.floor(cached7));
49872
50137
  }
49873
50138
  const seeded = Math.max(1, Math.floor(estimateHeight?.(index, key) ?? estimate));
49874
50139
  heights.set(key, seeded);
@@ -51800,7 +52065,7 @@ function createSlashHandler(ctx) {
51800
52065
  }
51801
52066
  }
51802
52067
  }
51803
- gw.request("slash.exec", { command: cmd.slice(1), session_id: sid }).then((r) => {
52068
+ const renderSlashExec = /* @__PURE__ */ __name((r) => {
51804
52069
  if (stale()) {
51805
52070
  return;
51806
52071
  }
@@ -51809,33 +52074,41 @@ function createSlashHandler(ctx) {
51809
52074
  ${body}` : body;
51810
52075
  const long = text.length > 180 || text.split("\n").filter(Boolean).length > 2;
51811
52076
  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") {
52077
+ }, "renderSlashExec");
52078
+ const runSlashExec = /* @__PURE__ */ __name(() => {
52079
+ gw.request("slash.exec", { command: cmd.slice(1), session_id: sid }).then(renderSlashExec).catch(guardedErr);
52080
+ }, "runSlashExec");
52081
+ const skillHint = findSkill(parsed.name);
52082
+ if (skillHint) {
52083
+ sys(`\u26A1 loading skill: ${skillHint.name}`);
52084
+ }
52085
+ gw.request("command.dispatch", { arg: parsed.arg, name: parsed.name, session_id: sid }).then((raw) => {
52086
+ if (stale()) {
52087
+ return;
52088
+ }
52089
+ const d = asCommandDispatch(raw);
52090
+ if (!d) {
52091
+ return runSlashExec();
52092
+ }
52093
+ if (d.type === "exec" || d.type === "plugin") {
52094
+ return sys(d.output || "(no output)");
52095
+ }
52096
+ if (d.type === "alias") {
52097
+ return handler(`/${d.target}${argTail}`);
52098
+ }
52099
+ if (d.type === "skill") {
52100
+ if (!skillHint || skillHint.name !== d.name) {
51828
52101
  sys(`\u26A1 loading skill: ${d.name}`);
51829
- return d.message?.trim() ? send(d.message) : sys(`/${parsed.name}: skill payload missing message`);
51830
52102
  }
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`);
52103
+ return d.message?.trim() ? send(d.message) : sys(`/${parsed.name}: skill payload missing message`);
52104
+ }
52105
+ if (d.type === "send") {
52106
+ if (d.notice?.trim()) {
52107
+ sys(d.notice);
51836
52108
  }
51837
- }).catch(guardedErr);
51838
- });
52109
+ return d.message?.trim() ? send(d.message) : sys(`/${parsed.name}: empty message`);
52110
+ }
52111
+ }).catch(runSlashExec);
51839
52112
  return true;
51840
52113
  }, "handler");
51841
52114
  return handler;
@@ -51845,6 +52118,7 @@ var init_createSlashHandler = __esm({
51845
52118
  "use strict";
51846
52119
  init_slash();
51847
52120
  init_rpc();
52121
+ init_registry2();
51848
52122
  init_registry4();
51849
52123
  init_uiStore();
51850
52124
  __name(createSlashHandler, "createSlashHandler");
@@ -53462,6 +53736,13 @@ var init_paste = __esm({
53462
53736
 
53463
53737
  // src/app/useSubmission.ts
53464
53738
  import { useCallback as useCallback9, useEffect as useEffect11, useRef as useRef12 } from "react";
53739
+ function resolveCompletionSubmit(value, row, replaceFrom) {
53740
+ if (!row?.text) return null;
53741
+ const text = value.startsWith("/") && row.text.startsWith("/") ? row.text.slice(1) : row.text;
53742
+ const next = value.slice(0, replaceFrom) + text;
53743
+ if (next === value) return null;
53744
+ return { next, submit: value.startsWith("/") && row.text.startsWith("/") && row.meta?.startsWith("skill \xB7") === true };
53745
+ }
53465
53746
  function useSubmission(opts) {
53466
53747
  const {
53467
53748
  appendMessage,
@@ -53686,12 +53967,9 @@ function useSubmission(opts) {
53686
53967
  (value) => {
53687
53968
  if (composerState.completions.length) {
53688
53969
  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
- }
53970
+ const completion = resolveCompletionSubmit(value, row, composerState.compReplace);
53971
+ if (completion) {
53972
+ return completion.submit ? dispatchSubmission(completion.next) : composerActions.setInput(completion.next);
53695
53973
  }
53696
53974
  }
53697
53975
  if (!value.trim() && !composerState.inputBuf.length) {
@@ -53748,6 +54026,7 @@ var init_useSubmission = __esm({
53748
54026
  return (value) => value.replace(PASTE_SNIPPET_RE, (tok) => byLabel.get(tok)?.shift() ?? tok);
53749
54027
  }, "expandSnips");
53750
54028
  spliceMatches = /* @__PURE__ */ __name((text, matches, results) => matches.reduceRight((acc, m, i) => acc.slice(0, m.index) + results[i] + acc.slice(m.index + m[0].length), text), "spliceMatches");
54029
+ __name(resolveCompletionSubmit, "resolveCompletionSubmit");
53751
54030
  __name(useSubmission, "useSubmission");
53752
54031
  }
53753
54032
  });
@@ -54332,7 +54611,7 @@ function useMainApp(gw) {
54332
54611
  const gitBranch = useGitBranch(cwd);
54333
54612
  const appStatus = useMemo7(
54334
54613
  () => ({
54335
- cwdLabel: fmtCwdBranch(cwd, gitBranch),
54614
+ cwdLabel: fullCwdBranch(cwd, gitBranch),
54336
54615
  goodVibesTick,
54337
54616
  sessionStartedAt: ui.sid ? sessionStartedAt : null,
54338
54617
  showStickyPrompt: !!stickyPrompt,
@@ -55402,7 +55681,7 @@ import { useStore as useStore5 } from "@nanostores/react";
55402
55681
  import { useEffect as useEffect14, useMemo as useMemo10, useRef as useRef15, useState as useState15 } from "react";
55403
55682
  import unicodeSpinners from "unicode-animations";
55404
55683
  import { Fragment as Fragment4, jsx as jsx19, jsxs as jsxs10 } from "react/jsx-runtime";
55405
- function FaceTicker({ color, startedAt }) {
55684
+ function useTickerText(startedAt) {
55406
55685
  const ui = useStore5($uiState);
55407
55686
  const style = ui.indicatorStyle;
55408
55687
  const [tick, setTick] = useState15(() => Math.floor(Math.random() * 1e3));
@@ -55425,11 +55704,7 @@ function FaceTicker({ color, startedAt }) {
55425
55704
  const verb = VERBS[verbTick % VERBS.length] ?? "";
55426
55705
  const verbSegment = showVerb ? ` ${padVerb(verb)}` : "";
55427
55706
  const durationSegment = startedAt ? ` \xB7 ${fmtDuration(now2 - startedAt)}` : "";
55428
- return /* @__PURE__ */ jsxs10(Text9, { color, children: [
55429
- frame,
55430
- verbSegment,
55431
- durationSegment
55432
- ] });
55707
+ return `${frame}${verbSegment}${durationSegment}`;
55433
55708
  }
55434
55709
  function ctxBarColor(pct, t) {
55435
55710
  if (pct == null) {
@@ -55451,6 +55726,27 @@ function ctxBar(pct, w = 10) {
55451
55726
  const filled = Math.round(p / 100 * w);
55452
55727
  return "\u2588".repeat(filled) + "\u2591".repeat(w - filled);
55453
55728
  }
55729
+ function fitVisible(text, width) {
55730
+ if (width <= 0) return "";
55731
+ if (stringWidth(text) <= width) return text + " ".repeat(width - stringWidth(text));
55732
+ let out = "";
55733
+ for (const ch of Array.from(text)) {
55734
+ if (stringWidth(out + ch + "\u2026") > width) break;
55735
+ out += ch;
55736
+ }
55737
+ return out + "\u2026" + " ".repeat(Math.max(0, width - stringWidth(out + "\u2026")));
55738
+ }
55739
+ function truncateTailVisible(text, width) {
55740
+ return fitVisible(text, width);
55741
+ }
55742
+ function cwdBranchWidth(cols) {
55743
+ const desired = Math.max(18, Math.floor(cols * 0.38));
55744
+ const maxByCols = Math.max(6, cols - 24);
55745
+ return Math.max(6, Math.min(64, desired, maxByCols));
55746
+ }
55747
+ function buildActivityText(status, width) {
55748
+ return fitVisible(` ${status}`, width);
55749
+ }
55454
55750
  function SpawnHud({ t }) {
55455
55751
  const delegation = useStore5($delegationState);
55456
55752
  const subagents = useTurnSelector((state) => state.subagents);
@@ -55515,12 +55811,14 @@ function GoodVibesHeart({ tick, t }) {
55515
55811
  }
55516
55812
  return /* @__PURE__ */ jsx19(Text9, { color, children: "\u2665" });
55517
55813
  }
55814
+ function ActivityStatusLine({ busy, cols, status, statusColor: statusColor2, turnStartedAt }) {
55815
+ const tickerStatus = useTickerText(busy ? turnStartedAt : null);
55816
+ const text = buildActivityText(busy ? tickerStatus : status, cols);
55817
+ return /* @__PURE__ */ jsx19(Box_default, { height: 1, width: cols, children: /* @__PURE__ */ jsx19(Text9, { color: statusColor2, children: text }) });
55818
+ }
55518
55819
  function StatusRule({
55519
55820
  cwdLabel,
55520
55821
  cols,
55521
- busy,
55522
- status,
55523
- statusColor: statusColor2,
55524
55822
  model,
55525
55823
  modelFast,
55526
55824
  modelReasoningEffort,
@@ -55528,7 +55826,6 @@ function StatusRule({
55528
55826
  bgCount,
55529
55827
  sessionStartedAt,
55530
55828
  showCost,
55531
- turnStartedAt,
55532
55829
  voiceLabel,
55533
55830
  t
55534
55831
  }) {
@@ -55536,29 +55833,15 @@ function StatusRule({
55536
55833
  const barColor = ctxBarColor(pct, t);
55537
55834
  const ctxLabel = usage2.context_max ? `${fmtK(usage2.context_used ?? 0)}/${fmtK(usage2.context_max)}` : usage2.total > 0 ? `${fmtK(usage2.total)} tok` : "";
55538
55835
  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,
55836
+ const cwdWidth = cwdBranchWidth(cols);
55837
+ const modelText = modelLabel(model, modelReasoningEffort, modelFast);
55838
+ return /* @__PURE__ */ jsxs10(Box_default, { height: 1, width: cols, children: [
55839
+ /* @__PURE__ */ jsx19(Text9, { color: t.color.border, children: "\u2500 " }),
55840
+ /* @__PURE__ */ jsx19(Box_default, { flexShrink: 0, width: cwdWidth, children: /* @__PURE__ */ jsx19(Text9, { color: t.color.systemNote, children: truncateTailVisible(cwdLabel, cwdWidth) }) }),
55841
+ /* @__PURE__ */ jsx19(Box_default, { flexShrink: 1, children: /* @__PURE__ */ jsxs10(Text9, { color: t.color.systemNote, wrap: "truncate-end", children: [
55842
+ modelText ? ` \u2502 ${modelText}` : "",
55843
+ ctxLabel ? ` \u2502 ${ctxLabel}` : "",
55844
+ bar ? /* @__PURE__ */ jsx19(Text9, { color: barColor, children: statusContextMeter(bar, pct) }) : null,
55562
55845
  sessionStartedAt ? /* @__PURE__ */ jsxs10(Text9, { color: t.color.systemNote, children: [
55563
55846
  " \u2502 ",
55564
55847
  /* @__PURE__ */ jsx19(SessionDuration, { startedAt: sessionStartedAt })
@@ -55590,9 +55873,7 @@ function StatusRule({
55590
55873
  " \u2502 $",
55591
55874
  usage2.cost_usd.toFixed(4)
55592
55875
  ] }) : null
55593
- ] }) }),
55594
- /* @__PURE__ */ jsx19(Text9, { color: t.color.border, children: " \u2500 " }),
55595
- /* @__PURE__ */ jsx19(Text9, { color: t.color.systemNote, children: cwdLabel })
55876
+ ] }) })
55596
55877
  ] });
55597
55878
  }
55598
55879
  function FloatBox({ children, color }) {
@@ -55667,7 +55948,7 @@ function TranscriptScrollbar({ focused = false, scrollRef, t }) {
55667
55948
  }
55668
55949
  );
55669
55950
  }
55670
- var FACE_TICK_MS, VERB_PAD_LEN, padVerb, EMOJI_FRAMES, ASCII_FRAMES, SPINNER_TICK_MS, renderIndicator, effortLabel, shortModelLabel, modelLabel;
55951
+ var FACE_TICK_MS, VERB_PAD_LEN, padVerb, EMOJI_FRAMES, ASCII_FRAMES, SPINNER_TICK_MS, renderIndicator, statusContextPercent, statusContextMeter, effortLabel, shortModelLabel, modelLabel;
55671
55952
  var init_appChrome = __esm({
55672
55953
  "src/components/appChrome.tsx"() {
55673
55954
  "use strict";
@@ -55678,6 +55959,7 @@ var init_appChrome = __esm({
55678
55959
  init_faces();
55679
55960
  init_verbs();
55680
55961
  init_messages();
55962
+ init_usage();
55681
55963
  init_viewport();
55682
55964
  init_subagentTree();
55683
55965
  init_text();
@@ -55710,9 +55992,15 @@ var init_appChrome = __esm({
55710
55992
  const frame = spinner.frames[tick % spinner.frames.length] ?? "\u280B";
55711
55993
  return { frame, intervalMs: Math.max(SPINNER_TICK_MS, spinner.interval), showVerb: false };
55712
55994
  }, "renderIndicator");
55713
- __name(FaceTicker, "FaceTicker");
55995
+ __name(useTickerText, "useTickerText");
55714
55996
  __name(ctxBarColor, "ctxBarColor");
55715
55997
  __name(ctxBar, "ctxBar");
55998
+ statusContextPercent = /* @__PURE__ */ __name((pct) => formatContextPercent(pct).padStart(5), "statusContextPercent");
55999
+ statusContextMeter = /* @__PURE__ */ __name((bar, pct) => ` \u2502 [${bar}] ${statusContextPercent(pct)}`, "statusContextMeter");
56000
+ __name(fitVisible, "fitVisible");
56001
+ __name(truncateTailVisible, "truncateTailVisible");
56002
+ __name(cwdBranchWidth, "cwdBranchWidth");
56003
+ __name(buildActivityText, "buildActivityText");
55716
56004
  __name(SpawnHud, "SpawnHud");
55717
56005
  __name(SessionDuration, "SessionDuration");
55718
56006
  effortLabel = /* @__PURE__ */ __name((effort) => {
@@ -55722,6 +56010,7 @@ var init_appChrome = __esm({
55722
56010
  shortModelLabel = /* @__PURE__ */ __name((model) => model.split("/").pop().replace(/^claude[-_]/, "").replace(/^anthropic[-_]/, "").replace(/[-_]/g, " ").replace(/\b(\d+)\s+(\d+)\b/g, "$1.$2").trim(), "shortModelLabel");
55723
56011
  modelLabel = /* @__PURE__ */ __name((model, effort, fast) => [shortModelLabel(model), effortLabel(effort), fast ? "fast" : ""].filter(Boolean).join(" "), "modelLabel");
55724
56012
  __name(GoodVibesHeart, "GoodVibesHeart");
56013
+ __name(ActivityStatusLine, "ActivityStatusLine");
55725
56014
  __name(StatusRule, "StatusRule");
55726
56015
  __name(FloatBox, "FloatBox");
55727
56016
  __name(StickyPromptTracker, "StickyPromptTracker");
@@ -59890,9 +60179,9 @@ function MdImpl({ compact, t, text }) {
59890
60179
  const nodes = useMemo14(() => {
59891
60180
  const bucket = cacheBucket(t);
59892
60181
  const cacheKey = `${compact ? "1" : "0"}|${text}`;
59893
- const cached8 = cacheGet(bucket, cacheKey);
59894
- if (cached8) {
59895
- return cached8;
60182
+ const cached7 = cacheGet(bucket, cacheKey);
60183
+ if (cached7) {
60184
+ return cached7;
59896
60185
  }
59897
60186
  const lines = ensureEmojiPresentation(text).split("\n");
59898
60187
  const nodes2 = [];
@@ -61928,26 +62217,34 @@ var init_appLayout = __esm({
61928
62217
  if (ui.statusBar !== at) {
61929
62218
  return null;
61930
62219
  }
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
- ) });
62220
+ return /* @__PURE__ */ jsxs28(Box_default, { flexDirection: "column", marginTop: at === "top" ? 1 : 0, children: [
62221
+ /* @__PURE__ */ jsx41(
62222
+ ActivityStatusLine,
62223
+ {
62224
+ busy: ui.busy,
62225
+ cols: composer.cols,
62226
+ status: ui.status,
62227
+ statusColor: status.statusColor,
62228
+ turnStartedAt: status.turnStartedAt
62229
+ }
62230
+ ),
62231
+ /* @__PURE__ */ jsx41(
62232
+ StatusRule,
62233
+ {
62234
+ bgCount: ui.bgTasks.size,
62235
+ cols: composer.cols,
62236
+ cwdLabel: status.cwdLabel,
62237
+ model: ui.info?.model ?? "",
62238
+ modelFast: ui.info?.fast || ui.info?.service_tier === "priority",
62239
+ modelReasoningEffort: ui.info?.reasoning_effort,
62240
+ sessionStartedAt: status.sessionStartedAt,
62241
+ showCost: ui.showCost,
62242
+ t: ui.theme,
62243
+ usage: ui.usage,
62244
+ voiceLabel: status.voiceLabel
62245
+ }
62246
+ )
62247
+ ] });
61951
62248
  }, "StatusRulePane"));
61952
62249
  AppLayout = memo7(/* @__PURE__ */ __name(function AppLayout2({
61953
62250
  actions,