@usabledev/usable-chat 1.150.0 → 1.151.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/cli.js +119 -18
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -157486,6 +157486,13 @@ var init_custom_expert = __esm({
157486
157486
  type: external_exports.literal("object"),
157487
157487
  properties: external_exports.record(external_exports.unknown()),
157488
157488
  required: external_exports.array(external_exports.string()).optional()
157489
+ }).optional(),
157490
+ skill: external_exports.object({
157491
+ id: external_exports.string().min(1).max(100).optional(),
157492
+ title: external_exports.string().min(1).max(200).optional(),
157493
+ summary: external_exports.string().max(1e3).optional(),
157494
+ tags: external_exports.array(external_exports.string().min(1).max(100)).max(20).optional(),
157495
+ scope: external_exports.enum(["global", "page"]).optional()
157489
157496
  }).optional()
157490
157497
  });
157491
157498
  MAX_PARENT_TOOLS = 32;
@@ -168340,29 +168347,38 @@ function buildDiscoverableSkillsPromptSection(skills) {
168340
168347
  "",
168341
168348
  "## DISCOVERABLE SKILLS IN CONTEXT",
168342
168349
  "",
168343
- "You have a catalog of reusable Skills scoped to the workspaces in this conversation. Each entry below lists the skill title, `/<name>` shortcut, UUID, and summary.",
168350
+ "You have a catalog of reusable Skills scoped to this conversation. Workspace skills are Usable fragments. Parent-local skills are provided dynamically by the embedding parent app and must be loaded through their parent tool.",
168344
168351
  "",
168345
168352
  "**BEFORE acting on the user's request, scan this catalog.** If any listed skill matches the user's intent \u2014 by title, `/<name>`, summary keywords, or obvious topical fit \u2014 you MUST use it instead of improvising:",
168346
168353
  "",
168347
- '1. Call `add-to-conversation-context` with `contextType: "fragment"` and `contextId: <fragmentId>` from the matching entry. Attaching a fragment pulls its FULL instructions into your system prompt for the rest of the conversation \u2014 that\'s how the skill gets "loaded".',
168348
- "2. Follow that skill's instructions exactly for the current request. Do not fall back to generic search once a matching skill is attached.",
168354
+ '1. For workspace skills, call `add-to-conversation-context` with `contextType: "fragment"` and `contextId: <fragmentId>` from the matching entry. Attaching a fragment pulls its FULL instructions into your system prompt for the rest of the conversation.',
168355
+ "2. For parent-local skills, call the listed `parent_<toolName>` tool first. The parent app returns the current skill instructions/content for this page or application. Follow that returned content exactly.",
168356
+ "3. Do not fall back to generic search once a matching skill is loaded.",
168349
168357
  "",
168350
168358
  '**Example** \u2014 the user asks "what tasks do we have?" and the catalog lists a `/usable-tasks` skill. You MUST attach that skill BEFORE searching for tasks yourself; the skill knows the correct queries, tag taxonomy, and output format.',
168351
168359
  "",
168352
168360
  "**Rules**",
168353
- "- The entries below are metadata only; their bodies are NOT loaded until you attach them.",
168354
- "- Load skills lazily \u2014 only attach the one(s) needed for the current request, not the whole catalog.",
168355
- "- If the user explicitly invokes a skill via `/<name>` or `/skill <uuid>`, the harness has already attached it; just follow its instructions.",
168356
- '- When a skill-driven task completes (the user moves to a new topic or confirms the work is done), call `remove-from-conversation-context` with `contextType: "fragment"` and the skill\'s fragmentId to free the skill token budget for the next activation.',
168361
+ "- The entries below are metadata only; their bodies are NOT loaded until you attach the workspace fragment or call the parent-local tool.",
168362
+ "- Load skills lazily \u2014 only load the one(s) needed for the current request, not the whole catalog.",
168363
+ "- If the user explicitly invokes a workspace skill via `/<name>` or `/skill <uuid>`, the harness has already attached it; just follow its instructions.",
168364
+ "- If the user explicitly invokes a parent-local skill via `/<name>` or `/skill parent:<id>`, call that skill's parent tool before doing the work.",
168365
+ '- When a workspace skill-driven task completes (the user moves to a new topic or confirms the work is done), call `remove-from-conversation-context` with `contextType: "fragment"` and the skill\'s fragmentId to free the skill token budget for the next activation.',
168357
168366
  ""
168358
168367
  ];
168359
168368
  for (const [workspaceId, workspaceSkills] of skillsByWorkspace.entries()) {
168360
168369
  section.push(`### Workspace: ${workspaceSkills.workspaceName} (\`${workspaceId}\`)`);
168361
168370
  for (const skill of workspaceSkills.skills) {
168362
168371
  const shortcut = skill.name ? ` (\`/${skill.name}\`)` : "";
168363
- section.push(
168364
- `- **${skill.title}**${shortcut} \u2014 \`${skill.fragmentId}\`${skill.summary ? ` \u2014 ${skill.summary}` : ""}`
168365
- );
168372
+ if (skill.source === "parent" && skill.parentToolName) {
168373
+ const scope = skill.parentScope === "global" ? "global" : "page";
168374
+ section.push(
168375
+ `- **${skill.title}**${shortcut} \u2014 parent-local \`${skill.fragmentId}\` \u2014 load with \`parent_${skill.parentToolName}\` \u2014 scope: ${scope}${skill.summary ? ` \u2014 ${skill.summary}` : ""}`
168376
+ );
168377
+ } else {
168378
+ section.push(
168379
+ `- **${skill.title}**${shortcut} \u2014 \`${skill.fragmentId}\`${skill.summary ? ` \u2014 ${skill.summary}` : ""}`
168380
+ );
168381
+ }
168366
168382
  }
168367
168383
  section.push("");
168368
168384
  }
@@ -168870,14 +168886,21 @@ var init_skill_decay = __esm({
168870
168886
  // src/lib/skill-command.ts
168871
168887
  var skill_command_exports = {};
168872
168888
  __export(skill_command_exports, {
168889
+ PARENT_SKILL_COMMAND_ID_REGEX: () => PARENT_SKILL_COMMAND_ID_REGEX,
168890
+ PARENT_SKILL_ID_PREFIX: () => PARENT_SKILL_ID_PREFIX,
168873
168891
  SKILL_COMMAND_UUID_REGEX: () => SKILL_COMMAND_UUID_REGEX,
168874
168892
  addSkillToPendingContext: () => addSkillToPendingContext,
168893
+ buildParentLocalSkillsFromTools: () => buildParentLocalSkillsFromTools,
168875
168894
  buildSkillCommandMessage: () => buildSkillCommandMessage,
168876
168895
  dedupeSkillsByName: () => dedupeSkillsByName,
168877
168896
  ensureSkillConversationContext: () => ensureSkillConversationContext,
168878
168897
  extractSkillName: () => extractSkillName,
168879
- findSkillByCommandName: () => findSkillByCommandName
168898
+ findSkillByCommandName: () => findSkillByCommandName,
168899
+ isParentLocalSkill: () => isParentLocalSkill
168880
168900
  });
168901
+ function isParentLocalSkill(skill) {
168902
+ return skill.source === "parent";
168903
+ }
168881
168904
  function kebabCase(value) {
168882
168905
  return value.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
168883
168906
  }
@@ -168919,11 +168942,43 @@ function buildSkillCommandMessage(skill, userArgs) {
168919
168942
  const name14 = extractSkillName(skill);
168920
168943
  const shortcut = name14 ? ` (\`/${name14}\`)` : "";
168921
168944
  const normalizedArgs = userArgs.trim();
168945
+ if (isParentLocalSkill(skill)) {
168946
+ return [
168947
+ `\u26A1 Activating parent skill **${skill.title}**${shortcut}. Call \`parent_${skill.parentToolName}\` to load and follow the parent-provided skill instructions.`,
168948
+ `Args: ${normalizedArgs || "(none)"}`
168949
+ ].join("\n");
168950
+ }
168922
168951
  return [
168923
168952
  `\u26A1 Activating skill **${skill.title}**${shortcut}. Follow its attached instructions.`,
168924
168953
  `Args: ${normalizedArgs || "(none)"}`
168925
168954
  ].join("\n");
168926
168955
  }
168956
+ function buildParentLocalSkillsFromTools(tools) {
168957
+ const skills = [];
168958
+ const seen = /* @__PURE__ */ new Set();
168959
+ for (const tool of tools) {
168960
+ if (!tool.skill) continue;
168961
+ const scope = tool.skill.scope ?? "page";
168962
+ const id = (tool.skill.id || tool.name).trim().toLowerCase();
168963
+ if (!/^[a-z][a-z0-9_-]{0,99}$/i.test(id)) continue;
168964
+ const fragmentId = `${PARENT_SKILL_ID_PREFIX}${id}`;
168965
+ if (seen.has(fragmentId)) continue;
168966
+ seen.add(fragmentId);
168967
+ const tags = Array.from(/* @__PURE__ */ new Set(["skill", `skill:${id}`, ...tool.skill.tags || []]));
168968
+ skills.push({
168969
+ source: "parent",
168970
+ fragmentId,
168971
+ workspaceId: scope === "global" ? "parent-global" : "parent-page",
168972
+ workspaceName: scope === "global" ? "Parent app" : "Current page",
168973
+ title: tool.skill.title || id,
168974
+ summary: tool.skill.summary || tool.description,
168975
+ tags,
168976
+ parentToolName: tool.name,
168977
+ parentScope: scope
168978
+ });
168979
+ }
168980
+ return skills;
168981
+ }
168927
168982
  function addSkillToPendingContext(pendingContextItems, skill) {
168928
168983
  if (pendingContextItems.some(
168929
168984
  (item) => item.contextType === "fragment" && item.contextId === skill.fragmentId
@@ -168977,11 +169032,13 @@ async function ensureSkillConversationContext(params) {
168977
169032
  throw new Error(`Failed to attach skill context (${addResponse.status})`);
168978
169033
  }
168979
169034
  }
168980
- var SKILL_COMMAND_UUID_REGEX, SKILL_NAME_TAG_PREFIX;
169035
+ var SKILL_COMMAND_UUID_REGEX, PARENT_SKILL_ID_PREFIX, PARENT_SKILL_COMMAND_ID_REGEX, SKILL_NAME_TAG_PREFIX;
168981
169036
  var init_skill_command = __esm({
168982
169037
  "src/lib/skill-command.ts"() {
168983
169038
  "use strict";
168984
169039
  SKILL_COMMAND_UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
169040
+ PARENT_SKILL_ID_PREFIX = "parent:";
169041
+ PARENT_SKILL_COMMAND_ID_REGEX = /^parent:[a-z][a-z0-9_-]{0,99}$/i;
168985
169042
  SKILL_NAME_TAG_PREFIX = "skill:";
168986
169043
  }
168987
169044
  });
@@ -241814,6 +241871,26 @@ Folder behavior:
241814
241871
  largeContextWarning: contextSizeWarning
241815
241872
  });
241816
241873
  }
241874
+ let discoverableSkillsSectionInjected = false;
241875
+ const buildParentPromptSkills = async () => {
241876
+ if (!context.registeredParentToolSchemas || context.registeredParentToolSchemas.size === 0) {
241877
+ return [];
241878
+ }
241879
+ const { buildParentLocalSkillsFromTools: buildParentLocalSkillsFromTools2, extractSkillName: extractSkillName2 } = await Promise.resolve().then(() => (init_skill_command(), skill_command_exports));
241880
+ return buildParentLocalSkillsFromTools2(
241881
+ Array.from(context.registeredParentToolSchemas.values())
241882
+ ).map((skill) => ({
241883
+ source: "parent",
241884
+ fragmentId: skill.fragmentId,
241885
+ workspaceId: skill.workspaceId,
241886
+ workspaceName: skill.workspaceName,
241887
+ title: skill.title,
241888
+ summary: skill.summary,
241889
+ name: extractSkillName2(skill),
241890
+ parentToolName: skill.parentToolName,
241891
+ parentScope: skill.parentScope
241892
+ }));
241893
+ };
241817
241894
  if (allowedWorkspaceIds.length > 0) {
241818
241895
  const workspaceRules = [
241819
241896
  "",
@@ -241921,18 +241998,25 @@ Folder behavior:
241921
241998
  });
241922
241999
  const { dedupeSkillsByName: dedupeSkillsByName2, extractSkillName: extractSkillName2 } = await Promise.resolve().then(() => (init_skill_command(), skill_command_exports));
241923
242000
  const dedupedSkills = dedupeSkillsByName2(skillDiscovery.skills);
242001
+ const parentPromptSkills = await buildParentPromptSkills();
241924
242002
  const discoverableSkillsSection = buildDiscoverableSkillsPromptSection(
241925
- dedupedSkills.map((skill) => ({
241926
- ...skill,
241927
- name: extractSkillName2(skill)
241928
- }))
242003
+ [
242004
+ ...dedupedSkills.map((skill) => ({
242005
+ source: "workspace",
242006
+ ...skill,
242007
+ name: extractSkillName2(skill)
242008
+ })),
242009
+ ...parentPromptSkills
242010
+ ]
241929
242011
  );
241930
242012
  if (discoverableSkillsSection) {
241931
242013
  systemMessageParts.push(discoverableSkillsSection);
242014
+ discoverableSkillsSectionInjected = true;
241932
242015
  orchestrationLogger.info("Discoverable skills injected into system prompt", {
241933
242016
  workspaceCount: new Set(skillDiscovery.skills.map((skill) => skill.workspaceId)).size,
241934
- skillCount: dedupedSkills.length,
242017
+ skillCount: dedupedSkills.length + parentPromptSkills.length,
241935
242018
  rawSkillCount: skillDiscovery.skills.length,
242019
+ parentSkillCount: parentPromptSkills.length,
241936
242020
  stale: skillDiscovery.stale
241937
242021
  });
241938
242022
  }
@@ -241964,6 +242048,23 @@ Folder behavior:
241964
242048
  conversationId
241965
242049
  });
241966
242050
  }
242051
+ if (!discoverableSkillsSectionInjected) {
242052
+ try {
242053
+ const parentPromptSkills = await buildParentPromptSkills();
242054
+ const discoverableSkillsSection = buildDiscoverableSkillsPromptSection(parentPromptSkills);
242055
+ if (discoverableSkillsSection) {
242056
+ systemMessageParts.push(discoverableSkillsSection);
242057
+ discoverableSkillsSectionInjected = true;
242058
+ orchestrationLogger.info("Parent-local discoverable skills injected into system prompt", {
242059
+ parentSkillCount: parentPromptSkills.length
242060
+ });
242061
+ }
242062
+ } catch (error41) {
242063
+ orchestrationLogger.warn("Failed to inject parent-local skills into prompt", {
242064
+ error: error41 instanceof Error ? error41.message : String(error41)
242065
+ });
242066
+ }
242067
+ }
241967
242068
  const contextManagementGuidance = [
241968
242069
  "",
241969
242070
  "## CONTEXT WINDOW MANAGEMENT - INTELLIGENT STRATEGY",
@@ -262423,7 +262524,7 @@ init_tui_select();
262423
262524
  init_model_registry();
262424
262525
 
262425
262526
  // package.json
262426
- var version = "1.150.0";
262527
+ var version = "1.151.0";
262427
262528
 
262428
262529
  // src/adapters/cli/model-catalog.ts
262429
262530
  init_codex_auth();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usabledev/usable-chat",
3
- "version": "1.150.0",
3
+ "version": "1.151.0",
4
4
  "description": "usable-chat — terminal harness for usable-chat (headless + TUI)",
5
5
  "type": "module",
6
6
  "bin": {